What is a platform-agnostic powershell way to remove nodes from large xml files?

Viewed 73

I have 90mb xml files that I'd like to process on Linux Mac & Windows. They have hundreds of "Label" nodes like this:

<net.rptools.maptool.model.Label> ...few children... </net.rptools.maptool.model.Label>

I want to process the files to remove all these Label nodes. However, it might be better to remove their parents, which seems to be a few

<labels class="linked-hash-map"> ...many megs of Label children... </labels>

I won't know which is better until I attempt to load the xml back into the application.

I've seen powershell XML processing examples that seem to use Windows specific APIs. What might be the preferred platform-agnostic API and style for deleting nodes from large-ish files?

3 Answers

If you don't want any fancy api and just plain access to the xml as an object it is as simple as casting the xml to an [xml] object

$xmldoc = [xml](Get-Content 'c:\temp\books.xml' -Raw)

or

$xmldoc = [xml]@"
<?xml version="1.0"?>
<catalog>
   <book id="bk101">
      <author>Gambardella, Matthew</author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications 
      with XML.</description>
   </book>
   <book id="bk102">
      <author>Ralls, Kim</author>
      <title>Midnight Rain</title>
      <genre>Fantasy</genre>
      <price>5.95</price>
      <publish_date>2000-12-16</publish_date>
      <description>A former architect battles corporate zombies, 
      an evil sorceress, and her own childhood to become queen 
      of the world.</description>
   </book>
</catalog>
"@

Then you can access or modify the xml through its properties

PS C:\temp> $xmldoc.catalog.book

id           : bk101
author       : Gambardella, Matthew
title        : XML Developer's Guide
genre        : Computer
price        : 44.95
publish_date : 2000-10-01
description  : An in-depth look at creating applications
                     with XML.

id           : bk102
author       : Ralls, Kim
title        : Midnight Rain
genre        : Fantasy
price        : 5.95
publish_date : 2000-12-16
description  : A former architect battles corporate zombies,
                     an evil sorceress, and her own childhood to become queen
                     of the world.

If you modify you can write modified object back to file using

$xmldoc.Save("C:\temp\testout.xml")

I went with something like the example below. The application would not let me delete "labels" nodes, and the "net.rptools.maptool.model.Label" nodes were children of "entry" nodes. So I used SelectNodes with an XPath expression to find all "entry" nodes under "labels" parents, and then removed them from the parent:

#!/usr/bin/env pwsh
Set-StrictMode -Version Latest
 
$branch = Get-Location 
$contentFileIn = "$branch\content.xml"
$contentFileOut = "$branch\content_pruned.xml"

$xmlDoc = [xml]::new()
$xmlDoc.Load($contentFileIn)

$entryNodes = $xmlDoc.SelectNodes("//labels/entry")
foreach ($entryNode in $entryNodes){
        $entryNode.ParentNode.RemoveChild($entryNode)
}
$xmldoc.Save($contentFileOut)

Removing elements with a given name is easy using XSLT, which is about as platform-agnostic as you can get. In XSLT 3.0 it's simply:

<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="label"/>
</xsl:transform>

XSLT 1.0 is more widely available but slightly more verbose:

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
   <xsl:copy><xsl:apply-templates/></xsl:copy>
</xsl:template>
<xsl:template match="label"/>
</xsl:transform>

By way of explanation, in both cases you're saying that by default, elements are recursively shallow-copied to the result, except for label elements, for which no output is generated (i.e. they are deleted).

Related