5

我有一个包含各种元素的 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
4

2 回答 2

1

I think DOM NodeLists are "live" collections in normal DOM use ("live" I think was the spec term in the W3C spec, the Microsoft docs calls them dynamic: https://docs.microsoft.com/en-us/dotnet/standard/data/xml/dynamic-updates-to-nodelists-and-namednodemaps) so your attempt $childNodes = $sequence.ChildNodes stores that live NodeList in a variable and as such a collection it reflects any change to the DOM tree. The Powershell use of piping that collection to Sort-Object with $sequence.ChildNodes | Sort-Object -Property "Name" does not return an XmlNodeList or other live/dynamic collection, rather it returns an array of objects, e.g. XmlNode[] or object[], so that way your code works as you want it to work as you later process that array and not the XmlNodeList that tracked the changes to the DOM tree.

于 2021-12-21T19:07:50.703 回答
1

To recap what you've already attempted in principle: In oder to modify the XML DOM stored in $rawXML directly, you need to use the [xml] (System.Xml.XmlDocument) .NET type's own methods.

The following simplified approach locates the child element of interest first and then uses .InsertBefore() to move it to the top (there is no need to remove the element being moved before inserting it):

$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)

}

As for what you tried:

  • $childNodes = $sequence.ChildNodes effectively makes $childNodes point to the live list of child nodes stored in the element's .ChildNodes property (which is of a type derived from System.Xml.XmlNodeList)

  • Therefore, after calling $sequence.ChildNodes.Clear() to remove all child nodes, both $sequence.ChildNodes and $childNodes refer to an empty collection, i.e. there's nothing to enumerate, which is why the ForEach-Object script block ($childNodes | ForEach-Object { ... } ) is never called.

  • By contrast, by involving a Sort-Object call ($childNodes = $sequence.ChildNode | Sort-Object Name), you're enumerating the child elements right there and then, and capturing a snapshot of the child elements as an independent array (a regular PowerShell array of type [object[]]), whose elements are enumerated as expected later, causing the ForEach-Object call for reattaching the elements in custom order to work as expected.

于 2021-12-21T19:16:30.230 回答