使用PowerShell解析XML文件,节点来自变量

wdebmtf2  于 2022-12-04  发布在  Shell
关注(0)|答案(4)|浏览(198)

亲爱的Powershell用户朋友们,您好!
我正在解析xml文件,这些文件的结构可能不同,因此,我希望根据从变量接收到的节点结构来访问节点值。
范例

#XML file
$xml = [xml] @'
<node1>
    <node2>
        <node3>
            <node4>test1</node4>
        </node3>
    </node2>
</node1>
'@

直接访问这些值是可行的。

#access XML node directly -works-
$xml.node1.node2.node3.node4        # working <OK>

无法通过变量中的节点信息访问值。

#access XML node via path from variable -does not work-
$testnodepath = 'node1.node2.node3.node4'

$xml.$testnodepath                  # NOT working
$xml.$($testnodepath)               # NOT working

是否有一种方法可以通过从变量接收节点信息来直接访问XML节点值?
PS:我知道,有一种通过Selectnode的方法,但我认为这是低效的,因为它基本上是搜索关键字。

#Working - but inefficient
$testnodepath = 'node1/node2/node3/node4'
$xml.SelectNodes($testnodepath)

我需要一种非常有效的方法来解析XML文件,因为我需要解析巨大的XML文件。是否有一种方法可以通过从变量接收节点结构来直接访问$xml.node1.node2.node3.node4形式的节点值?

kupeojn6

kupeojn61#

您可以使用ExecutionContext ExpandString

$ExecutionContext.InvokeCommand.ExpandString("`$(`$xml.$testnodepath)")
test1

如果节点路径($testnodepath)来自外部(例如参数),您可能希望通过条带化任何非单词字符或点的字符(.)来执行prevent any malicious code injections

$securenodepath = $testnodepath -Replace '[^\w\.]'
$ExecutionContext.InvokeCommand.ExpandString("`$(`$xml.$securenodepath)")
iezvtpos

iezvtpos2#

您可以将包含属性路径的字串分割成个别的名称,然后逐一取消指涉:

# define path
$testnodepath = 'node1.node2.node3.node4'

# create a new variable, this will be our intermediary for keeping track of each node/level we've resolved so far
$target = $xml

# now we just loop through each node name in the path
foreach($nodeName in $testnodepath.Split('.')){
  # keep advancing down through the path, 1 node name at a time
  $target = $target.$nodeName
}

# this now resolves to the same value as `$xml.node1.node2.node3.node4`
$target
hfsqlsce

hfsqlsce3#

我需要解析巨大的XML文件
下面介绍了一种内存友好的 * 流式 * 方法,它不需要加载整个XML文档(DOM)到内存中。因此,即使内存放不下,您也可以解析非常大的XML文件。它还可以提高 * 解析速度 *,因为我们可以跳过不感兴趣的元素。为了实现这一点,我们使用System.Xml.XmlReader * 动态 * 处理XML元素,同时从文件中读取它们。
我已经将代码 Package 在一个可重用函数中:

Function Import-XmlElementText( [String] $FilePath, [String[]] $ElementPath ) {

    $stream = $reader = $null

    try {
        $stream = [IO.File]::OpenRead(( Convert-Path -LiteralPath $FilePath )) 
        $reader = [System.Xml.XmlReader]::Create( $stream )

        $curElemPath = ''  # The current location in the XML document

        # While XML nodes are read from the file
        while( $reader.Read() ) {
            switch( $reader.NodeType ) {
                ([System.Xml.XmlNodeType]::Element) {
                    if( -not $reader.IsEmptyElement ) {
                        # Start of a non-empty element -> add to current path
                        $curElemPath += '/' + $reader.Name
                    }
                }
                ([System.Xml.XmlNodeType]::Text) {
                    # Element text -> collect if path matches
                    if( $curElemPath -in $ElementPath ) {
                        [PSCustomObject]@{
                            Path  = $curElemPath
                            Value = $reader.Value
                        }
                    }
                }
                ([System.Xml.XmlNodeType]::EndElement) {
                    # End of element - remove current element from the path
                    $curElemPath = $curElemPath.Substring( 0, $curElemPath.LastIndexOf('/') ) 
                }
            }
        }
    }
    finally {
        if( $reader ) { $reader.Close() }
        if( $stream ) { $stream.Close() }
    }
}

这样称呼它:

Import-XmlElementText -FilePath test.xml -ElementPath '/node1/node2a/node3a', '/node1/node2b'

给定此输入XML

<node1>
    <node2a>
        <node3a>test1</node3a>
        <node3b/>
        <node3c a='b'/>
        <node3d></node3d>
    </node2a>
    <node2b>test2</node2b>
</node1>

输出由以下人员生成:

Path                 Value
----                 -----
/node1/node2a/node3a test1
/node1/node2b        test2

实际上,该函数输出的对象可以由管道命令照常处理,也可以存储在数组中:

$foundElems = Import-XmlElementText -FilePath test.xml -ElementPath '/node1/node2a/node3a', '/node1/node2b'

$foundElems[1].Value  # Prints 'test2'

备注:

  • Convert-Path用于将PowerShell路径(也称为PSPath)(可能是相对路径)转换为.NET函数可以使用的绝对路径。这是必需的,因为.NET使用的 * 当前目录 * 与PowerShell不同,PowerShell路径的格式可能是.NET甚至无法理解的(例如Microsoft.PowerShell.Core\FileSystem::C:\something.txt)。
  • 当遇到元素的开头时,我们必须跳过空元素,如<node/>,因为对于这样的元素,我们不进入EndElement case分支,这将使当前路径($curElemPath)无效(该元素不会再次从当前路径中删除)。
w6mmgewl

w6mmgewl4#

我有一个类似的要求,但是,我的要求是使用变量设置引用节点的值。我们需要这个能力,这样我们就可以有一个脚本,它可以引用不同的psd 1文件,并正确地设置信息。硬编码路径意味着我们需要多个脚本来做同样的事情。正如你可以想象这是一个噩梦。
...以下作品。

[XML]$doc = Get-Content $my_xml_file
$xml_cfg = Import-LocalizedData = xml_information.psd1
$xml_path = "FinData.Header.Hdrinfo.From.CpnyId.Id.StoreId.Report.Id"
$doc.FinData.Header.Hdrinfo.From.CpnyId.Id.StoreId.Report.Id = $xml_cfg.from_id

但是,这会失败:$doc.$xml路径= xml配置文件.来源标识

ERROR: "The property 'FinData.Header.Hdrinfo.From.CpnyId.Id.StoreId.Report.Id' cannot be found on this object. Verify that the property exists and can be set."

...
PowerShell不能处理对对象的变量引用,这是一个真实的的耻辱。使用变量引用对象在Perl中工作得很好,由于这些限制,我们无法将所有代码迁移到PowerShell。

相关问题