StUffz
StUffz

Reputation: 123

cleaning up a XML parent node with all child nodes in PowerShell

Im writing a PowerShell script for a game that I'm playing to assist with the trading.

I created an XML where I store all the informations I need. The XML looks like:

<stuff>
  <resource>
    <type Name="Cotton" ID="0000">
      <isDGV>1</isDGV>
      <Storage>666</Storage>
      <AvgBuyingPrice>
      </AvgBuyingPrice>
      <lastBuyingPrice>42</lastBuyingPrice>
      <lowestBuyingPrice>
      </lowestBuyingPrice>
      <highestBuyingPrice>
      </highestBuyingPrice>
      <AvgSellingPrice>
      </AvgSellingPrice>
      <lastSellingPrice>
      </lastSellingPrice>
      <lowestSellingPrice>
      </lowestSellingPrice>
      <highestSellingPrice>
      </highestSellingPrice>
      <BuyingPricesHistory-Last42>
        <price>62</price>
        <price>42</price>
      </BuyingPricesHistory-Last42>
      <SellingPricesHistory-Last42>
        <price>
        </price>
      </SellingPricesHistory-Last42>
      <TownsProducedIn>
        <town>Sampletown</town>
      </TownsProducedIn>
      <TownsNeededIn>
      </TownsNeededIn>
    </type>
    <type Name="Spices" ID="0001">
      <isDGV>0</isDGV>
      .
      .
  </resource>
</stuff>

Now I want to have the possibility to "clean" all the child nodes of a specific resource. So that for example <Storage>666</Storage> becomes <Storage></Storage> again.

This is what I thought should work, but apparently, it's not that easy? (Code is not complete, but $XMLfile is loaded just fine as it's also used for displaying the values of the resources. the saveXML function does also work)

$target = $XMLfile.stuff.resource.SelectNodes('type/child::*') | where { $_.Name -eq "$resName" -and $_.ID -eq $ID }

        foreach ($Child in $target)
        {
            $Child.InnerText = ""
        }
        saveXML $xmlFilePathAndName

Even worse: I don't get any error messages out of that code :-)

Upvotes: 0

Views: 884

Answers (2)

Parfait
Parfait

Reputation: 107737

Consider an XSLT solution, the special-purpose declarative programming language designed to transform XML documents. In fact, XSLT scripts are well-formed XML files! Like most general purpose languages (C#, Java, Perl, PHP, Python, VB), command line interpreters such as PowerShell and Bash can run XSLT. Of course they can also call external processors (Saxon and Xalan) and aforementioned languages with arguments.

XSLT Script (save as external .xsl file; portable to other languages/platforms)

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output version="1.0" encoding="UTF-8" indent="yes" method="xml" />
<xsl:strip-space elements="*"/>

  <!-- Identity Transform -->
  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>      
    </xsl:copy>    
  </xsl:template>  

  <!-- Re-Write for Empty Storage Element -->
  <xsl:template match="Storage">
    <xsl:copy/>
  </xsl:template>

</xsl:transform>

PowerShell Script

param ($xml, $xsl, $output)

if (-not $xml -or -not $xsl -or -not $output) {
    Write-Host "& .\xslt.ps1 [-xml] xml-input [-xsl] xsl-input [-output] transform-output"
    exit;
}

trap [Exception]{
    Write-Host $_.Exception;
}

$xslt = New-Object System.Xml.Xsl.XslCompiledTransform;
$xslt.Load($xsl);
$xslt.Transform($xml, $output);

Write-Host "generated" $output;

Command line

PS > powershell -File PowerShellScript.ps1 Input.xml, XSLTScript.xsl, Output.xml

Upvotes: 0

har07
har07

Reputation: 89325

The problem was, that your SelectNodes() returns child elements of type, while the Name and ID attributes are at the type parent element level. So the subsuquent where ... filter didn't find any element that match the attribute value criteria.

You can use XPath to filter type element by its attributes, and then return the child elements, all in one expression, like so :

$target = $XMLfile.stuff.resource.SelectNodes("type[@Name='$resName' and @ID='$ID']/*")

Notice that child:: is the default axis in XPath, so when you remove it, leaving only the path separator /, the engine will implies that you want the default axis to be used here.

Upvotes: 1

Related Questions