【问题标题】:如何更改 XmlElements 的顺序?
【发布时间】:2022-01-23 03:24:00
【问题描述】:

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

  1. 实现此目的的最佳方法是什么?和/或有比我现在做的更好的方法吗?这看起来很笨拙。

  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

【问题讨论】:

    标签: xml powershell element xmlelement


    【解决方案1】:

    总结一下您已经尝试过的原则:为了直接修改存储在$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 调用以自定义顺序重新附加元素预计。

    【讨论】:

    • Marting 指出它返回的是 XmlNodeList 而不是数组。只需通过[Array] 进行投射,我的原始代码就可以工作。
    【解决方案2】:

    我认为 DOM NodeLists 是正常 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 使用管道将该集合与$sequence.ChildNodes | Sort-Object -Property "Name" 连接到$sequence.ChildNodes | Sort-Object -Property "Name" 不会返回 XmlNodeList 或其他实时/动态集合,而是返回一个对象数组,例如XmlNode[]object[],这样您的代码就会按照您希望的方式工作,因为您稍后会处理该数组,而不是跟踪 DOM 树更改的 XmlNodeList。

    【讨论】:

    • 它看起来像一个指针,我需要以某种方式复制ChildNodes
    • @WilliamYK,.NET 有引用,而不是指针,但这不是问题,一旦您在 XmlNodeList 上使用 Sort-Object,您就不再有一个存储在新分配的变量 $childNodes = $sequence.ChildNodes | Sort-Object -Property "Name"但只有 XmlNodes 的对象数组,因此您可以迭代这样的数组,因为它不会跟踪 DOM 树中的更改。
    • 根据您的逻辑,只需转换返回值 ($childNodes = [Array]$sequence.ChildNodes) 就可以使我的原始代码工作......它现在可以工作了。我只能选择一个答案,但我可以投票。感谢您的帮助!
    猜你喜欢
    • 1970-01-01
    • 2015-11-17
    • 2014-05-09
    • 2011-04-15
    • 2021-01-26
    相关资源
    最近更新 更多