- Uzyskiwanie dostępu do danych XML w PowerShell
- Uzyskiwanie dostępu do XML za pomocą XPath.
- Dostęp do XML jako obiektów.
- Porównanie podejść XPath i obiektowego
- Modyfikowanie lub tworzenie danych XML
- Dodawanie danych XML
- Używanie XML do serializacji obiektów
Wprowadzenie
Jak w przypadku każdego języka wysokiego poziomu, the data
jest sercem PowerShella. W przypadku PowerShella sprowadza się to do konwersji zewnętrznych danych na obiekty PowerShella i odwrotnie. Jest to drugi z serii artykułów, które pokazują, jak importować prawie wszystkie popularne formaty danych, z którymi można się spotkać, a także jak eksportować do niektórych z nich. Pierwszy artykuł z tej serii, Podstawy danych PowerShell: Dane oparte na plikach, omawia różne formaty tekstowe, od plików o stałej szerokości, zmiennej szerokości i ragged-right do CSV, list właściwości, plików INI i danych JSON, a kończy się omówieniem importu i eksportu do Excela. Tutaj skoncentrujemy się na maksymalnym wykorzystaniu XML.
Dostęp do danych XML w PowerShellu
W PowerShellu istnieją dwie wbudowane techniki pracy z danymi XML; podejście XPath i podejście dot-notacji obiektu. Opiszemy i porównamy te dwa podejścia, a także wypróbujemy je na kilku przykładowych XML-ach. Dla wygody, wszystkie przykłady kodu używają tego przykładowego pliku XML z MSDN. Rysunek 1 pokazuje reprezentację schematu leżącego u podstaw tego pliku w zwięzłym formacie (szczególnie w porównaniu z czytaniem surowego pliku XSD!), dzięki uprzejmości Visual Studio’s XML Schema Explorer. Na rysunku można zauważyć, że plik XML jest katalogiem zawierającym kolekcję książek. Każda książka ma siedem cech, z których sześć jest elementami potomnymi, a jedna atrybutem potomnym.
Rysunek 1 Schemat dla przykładowego pliku XML firmy Microsofto:p>
Aby załadować ten przykładowy plik XML, możesz użyć któregoś z tych:
jeśli wolisz eksperymentować z natychmiastowymi danymi XML zamiast ładować plik XML do dokumentu XmlDocument, jest to proste do zrobienia. Po prostu zdefiniuj swój XML jako ciąg znaków i przekształć go w typ XML, tak jak to właśnie zrobiliśmy z cmdletem Get-Content dla plików. Oto fragment przykładowego pliku XML zawierającego tylko dwie książki:
Dostęp do XML za pomocą XPath
Po załadowaniu pliku do obiektu XmlDocument można następnie nawigować po drzewie XML za pomocą XPath. Aby wybrać zestaw węzłów, należy użyć metody SelectNodes:
$xdoc.SelectNodes(„//author”)
#text
—
Gambardella, Matthew
Ralls, Kim
Corets, Eva
Corets, Eva
Randall, Cynthia
Thurman, Paula
Knorr, Stefan
Kress, Peter
O’Brien, Tim
O’Brien, Tim
Galos, Mike
Lub użyj SelectSingleNode, aby zwrócić tylko jeden węzeł:
$xdoc.SelectSingleNode(„//book”)
id : bk102
autor : Ralls, Kim
tytuł : Midnight Rain
gatunek : Fantasy
cena : 5.95
publish_date : 2000-12-16
opis : Była architektka walczy z korporacyjnymi zombie,
złą czarodziejką i własnym dzieciństwem, by zostać królową
świata.
Zauważ w pierwszym przykładzie, że są duplikaty. Załóżmy zamiast tego, że chcesz mieć listę unikalnych autorów w katalogu. Można by pomyśleć, że coś takiego …
1
|
$xdoc.SelectNodes(„//author”) | select -Unique
|
…zadziałałoby, ale w rzeczywistości zwraca tylko nazwisko pierwszego autora. Aby zrozumieć dlaczego to się nie udało, musiałbyś zrozumieć więcej na temat struktury dokumentów XML. Pierwszy przykład faktycznie zwrócił listę węzłów tekstowych (tj. nie listę tekstu), a filtr unikalny działa na tej liście, szukając unikalności typu elementu. Ponieważ wszystkie elementy w kolekcji są węzłami tekstowymi, a więc wszystkie są tego samego typu elementu, wszystkie węzły poza pierwszym są uważane za duplikaty. W rezultacie zwracany jest tylko pierwszy z nich.
To, czego tak naprawdę szukasz, to wartość łańcuchowa każdego węzła autor. Z węzła autor musisz najpierw uzyskać dostęp do jego węzła tekstowego (jego pierwszego i jedynego dziecka), a następnie do wartości tego węzła tekstowego (linia A w następnym przykładzie). Alternatywnie, można użyć nieco krótszego wyrażenia z właściwością InnerText (linia B). Jeszcze jedna wariacja wykorzystuje cmdlet Select-Xml, który pozwala uniknąć wywołania metody, a więc jest w pewnym sensie podejściem bardziej charakterystycznym dla PowerShella (linia C). Wszystkie trzy linie zwracają ten sam wynik.
SelectNodes i SelectSingleNode razem dają równoważną funkcjonalność do Select-Xml. Obie obsługują przestrzenie nazw, o których jeszcze nie wspomniałem. Obie metody pobierają XmlNamespaceManager jako opcjonalny drugi parametr, podczas gdy cmdlet Select-Xml pobiera opcjonalny parametr Namespace, który określa przestrzenie nazw w tablicy hash.
SelectSingleNode zwraca XmlNode, a SelectNodes zwraca XmlNodeList. Z kolei Select-Xml zwraca obiekt SelectXmlInfo (lub ich tablicę), a jego właściwość Node zapewnia dostęp do bazowego węzła. Przykład powyżej ilustruje te różnice.
Dostęp do XML jako obiektów
Dzięki temu samemu obiektowi XmlDocument z ostatniej sekcji, PowerShell zapewnia również dynamiczną obsługę obiektów dla danych XML: Pozwala to na dostęp do danych XML jako obiektów pierwszej klasy PowerShella, nie wymagając selektora XPath ani znajomości szczegółów takich rzeczy jak węzły XML, wartości czy węzły tekstowe. Co więcej, otrzymujesz natychmiastowy, dynamiczny Intellisense schematu XML, gdy ładujesz swoje dane XML! Rysunek 2 ilustruje to dla PowerShell ISE, gdzie otrzymujemy zarówno wybór jak i uzupełnianie słów, tak jak w przypadku natywnych tokenów PowerShell. Zwróć uwagę szczególnie na dolne rozwinięcie, że szuka ono tego, co wpisałeś w dowolnym miejscu nazwy właściwości, a nie tylko zaczynając od pierwszego znaku: Intellisense znalazłby tutaj właściwość date bez względu na to, czy jest ona nazwana published_date, releaseDate czy date_of_publication. Zauważ, że masz uzupełnianie słów dostępne w PowerShell V2 lub V3, a także w PowerShell ISE lub konsoli PowerShell. Ale opcje wyboru są dostępne tylko w PowerShell ISE w V3.
Figure 2 Automatic Intellisense upon loading an XML document
Oto kilka przykładów pokazujących, że XML jest rzeczywiście automatycznie konwertowany na obiekty PowerShell:
$xdoc
xml catalog
– —
version=”1.0″ katalog
$xdoc.catalog
książka
—
{książka, książka, książka, książka…}
$xdoc.catalog.book | Format-Table -AutoSize
id author title genre price publish_date description
– — — — — —- —-
bk101 Gambardella, Matthew XML Developer’s Guide Komputer 44.95 2000-10-01 Dogłębne spojrzenie …
bk102 Ralls, Kim Midnight Rain Fantasy 5.95 2000-12-16 Były architek…
bk103 Corets, Eva Maeve Ascendant Fantasy 5.95 2000-11-17 Po upadku …
bk104 Corets, Eva Oberon’s Legacy Fantasy 5.95 2001-03-10 W postapokalips…
bk105 Corets, Eva The Sundered Grail Fantasy 5.95 2001-09-10 Dwie córki…
bk106 Randall, Cynthia Lover Birds Romans 4.95 2000-09-02 Kiedy Carla poznaje …
bk107 Thurman, Paula Splish Splash Romans 4.95 2000-11-02 Nurek głębinowy …
bk108 Knorr, Stefan Creepy Crawlies Horror 4.95 2000-12-06 Antologia h…
bk109 Kress, Peter Paradox Lost Science Fiction 6.95 2000-11-02 Po nieumyślnym …
bk110 O’Brien, Tim Microsoft .NET: The Prog…Komputer 36.95 2000-12-09 Microsoft’s .NET …
bk111 O’Brien, Tim MSXML3: A Comprehensive …Komputer 36.95 2000-12-01 The Microsoft MSX…
bk112 Galos, Mike Visual Studio 7: A Compr…Komputer 49.95 2001-04-16 Microsoft Visual …
$xdoc.catalog.book
id : bk103
autor : Corets, Eva
tytuł : Maeve Ascendant
gatunek : Fantasy
cena : 5,95
publish_date : 2000-11-17
opis : Po upadku nanotechnologicznego
społeczeństwa w Anglii, młodzi ocaleni kładą
podwaliny pod nowe społeczeństwo
$xdoc.catalog.book.author
Randall, Cynthia
$xdoc.catalog.book.id
bk106
Zauważ, że wszystkie węzły XML w dokumencie są konwertowane na standardowe właściwości PowerShell, niezależnie od tego, czy węzeł ma dzieci (np. katalog), czy jest węzłem typu liść (np. cena), czy węzeł typu liść jest elementem (np. autor), czy atrybutem (np. id). W szczególności (jak ilustrują dwa ostatnie przykłady powyżej), wartości elementów i wartości atrybutów są traktowane dokładnie tak samo przy użyciu standardowej notacji „kropkowej”.
Porównanie podejść XPath i obiektowego
Które podejście jest lepsze dla dostępu do danych XML? Tabela 1 pomoże Ci odpowiedzieć na to pytanie. Podejście obiektowe jest zwykle bardziej zwięzłe (np. linia 3), ale nie zawsze (linia 4). XPath jest jednak bardziej ekspresyjny, ponieważ pozwala na określenie pewnych selektorów, które nie są możliwe w przypadku notacji obiektowej (linia 7). Jednak własne możliwości PowerShella mogą łatwo wypełnić tę lukę, gdy używamy notacji obiektowej (linia 8).
Tabela 1 Porównanie podejść XPath i selektorów obiektowych dla dostępu do XML.
Modyfikowanie lub tworzenie danych XML
Przy uwzględnieniu selektorów XML z poprzednich sekcji, modyfikowanie dokumentu XML jest dość proste, ponieważ selektory XML (zarówno XPath, jak i obiektowe) są wartościami L, tzn. można do nich pisać, jak również z nich czytać! Tak więc każdy z tych elementów zmodyfikuje autora szóstej książki:
1
2
|
$xdoc.SelectSingleNode(„//book/author”).InnerText = 'jones’
$xdoc.catalog.book.author = 'smith’
|
Dość często możesz mieć istniejący plik XML, w którym chcesz zmienić jedną lub wartości węzłów. Prawdopodobnie chciałbyś odczytać plik, zmodyfikować dane i zapisać plik z powrotem pod tą samą nazwą. Widziałeś pierwsze dwa kroki; trzeci krok jest wykonywany za pomocą metody Save na XmlDocument. Składając to wszystko razem, otrzymujemy następujący podstawowy kod:
Ten kod działa dobrze – z wyjątkiem tego, że przez większość czasu będzie wyglądał na nieudany! Ten prosty fragment kodu ilustruje pozornie niewielkie, ale ważne pojęcie PowerShell; nieznajomość tego doprowadziła do wielu wpisów na blogach twierdzących, że jest to błąd w PowerShell. Problem polega na tym, że twój katalog roboczy i lokalizacja PowerShell nie są tym samym. Powyższy kod odczytuje plik tak samo dobrze, modyfikuje dane tak samo dobrze, ale niekoniecznie zapisuje nowy plik tam, gdzie tego oczekujesz. Get-Content, będąc cmdletem PowerShell, widzi ścieżkę do pliku w stosunku do lokalizacji PowerShell. Metoda XmlDocument.Save, z drugiej strony, widzi ścieżkę do pliku względem katalogu roboczego procesu PowerShell, ponieważ wywołanie tej metody znajduje się poza PowerShell. Jeśli nie wykonałeś Set-Location (lub jego aliasu cd) w bieżącej sesji PowerShell, oba wskazują na ten sam katalog. Aby to potwierdzić, wykonaj te dwie instrukcje:
1
2
|
Get-Location # wyświetla lokalizację PowerShell
::CurrentDirectory # wyświetla katalog roboczy
|
Najbezpieczniejszym podejściem jest więc użycie ścieżek bezwzględnych, aby całkowicie uniknąć tego problemu. Zobacz artykuł Alexa Angelopoulosa Why the PowerShell Working Directory and the PowerShell Location Aren’t One in the Same, aby dowiedzieć się więcej.
Dodawanie danych XML
Dodawanie nowych węzłów do dokumentu XML wymaga nieco więcej pracy niż modyfikacja wartości istniejącego węzła. Jedno z podejść, które lubię, pochodzi z wpisu Tobiasa Weltnera na blogu Write, Add and Change XML Data: weź istniejący węzeł typu, który chcesz utworzyć, zrób kopię tego węzła i zmodyfikuj kopię swoimi nowymi danymi, a na koniec wstaw skopiowany węzeł do XML jako rodzeństwo oryginału. Stosując to do naszego przykładu z katalogiem książek, ten fragment kodu tworzy nowy węzeł książki i dodaje go na końcu kolekcji:
Jeśli chcesz dodać nową książkę w innym miejscu, powiedzmy za 3. książką, użyj InsertAfter zamiast AppendChild:
1
|
$xdoc.catalog.InsertAfter($book,$xdoc.catalog.book)
|
(Nazwa metody InsertAfter nie wskazuje na to, że nie tylko dodaje ona węzły; może również przenosić węzły! Metoda sprawdza, czy węzeł, który chcesz dodać, znajduje się już w dokumencie. Jeśli tak, to przenosi go do nowej lokalizacji, którą podasz. Tak więc, gdy chcesz skopiować węzły, musisz zacząć od metody Clone, jak pokazano powyżej.)
Dalsze zgłębianie manipulacji danymi XML za pomocą metod .NET, zobacz Process XML Data Using the DOM Model na MSDN.
Używanie XML do serializacji obiektów
PowerShell dostarcza łatwego sposobu na zachowanie obiektów poprzez użycie Export-Clixml do serializacji dowolnego obiektu i przechowywania go w pliku XML oraz Import-Clixml do odtworzenia obiektu z XML. Dzięki XML, w przeciwieństwie do większości innych technik serializacji, integralność obiektu jest zachowana: po przywróceniu obiektu z XML wszystkie właściwości są poprawnie wpisane, tak jak sam obiekt nadrzędny, więc wszystkie metody na oryginalnym obiekcie są dostępne również na zregenerowanym obiekcie. Aby użyć Export-Clixml, wystarczy przekazać do niego dowolną kolekcję obiektów i określić plik docelowy. Oto prosty przykład pokazujący, że wyjście z Get-ChildItem, kolekcji obiektów FileSystemInfo, jest regenerowane: