Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding and Removing Xmlnode using Powershell

I am trying to add and remove elements from multiple xml files. i ran into two issue. it removed empty elements automatically and second it did not remove what i wanted to

I have my xml like this

<Entity>
   <App>
      <item1>1</item1>
      <emptyItem/>
      <person>
         <itemToRemove>true</itemToRemove>
         <emptyItem/>
         <otheritem>1</otheritem>
      </person>
      <person>
         <itemToRemove>false</itemToRemove>
         <emptyItem/>
         <otheritem>3</otheritem>
      </person>
      <person>
         <itemToRemove>false</itemToRemove>
         <emptyItem/>
         <otheritem>3</otheritem>
      </person>
   </App>
</Entity>

what i want is

<Entity>
   <App>
      <item1>1</item1>
      <emptyItem/>
      <person>

         <emptyItem/>
         <otheritem>1</otheritem>
      </person>
      <person>

         <emptyItem/>
         <otheritem>3</otheritem>
      </person>
      <person>

         <emptyItem/>
         <otheritem>3</otheritem>
      </person>
      <newItemtoAdd>2001-01-01</newItemtoAdd>
   </App>
</Entity>

i added newItemtoAdd element and removed itemToRemove on output xml above.

with the current script i have used what i get is this

<Entity>
   <App>
      <item1>1</item1>
      <emptyItem/>
      <person>
         <itemToRemove>true</itemToRemove>
         <otheritem>1</otheritem>
      </person>
      <person>
         <itemToRemove>false</itemToRemove>
         <otheritem>3</otheritem>
      </person>
      <person>
         <itemToRemove>false</itemToRemove>
         <otheritem>3</otheritem>
      </person>
      <newItemtoAdd>2001-01-01</newItemtoAdd>
   </App>
</Entity>

emptyItem node is removed which i do not want to happen, newItemtoAdd is added which is good, and itemToRemove node is not removed which is not what i want

here is my script

Get-ChildItem D:\Projects\*.xml | 
    % { 
        [xml]$xml      = [xml](Get-Content $_.fullname)
        $newItemtoAdd = $xml.CreateElement('newItemtoAdd')
        $newItemtoAdd.PsBase.InnerText = '1900-01-01'
        $null     = $xml.Entity.App.AppendChild($newItemtoAdd)

    $xml.SelectNodes("//Entity/App/person") | ? {
    $_.name -eq "itemToRemove"   } | % {$_.ParentNode.RemoveChildNode($_) }
       $xml.Save($_.FullName)
    }
like image 815
Justin Homes Avatar asked May 29 '14 18:05

Justin Homes


Video Answer


3 Answers

Get-ChildItem D:\Projects\*.xml | % {
    [Xml]$xml = Get-Content $_.FullName

    $newItemtoAdd = $xml.CreateElement('newItemtoAdd')
    $newItemtoAdd.PsBase.InnerText = '1900-01-01'
    $xml.Entity.App.AppendChild($newItemtoAdd) | Out-Null

    $parent_xpath = '/Entity/App/person'
    $nodes = $xml.SelectNodes($parent_xpath)
    $nodes | % {
        $child_node = $_.SelectSingleNode('itemToRemove')
        $_.RemoveChild($child_node) | Out-Null
    }

    $xml.OuterXml | Out-File $_.FullName
}
like image 159
Alexander Obersht Avatar answered Oct 13 '22 20:10

Alexander Obersht


To remove a node from all xml-files in a directory, and save result as a new xml-file, I use this powershell-script:

Get-ChildItem .\*.xml | % {
    [Xml]$xml = Get-Content $_.FullName
    $xml | Select-Xml -XPath '//*[local-name() = ''itemToRemove'']' | ForEach-Object{$_.Node.ParentNode.RemoveChild($_.Node)}
    $xml.OuterXml | Out-File .\result.xml -encoding "UTF8"
} 

Note: the "local-name"-syntax is for ignoring namespaces.

like image 38
cederlof Avatar answered Oct 13 '22 20:10

cederlof


PowerShell has two nice syntax features which might be used in this case:

1. Xml dot notation

Where you might directly reference a node in your xml file.
For example, to select the itemToRemove for the first (0 based) Person:

$Xml.Entity.App.person[0].itemToRemove
true

Unfortunately this PowerShell feature is a little overdone as you can see from the example where instead of returning an XmlElement, it returns a string with the innerText (#Text) if it concerns a leaf note, see: #16878 Decorate dot selected Xml strings (leaves) with XmlElement methods.

2. Member-Access Enumeration

You can use member enumeration to get property values from all members of a collection. When you use the member access operator (.) with a member name on a collection object, such as an array, if the collection object does not have a member of that name, the items of the collection are enumerated and PowerShell looks for that member on each item. This applies to both property and method members.

Taking the xml file in the question as an example:

$Xml.Entity.App.person.itemToRemove
true
false
false

Note that in this example, there is no index behind the Person property, meaning that all persons are enumerated and returned.
To overcome a possible returned string (described in 1. xml dot notation) you might enumerate the SelectNodes() on each person node instead and use that to removed the concerned nodes from their parent:

$Xml.Entity.App.person.SelectNodes('itemToRemove').ForEach{ $Null = $_.ParentNode.RemoveChild($_) }
[System.Xml.Linq.XDocument]::Parse($Xml.OuterXml).ToString()

<Entity>
  <App>
    <item1>1</item1>
    <emptyItem />
    <person>
      <emptyItem />
      <otheritem>1</otheritem>
    </person>
    <person>
      <emptyItem />
      <otheritem>3</otheritem>
    </person>
    <person>
      <emptyItem />
      <otheritem>3</otheritem>
    </person>
  </App>
</Entity>
like image 27
iRon Avatar answered Oct 13 '22 21:10

iRon