Looking to read a list of xml elements (as xpaths) & their new values from a csv file -> and then, look into an existing xml file to replace whatever xml elements are to be updated, with the new values on the given xpaths.
I got the values for the xpaths I need to change in the big xml by online parsing it. There can/will be any number of values to replace at one time, so I can't hard-code these into the powershell - it will need to read each set of changes required each time - from a file (the csv), I am suggesting.
One xpath target to replace a value is is :
/configuration/services/objects/object/app-args/list/value[0]/text()
So I have the csv line as:
"/configuration/services/objects/object/app-args/list/value[0]/text()","new value"
I then call a fn to replace this xpath with the new value, in the xml.
I am struggling on just that one line, where the "-" in app-args xpath string above, is being parsed and somehow cutting the xpath string short there, and therefore tripping out the replace. I can't change the node names in the xml - not an option.
I can browse the xml structure in powershell when I load it:
$xml = [xml](Get-Content "C:\temp\big.xml")
So autocomplete browsing on prompt, I can then see that:
PS c:\temp> $xml.configuration.services.objects.object.'app-args'.list.value[0]
will show me the correct "replace this value 1" in the xml.
But I can't get the xpath phrasing correct in the csv to replace this node. How can I read that node with the "-" in the name, from a file? I've tried /"app-args"/ and a host of other escape chars but out of ideas for now. Use something other than csv?
No experience with xpath before and it's really killing me!
Ref: (simplified) xml with problem area is:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<spring xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.net ../../../../lib/spring.net/spring-objects-1.1.xsd">
<context>
<resource uri="config://spring/objects" />
<context name="services">
<resource uri="config://spring/services/objects" />
</context>
</context>
<objects xmlns="http://www.springframework.net" default-autowire="constructor">
<object name="SomeServicefactory" type="ServiceFactory" />
</objects>
<services>
<objects xmlns="http://www.springframework.net" default-lazy-init="true">
<object name="SystemConfiguration" type="SystemConfiguration">
<app-args name="ConfigCodes">
<list element-type="string">
<value>old value</value>
<!-- Automate me -->
<!-- use case, assume default value as per above, allow the powershell module to override this -->
<value>old value</value>
<!-- Automate me -->
<!-- use case, assume default value as per above, allow the powershell module to override this -->
</list>
</app-args>
</object>
</objects>
</services>
</spring>
</configuration>
And the most simple csv I am using is (xpath,new value for it):
"/configuration/services/objects/object/app-args/list/value[0]/text()","new value 1"
"/configuration/services/objects/object/app-args/list/value[1]/text()","new value 2"
Since you are providing an example which has namespace, that will have a little bit of a different way to define your paths.
Your XML tags namespace at objects, so everything under objects is considered to be part of a specific namespace. This namespace is necessary when looking up, editing or deleting nodes.
$xml.SelectSingleNode("/configuration/services/x:objects/x:object/x:app-args/x:list/x:value[1]", $Namespace)
$Namespace = New-Object -TypeName "Xml.XmlNamespaceManager" -ArgumentList $xml.NameTable
$Namespace.AddNamespace("x", "http://www.springframework.net")
Since /configuration/services
are outside of that namespace, you dont have to tag them with that namespaceManager
. But everything under, and including, objects
will have to be tagged.
If you are interested in changing the values, you will need to update the InnerText
of the elements.
$xml.DocumentElement.SelectSingleNode("/configuration/services/x:objects/x:object/x:app-args/x:list/x:value[1]", $Namespace).InnerText = "New Value for First"
# or you can use // to search for the first element
$xml.DocumentElement.SelectSingleNode("//x:objects/x:object/x:app-args/x:list/x:value[1]", $Namespace).InnerText = "New Value for First"
Also note, value is indexed starting with 1. There is no value[0]
. From all of this, your csv will need to be updated to include the namespace tags.
"//x:objects/x:object/x:app-args/x:list/x:value[1]","new value 1"
"//x:objects/x:object/x:app-args/x:list/x:value[2]","new value 2"
Complete Code
$Namespace = New-Object -TypeName "Xml.XmlNamespaceManager" -ArgumentList $xml.NameTable
$Namespace.AddNamespace("x", "http://www.springframework.net")
$csv = Get-Content C:\temp\csv.txt
[xml]$xml = Get-Content C:\temp\xml.txt
foreach($line in $csv) {
$items = $line.Replace("""", "").Split(",") # Remove extra quotes
$xml.DocumentElement.SelectSingleNode($items[0], $Namespace).InnerText = $Items[1]
}
$xml.Save("C:\temp\new.txt")
new.txt content:
<configuration>
<services>
<objects xmlns="http://www.springframework.net" default-lazy-init="true">
<object name="SystemConfiguration" type="SystemConfiguration">
<app-args name="Codes">
<list element-type="string">
<value>new value 1</value>
<value>new value 2</value>
</list>
</app-args>
</object>
</objects>
</services>
</configuration>
--------------------------------------------------------
Suggested Solution with updated question
Based on your new xml, i would recommend going with the Select-XML
route.
CSV
"//ns:objects/ns:object/ns:app-args/ns:list/ns:value[1]","new value 1"
"//ns:objects/ns:object/ns:app-args/ns:list/ns:value[2]","new value 2"
CODE
$csv = Get-Content C:\temp\csv.txt
[xml]$xml = Get-Content C:\temp\xml.txt
$ns = @{ns='http://www.springframework.net'}
foreach($line in $csv)
{
$items = $line.Replace("""", "").Split(",") # Remove extra quotes
$nodeRef = Select-Xml -Xml $xml -XPath $items[0] -Namespace $ns
$nodeRef.Node.InnerText = $items[1]
}
$xml.Save("C:\temp\new.txt")
New file has the values updated.
Please mark the post answered if it answers the issue in your post.