如何更改 XmlElements 的顺序?

How do I change the order of XmlElements?

我有一个包含各种元素的 XML,但其中一个名为 RowId,我想移动到相应 XML 数组的顶部...基本上以我的方式对元素进行排序。我下面的代码可以 copied/pasted 供您测试。

  1. 完成此任务的最佳方法是什么? And/or有没有比我现在做的更好的方法?这看起来很笨拙。

  2. 如果我包含 | 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 按自定义顺序重新附加元素的调用按预期工作。