kraemer
Goto Top

Verständnisprobleme XML mit Powershell

Moin zusammen,

ich hoffe mir kann jemand auf die Sprünge helfen. Ich hatte bisher kaum was mit XML-Dateien zu tun, was das programmieren angeht. Wie ich den Dokus, die ich bisher versucht habe zu verstehen, anscheinend eine extreme Wissenslücke.

Es geht auf jeden Fall um folgendes: Ich habe hier eine XML-Datei vorliegen, in denen ich einen Wert austauschen muss. Dieses leider aber immer wieder - deswegen ist das Suchen und Ersetzen mit einem Editor keine Option.

Der Aufbau der XML sieht wie folgt aus:

<Stamm>
   <Haupt>BezeichnungHaupt
      <Unter>BezeichnungUnter
         <Punkt>
            <Eigenschaften>
               <Eigenschaft>Farbe</Eigenschaft>
               <Wert>Rot</Wert>
            </Eigenschaften>
         </Punkt>
      </Unter>
   </Haupt>
</Stamm>

<Haupt>...</Haupt> wiederholt sich 1000de male

Nun möchte ich bei den "Punkt"en, bei denen die "Eigenschaft" "Farbe" ist auf "Hauptfarbe" ändern.

Mein Ansatz sieht bisher so aus:

$MYXML=$TempPath+"MY.XML"  
$doc=[XML] (Get-Content -path $MYXML)
$doc.SelectNodes("Eigenschaften[contains(Eigenschaft,'Farbe')]")  
Wenn ich das soweit richtig verstanden habe, müsste $doc jetzt eine Art Array repräsentieren, in welchen nur noch die passenden Einträge sind.
Leider ist das nicht so - bzw. kann nicht so sein, weil ich, wenn ich ein
$doc.GetElementsByTagName("Wert")  
ausführe, ich alle Werte angezeigt bekomme - sprich ungefiltert.

Noch einmal zusammengefasst. Was ich erreichen will ist folgendes: XML laden, einen bestimmten Wert, der mehrfach vorkommen kann, auf einen anderen Wert ändern, und das ganze zurück in die Datei schreiben.

mein Gefühl sagt mir, dass das irgendwie mit foreach funktionieren müsste. Ich bekomme das Ganze aber irgendwie nicht zusammen.
Die Tutorials, die ich bisher gefunden habe, u.a. von @colinardo gehen leider immer davon aus, das sich jedes Element eindeutig referenzieren lässt (ID, Name oder ähnliches).

Vielleicht habe ich aber nur den Faden verloren.

Vielen Dank im voraus

Krämer

Content-Key: 314027

Url: https://administrator.de/contentid/314027

Printed on: April 24, 2024 at 10:04 o'clock

Member: H41mSh1C0R
H41mSh1C0R Aug 31, 2016 at 08:45:21 (UTC)
Goto Top
Hi Krämer,

na irgend einen Identifier brauchst du schon oder willst du bei allen die Farbe in Hauptfarbe ändern?

Gruß
Member: Kraemer
Kraemer Aug 31, 2016 at 08:47:56 (UTC)
Goto Top
Zitat von @H41mSh1C0R:
na irgend einen Identifier brauchst du schon oder willst du bei allen die Farbe in Hauptfarbe ändern?
Hi,

genau. Überall da, wo die <Eigenschaft>="Farbe" ist soll hinterher <Eigenschaft>="Hauptfarbe" stehen.

Gruß Krämer
Member: colinardo
Solution colinardo Aug 31, 2016 updated at 08:56:27 (UTC)
Goto Top
Hallo Krämer,
du musst schon das Ergebnis der Funktion selectNodes() in eine Variable speichern face-wink. $doc verändert sich damit nicht!

Hier ein simples Beispiel (Der Einfachheit habe ich das XML hier ins Skript eingebaut):
$xml = [xml]@"  
<Stamm>
   <Haupt>BezeichnungHaupt
      <Unter>BezeichnungUnter
         <Punkt>
            <Eigenschaften>
               <Eigenschaft>Farbe</Eigenschaft>
               <Wert>Rot</Wert>
            </Eigenschaften>
         </Punkt>
      </Unter>
   </Haupt>
</Stamm>
"@  
# Nodes selektieren
$nodes = $xml.SelectNodes("//Eigenschaften[contains(Eigenschaft,'Farbe')]")  
# Eigenschaft der Nodes ändern
$nodes | %{$_.Wert = "Anderer Wert"}  
# XML speichern
$xml.Save('D:\out.xml')  
Grüße Uwe
Member: Kraemer
Kraemer Aug 31, 2016 updated at 09:17:58 (UTC)
Goto Top
Cool. Vielen Dank Uwe!
Zitat von @colinardo:
$nodes = $xml.SelectNodes("//Eigenschaften[contains(Eigenschaft,'Farbe')]")
Verstehe ich das richtig, das hier nicht das "Ergebnis" nach $nodes "kopiert" wird, sondern $nodes "nur" ein Pointer auf die Ergebnismenge ist?

$nodes | %{$_.Wert = "Anderer Wert"}
Dieses Pipe-Konstrukt verstehe ich noch nicht. Wenn ich das irgendwo nachlesen will - nach welchem Suchbegriff muss ich suchen?

Gruß Krämer
Member: colinardo
Solution colinardo Aug 31, 2016 updated at 09:22:38 (UTC)
Goto Top
Zitat von @Kraemer:

Cool. Vielen Dank Uwe!
Zitat von @colinardo:
$nodes = $xml.SelectNodes("//Eigenschaften[contains(Eigenschaft,'Farbe')]")
Verstehe ich das richtig, das hier nicht das "Ergebnis" nach $nodes "kopiert" wird, sondern $nodes "nur" ein Pointer auf die Ergebnismenge ist?
Ja. Wenn du hier Änderungen vornimmst sind diese auch im XML-Dokument wiederzufinden weil man hier das XML-Dokument und deren Nodes verändert.
$nodes | %{$_.Wert = "Anderer Wert"}
Dieses Pipe-Konstrukt verstehe ich noch nicht. Wenn ich das irgendwo nachlesen will - nach welchem Suchbegriff muss ich suchen?
Das ist eine abgekürzte foreach schleife %{} ist eine Abkürzung für foreach-object oder auch foreach($element in $list){}.
Mit dem $_ sprichst du das aktuelle Objekt und dessen Eigenschaften in der Schleife an.
Member: H41mSh1C0R
Solution H41mSh1C0R Aug 31, 2016 updated at 09:23:55 (UTC)
Goto Top
Punkt 1: ja
Punkt 2: % --> ForEach Schleife, $_ aktuelles Element innerhalb der Schleife.Wert

Gruß


EDIT: Und der Uwe war schneller ^^. =)
Member: Kraemer
Kraemer Aug 31, 2016 at 09:30:10 (UTC)
Goto Top
Zitat von @colinardo:
Das ist eine abgekürzte foreach schleife %{} ist eine Abkürzung für foreach-object oder auch foreach($element in $list){}.
Mit dem $_ sprichst du das aktuelle Objekt und dessen Eigenschaften in der Schleife an.
OK. Das wird man sich dann wohl einfach merken müssen.

Eine kurze Frage noch. Die Original-XML ist optisch eine schöne Datei (Einrückungen, Linebreaks etc). Leider verstümmelt die Powershell das beim .save. Dafür gibt es nicht zufällig auch eine Abhilfe?
Member: colinardo
colinardo Aug 31, 2016 updated at 12:58:31 (UTC)
Goto Top
Eine kurze Frage noch. Die Original-XML ist optisch eine schöne Datei (Einrückungen, Linebreaks etc). Leider verstümmelt die Powershell das beim .save. Dafür gibt es nicht zufällig auch eine Abhilfe?
Das kannst du mit einem XMLWriter machen:
$xml = [xml]@"  
<Stamm>
   <Haupt>
      <Unter>
         <Punkt>
            <Eigenschaften>
               <Eigenschaft>Farbe</Eigenschaft>
               <Wert>Rot</Wert>
            </Eigenschaften>
            <Eigenschaften>
               <Eigenschaft>Farbe</Eigenschaft>
               <Wert>Rot</Wert>
            </Eigenschaften>
         </Punkt>
      </Unter>
   </Haupt>
</Stamm>
"@  

$nodes = $xml.SelectNodes("//Eigenschaften[contains(Eigenschaft,'Farbe')]")  
$nodes | %{$_.Wert = "Anderer Wert"}   
$xmlwritersettings = New-Object System.Xml.XmlWriterSettings
$xmlwritersettings.Indent = $true
$xmlwriter = [System.Xml.XmlWriter]::Create('D:\out.xml',$xmlwritersettings)  
$xml.Save($xmlWriter)
$xmlwriter.Close()
Member: Kraemer
Kraemer Aug 31, 2016 at 12:49:14 (UTC)
Goto Top
Hi Uwe,

sorry aber das scheint nicht zu funktionieren. Das einzige Ergebnis daraus ist, das die Datei in UTF8 gespeichert ist.

Ich habe natürlich mich mittlerweile auch bei M$ umgesehen und deswegen folgendes versucht:

$xmlwritersettings = New-Object System.Xml.XmlWriterSettings 
$xmlwritersettings.Indent = $true
$xmlwritersettings.IndentChars = "`t"  
$xmlwritersettings.NewLineOnAttributes = $true
$xmlwritersettings.NewLineChars = "`r`n"  
$xmlwriter = [System.Xml.XmlWriter]::Create($MYXML,$xmlwritersettings) 
$doc.Save($xmlwriter)
$xmlwriter.close()

Nur leider steht in der XML immer noch endlos folgendes:
</EINS></ZWEI><DREI>
alse mehrere Tags in einer Reihe.

Hast du noch eine Idee?

Gruß Krämer
Member: colinardo
Solution colinardo Aug 31, 2016 updated at 12:55:54 (UTC)
Goto Top
Doch das funktioniert 100%, das liegt dann an deiner Quelldatei und wie dort die Inhalte gesetzt sind. Geht hier einwandfrei.

Das Problem bei deinem XML oben ist das dier:
   <Haupt>BezeichnungHaupt
      <Unter>BezeichnungUnter 
Der verhindert das das gesamte Dokument vernünftig formatiert wird, -> Bezeichnung ohne Tag, das führt dazu.

Die zusätzlichen Attribute die du dem $xmlsettings Objekt hinzugefügt hast sind nicht nötig.
Member: Kraemer
Kraemer Aug 31, 2016 at 13:00:06 (UTC)
Goto Top
Zitat von @colinardo:
Das Problem bei deinem XML oben ist das dier:
>    <Haupt>BezeichnungHaupt
>       <Unter>BezeichnungUnter 
> 
Also habe ich das doch richtig verstanden, dass das Murks ist...
Leider bekomme ich die Datei so geliefert. Also muss ich das auch noch irgendwie in Ordnung bringen...
Danke dir noch einmal.

Gruß Krämer
Member: colinardo
colinardo Aug 31, 2016 updated at 13:03:54 (UTC)
Goto Top
Also habe ich das doch richtig verstanden, dass das Murks ist...
Suboptimal, normalerweise schreibt man diese Infos in ein Attribut des Knotens oder spendiert Ihnen einen Unterknoten.
Das ließe sich ja einfach ändern, wenn du es nach deinen Bedürfnissen ändern darfst/kannst.
Member: Kraemer
Kraemer Aug 31, 2016 updated at 13:15:08 (UTC)
Goto Top
Zitat von @colinardo:
Das ließe sich ja einfach ändern, wenn du es nach deinen Bedürfnissen ändern darfst/kannst.
Glücklicherweise kann ich das. Die Datei kommt bei uns rein - ich "überarbeite" die - und dann geht die wieder an einen externen Dienstleister raus. Letzterem ist es schnurz, was da ankommt. Ich weiß nicht wie die das machen - aber die können nahezu alles verarbeiten.
Mein "Problem" ist nur, das die Datei stichprobenartig immer mal wieder von Mitarbeitern geprüft werden. Deswegen muss die "humanreadable" bleiben.

Werde jetzt versuchen das wie folgendes Muster umzusetzen: <gangster name='George "Shotgun" Ziegler'>
Member: colinardo
colinardo Aug 31, 2016 updated at 13:19:29 (UTC)
Goto Top
Zitat von @Kraemer:
Werde jetzt versuchen das wie folgendes Muster umzusetzen: <gangster name='George "Shotgun" Ziegler'>
Ein Attribut kannst du so erzeugen und an einen Knoten anhängen:
# Attribut erstellen
$attr = $xml.CreateAttribute("name")  
# Wert zuweisen
$arr.value = "George Shotgun Ziegler"  
# Attribut einem Knoten zuweisen
$xml.Stamm.Haupt.Attributes.Append($attr)