16

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)
    }

3 Answers 3

22
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
}
Sign up to request clarification or add additional context in comments.

3 Comments

When i am using some code to itterate the nodes and don't know the path
I know this is old, but this script removes all my whitespace/linebreaks. Thoughts?
@theB3RV - try the Save method of XML document. It may behave differently. (instead of the Out-File cmdlet)
6

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.

3 Comments

I believe that this will remove the children of 'itemToRemove', not that node. To remove that node, change to "$_.Node.ParentNode.RemoveChild($_.Node) . Otherwise, looks good, thanks for the example.
What does the % mean between the pipe and the leading bracket here?
@Sly_Boots It is short for ForEach, looping through all matched files.
1

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>

Comments

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.