Narav
Narav

Reputation: 59

How to get depth in depth xml value in powershell

I have below xml file , have id and value . Need result of field_name =test2 with id 3

    <Types>
<Type field_name="Test1">
    <items>
        <item>
            <id>1</id>
            <value>A</value>
        </item>
        <item>
            <id>2</id>
            <value>B</value>
        </item>
        <item>
            <id>3</id>
            <value>C</value>
        </item>
        <item>
            <id>4</id>
            <value>D</value>
        </item>
    </items>
    </Type>
    <Type field_name="Test2">
    <items>
        <item>
            <id>1</id>
            <value>A</value>
        </item>
        <item>
            <id>2</id>
            <value>B</value>
        </item>
        <item>
            <id>3</id>
            <value>C</value>
        </item>
        <item>
            <id>4</id>
            <value>D</value>
        </item>
    </items>
    </Type>
</Types>

I needs to get result by id with field_name =test2 , I had tried below code but no luck.

$xml = [xml](Get-Content "C:\Test.xml")
$xml.types.type.items.item | ? {$_.id -eq 3} | select value 

Upvotes: 5

Views: 589

Answers (1)

mklement0
mklement0

Reputation: 440337

# This is a more robust way to load an XML file (PSv5+).
# In earlier versions, use ($xml = New-Object xml)...
($xml = [xml]::new()).Load((Convert-Path C:\Test.xml))

# PSv4+ syntax
@($xml.types.type.Where({ $_.'field_name' -eq 'Test2' }, 'First').
  items).ForEach({ $_.item }).Where({ $_.id -eq 3 }).value

Note the use of the PSv4+ .Where() and .ForEach() array methods for filtering and enumeration, and, specifically, the need to explicitly enumerate the <item> child elements with .ForEach() (see bottom section).

Note: Strictly speaking, since the .Where() call only returns a single object that has only a single items child element, the .ForEach() isn't strictly necessary in this case, but in your original code it is.
Also note the use of @(...), which is only necessary in Windows PowerShell, due to a bug that has since been fixed in PowerShell (Core) 7+


However, it's easier to use an XPath query, which, in combination with Select-Xml, enables a single-command solution:

(Select-Xml '//Type[@field_name="Test2"]/items/item[id=3]' C:\Test.xml).Node.value

Note:

  • If your XML document uses namespaces, you must pass a hashtable with prefix-to-URI mappings to the -Namespace parameter, and use these prefixes in the query when referring to elements - see this answer for more information.

  • By contrast, if you use PowerShell's XML DOM adaption (property-based access), namespace declarations and namespace prefixes in element names are ignored.


As for what you tried:

Apart from your code not trying to filter by field_name="Test2" (which may not be necessary if the id values are unique across the entire document), your problem was the use of the .item property on the array-valued parent property, .items:

Because .items is an array and arrays have a type-native .Item member[1], that member takes precedence over PowerShell's adaptation of the XML DOM (where attributes and child elements appear as if they were properties), so that the child XML elements named item are no longer directly accessible with .item.

Explicit enumeration of the array elements (which are XML elements in this case) - via .ForEach({ $_.item })[2] - bypasses the problem, and accessing the .item property on each [XmlElement] then works as intended.

Note: XmlElements too have a type-native .Item property, but in the context of PowerShell's adaptation of the XML DOM the logic is reversed: it is the adapted properties that take precedence over the type-native ones (the latter can be accessed as methods with a get_ prefix; e.g., .get_Item()) - see this answer for an example with an adapted .Name property (from a child element or attribute) shadowing the element's type-native one.


[1] Technically, the parameterized .Item property is exposed via an interface (IList), but PowerShell surfaces such explicit-interface-implementation properties as if they were direct properties of a type.

[2] Ideally, you'd be able to use .ForEach('item') as a faster alternative, but as of PowerShell (Core) 7.2 this doesn't work due to a bug, detailed in GitHub issue #15994.

Upvotes: 5

Related Questions