如何更改 XmlElements 的顺序?
How do I change the order of XmlElements?
我有一个包含各种元素的 XML,但其中一个名为 RowId
,我想移动到相应 XML 数组的顶部...基本上以我的方式对元素进行排序。我下面的代码可以 copied/pasted 供您测试。
完成此任务的最佳方法是什么? And/or有没有比我现在做的更好的方法?这看起来很笨拙。
如果我包含 | Sort-Object -Property "Name"
,为什么下面的代码 有效 ?
复制并粘贴以下代码:
$rawXML = [xml]@"
<?xml version="1.0" encoding="utf-8"?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Document">
<xs:complexType>
<xs:sequence>
<xs:element name="Object1" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Element1" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="80"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Element2" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="-1"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="RowId">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Object2" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Element4" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="80"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Element5" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="-1"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="RowId">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
"@
$rawXML.schema.element.complexType.sequence.element.complexType.sequence | ForEach-Object {
$sequence = $_
# This does NOT work
$childNodes = $sequence.ChildNodes #| Sort-Object -Property "Name"
$childNodes.Count # Output = 3
$sequence.RemoveAll()
$childNodes.Count # Output = 0
# This DOES work; Only difference is '| Sort-Object -Property "Name"'
<#
$childNodes = $sequence.ChildNodes | Sort-Object -Property "Name"
$childNodes.Count # Output = 3
$sequence.RemoveAll()
$childNodes.Count # Output = 3
#>
$childNodes | ForEach-Object {
$child = $_
if ($child.Name -eq "RowId") {
$sequence.InsertBefore($child, $sequence.FirstChild)
} else {
$sequence.AppendChild($child) | out-null
}
}
}
$rawXML.InnerXml
我认为 DOM 节点列表是正常 DOM 使用中的“实时”集合(“实时”我认为是 W3C 规范中的规范术语,Microsoft 文档称它们为动态:https://docs.microsoft.com/en-us/dotnet/standard/data/xml/dynamic-updates-to-nodelists-and-namednodemaps) 所以你的尝试 $childNodes = $sequence.ChildNodes
将活动的 NodeList 存储在一个变量中,作为这样一个集合,它反映了对 DOM 树的任何更改。 Powershell 使用管道将集合 Sort-Object
与 $sequence.ChildNodes | Sort-Object -Property "Name"
连接起来并不是 return XmlNodeList 或其他 live/dynamic 集合,而是 return 对象数组,例如XmlNode[]
或 object[]
,这样您的代码就可以按照您希望的方式工作,因为您稍后会处理该数组,而不是跟踪 DOM 树更改的 XmlNodeList。
概括一下你已经尝试过的原则:为了直接修改存储在$rawXML
中的XML DOM,您需要使用 [xml]
(System.Xml.XmlDocument
) .NET 类型自己的方法。
以下简化方法首先定位感兴趣的子元素,然后使用 .InsertBefore()
将其移动到顶部(不需要 删除 元素在插入之前移动):
$rawXML.schema.element.complexType.sequence.element.complexType.sequence | ForEach-Object {
# Find the child element whose 'name' attribute is 'RowId'
$rowIdElem = $_.ChildNodes.Where({ $_.name -eq 'RowId' })[0]
# Insert the row-ID element before the currently first child element,
# which effectively moves it to the top.
$null = $_.InsertBefore($rowIdElem, $_.FirstChildNode)
}
至于你试过的:
$childNodes = $sequence.ChildNodes
有效地使 $childNodes
指向存储在元素 .ChildNodes
中的 子节点 的活动列表 属性(它是从 System.Xml.XmlNodeList
派生的类型)
因此,在调用$sequence.ChildNodes.Clear()
移除所有子节点后,$sequence.ChildNodes
和$childNodes
都引用了一个空集合,即没有什么可枚举的,这就是为什么 ForEach-Object
脚本块 ($childNodes | ForEach-Object { ... }
) 永远不会被调用的原因。
相比之下,通过涉及 Sort-Object
调用 ($childNodes = $sequence.ChildNode | Sort-Object Name
),您会立即枚举子元素,并捕获 子元素的快照作为一个独立的数组([object[]]
类型的常规PowerShell数组),其元素是 稍后按预期枚举,导致 ForEach-Object
按自定义顺序重新附加元素的调用按预期工作。
我有一个包含各种元素的 XML,但其中一个名为 RowId
,我想移动到相应 XML 数组的顶部...基本上以我的方式对元素进行排序。我下面的代码可以 copied/pasted 供您测试。
完成此任务的最佳方法是什么? And/or有没有比我现在做的更好的方法?这看起来很笨拙。
如果我包含
| Sort-Object -Property "Name"
,为什么下面的代码 有效 ?
复制并粘贴以下代码:
$rawXML = [xml]@"
<?xml version="1.0" encoding="utf-8"?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Document">
<xs:complexType>
<xs:sequence>
<xs:element name="Object1" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Element1" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="80"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Element2" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="-1"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="RowId">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Object2" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="Element4" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="80"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Element5" nillable="true">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="-1"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="RowId">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="20"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
"@
$rawXML.schema.element.complexType.sequence.element.complexType.sequence | ForEach-Object {
$sequence = $_
# This does NOT work
$childNodes = $sequence.ChildNodes #| Sort-Object -Property "Name"
$childNodes.Count # Output = 3
$sequence.RemoveAll()
$childNodes.Count # Output = 0
# This DOES work; Only difference is '| Sort-Object -Property "Name"'
<#
$childNodes = $sequence.ChildNodes | Sort-Object -Property "Name"
$childNodes.Count # Output = 3
$sequence.RemoveAll()
$childNodes.Count # Output = 3
#>
$childNodes | ForEach-Object {
$child = $_
if ($child.Name -eq "RowId") {
$sequence.InsertBefore($child, $sequence.FirstChild)
} else {
$sequence.AppendChild($child) | out-null
}
}
}
$rawXML.InnerXml
我认为 DOM 节点列表是正常 DOM 使用中的“实时”集合(“实时”我认为是 W3C 规范中的规范术语,Microsoft 文档称它们为动态:https://docs.microsoft.com/en-us/dotnet/standard/data/xml/dynamic-updates-to-nodelists-and-namednodemaps) 所以你的尝试 $childNodes = $sequence.ChildNodes
将活动的 NodeList 存储在一个变量中,作为这样一个集合,它反映了对 DOM 树的任何更改。 Powershell 使用管道将集合 Sort-Object
与 $sequence.ChildNodes | Sort-Object -Property "Name"
连接起来并不是 return XmlNodeList 或其他 live/dynamic 集合,而是 return 对象数组,例如XmlNode[]
或 object[]
,这样您的代码就可以按照您希望的方式工作,因为您稍后会处理该数组,而不是跟踪 DOM 树更改的 XmlNodeList。
概括一下你已经尝试过的原则:为了直接修改存储在$rawXML
中的XML DOM,您需要使用 [xml]
(System.Xml.XmlDocument
) .NET 类型自己的方法。
以下简化方法首先定位感兴趣的子元素,然后使用 .InsertBefore()
将其移动到顶部(不需要 删除 元素在插入之前移动):
$rawXML.schema.element.complexType.sequence.element.complexType.sequence | ForEach-Object {
# Find the child element whose 'name' attribute is 'RowId'
$rowIdElem = $_.ChildNodes.Where({ $_.name -eq 'RowId' })[0]
# Insert the row-ID element before the currently first child element,
# which effectively moves it to the top.
$null = $_.InsertBefore($rowIdElem, $_.FirstChildNode)
}
至于你试过的:
$childNodes = $sequence.ChildNodes
有效地使$childNodes
指向存储在元素.ChildNodes
中的 子节点 的活动列表 属性(它是从System.Xml.XmlNodeList
派生的类型)因此,在调用
$sequence.ChildNodes.Clear()
移除所有子节点后,$sequence.ChildNodes
和$childNodes
都引用了一个空集合,即没有什么可枚举的,这就是为什么ForEach-Object
脚本块 ($childNodes | ForEach-Object { ... }
) 永远不会被调用的原因。相比之下,通过涉及
Sort-Object
调用 ($childNodes = $sequence.ChildNode | Sort-Object Name
),您会立即枚举子元素,并捕获 子元素的快照作为一个独立的数组([object[]]
类型的常规PowerShell数组),其元素是 稍后按预期枚举,导致ForEach-Object
按自定义顺序重新附加元素的调用按预期工作。