无法在PowerShell中完全解析XML

4
我是一名有用的助手,可以为您翻译文本。

我有一个XML文件,想要解析它,并检索特定的信息。

为了更容易理解,这里是XML文件的截图:

enter image description here

我希望能够解析XML,并针对每个Item节点,检索出截图中指示的字段。检索到的每个值都需要按项节点格式化。 最后,我希望能够指定一个条件进行查找,并仅在找到时检索该条件。 我一直在尝试,但没有成功。以下是我能想到的代码:
[xml]$MyXMLFile = gc 'X:\folder\my.xml'
$XMLItem = $MyXMLFile.PatchScan.Machine.Product.Item
$Patch = $XMLItem | Where-Object {$_.Class -eq 'Patch'}
$Patch.BulletinID
$Patch.PatchName
$Patch.Status
当我运行以上代码时,没有返回结果。但是,仅用于测试目的,我删除了 Item 部分。现在,通过修改上面的代码,我可以使其工作。 我将 XML 加载到一个 XML 对象中。现在我尝试将其遍历到 product,它完美地工作: PS> $xmlobj.PatchScan.Machine.Product | Select-Object -Property Name, SP Name SP ---- -- Windows 10 Pro (x64) 1607 Internet Explorer 11 (x64) Gold Windows Media Player 12.0 Gold MDAC 6.3 (x64) Gold .NET Framework 4.7 (x64) Gold MSXML 3.0 SP11 MSXML 6.0 (x64) SP3 DirectX 9.0c Gold Adobe Flash 23 Gold VMware Tools x64 Gold Microsoft Visual C++ 2008 SP1 Redistributable Gold Microsoft Visual C++ 2008 SP1 Redistributable (x64) Gold 现在添加Item,智能感知会放置一个括号,就好像Item是一个方法一样$xmlobj.PatchScan.Machine.Product.Item( ← 看到了吗?这就是为什么我认为某种原因导致Item节点做了一些奇怪的事情,这就是我的障碍所在。 这张截图更好地展示了它是如何从许多产品文件夹开始的,然后在每个产品文件夹中有许多项目文件夹。

enter image description here

我不关心产品文件夹中的XML。我需要每个项目文件夹中的个人信息。

2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
4

XML是一种结构化文本格式,它并不知道所谓的“文件夹”。您在截图中看到的只是您用于显示数据的程序渲染数据的方式。

无论如何,获得您想要的最佳方法是使用带有XPath表达式的SelectNodes()。像往常一样。

[xml]$xml = Get-Content 'X:\folder\my.xml'
$xml.SelectNodes('//Product/Item[@Class="Patch"]') |
    Select-Object BulletinID, PatchName, Status

+1 对于 XPath 解决方案,但请注意 OP 的唯一问题是名称冲突。简而言之,OP 的命令中断了,因为 Item[Array] 类型的属性名称发生了冲突。 - mklement0

3

简短概述:

如您所怀疑的,名称冲突阻碍了对感兴趣的XML元素上的.Item属性的访问;使用父级元素的显式枚举来解决问题:

$xml.PatchScan.Machine.Product |
  % { $_.Item | select BulletinId, PatchName, Status }

% 是内置别名,代表 ForEach-Object 命令;请参见底部的说明部分。


作为一种替代方案Ansgar Wiecher的有用答案提供了一个简洁的XPath解决方案,既高效又允许复杂查询。 另外:PowerShell v3+附带了Select-Xml cmdlet,它以文件路径作为参数,允许单管道解决方案:
(Select-Xml -LiteralPath X:\folder\my.xml '//Product/Item[@Class="Patch"]').Node |
  Select-Object BulletinId, PatchName, Status

注意:

  • Select-Xml将匹配的XML节点包装在外部对象中,因此需要访问.Node属性。

  • 与直接使用.NET API一样,查询具有命名空间的XML文档需要进行额外的工作,即声明一个(散列)表格,其中包含映射到命名空间URI的命名空间前缀,并在XPath查询中使用这些前缀-请参见this answer


PowerShell对XML DOM(点表示法)的适应:

PowerShell“装饰”了包含在[System.Xml.XmlDocument]实例中的对象层次结构(例如使用转换[xml]创建):

  • 在每个级别上,使用命名为输入文档特定元素和属性的属性[1];例如:

     ([xml] '<foo><bar>baz</bar></foo>').foo.bar # -> 'baz'
     ([xml] '<foo><bar id="1" /></foo>').foo.bar.id # -> '1'
    
  • 将给定层次结构级别上的多个相同名称的元素隐式转换为数组(具体而言,类型为[object[]]);例如:

     ([xml] '<foo><C>one</C><C>two</C></foo>').foo.C[1] # -> 'two'
    

正如示例(以及您在问题中的代码)所示,这允许通过方便的点符号访问。

注意:如果您使用点符号来定位至少具有一个属性和/或子元素的元素,则返回元素本身(XmlElement实例);否则,它是元素的文本内容;有关通过点符号更新XML文档的信息,请参见this answer

点符号的缺点是可能会出现名称冲突,如果偶然输入的XML元素名称恰好与内在的[System.Xml.XmlElement]属性名称(对于单个元素属性)或内在的[Array]属性名称(对于数组值属性;[System.Object[]]派生自[Array])相同。

在名称冲突的情况下: 如果被访问的属性包含:

  • 一个单一的子元素 ([System.Xml.XmlElement]),偶然的属性会胜出

    • 这也可能会有问题,因为它使访问内在类型属性变得不可预测 - 请参见底部章节。
  • 一组子元素的数组[Array]类型的属性胜出。

    • 因此,以下元素名称打破了使用反射命令获取带有阵列值属性的点符号的约定
      Get-Member -InputObject 1, 2 -Type Properties, ParameterizedProperty

          Item Count IsFixedSize IsReadOnly IsSynchronized Length LongLenth Rank SyncRoot
      

参见最后一节,了解此差异的讨论以及在发生冲突时如何访问内在的 [System.Xml.XmlElement] 属性。

解决方法是使用显式枚举数组值属性,使用上面演示过的 ForEach-Object 命令,下面是完整示例:

[xml] $xml = @'
<PatchScan>
  <Machine>
    <Product>
      <Name>Windows 10 Pro (x64)</Name>
      <Item Class="Patch">
        <BulletinId>MSAF-054</BulletinId>
        <PatchName>windows10.0-kb3189031-x64.msu</PatchName>
        <Status>Installed</Status>
      </Item>
      <Item Class="Patch">
        <BulletinId>MSAF-055</BulletinId>
        <PatchName>windows10.0-kb3189032-x64.msu</PatchName>
        <Status>Not Installed</Status>
      </Item>
    </Product>
    <Product>
      <Name>Windows 7 Pro (x86)</Name>
      <Item Class="Patch">
        <BulletinId>MSAF-154</BulletinId>
        <PatchName>windows7-kb3189031-x86.msu</PatchName>
        <Status>Partly Installed</Status>
      </Item>
      <Item Class="Patch">
        <BulletinId>MSAF-155</BulletinId>
        <PatchName>windows7-kb3189032-x86.msu</PatchName>
        <Status>Uninstalled</Status>
      </Item>
    </Product>
  </Machine>
</PatchScan>
'@

# Enumerate the array-valued .Product property explicitly, so that
# the .Item property can successfully be accessed on each XmlElement instance.
$xml.PatchScan.Machine.Product | 
  ForEach-Object { $_.Item | Select-Object BulletinID, PatchName, Status }

以上产生:

Class BulletinId PatchName                     Status          
----- ---------- ---------                     ------          
Patch MSAF-054   windows10.0-kb3189031-x64.msu Installed       
Patch MSAF-055   windows10.0-kb3189032-x64.msu Not Installed   
Patch MSAF-154   windows7-kb3189031-x86.msu    Partly Installed
Patch MSAF-155   windows7-kb3189032-x86.msu    Uninstalled     

深入了解:当什么属性被遮蔽时:

注意:在这里,遮蔽的意思是,在名称冲突的情况下,“获胜”的属性 - 其值被报告的属性 - 有效地隐藏了另一个属性,从而将其“放在阴影中”。


在使用数组的点符号表示法时,一个名为成员访问枚举的特性就会发挥作用,它适用于PowerShell v3+中的任何集合;换句话说:这种行为不仅仅适用于[xml]类型。

简而言之:隐式地访问集合上的属性将访问集合中每个成员(集合中的项)的属性,并将结果值作为数组[System.Object[]])返回;例如:

# Using member-access enumeration, collect the value of the .prop property from
# the array's individual *members*.
> ([pscustomobject] @{ prop = 10 }, [pscustomobject] @{ prop = 20 }).prop
10
20

然而,如果集合类型本身具有该名称的属性,则集合自己的属性优先;例如:

# !! Since arrays themselves have a property named .Count,
# !! member-access enumeration does NOT occur here.
> ([pscustomobject] @{ count = 10 }, [pscustomobject] @{ count = 20 }).Count
2  # !! The *array's* count property was accessed, returning the count of elements

在使用点符号与[xml](PowerShell修饰的System.Xml.XmlDocumentSystem.Xml.XmlElement实例)时,PowerShell添加的附加属性会遮盖类型固有属性:[2]

虽然这种行为很容易理解,但结果取决于具体的输入,这也可能是危险的:

例如,在以下示例中,附加的name 子元素会遮盖元素本身上同名的固有属性:

> ([xml] '<xml><child>foo</child></xml>').xml.Name
xml  # OK: The element's *own* name

> ([xml] '<xml><name>foo</name></xml>').xml.Name
foo  # !! .name was interpreted as the incidental *child* element

如果您确实需要访问内置类型的属性,请使用.get_<property-name>()

> ([xml] '<xml><name>foo</name></xml>').xml.get_Name()
xml  # OK - intrinsic property value to use of .get_*()

[1] 如果给定元素具有相同名称的属性元素,则PowerShell报告两者,作为数组[object[]]的元素。

[2] 表面上看起来,当PowerShell在幕后适应基础System.Xml.XmlElement类型时,它不会直接公开其属性,而是通过get_*访问器方法进行访问,这仍然允许访问,就好像它们属性,但由于PowerShell添加的附带但合法的属性优先级更高。如果您对此了解更多,请让我们知道。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,