How to insert element after a specific element in XML with insertAfter()
Asked Answered
A

2

5

My file .xml looks like this

<?xml version="1.0" encoding="UTF-8"?>
<layoutMaster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="..\schema\m.xsd">
  <deviceFamily>
    <deviceFamilyUniqueID>DeviceFamilyWayside_2</deviceFamilyUniqueID>
    <name>DeviceFamilyWayside_2</name>
    <device>SAMSUNG</device>
    <device>SONY</device>
    <deviceFamilyOptions>
      <name>false</name>
    </deviceFamilyOptions>
  </deviceFamily>
  <deviceFamily>
    <deviceFamilyUniqueID>DeviceFamilyWayside_4</deviceFamilyUniqueID>
    <name>DeviceFamilyWayside_4</name>
    <device>IPHONE</device>
    <device>MAC</device>
    <deviceFamilyOptions>
      <name>false</name>
    </deviceFamilyOptions>
  </deviceFamily>
</layoutMaster>

And I would like to add a new device in deviceFamily[deviceFamilyUniqueID ='DeviceFamilyWayside_2'] after the last device:

So I tried this following code in PowerShell:

$Path = ".\config.xml"
[xml]$xmlFile = Get-Content -Path $Path

$xmlNewElementDevice = $xmlFile.CreateElement("device")
$xmlNewElementDevice.AppendChild($xmlFile.CreateTextNode("ALCATEL"))

$xmlTargetDeviceWayside = $xmlFile.SelectSingleNode('//layoutMaster/deviceFamily[deviceFamilyUniqueID = "DeviceFamilyWayside_2"]')
$xmlTargetDeviceWayside.InsertAfter($xmlNewElementDevice,$xmlTargetDeviceWayside.name)

but I got this error:

Unable to convert the argument "1" (value "DeviceFamilyWayside_2") from "InsertAfter" to type "System.Xml.XmlNode":" Unable to convert "DeviceFamilyWayside_2" value from "System.String" to type "System.Xml.XmlNode". "

The final result I would like to get is like this:

<?xml version="1.0" encoding="UTF-8"?>
<layoutMaster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="..\schema\m.xsd">
  <deviceFamily>
    <deviceFamilyUniqueID>DeviceFamilyWayside_2</deviceFamilyUniqueID>
    <name>DeviceFamilyWayside_2</name>
    <device>SAMSUNG</device>
  <device>SONY</device>
    <device>ALCATEL</device>    <--------------- here
    <deviceFamilyOptions>
      <name>false</name>
    </deviceFamilyOptions>
  </deviceFamily>
  <deviceFamily>
    <deviceFamilyUniqueID>DeviceFamilyWayside_4</deviceFamilyUniqueID>
    <name>DeviceFamilyWayside_4</name>
    <device>IPHONE</device>
  <device>MAC</device>
    <deviceFamilyOptions>
      <name>false</name>
    </deviceFamilyOptions>
  </deviceFamily>
</layoutMaster>
Amenity answered 3/2, 2018 at 22:27 Comment(0)
N
6

Insert the new node before the node <deviceFamilyOptions>:

[xml]$xml = Get-Content -Path $Path

$newNode = $xml.CreateElement("device")
$newNode.InnerText = 'ALCATEL'

$parent = $xml.SelectSingleNode('//layoutMaster/deviceFamily[deviceFamilyUniqueID="DeviceFamilyWayside_2"]')
$ref    = $parent.SelectSingleNode('./deviceFamilyOptions')

$parent.InsertBefore($newNode, $ref) | Out-Null

Alternatively select the last <device> node as the reference node after which to insert the new node:

$parent = $xml.SelectSingleNode('//layoutMaster/deviceFamily[deviceFamilyUniqueID="DeviceFamilyWayside_2"]')
$ref    = $parent.SelectSingleNode('./device[last()]')

$parent.InsertAfter($newNode, $ref) | Out-Null
Nyssa answered 3/2, 2018 at 23:21 Comment(1)
man.. you saved me twice, thank you so much , i really appreciate you so much thank you !Amenity
I
0

Consider, too, XSLT (sibling to XPath), the special-purpose language designed to transform XML. PowerShell can run XSLT 1.0 scripts with the System.Xml.Xsl.XslCompiledTransform, extension.

XSLT (save as .xsl, a special .xml file)

<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 Update Needed Node -->
  <xsl:template match="deviceFamily[deviceFamilyUniqueID = 'DeviceFamilyWayside_2']">    
     <xsl:copy>        
       <xsl:apply-templates select="deviceFamilyUniqueID|name|device"/>          
       <device>ALCATEL</device>
       <xsl:apply-templates select="deviceFamilyOptions"/>
     </xsl:copy>    
  </xsl:template>

</xsl:transform>

Powershell (generalized script for any XML source and XSLT 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);

Command line

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 (pretty printed indentation)

<?xml version="1.0" encoding="UTF-8"?>
<layoutMaster xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:noNamespaceSchemaLocation="..\schema\m.xsd">
   <deviceFamily>
      <deviceFamilyUniqueID>DeviceFamilyWayside_2</deviceFamilyUniqueID>
      <name>DeviceFamilyWayside_2</name>
      <device>SAMSUNG</device>
      <device>SONY</device>
      <device>ALCATEL</device>
      <deviceFamilyOptions>
         <name>false</name>
      </deviceFamilyOptions>
   </deviceFamily>
   <deviceFamily>
      <deviceFamilyUniqueID>DeviceFamilyWayside_4</deviceFamilyUniqueID>
      <name>DeviceFamilyWayside_4</name>
      <device>IPHONE</device>
      <device>MAC</device>
      <deviceFamilyOptions>
         <name>false</name>
      </deviceFamilyOptions>
   </deviceFamily>
</layoutMaster>

XSL Transform Demo

Inhabiter answered 4/2, 2018 at 0:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.