Reputation: 59
I am facing a problem. I want to get a specific id
string of product
node from XML content.
Example:
Run $xml.group.product[0].id
it returns 1234/5678
But I just want a $variable="1234"
to match id = "1234/5678"
to get or return a index of array ofproduct
node
How can I do that?
$xml=[xml]@"
<?xml version="1.0" encoding="UTF-8"?>
<group>
<product description="phone" id="1234/5678">
<item name="apple" version="50" />
<item name="banana" version="100" />
</product>
<product description="notebook" id="6666/7777">
<item name="orange" version="150" />
</product>
</group>
"@
Upvotes: 2
Views: 4868
Reputation: 437042
The simplest (but case-sensitive) way to perform literal prefix matching is to use the [string]
type's .StartsWith()
method.
You can combine it with the .Where()
array method (PSv4+) to find the first matching element whose id
attribute value starts with a given prefix, along with its (zero-based) index among its siblings.
# The prefix to match.
$variable = '1234'
# Initialize the matched element's index to -1
# which signals that no element was found.
$ndx = -1
# Use the .Where() method to find the (first) matching element.
$el = $xml.group.product.Where({ $_.id.StartsWith($variable); ++$ndx }, 'First')
Note: If you're using Windows PowerShell (as opposed to PowerShell 6+), it is better to use @($xml.group.product).Where(...)
to ensure that the .Where()
method call works even if there happens to be just one product
element (thanks, Steven).
With your sample XML,
$ndx
now contains 0
(indicating that the first product
element matched),$el
contains the matching element,<product description="phone" id="1234/5678">...</product>
.An aside re PowerShell's unified handling of scalars and collections:
PowerShell [Core] commendably consistently supports the .Where()
and .ForEach()
array methods even on scalars.
This unified handling also extends to:
scalars having an implied .Count
property that reports 1
(e.g., $scalar = 42; $scalar.Count
reporting 1
), sensibly treating a scalar as a one-element collection.
supporting indexing (e.g., $scalar[0]
and $scalar[-1]
being the same as $scalar
).
XmlElement
instances, whose own indexing provides access to child elements by name. This GitHub issue suggests that the PowerShell-provided positional indexing should still work in such cases, as long as it doesn't conflict with the type-native indexing.Upvotes: 1
Reputation: 7057
Considering the discussion:
$Index = $null
$Products = @($xml.group.product)
$Product =
:loop For($i = 0; $i -lt $Products.Length; ++$i)
{
If($Products[$i].id -match "^1234/") {
$Products[$i]
$Index = $i
break loop
}
}
Write-Host "Last index : $Index"
$Product
Recognizing the -1 trick from mklement0, but still playing around. I did have the same issue I mentioned, if there happens to be only 1 product there's no length property, so the loop won't even run. I had to wrap in @() to get around that. I can't flip to a string because 0 will always be -lt .length. So I thought I was being smart by spitting out $i after the break, but it will incorrectly be 0 too often.
So I had to use $Index. In concept this isn't very different if $Index is $null you didn't get a hit versus if $ndx is -1 you didn't get a hit. It also forced me to introduce $Products as wrapping in @() right in the For statement also showed some issues.
So this is a lot less concise... Just an exercise mklement0's answer is likely to be faster. Forgive me for going on & on...
Upvotes: 0
Reputation: 7057
There are a couple of ways I can think to quickly do this.
$xml.group.product | Where-Object{$_.id -match "^1234/"}
This basically means starts with 1234 and should output something like:
description id
phone 1234/5678
If you only want the first part you are going may have to split it off :
($xml.group.product | Where-Object{$_.id -match "^1234/"}).id.Split("/")[0]
It might help to know what the 2 parts of the number mean. if there's an intention on using that first part throughout the program, it might be worth adding it as it's own property.
Oh if you really want to return a Boolean the RegEx match will still work. but it would look more like:
'1234/5678' -match '^1234/'
This would return true, but given you have multiple products the If logic will have to be in a loop somewhere. In the answers so far the loop and the if logic are implied by the Where clause.
Let me know if that helps...
Upvotes: 0
Reputation: 58931
If I understand it correctly, you want to retrieve a product based on a specific id?
You can do this using the Where-Object
cmdlet. Example:
$xml.group.product | Where-Object { $_.id -eq '1234/5678'}
Upvotes: 0