【问题标题】:How to convert XML to object when some attributes are missing?缺少某些属性时如何将 XML 转换为对象?
【发布时间】:2023-03-06 03:47:01
【问题描述】:

问题

我正在尝试将位置的 XML 列表转换为一组对象。

XML 文件的每个位置都有一个 Location 元素。 XML 属性包含对象属性值。

我希望某些目标属性具有与源属性不同的名称。

稍后我想将对象序列化为 CSV 文件,因此属性的顺序很重要。

这里有两个位置的示例来说明问题:

[xml] $InputXml = @'
<?xml version="1.0" encoding="UTF-8"?>

<CTLocations xmlns="http://www.cartrawler.com/">
<Country code="AL" name="Albania" continent="Europe">
<Location Id="7188" Name="Tirana Airport" Lat="41.42108838" Lng="19.71271276" CountryCode="AL" Address="Tirana Airport Muhamet Gjollesha Str., Muhamet Gjollesha Str., Tirana" CityName="Tirana" Airport="1" AirportCode="TIA" RailwayStation="0"/>
<Location Id="30768" Name="Tirana Downtown" Lat="41.332" Lng="19.832" CountryCode="AL" Address="Rruga E Durresit. Nr 61, Tirana" CityName="Tirana" Airport="0" RailwayStation="0"/>
</Country>
</CTLocations>
'@

部分解决方案

我的解决方案适用于具有完整属性集的元素,但在缺少任何属性时会失败。

我使用Select-Xml cmdlet 来选择重要元素,并使用PSCustomObject 创建一个具有有序、重命名属性的对象。

Select-Xml -Xml $InputXml -XPath '//ns:Location' -Namespace @{ns = 'http://www.cartrawler.com/'} |
% {
  $n = $_.Node

  [PSCustomObject] @{
    LocationCode = $n.Id
    LocationName = $n.Name
    Latitude = $n.Lat
    Longitude = $n.Lng
    CountryCode = $n.CountryCode
    FormattedAddress = $n.Address
    CityName = $n.CityName
    ServesAirport = $n.Airport
    AirportCode = $n.AirportCode
    ServesRailwayStation = $n.RailwayStation
  }
}

严格模式结果

当我设置了严格模式时:

Set-StrictMode -Version 3.0

第一个元素包含所有属性,所以被转换为一个对象:

LocationCode         : 7188
LocationName         : Tirana Airport
Latitude             : 41.42108838
Longitude            : 19.71271276
CountryCode          : AL
FormattedAddress     : Tirana Airport Muhamet Gjollesha Str., Muhamet Gjollesha Str., Tirana
CityName             : Tirana
ServesAirport        : 1
AirportCode          : TIA
ServesRailwayStation : 0

第二个元素缺少 AirportCode 属性,因此 PowerShell 会引发异常:

Property 'AirportCode' cannot be found on this object. Make sure that it exists.
At line:16 char:3
+   [PSCustomObject] @{
+   ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], PropertyNotFoundException
    + FullyQualifiedErrorId : PropertyNotFoundStrict

Unstrict 模式结果

当我关闭严格模式时:

Set-StrictMode -Off

PowerShell 将这两个位置都转换为对象。缺少的属性为空:

LocationCode         : 7188
LocationName         : Tirana Airport
Latitude             : 41.42108838
Longitude            : 19.71271276
CountryCode          : AL
FormattedAddress     : Tirana Airport Muhamet Gjollesha Str., Muhamet Gjollesha Str., Tirana
CityName             : Tirana
ServesAirport        : 1
AirportCode          : TIA
ServesRailwayStation : 0

LocationCode         : 30768
LocationName         : Tirana Downtown
Latitude             : 41.332
Longitude            : 19.832
CountryCode          : AL
FormattedAddress     : Rruga E Durresit. Nr 61, Tirana
CityName             : Tirana
ServesAirport        : 0
AirportCode          : 
ServesRailwayStation : 0

有没有更好的办法?

如果有更好的方法,我不想关闭严格模式来完成这项工作。

任何在严格模式下工作的东西都是可以接受的。我一直在寻找类似T-SQL CASE expressionPython's dict get method 的东西。也许还有一种 XPath 方法可以做到这一点。

【问题讨论】:

  • ($n | select AirportCode).AirportCode 是可接受的解决方法吗?

标签: xml powershell


【解决方案1】:

在你的情况下,这很容易:

select-Xml -Xml $InputXml -XPath '//ns:Location' -Namespace @{ns = 'http://www.cartrawler.com/'} |
% {
  $n = $_.Node

  [PSCustomObject] @{
    LocationCode = $n.Id
    LocationName = $n.Name
    Latitude = $n.Lat
    Longitude = $n.Lng
    CountryCode = $n.CountryCode
    FormattedAddress = $n.Address
    CityName = $n.CityName
    ServesAirport = $n.Airport
    AirportCode = $(if($n.Airport -eq '1'){$n.AirportCode}else{""})
    ServesRailwayStation = $n.RailwayStation
  }
}

更笼统地说:

select-Xml -Xml $InputXml -XPath '//ns:Location' -Namespace @{ns = 'http://www.cartrawler.com/'} |
% {
  $n = $_.Node

  [PSCustomObject] @{
    LocationCode = $n.Id
    LocationName = $n.Name
    Latitude = $n.Lat
    Longitude = $n.Lng
    CountryCode = $n.CountryCode
    FormattedAddress = $n.Address
    CityName = $n.CityName
    ServesAirport = $n.Airport
    AirportCode = $(if($n.GetAttributeNode("AirportCode") -ne $null){$n.AirportCode}else{""})
    ServesRailwayStation = $n.RailwayStation
  }
}

写完后,我的老程序员意见(你不在乎的那种)是,如果你想使用严格模式,只需停止脚本并开始编写 java、C#、C 或 C++。您正在与严格模式作斗争的那种近似是脚本的+,这对我来说可以快速解决桌子一角的问题。我的观点是,使用严格模式会带来传统编程没有兴趣的所有缺点,但这只是我的观点。

【讨论】:

  • 一声“啊哈!”片刻。我希望我有类似C's ternary operator (?:) 的东西。 $(if(){}else{}) 似乎是 PowerShell 的等价物。
  • 我重视您的意见。严格模式当然可以捕获意外错误,但我不应该对此着迷。我更喜欢 unstrict 解决方案的优雅,它完全符合我的要求,而且更容易理解。感谢您向我展示了如何严格执行此操作,但最后我可能会更好地放松语法。
  • @IainElder 你是 not alone 有这个愿望。
  • 这不是仍然会引发同样的异常吗?
  • 我的测试不是这样。你有例外吗?
猜你喜欢
  • 2013-07-29
  • 2017-03-01
  • 2019-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-10
  • 1970-01-01
相关资源
最近更新 更多