Modify XML Parent Attributes iterating through Child attributes using powershell

我们两清 提交于 2019-12-29 08:18:45

问题


So I have the following xml (items.xml) and I want to find the attributes of the child node item iterate through the attributes and if I find similar attributes at parent node level replace the same with the child node attributes and remove the child attributes other than the name.

  <items>    
    <model type="model1" name="default" price="12.12" date="some_value">
      <PriceData>
        <item name="watch" price="24.28" date="2013-12-01" />
      </PriceData>
    </model>
    <model type="model2" name="default" price="12.12" date="some_value">
      <PriceData>
        <item name="toy" price="22.34" date="2013-12-02"/>
      </PriceData>
    </model>
    <model type="model3" name="default" price="12.12" date="some_value">
      <PriceData>
        <item name="bread" price="24.12" date="2013-12-03"/>
      </PriceData>
    </model>
  </items>    

The final xml should look like this

  <items>    
    <model type="model1" name="watch" price="24.28" date="2013-12-0">
      <PriceData>
        <item name="watch" />
      </PriceData>
    </model>
    <model type="model2" name="toy" price="22.34" date="2013-12-02">
      <PriceData>
        <item name="toy" "/>
      </PriceData>
    </model>
    <model type="model3" name="bread" price="24.12" date="2013-12-03">
      <PriceData>
        <item name="bread" />
      </PriceData>
    </model>
  </items>    

I'm able to get the attributes at child level, but I'm unable to traverse back to the parent node from the child level.

Following is the code that I tried to get to the parent nodes

[xml]$model = get-content items.xml
$model.SelectNodes("//item/@*") 

Output:

#text                                                                                                                                                  
-----                                                                                                                                                  
watch                                                                                                                                                  
24.28                                                                                                                                                  
2013-12-01                                                                                                                                             
toy                                                                                                                                                    
22.34                                                                                                                                                  
2013-12-02                                                                                                                                             
bread                                                                                                                                                  
24.12                                                                                                                                                  
2013-12-03  


$model.SelectNodes("//item/@*") | foreach {write-host $_.parentnode}

No Output:

$model.SelectNodes("//item/@*") | foreach {write-host $_.parentnode.parentnode}

No Output:

I can get the attribute names of the child node as follows:

$model.SelectNodes("//item/@*") | foreach {write-host $_.name} 

Output:
PS C:\BIOS_Work_Dir\Kit_Manifest_test> $model.SelectNodes("//item/@*") | foreach {write-host $_.name}
name
price
date
name
price
date
name
price
date

Now for each attribute, I just need to go back to the parent node, check if similar attribute exists and replace it with the child node attribute

So, I'm looking for something like

$model.SelectNodes("//item/@*") | foreach {($_.name).parentnode.parentnode.($_.name)} | <some code to replace parentnode attribute with child attribute>

Then to remove the child attributes something like

$model.SelectNodes("//item/@*") | where {$_.name -notlike "name"} | foreach {$_.Removeattribute()}

and if both these can be done in one single command that would be awesome

Maybe I'm also trying to do a lot of things in a single line

Any pointers are greatly appreciated! Not really sure what am I doing wrong here as powershell does not throw an error for parent node usage but just does not print anything. Any help is amazing from all you experienced programmers!!


回答1:


You can get parent element from an attribute via OwnerElement property. So this is one possible way to get the desired output :

$model.SelectNodes("//item/@*") |
    ForEach {
        # set attributes value on `model` element
        $_.OwnerElement.ParentNode.ParentNode.SetAttribute($_.LocalName, $_.Value)
        # remove attributes except `name` from `item` element
        If ($_.LocalName -ne "name") { $_.OwnerElement.RemoveAttribute($_.LocalName) }
    }



回答2:


Since your title mentions Modify XML, consider XSLT, the special purpose language designed solely to transform XML files. Specifically, you can run the Identity Transform (copy document as is) and then keep only the attributes that match attribute names of ancestor::model (grandparent) using the <xsl:when> and <xsl:otherwise> conditionals. PowerShell can create an object of .NET Framework class, System.Xml.Xsl.XslCompiledTransform, to run XSLT 1.0 scripts.

XSLT (save as .xsl to be passed as argument in PowerShell)

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

  <!-- Conditionally Keep Item Attributes -->
  <xsl:template match="item/@*[name()!='name']">    
     <xsl:variable name="item_attr" select="name()"/>        
     <xsl:choose>
       <xsl:when test="ancestor::model/@*[name()=$item_attr]"/>          
       <xsl:otherwise><xsl:copy/></xsl:otherwise>
     </xsl:choose>    
  </xsl:template>

</xsl:transform>

PowerShell (general script for any XML input and XSLT 1.0 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;

Read-Host -Prompt "Press Enter to exit";

Command line call

Powershell.exe -File "C:\Path\To\PowerShell\Script.ps1"^
 "C:\Path\To\Input.xml" "C:\Path\To\XSLTScript.xsl" "C:\Path\To\Ouput.xml"

Output

<?xml version="1.0" encoding="utf-8"?>
<items>
  <model type="model1" name="default" price="12.12" date="some_value">
    <PriceData>
      <item name="watch" />
    </PriceData>
  </model>
  <model type="model2" name="default" price="12.12" date="some_value">
    <PriceData>
      <item name="toy" />
    </PriceData>
  </model>
  <model type="model3" name="default" price="12.12" date="some_value">
    <PriceData>
      <item name="bread" />
    </PriceData>
  </model>
</items>


来源:https://stackoverflow.com/questions/42868790/modify-xml-parent-attributes-iterating-through-child-attributes-using-powershell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!