mystack
mystack

Reputation: 5502

Grouping the elements inside the XML based on Attribute value

I have an xml file as given below

   <Books>
 <Book rev="19ver" nver="1.0.0.0" >
<Book rev="19Sub4" nver="1.4.0.250">
  <Book rev="19Sub5" nver="1.5.0.250" >
     <Book rev="19Sub5Fix2" nver="1.5.2.250" req="true">
    </Book>
    <Book rev="19Sub5Fix1" nver="1.5.1.250" req="false">
    </Book>
  </Book>
</Book>
<Book rev="20ver" nver="2.0.0.0" >
 <Book rev="20Sub3" nver="2.3.0.1111">
<Book rev="20Sub3Fix5" nver="2.3.5.1111"  req="true" >
</Book>
<Book rev="20Sub3Fix4" nver="2.3.4.1111"  req="true" >
</Book>
 <Book rev="20Sub3Fix3" nver="2.3.3.1111"  req="false" >
</Book>
<Book rev="20Sub3Fix2" nver="2.3.2.1111"  req="false" >
</Book>
<Book rev="20Sub4" nver="2.4.0.1567" >
  <Book rev="20Sub4Fix5" nver="2.4.5.1567"  req="true" >
  </Book>
  <Book rev="20Sub4Fix4" nver="2.4.4.1567"  req="true" >
  </Book>
  <Book rev="20Sub4Fix3" nver="2.4.3.1567"  req="false" >
  </Book>
  <Book rev="20Sub4Fix2" nver="2.4.2.1567"  req="false" >
  </Book>
   <Book rev="20Sub4Fix1" nver="2.4.1.1567"  req="false" >
    </Book>
    </Book>
 </Book>
 </Book>
 </Book>
 </Books>

I have a powershell script to add a new element inside the specified node. For example if i want to enter the element with rev "20Sub3Fix6" and nver "2.3.6.1111" inside "20Sub3" i can use the following code.

$parentXML = Select-Xml -Xml $bookInfo -XPath "//*[@rev='20Sub3']"
$bookInfo = Get-Content -Raw -Path $XMLPATH
$book = $bookInfo.CreateNode("element","book","")             
$book.SetAttribute("rev","20Sub3Fix6")
$book.SetAttribute("nver","2.3.6.1111")
$book.SetAttribute("req","true")
$rslt = $parentXML.Node.prependChild($book)
$bookInfo.Save($XMLPATH)

What I'm looking is to group the element based on the value of attribute "req". When I add an element with req "false" then it should add on top of the last element with "req=false. Also when I add an element with req "true" then it should add on top of the last element with "req=true".

<Book rev="20Sub1" nver="2.1.0.769" >
   <Book rev="20Sub1Fix7" nver="2.1.7.769" req="true"  >
  </Book>
  <Book rev="20Sub1Fix6" nver="2.1.6.769"  req="true" >
  </Book>
  <Book rev="20Sub1Fix4" nver="2.1.4.769"  req="true" >
  </Book>
  <Book rev="20Sub1Fix3" nver="2.1.3.769"  req="false" >
  </Book>
  <Book rev="20Sub1Fix2" nver="2.1.2.769"  req="false" >
  </Book>
  <Book rev="20Sub1Fix1" nver="2.1.1.769"  req="false" >
  </Book>
 </Book>

Adding C# and .Net tags also because if you know the answer in C# then you can post that too

Upvotes: 0

Views: 219

Answers (2)

Theo
Theo

Reputation: 61148

I have formatted your XML to have a better understanding of its format:

<Books>
  <Book rev="19ver" nver="1.0.0.0" >
    <Book rev="19Sub4" nver="1.4.0.250">
      <Book rev="19Sub5" nver="1.5.0.250" >
        <Book rev="19Sub5Fix2" nver="1.5.2.250" req="true"></Book>
        <Book rev="19Sub5Fix1" nver="1.5.1.250" req="false"></Book>
      </Book>
    </Book>
    <Book rev="20ver" nver="2.0.0.0" >
      <Book rev="20Sub3" nver="2.3.0.1111">
        <Book rev="20Sub3Fix5" nver="2.3.5.1111"  req="true" ></Book>
        <Book rev="20Sub3Fix4" nver="2.3.4.1111"  req="true" ></Book>
        <Book rev="20Sub3Fix3" nver="2.3.3.1111"  req="false" ></Book>
        <Book rev="20Sub3Fix2" nver="2.3.2.1111"  req="false" ></Book>
        <Book rev="20Sub4" nver="2.4.0.1567" >
          <Book rev="20Sub4Fix5" nver="2.4.5.1567"  req="true" ></Book>
          <Book rev="20Sub4Fix4" nver="2.4.4.1567"  req="true" ></Book>
          <Book rev="20Sub4Fix3" nver="2.4.3.1567"  req="false" ></Book>
          <Book rev="20Sub4Fix2" nver="2.4.2.1567"  req="false" ></Book>
          <Book rev="20Sub4Fix1" nver="2.4.1.1567"  req="false" ></Book>
        </Book>
      </Book>
    </Book>
  </Book>
</Books>

To do what you want, you could use the code below:

# load the xml from file
$xml= New-Object System.XML.XMLDocument
$xml.Load("D:\Test\MyBooks.xml")

# create your new node
$newNode = $xml.CreateElement("Book")
$newNode.SetAttribute("rev", "20Sub3Fix6")
$newNode.SetAttribute("nver", "2.3.6.1111")
$newNode.SetAttribute("req", "true")

# get the parentnode to insert the new node in
$node20Sub3   = $xml.SelectSingleNode("//Book[@rev='20Sub3']")
# find the first node where attribute 'req' is 'true'
$insertBefore = ($node20Sub3.ChildNodes | Where-Object { $_.rev.StartsWith("20Sub3Fix") -and $_.req -eq 'true' })[0]
# insert your new node on top of that
$node20Sub3.InsertBefore($newNode, $insertBefore)

# save the XML
$xml.Save("D:\Test\MyBooks.xml")

Result:

<Books>
  <Book rev="19ver" nver="1.0.0.0">
    <Book rev="19Sub4" nver="1.4.0.250">
      <Book rev="19Sub5" nver="1.5.0.250">
        <Book rev="19Sub5Fix2" nver="1.5.2.250" req="true"></Book>
        <Book rev="19Sub5Fix1" nver="1.5.1.250" req="false"></Book>
      </Book>
    </Book>
    <Book rev="20ver" nver="2.0.0.0">
      <Book rev="20Sub3" nver="2.3.0.1111">
        <Book rev="20Sub3Fix6" nver="2.3.6.1111" req="true"></Book>
        <Book rev="20Sub3Fix5" nver="2.3.5.1111" req="true"></Book>
        <Book rev="20Sub3Fix4" nver="2.3.4.1111" req="true"></Book>
        <Book rev="20Sub3Fix3" nver="2.3.3.1111" req="false"></Book>
        <Book rev="20Sub3Fix2" nver="2.3.2.1111" req="false"></Book>
        <Book rev="20Sub4" nver="2.4.0.1567">
          <Book rev="20Sub4Fix5" nver="2.4.5.1567" req="true"></Book>
          <Book rev="20Sub4Fix4" nver="2.4.4.1567" req="true"></Book>
          <Book rev="20Sub4Fix3" nver="2.4.3.1567" req="false"></Book>
          <Book rev="20Sub4Fix2" nver="2.4.2.1567" req="false"></Book>
          <Book rev="20Sub4Fix1" nver="2.4.1.1567" req="false"></Book>
        </Book>
      </Book>
    </Book>
  </Book>
</Books>

Upvotes: 1

Martin Honnen
Martin Honnen

Reputation: 167706

Try e.g. $rslt = $parentXML.Node.InsertBefore($book, $parentXML.Node.SelectSingleNode(string.Format("Book[@req = '{0}']", $req))). So basically introduce a variable for the req value, select the right sibling using SelectSingleNode if it exists and use InsertBefore.

I am not sure Powershell groks the C# string.Format but I guess you can adapt that (System.String.Format?).

Upvotes: 0

Related Questions