简体   繁体   English

Powershell:读取/编辑命名节点

[英]Powershell: Read / Edit named nodes

I would like to read and edit node content from a given XML file and write the changed content back.我想从给定的 XML 文件中读取和编辑节点内容,并将更改后的内容写回。

Content of the given XML file:给定的 XML 文件的内容:

<?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.读取和写回 XML 文件的新副本工作正常,但我完全不知道如何读取/编辑名为“DateTimeSeason”的节点的内容。 its nodes since I do not know XPath.它的节点,因为我不知道 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非常感谢任何帮助或提示 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;注意:XPath 区分大小写,“@name”在这里不起作用; "@Name" must be used.必须使用“@Name”。

To offer a more concise, PowerShell-idiomatic solution , using the Select-Xml cmdlet:要提供更简洁的 PowerShell 惯用解决方案,请使用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允许您使用 Xpath 查询直接从 XML 文件中提取信息,而无需首先通过[xml]类型 ( System.Xml.XmlDocument ) 手动将文件内容解析为 XML DOM。

  • 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返回Microsoft.PowerShell.Commands.SelectXmlInfo实例,这些实例包装了 XPath 查询返回的 XML 节点 (System.Xml.XmlNode ),可以通过.Node属性访问。

    • As an aside: It would be convenient to have an option to make 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 .顺便说一句:有一个选项可以让Select-Xml直接返回 XML 节点会很方便,该节点在 PowerShell 7.1 中不存在,但它是GitHub 建议#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.要访问封闭的 XML 文档 ( [xml] ) 实例,请使用节点的.OwnerDocument属性,它允许您调用后者的.Save()方法。

    • Note: Be sure to specify a full output path, because .NET's working directory usually differs from PowerShell's.注意:请务必指定完整的 output 路径,因为 .NET 的工作目录通常与 PowerShell 的不同。

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'] ).您错误地将 PowerShell 的基于属性的访问(例如.'SimBase.Document'.'Flight.Sections' )与 XPath 语法(例如[@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):虽然 PowerShell基于属性适配 XML DOM非常方便,但它没有内置查询功能,因此您需要使用 PowerShell 常用的过滤功能,特别是.Where()数组方法(也可以使用Where-Object cmdlet一个选项,但速度较慢):

# 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 .或者,您可以混合基于属性的访问和 XPath 查询,但只能通过将 XPath 查询传递给对.SelectSingleNode()SelectNodes()方法的调用,如Brian Reynolds 的回答所示。

Note : As Brian points out, XPath and XML in general are case- sensitive - unlike PowerShell's property-based access.注意:正如 Brian 指出的那样,XPath 和 XML 通常区分大小写- 与 PowerShell 基于属性的访问不同。

However, for conceptual clarity I recommend using either property-based access with PowerShell filtering or a .SelectSingleNode() / SelectNodes() method on the document (root) :但是,为了概念清晰,我建议使用带有 PowerShell 过滤的基于属性的访问文档(根)上的.SelectSingleNode() / SelectNodes()方法:

$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 // :或者,如果您愿意假设<Property>元素仅作为<Section>元素的子元素出现,例如,您可以通过//使用快捷方式

$data = $xmlFlight.SelectSingleNode("//Section/Property[@Name = 'Year']")

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM