【问题标题】:InnerXml replace, but only onceInnerXml 替换,但只有一次
【发布时间】:2019-01-01 21:57:31
【问题描述】:

我有两个 XML 文件,一个具有默认名称和值(名为 Test.xml),另一个仅具有默认名称(名为 document.xml)。目标是用值替换默认名称 - 但仅在第一次出现时。

这里是Test.xml

<XML-TEST>
    <MyText>Dies ist ein Test</MyText>
    <MyTexttwo>Dies ist noch ein Test</MyTexttwo>
</XML-TEST>

这是document.xml(几乎在最后):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"
    xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex"
    xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:o="urn:schemas-microsoft-com:office:office"
    xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
    xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
    xmlns:v="urn:schemas-microsoft-com:vml"
    xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
    xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
    xmlns:w10="urn:schemas-microsoft-com:office:word"
    xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
    xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml"
    xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"
    xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex"
    xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
    xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk"
    xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml"
    xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
    mc:Ignorable="w14 w15 w16se wp14">
  <w:body>
    <w:p w:rsidR="00E64ECE" w:rsidRDefault="00E64ECE" w:rsidP="00E64ECE">
      <w:proofErr w:type="spellStart" />
      <w:r>
        <w:t>MyText</w:t>
      </w:r>
      <w:proofErr w:type="spellEnd" />
    </w:p>
    <w:p w:rsidR="00D50239" w:rsidRPr="00E64ECE" w:rsidRDefault="00E64ECE" w:rsidP="00E64ECE">
      <w:r>
        <w:t>MyTexttwo</w:t>
      </w:r>
      <w:bookmarkStart w:id="0" w:name="_GoBack" />
      <w:bookmarkEnd w:id="0" />
    </w:p>
    <w:sectPr w:rsidR="00D50239" w:rsidRPr="00E64ECE">
      <w:pgSz w:w="11906" w:h="16838" />
      <w:pgMar w:top="1417" w:right="1417" w:bottom="1134" w:left="1417" w:header="708" w:footer="708" w:gutter="0" />
      <w:cols w:space="708" />
      <w:docGrid w:linePitch="360" />
    </w:sectPr>
  </w:body>
</w:document>

我在用 PowerShell 做什么?

  1. 我将Test.xml(有值的那个)保存在哈希表中:

    PS> $XMLSourceHashtable 名称 值 ---- ----- MyText 在测试中消亡 MyTexttwo Dies ist noch ein 测试
  2. document.xml保存到变量$DocumentXml中。

  3. 使用foreach替换我需要的:

    foreach ($key in ($XMLSourceHashtable.GetEnumerator())) {
        # If one key.value is "false" replace the 1:1 name with Char
        if ($key | Where-Object {$_.Value -eq "false"}) {
            #$key.Name.Trim()
            #$DocumentXml.InnerXml = $DocumentXml.InnerXml.Replace($key.Name.Trim(), "â˜")
        } elseif ($key | Where-Object {$_.Value -eq "true"}) {
            # If one key.value is "true" replace the 1:1 name with Char
            #$key.Name.Trim()
            #$DocumentXml.InnerXml = $DocumentXml.InnerXml.Replace($key.Name.Trim(), "☒")
        } else {
            # Everything else needs to be replaced by value in hashtable
            #Write-Host $key.Name.Trim() "--------------" $key.Value.Trim()
            #$DocumentXml.InnerXml = $DocumentXml.InnerXml.Replace($key.Name.Trim(), $key.Value.Trim())
        }
    }
    

前两个elseif 工作正常,不应考虑它们。我担心的是else

会发生什么?

文本当然会替换,但替换方法会执行以下操作:

document.xml 中的值被替换如下:

“MyText”→“Dies ist ein Test”
"MyTexttwo" → 死在 Testtwo"

但应该是:

“MyText”→“Dies ist ein Test”
“MyTexttwo”→ Dies ist noch ein Test”

重点是,“MyTexttwo”中正在识别“MyText”。每个“名称”实际上都是唯一的,但它并没有像唯一一样被处理。我知道这可以在第一次出现时替换,但只能使用 RegEx。但我无法将 xml 转换为正则表达式并再次返回。还有什么我可以做的吗?

【问题讨论】:

  • 请采纳这个普遍的建议。 从不、从不、从不对 XML 源代码使用字符串替换工具。这总是完全错误的做法。很难解释这是多么错误,因为对于初学者来说它看起来很容易。简单地决定永远不要这样做。学习正确的工具(在本例中:XPath)并使用它们。
  • @Tomalak Point,编辑了我的答案。也许您可以将您的警告作为 Blockquote 放在您的答案中,以便更好地脱颖而出?
  • @Tomalak 注意!

标签: xml powershell replace


【解决方案1】:

你的方法太复杂了。使用 XPath。原则上——加载、修改、保存:

$document = New-Object xml
$document.Load('Document.xml')

$element = $document.SelectSingleNode("//some/path")
$element.InnerText = "some new value"

$document.Save('Document_2.xml')

这里唯一的小麻烦是您正在处理一个 Word 文档,并且它们使用 XML 命名空间(在 XML 源中写为 xmlns:foo="...namespace URI..."),因此您也需要使用命名空间(参见:Using PowerShell, how do I add multiple namespaces (one of which is the default namespace)?) :

$document = New-Object xml
$document.Load('Document.xml')

# use a namespace manager to register the w: namespace prefix
$namespaces = New-Object System.Xml.XmlNamespaceManager $document.NameTable
$namespaces.AddNamespace('w', 'http://schemas.openxmlformats.org/wordprocessingml/2006/main')

foreach ($item in $XMLSourceHashtable) {
    $searchText = $item.Name;
    $element = $document.SelectSingleNode("//w:t[.='$searchText']", $namespaces)
    $element.InnerText = $item.Value
}

$document.Save('Document_2.xml')

"//w:t[.='$searchText']" 将被插入到 XPath 表达式中,例如 //w:t[.='MyText'] - 此路径将选择输入 XML 中所有以 'MyText' 为值的 &lt;w:t&gt; 元素。使用 .SelectSingleNode() 将只返回其中的第一个,这似乎是您想要的。

您可以使用.SelectNodes() 和另一个foreach 循环来编辑所有出现:

foreach ($element in $document.SelectNodes("//w:t[.='$searchText']", $namespaces)) {
    $element.InnerText = $item.Value
}

【讨论】:

  • 我正在尝试这个 - 请等待
  • 查看我关于命名空间使用的补充说明,您可能也需要这里。
  • @Ansgar Ahh,现在 XML 已在问题中格式化,它更加清晰。是的,我认为你是对的。
  • 我猜你的解决方法是迄今为止最好的方法。但我的 PS 在 $namespaces.AddNamespace 上给我一个错误 - “没有方法 AddNamespace” 我只有“Add()”作为选项。
  • @J.Doe 你说得对,我使用了链接答案中的代码并没有对其进行测试。我已经更正了我的答案。
【解决方案2】:

虽然 Tomalak 给 NEVER 在 XML 中使用字符串替换的建议是很好的建议,但这里是您问题的答案 关键是,“MyText” 正在“MyTexttwo”中被识别.每个“名称”实际上都是唯一的,但它并没有像唯一一样处理

您使用的 Replace 方法与整个字符串不匹配。 “MyTextTwo”以“MyText”开头,因此在您的函数中,名称的一部分被替换。那么“MyTextTwo”就不再存在了。

为了仅在完整字符串匹配而不仅仅是部分匹配时进行替换。如果您仍想使用字符串替换,我建议:

$nameToReplace = $key.Name.Trim()
$DocumentXml.InnerXml = $DocumentXml.InnerXml -replace "\A$nameToReplace\z", $key.Value.Trim()

\A\z 符号是告诉正则表达式替换字符串的锚点必须与您提供的完全一致。 (位置断言)

如果您还需要确保只有在外壳也匹配时才进行更换,您可以使用

$nameToReplace = $key.Name.Trim()
$DocumentXml.InnerXml = $DocumentXml.InnerXml -creplace "\A$nameToReplace\z", $key.Value.Trim()

【讨论】:

  • 不要在 XML 上使用正则表达式或字符串替换。这是一件可怕的事情。
  • 你好 Theo,这不起作用 - 不幸的是,直到我以任何方式将 $key.Name 设置为引号 "" ,该变量将不再被解析。因此不会对 document.xml 进行任何更改。
猜你喜欢
  • 1970-01-01
  • 2012-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-29
相关资源
最近更新 更多