Ant Pro
Ant Pro

Reputation: 23

XML remove node by child node value

I am trying to figure out how to delete a xml node by searching the ModelName or PlatformID based on the XML below. I've tried a few ways but I can't seem to get it to work. Any ideas?

XML:

<?xml version="1.0" encoding="ISO-8859-1"?>
<HPModels>
  <Model>
    <ModelName>HP EliteBook x360</ModelName>
    <PlatformID>8725</PlatformID>
  </Model>
  <Model>
    <ModelName>HP EliteBook x360</ModelName>
    <PlatformID>876d</PlatformID>
  </Model>
</HPModels>

I'm trying to use this but I've had no luck

$file = "C:\Temp\test1.xml"
$xmlfile = [XML](Get-Content $file)
$item = Select-XML -Xml $xmlfile -XPath '//ModelName[Name="HP EliteBook x360"]'
$item.Node.ParentNode.RemoveChild($item.node)

I am getting:

You cannot call a method on a null-valued expression.
At line:4 char:1

Upvotes: 0

Views: 1544

Answers (2)

Ant Pro
Ant Pro

Reputation: 23

Thank you @Mathias R. Jessen. This is the correct way to do it if anyone else wants to know.

XML:

<?xml version="1.0" encoding="ISO-8859-1"?>
<HPModels>
  <Model>
    <ModelName>HP EliteBook x360</ModelName>
    <PlatformID>8725</PlatformID>
  </Model>
  <Model>
    <ModelName>HP EliteBook x360 1030</ModelName>
    <PlatformID>876d</PlatformID>
  </Model>
</HPModels>
$modelselect = "HP EliteBook x360"

$file = "C:\Temp\test1.xml"
$xmlfile = [XML](Get-Content $file)
$item = Select-Xml $xmlFile -XPath "//Model[./ModelName = '$modelselect']"

foreach($node in $item.Node)
{
$node.ParentNode.RemoveChild($node)
}

$xmlFile.Save($file)

Upvotes: 0

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174485

From your comments:

I would want to delete the whole <Model> node if a match is found.

In that case, I would suggest using the following as the base expression//Model - then you can use your existing expression as a predicate [...]:

$items = Select-Xml $xmlFile -XPath '//Model[./ModelName = "HP EliteBook x360"]'

Here, we get Select-Xml to select all <Model> nodes that have an immediate child <ModelName>, the inner value of which is "HP EliteBook x360"

You can also use the .. selector to "walk back up" to the parent node after resolving the appropriate child node:

$items = Select-Xml $xmlFile -XPath '//ModelName[. = "HP EliteBook x360"]/..'

I (personally) don't like this pattern, but it is perfectly valid XPath :)


Once the selector expression is fixed, make sure you process the results 1 at a time - Select-Xml might return multiple nodes:

foreach($item in $items){
    $node = $item.Node
    $node.ParentNode.RemoveChild($node)
}

If you want to accept different model names as parameter arguments, add a param() block on the first line of the script and then use that in the XPath expression:

param(
  [Parameter(Mandatory)]
  [string]$FilePath,

  [Parameter(Mandatory)]
  [string]$ModelName
)

if($ModelName -like "*'*"){
  throw 'Cannot process model name with apostrophe'
  return
}

$file = Convert-Path $FilePath
$xmlfile = [XML](Get-Content $file)
$items = Select-XML -Xml $xmlfile -XPath "//ModelName[Name='${ModelName}']"

foreach($item in $items){
  $node = $item.Node
  $node.ParentNode.RemoveChild($node)
}

$xmlfile.Save($file)

Then pass the parameter arguments for the file path and model name when calling the script:

.\Remove-ModelFromXml.ps1 -FilePath .\path\to\file.xml -ModelName "HP EliteBook x360"

Upvotes: 4

Related Questions