Reputation: 59
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
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