I would like to read and edit node content from a given XML file and write the changed content back.
Content of the given XML file:
<?xml version="1.0" encoding="UTF-8"?>
<SimBase.Document Type="ScenarioFile" version="4,5" id="Test">
<Descr>AceXML Document</Descr>
<Filename>Test.fxml</Filename>
<Flight.Sections>
...
<Section Name="DateTimeSeason">
<Property Name="Season" Value="Summer" />
<Property Name="Year" Value="2020" />
<Property Name="Day" Value="224" />
<Property Name="Hours" Value="12" />
<Property Name="Minutes" Value="2" />
<Property Name="Seconds" Value="17" />
</Section>
...
</Flight.Sections>
</SimBase.Document>
Reading and writing back a new copy of the XML file works fine but I am at total loss how the read / edit the content of the node named 'DateTimeSeason' resp. its nodes since I do not know XPath.
Content of the script that I have written:
Write-Host "Edit XML file"
# Load local functions
. .\LocalFunctions.ps1
# Working environment
$flightFileDir = 'E:\Temp\PowerShell\P3D\'
$flightFileNameIn = 'MauleLSZH.fxml'
$flightFileNameOut = 'MauleLSZHNew.fxml'
$flightFileIn = $flightFileDir + $flightFileNameIn
$flightFileOut = $flightFileDir + $flightFileNameOut
# Get correct date and time for flight file
$dateTimeArr = SetupDateTime
#$dateTimeArr # output content of resulting array
# Set up new XML object and read file content
$xmlFlight = New-Object System.XML.XMLDocument
$xmlFlight.Load($flightFileIn)
# Edit content of node named 'DateTimeSeason'
$data = $xmlFlight.'SimBase.Document'.'Flight.Sections'.Section[@name -eq 'DateTimeSeason'].Property[@name = 'Year']
$data # output for test purposes
# Output modified flight file
$xmlFlight.Save($flightFileOut)
Write-Host "New XML file created"
Thanks a lot for any help or hints Hannes
You were close; here's a corrected version that adds a new "Property" and edits the "Value" of an existing one.
# Set up new XML object and read file content
$xmlFlight = New-Object System.XML.XMLDocument
$xmlFlight.Load($flightFileIn)
# Create new "Property" and append to the "DateTimeSeason" node
[System.Xml.XmlElement]$newElement = $xmlFlight.CreateElement('Property')
$newElement.SetAttribute('Name','NewName')
$newElement.SetAttribute('Value','NewValue')
$data = $xmlFlight.'SimBase.Document'.'Flight.Sections'.SelectSingleNode("//Section[@Name='DateTimeSeason']")
[void]$data.AppendChild($newElement)
# Edit "Year" Property on the "DateTimeSeason" node
$xmlFlight.'SimBase.Document'.'Flight.Sections'.SelectSingleNode("//Section[@Name='DateTimeSeason']").SelectSingleNode("//Property[@Name = 'Year']").Value = '2021'
# Could also use: $data.SelectSingleNode("//Property[@Name = 'Year']").Value = '2021'
$data.Property # output for test purposes
# Output modified flight file
$xmlFlight.Save($flightFileOut)
Write-Host "New XML file created"
Note: XPaths are case-sensitive and "@name" would not have worked here; "@Name" must be used.
To offer a more concise, PowerShell-idiomatic solution , using the Select-Xml
cmdlet:
# XPath query to locate the element of interest.
$xpathQuery = '/SimBase.Document/Flight.Sections/Section[@Name="DateTimeSeason"]/Property[@Name="Year"]'
# Get the element of interest (<Property Name="Year" Value="2020" />)...
$elem = (Select-Xml $xpathQuery $flightFileIn).Node
# ... update its 'Value' attribute ...
$elem.Value = 2021
# ... and save the modified document to the output file.
$elem.OwnerDocument.Save($flightFileOut)
Using Select-Xml
allows you to extract information directly from an XML file using an Xpath query, without having to manually parse the file contents into an XML DOM via the [xml]
type ( System.Xml.XmlDocument
) first.
Select-Xml
returns Microsoft.PowerShell.Commands.SelectXmlInfo
instances that wrap the XML node(s) (System.Xml.XmlNode
) returned by the XPath query, which can be accessed via the .Node
property.
Select-Xml
return the XML node(s) directly , which doesn't exist as of PowerShell 7.1 but is the subject of GitHub suggestion #13669 . To gain access to the enclosing XML document ( [xml]
) instance, use the .OwnerDocument
property of the nodes, which is what allows you to call the .Save()
method on the latter.
As for what you tried :
$data = $xmlFlight.'SimBase.Document'.'Flight.Sections'.Section[@name -eq 'DateTimeSeason'].Property[@name = 'Year']
You mistakenly mixed PowerShell's property-based access (eg .'SimBase.Document'.'Flight.Sections'
) with XPath syntax (eg, [@name -eq 'DateTimeSeason']
).
While PowerShell's property-based adaptation of the XML DOM is very convenient, it has no built-in query capabilities, so you need to use PowerShell's usual filtering functionality, notably the .Where()
array method (using the Where-Object
cmdlet is also an option, but is slower):
# Returns element <Property Name="Year" Value="2020" />
$data = $xmlFlight.'SimBase.Document'.'Flight.Sections'.Section.
Where({ $_.Name -eq 'DateTimeSeason' }).Property.
Where({ $_.Name -eq 'Year' })
# Sample modification of the 'Value' attribute.
$data.Value = '2021'
Alternatively, you can mix property-based access and XPath queries, but only by passing the XPath queries to calls to the .SelectSingleNode()
or SelectNodes()
method, as shown in Brian Reynolds' answer .
Note : As Brian points out, XPath and XML in general are case- sensitive - unlike PowerShell's property-based access.
However, for conceptual clarity I recommend using either property-based access with PowerShell filtering or a .SelectSingleNode()
/ SelectNodes()
method on the document (root) :
$data = $xmlFlight.SelectSingleNode('/SimBase.Document/Flight.Sections/Section[@Name="DateTimeSeason"]/Property[@Name="Year"]')
Or, if you're willing to assume that <Property>
elements occur only as children of <Section>
elements, for instance, you can take a shortcut via //
:
$data = $xmlFlight.SelectSingleNode("//Section/Property[@Name = 'Year']")
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.