从 CSV 传递 xpath 以用 Powershell 替换 XML 中的值

Passing xpath from CSV to replace value in XML with Powershell

希望从 csv 文件中读取 xml 元素列表(作为 xpath)及其新值 -> 然后,查看现有的 xml 文件以替换任何 xml 元素将被更新,新值在给定的 xpaths 上。

我通过在线解析得到了我需要在大 xml 中更改的 xpath 的值。 can/will 一次可以替换任意数量的值,所以我不能将这些值硬编码到 powershell 中——它需要读取每次所需的每组更改——来自文件(csv) ,我建议。

替换值的一个 xpath 目标是:

    /configuration/services/objects/object/app-args/list/value[0]/text()

所以我的 csv 行是:

    "/configuration/services/objects/object/app-args/list/value[0]/text()","new value"

然后我在 xml.

中调用 fn 将此 xpath 替换为新值

我正在努力 就在那一行 上面的 app-args xpath 字符串中的“-”正在被解析并以某种方式将 xpath 字符串剪短,并且因此跳出更新。我无法更改 xml 中的节点名称 - 不是一个选项。

加载时可以在powershell中浏览xml结构:

    $xml = [xml](Get-Content "C:\temp\big.xml")

所以在提示时自动完成浏览,然后我可以看到:

    PS c:\temp> $xml.configuration.services.objects.object.'app-args'.list.value[0]

会在 xml 中显示正确的 "replace this value 1"。

但我无法在 csv 中使用正确的 xpath 措辞来替换此节点。如何从文件中读取名称中带有“-”的节点?我试过 /"app-args"/ 和许多其他转义字符,但暂时没有想法。使用 csv 以外的东西?

之前没有使用过 xpath 的经验,这真的让我非常难受!

参考:(简化)xml 问题区域是:

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

我使用的最简单的 csv 是(xpath,它的新值):

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

XPath 是用于查找节点的路径,使用 SelectNodes(获取与路径匹配的多个节点)或 SelectSingleNode 获取第一个匹配项。当您更新检索到的节点时,您也会更新父 $xml,因为节点是对父文档的一部分的引用。

用法

$xpath = "/configuration/services/objects/object/app-args/list/value[0]/text()"
$node = $xml.SelectSingleNode($XPath)
$node.Value = "newValue"

$xml.Save("PathToSave")

如果您在某个地方有多个节点,您可以使用 SelectNodes 获取节点列表,然后迭代这些节点以更新值。

$nodes = $xml.SelectNodes("//configuration/services/objects/object/app-args/list/value")
foreach($node in $nodes) {
    // Do something with $node.attribute $node.something 
}

没有看到您 xml 的实际布局,不确定如何提出其他建议。

由于您提供了一个具有命名空间的示例,因此定义路径的方式会有所不同。

您的 XML 在对象上标记名称空间,因此对象下的所有内容都被视为特定名称空间的一部分。查找、编辑或删除节点时需要此命名空间。

$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")

由于 /configuration/services 在该命名空间之外,您不必使用 namespaceManager 标记它们。但是 objects 下的所有内容(包括 objects)都必须进行标记。

如果您有兴趣更改值,则需要更新元素的 InnerText

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

另请注意,值的索引从 1 开始。没有 value[0]。因此,您的 csv 需要更新以包含命名空间标签。

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

完整代码

$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内容:

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

---------------------------------------- --------------

更新问题的建议解决方案

根据您的新 xml,我建议使用 Select-XML 路线。

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"

代码

$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")

新文件更新了值。

如果 post 回答了您 post 中的问题,请将其标记为已回答。