1

If I have a file with XML like:

<?xml version="1.0" encoding="utf-8"?>
<package date-created="19/07/2016" version="6" system="6">
  <description />
  <system />
  <res id="1057002654" name="ABC" path="/DEF" type="F" mimeType="application/x-shared">
    <description />
    <keywords />
    <resver id="1057014163" major="1" minor="0" revision="0" effective-from="20010101000000" effective-to="20991231235959" />
    <resver id="1057014677" major="2" minor="0" revision="0" effective-from="20010101000000" effective-to="20991231235959" />
    <resver id="1057015106" major="3" minor="0" revision="0" effective-from="20010101000000" effective-to="20991231235959" />
    <resver id="1057016183" major="4" minor="0" revision="0" effective-from="20010101000000" effective-to="20991231235959" />
    <resver id="1057016374" major="5" minor="0" revision="0" effective-from="20010101000000" effective-to="20991231235959" />
    <resver id="1057017622" major="6" minor="0" revision="0" effective-from="20010101000000" effective-to="20991231235959" />
    <resver id="1057017704" major="7" minor="0" revision="0" effective-from="20010101000000" effective-to="20991231235959" />
    <resver id="1057017863" major="8" minor="0" revision="0" effective-from="20010101000000" effective-to="20991231235959" />
  </res>
</package>

There could be hundreds of items, and within them there could be one or more . In Powershell, I'm trying to look for the last resver id value where the res id equalls 1057002654.

I started off with this:

$path = 'C:\TEST\package.xml'
$myxml = [xml](Get-Content $path)
$node = $myxml.package.res.resver | where {$_.id -eq "1057002654"}
$node.id = '123'
$myxml.Save($path)

This was just a quick test to see if I could get to the bit I wanted and update it, but that fails with:

Property 'id' cannot be found on this object; make sure it exists and is settable.

I then tried something like:

$xml = [xml](Get-Content 'C:\TEST\package.xml')
$nodes = $xml.SelectNodes("//*[@res]")
foreach ($node in $nodes) {

$node.Attributes | %{$_}  > C:\TEST\disk.txt }

This actually writes all the values in the attributes to a .txt file but cannot seem to find a way to actually do what I want.

/Edit - Corrected with what I actually meant to do. Ended up with code like this, where $Ref is an item in the array $References

foreach ($Ref in $References)
{
    $xml = [xml](Get-Content 'C:\TEST\package.xml')
    $res = $xml.SelectSingleNode('//res[@id = $Ref]/child::resver')
    $resource = $res.id + ".txt"

    # more stuff here
} 

Getting this error though:

Exception calling "SelectSingleNode" with "1" argument(s): "Namespace Manager or XsltContext needed. This query has a prefix, variable, or user-defined function."
At C:\TEST\script.ps1:22 char:38
+ [string] $res = $xml.SelectSingleNode <<<< ('//res[@id = $Ref]/child::resver')
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : DotNetMethodException
7
  • 5
    You should not use a regex to parse xml but xpath or xquery. fwiw - I can recommend RegexBuddy to get a better feel for regex (not affiliated but avid user). Composing the xquery is left as an excercise (read as: not my cup of tea, sorry) Commented Jul 20, 2016 at 5:31
  • 2
    That's not valid XML, the node name itself cannot have an attribute value (and = is not allowed as part of a name) Commented Jul 20, 2016 at 7:41
  • @LievenKeersmaekers - apologies for the really rubbish question. I have completely refactored it so it would be greatly appreciated if you could take a look please. Commented Jul 20, 2016 at 17:35
  • 1
    $xml.SelectNodes("//res[resver/@id=1057014163]/@id") Commented Jul 20, 2016 at 17:42
  • 2
    @hshah - really no need to apoligize, I was not offended in any way :). You have your answer (I couldn't provide it even if I wanted to) and I learned from it. Thank you ;) Commented Jul 25, 2016 at 18:49

1 Answer 1

3

Use the parent:: axis to find the appropriate parent node after locating the <resver /> node with the correct ID:

$path = 'C:\TEST\package.xml'
$myxml = [xml](Get-Content $path)
# Locate resver, select parent res node
$res = $myxml.SelectSingleNode('//resver[@id = "1057014163"]/parent::res')
# Update id
$res.SetAttribute('id','123')
# Save document
$myxml.Save($path)

You could also use the .. alias for the parent node:

//resver[@id = "1057014163"]/..

Or as PetSerAl notes, select the res node that has the described child node:

//res[resver/@id = "1057014163"]

For doing it the other way around (find all resver child node id values based on parent nodes id):

$ids = $myxml.SelectNodes('//res[@id = "1057002654"]/resver[@id]') |Select -Expand id

(notice use of SelectNodes() rather than SelectSingleNode())

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks to both you and @PetSerAl. Had been driving me nuts for ages! :)
Sorry guys, added a little more to my question as I realised I was being an idiot and looking at it the other way around. I have the res ids and need the last resver id. Got the code but it is giving me errors :(
I was still using SelectSingleNode() as I only want the last node :)
I think my error is occuring because I am trying to use a variable in the SelectSingleNode() and it doesn't seem to like that.
Swap double-quotes with single-quotes if you want variable expansion
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.