- Toegang tot XML-gegevens in PowerShell
- Toegang tot XML met XPath.
- Toegang tot XML als Objecten.
- Vergelijking van XPath en Object Approaches
- Wijziging of creatie van XML-gegevens
- Toevoeging van XML-gegevens
- Gebruik XML voor Object Serialization
Inleiding
Zoals bij elke high-level taal, the data
is het hart van PowerShell. In het geval van PowerShell, komt dit neer op het omzetten van externe gegevens in PowerShell objecten en vice versa. Dit is het tweede artikel in een serie artikelen over het importeren van bijna alle gangbare gegevensformaten die je kunt tegenkomen, en over het exporteren naar sommige van deze formaten. Het eerste artikel in de serie, PowerShell Data Basics: File-Based Data, behandelt een verscheidenheid aan tekstformaten, van bestanden met vaste breedte, variabele breedte en ragged-right-bestanden tot CSV, eigenschappenlijsten, INI-bestanden en JSON-gegevens, en wordt afgesloten met een behandeling van het importeren en exporteren naar Excel. Hier concentreren we ons op het optimaal benutten van XML.
Toegang tot XML-gegevens in PowerShell
Er zijn twee ingebouwde technieken voor het werken met XML-gegevens in PowerShell: de XPath-benadering en de object dot-notatie-benadering. We zullen deze twee benaderingen beschrijven en vergelijken, en ze uitproberen op een aantal XML-voorbeelden. Voor het gemak wordt in alle codevoorbeelden gebruik gemaakt van dit XML-voorbeeldbestand van MSDN. Figuur 1 toont een beknopte weergave van het schema dat aan het bestand ten grondslag ligt (vooral vergeleken met het lezen van het onbewerkte XSD-bestand!), met dank aan Visual Studio’s XML Schema Explorer. In de figuur is te zien dat het XML-bestand een catalogus is die een verzameling boeken bevat. Elk boek heeft zeven kenmerken, waarvan er zes kind-elementen zijn en één een kind-attribuut.
Figuur 1 Schema voor Microsofts voorbeeld-XML-bestando:p>
Om dit voorbeeld-XML-bestand te laden, kunt u het volgende gebruiken:
Als u liever experimenteert met onmiddellijke XML-gegevens in plaats van een XML-bestand in een XmlDocument te laden, is dat eenvoudig te doen. Definieer uw XML gewoon als een string en giet die naar het XML-type, zoals we zojuist hebben gedaan met het cmdlet Get-Content voor bestanden. Hier is een deel van het voorbeeld XML bestand met slechts twee boeken:
Toegang tot XML met XPath
Met het bestand geladen in een XmlDocument object, kunt u vervolgens door de XML boom navigeren met XPath. Om een reeks knooppunten te selecteren gebruikt u de methode SelectNodes:
$xdoc.SelectNodes(“//author”)
#text
—
Gambardella, Matthew
Ralls, Kim
Corets, Eva
Corets, Eva
Corets, Eva
Randall, Cynthia
Thurman, Paula
Knorr, Stefan
Kress, Peter
O’Brien, Tim
O’Brien, Tim
Galos, Mike
Of gebruik SelectSingleNode om slechts één knooppunt te retourneren:
$xdoc.SelectSingleNode(“//book”)
id : bk102
auteur : Ralls, Kim
titel : Midnight Rain
genre : Fantasy
prijs : 5.95
publish_date : 2000-12-16
description : Een voormalig architecte strijdt tegen bedrijfszombies,
een boze tovenares, en haar eigen jeugd om koningin
van de wereld te worden.
Merk in het eerste voorbeeld op dat er duplicaten zijn. Stel dat je in plaats daarvan een lijst van unieke auteurs in de catalogus wilt hebben. Je zou kunnen denken dat iets als …
1
|
$xdoc.SelectNodes(“//author”) | select -Unique
|
…zou werken, maar het geeft in feite alleen de naam van de eerste auteur. Om te begrijpen waarom dat mislukt, zou je meer moeten begrijpen over de structuur van XML documenten. Het eerste voorbeeld gaf in feite een lijst van tekstknooppunten terug (dus geen lijst van tekst) en het unieke filter werkt op die lijst, op zoek naar element type uniciteit. Aangezien alle items in de verzameling tekstknooppunten zijn, en dus allemaal hetzelfde elementtype hebben, worden alle knooppunten na de eerste dus als duplicaten beschouwd. Het resultaat is dat alleen de eerste wordt geretourneerd.
Wat je eigenlijk wilt weten is de string-waarde van elke auteur-node. Vanuit de auteur node moet je eerst naar zijn tekst node gaan (zijn eerste en enige child), dan naar de waarde van die tekst node (regel A in het volgende voorbeeld). Als alternatief kun je een iets kortere expressie gebruiken met de InnerText eigenschap (regel B). Nog een variatie maakt gebruik van het Select-Xml cmdlet dat een methode-aanroep vermijdt en dus, in zekere zin, een meer uitgesproken PowerShell aanpak is (regel C). Alle drie de regels geven hetzelfde resultaat.
SelectNodes en SelectSingleNode samen geven je gelijkwaardige functionaliteit als Select-Xml. Beide ondersteunen namespaces, die ik nog niet heb genoemd. De twee methoden nemen beide een XmlNamespaceManager als optionele tweede parameter, terwijl het cmdlet Select-Xml een optionele Namespace-parameter neemt die uw namespaces in een hash-tabel specificeert.
SelectSingleNode retourneert een XmlNode en SelectNodes retourneert een XmlNodeList. Select-Xml daarentegen retourneert een object SelectXmlInfo (of een matrix daarvan) en de eigenschap Node geeft toegang tot de onderliggende node. Het voorbeeld hierboven illustreert deze verschillen.
Toegang tot XML als objecten
Met hetzelfde XmlDocument-object uit de vorige sectie biedt PowerShell ook dynamische objectondersteuning voor XML-gegevens: Hiermee hebt u toegang tot XML-gegevens als eersteklas PowerShell-objecten, waarvoor geen XPath-selector of kennis van de details van XML-knooppunten, -waarden of -tekstknooppunten nodig is. Bovendien krijgt u direct dynamische intellisense van uw XML-schema wanneer u uw XML-gegevens laadt! Figuur 2 illustreert dit voor de PowerShell ISE, waar u zowel keuzemogelijkheden als woordaanvulling krijgt, net als bij PowerShell native tokens. Merk met name in de onderste uitbreiding op dat wordt gezocht naar wat u overal in de naam van de eigenschap hebt getypt, niet alleen vanaf het eerste teken: Intellisense zou je hier een datum eigenschap vinden of het nu de naam published_date of releaseDate of datum_van_publicatie is. Merk op dat u woordaanvulling beschikbaar hebt in PowerShell V2 of V3, en in PowerShell ISE of PowerShell console. Maar selectiekeuzes zijn alleen beschikbaar in PowerShell ISE in V3.
Figuur 2 Automatische Intellisense bij het laden van een XML-document
Hier volgen enkele voorbeelden om te laten zien dat de XML inderdaad automatisch wordt geconverteerd naar PowerShell-objecten:
$xdoc
xml catalog
– —
version=”1.0″ catalog
$xdoc.catalog
book
—
{book, book, book, book…}
$xdoc.catalog.book | Format-Table -AutoSize
id author title genre price publish_date description
– — — — — —-
bk101 Gambardella, Matthew XML Developer’s Guide Computer 44.95 2000-10-01 Een diepgaande kijk …
bk102 Ralls, Kim Midnight Rain Fantasy 5.95 2000-12-16 Een voormalig architec…
bk103 Corets, Eva Maeve Ascendant Fantasy 5.95 2000-11-17 Na de ineenstorting…
bk104 Corets, Eva Oberon’s Legacy Fantasy 5.95 2001-03-10 In post-apocalyps…
bk105 Corets, Eva The Sundered Grail Fantasy 5.95 2001-09-10 De twee dochters…
bk106 Randall, Cynthia Lover Birds Romance 4.95 2000-09-02 Als Carla …
bk107 Thurman, Paula Splish Splash Romance 4.95 2000-11-02 Een diepzeeduiker …
bk108 Knorr, Stefan Creepy Crawlies Horror 4.95 2000-12-06 Een bloemlezing van h…
bk109 Kress, Peter Paradox Lost Science Fiction 6.95 2000-11-02 Na een onbedoelde …
bk110 O’Brien, Tim Microsoft .NET: The Prog… Computer 36.95 2000-12-09 Microsoft’s .NET …
bk111 O’Brien, Tim MSXML3: A Comprehensive … Computer 36.95 2000-12-01 De Microsoft MSX…
bk112 Galos, Mike Visual Studio 7: A Compr… Computer 49.95 2001-04-16 Microsoft Visual …
$xdoc.catalog.book
id : bk103
author : Corets, Eva
title : Maeve Ascendant
genre : Fantasy
prijs : 5.95
publish_date : 2000-11-17
description : Na de ineenstorting van een nanotechnologie
maatschappij in Engeland leggen de jonge overlevenden de
basis voor een nieuwe samenleving
$xdoc.catalog.book.author
Randall, Cynthia
$xdoc.catalog.book.id
bk106
Merk op dat alle XML-knooppunten in het document worden geconverteerd naar standaard PowerShell-eigenschappen, ongeacht of een knooppunt kinderen heeft (bijv. catalogus) of een bladknooppunt is (bijv. prijs), en ongeacht of een bladknooppunt een element is (bijv. auteur) of een attribuut (bijv. id). In het bijzonder (zoals de laatste twee voorbeelden hierboven illustreren) worden elementwaarden en attribuutwaarden precies hetzelfde behandeld met de standaard “punt”-notatie.
Vergelijking van XPath en Objectbenaderingen
Welke benadering is beter om XML-gegevens te benaderen? Tabel 1 helpt u deze vraag te beantwoorden. De objectbenadering is meestal beknopter (b.v. regel 3), maar niet altijd (regel 4). XPath is echter expressiever, in die zin dat het u in staat stelt een aantal selectors op te geven die niet mogelijk zijn met objectnotatie (regel 7). De eigen mogelijkheden van PowerShell kunnen dit gat echter gemakkelijk opvullen wanneer objectnotatie wordt gebruikt (regel 8).
Tabel 1 Vergelijking van XPath- en object-selectorbenaderingen voor XML-toegang.
XML-gegevens wijzigen of maken
Gezien de waardering voor XML-selectors uit de vorige secties, is het wijzigen van een XML-document vrij eenvoudig, omdat XML-selectors (XPath of object) L-waarden zijn, d.w.z. u kunt er zowel naar schrijven als van lezen! Zo zal een van deze de auteur van het 6e boek wijzigen:
1
2
|
$xdoc.SelectSingleNode(“//book/author”).InnerText = ‘jones’
$xdoc.catalog.book.author = ‘smith’
|
Heel vaak heb je misschien een bestaand XML-bestand waarin je een of meer waarden van een knooppunt wilt wijzigen. U wilt waarschijnlijk het bestand lezen, de gegevens wijzigen, en het bestand onder dezelfde naam weer opslaan. Je hebt de eerste twee stappen gezien; de derde stap wordt gedaan met de Save methode op het XmlDocument. Dit alles bij elkaar opgeteld levert de volgende basiscode op:
Die code loopt prima, behalve dat het er meestal op lijkt dat hij is mislukt! Dit eenvoudige stukje code illustreert een schijnbaar onbelangrijk maar belangrijk PowerShell begrip; onwetendheid hierover heeft geleid tot vele blog posts die beweren dat het een bug in PowerShell is. Het probleem is dat uw werkdirectory en uw PowerShell-locatie niet hetzelfde zijn. De bovenstaande code leest het bestand prima, het wijzigt de gegevens prima, maar het slaat het nieuwe bestand niet noodzakelijkerwijs op waar u dat verwacht. Get-Content, een PowerShell cmdlet, ziet een bestandspad relatief aan de PowerShell locatie. De methode XmlDocument.Save daarentegen ziet een bestandspad relatief aan de werkdirectory van het PowerShell-proces, omdat die methode buiten PowerShell wordt aangeroepen. Als u Set-Location (of zijn alias cd) niet hebt uitgevoerd in uw huidige PowerShell-sessie, wijzen beide naar dezelfde directory. Om dit te bevestigen, voert u deze twee verklaringen uit:
1
2
|
Get-Location # geeft PowerShell-locatie weer
::CurrentDirectory # geeft werkdirectory weer
|
De veiligste aanpak is dus om absolute paden te gebruiken om dit probleem helemaal te omzeilen. Zie het artikel Why the PowerShell Working Directory and the PowerShell Location Aren’t One in the Same van Alex Angelopoulos voor meer informatie.
Adding XML Data
Het toevoegen van nieuwe nodes aan uw XML-document kost net iets meer werk dan het wijzigen van de waarde van een bestaande node. Een aanpak die ik goed vind komt uit Tobias Weltner’s blog entry Write, Add and Change XML Data: neem een bestaand knooppunt van het type dat je wilt maken, maak een kopie van dat knooppunt en pas de kopie aan met je nieuwe gegevens, en voeg tenslotte het gekopieerde knooppunt in je XML in als een sibling van het origineel. Als we dat toepassen op ons voorbeeld van de boekencatalogus, maakt dit stukje code een nieuw boek-knooppunt en voegt het toe aan het eind van de collectie:
Als je het nieuwe boek op een andere plaats wilt toevoegen, bijvoorbeeld na het 3e boek, gebruik dan InsertAfter in plaats van AppendChild:
1
|
$xdoc.catalog.InsertAfter($book,$xdoc.catalog.book)
|
(De naam van de methode InsertAfter doet vermoeden dat het niet alleen knooppunten toevoegt; het kan ook knooppunten verplaatsen! De methode controleert of de node die je vraagt toe te voegen al in het document staat. Zo ja, dan wordt de node verplaatst naar de nieuwe locatie die je opgeeft. Wanneer u dus nodes wilt kopiëren, moet u beginnen met de methode Clone, zoals hierboven is geïllustreerd.)
Voor meer informatie over het manipuleren van XML-gegevens met .NET-methoden, zie Process XML Data Using the DOM Model op MSDN.
U using XML for Object Serialization
PowerShell biedt een eenvoudige manier om objecten te bewaren door gebruik te maken van Export-Clixml om elk object te serialiseren en op te slaan in een XML-bestand en Import-Clixml om het object vanuit XML te herstellen. Met XML blijft, in tegenstelling tot de meeste andere serialisatietechnieken, de objectintegriteit behouden: bij het herstellen van een object vanuit XML zijn alle eigenschappen juist getypeerd, net als het bovenliggende object zelf, zodat alle methoden op het oorspronkelijke object ook beschikbaar zijn op het geregenereerde object. Om Export-Clixml te gebruiken, hoeft u er alleen maar een objectverzameling aan te koppelen en een doelbestand op te geven. Hier is een eenvoudig voorbeeld dat laat zien dat de uitvoer van Get-ChildItem, een verzameling FileSystemInfo objecten, wordt geregenereerd: