【问题标题】:Replace an XML element's value? Sed regular expression?替换 XML 元素的值? sed正则表达式?
【发布时间】:2010-11-23 19:17:20
【问题描述】:

我想获取一个 XML 文件并替换一个元素的值。例如,如果我的 XML 文件如下所示:

<abc>
    <xyz>original</xyz>
</abc>

我想用另一个字符串替换 xyz 元素的原始值(无论它可能是什么),以便生成的文件如下所示:

<abc>
    <xyz>replacement</xyz>
</abc>

你会怎么做?我知道我可以编写一个 Java 程序来执行此操作,但我认为这对于替换单个元素的值来说太过分了,并且使用 sed 使用正则表达式进行替换很容易做到这一点。但是,我对这个命令还不是新手,我希望阅读这篇文章的好心人能够为我提供正确的正则表达式来完成这项工作。

一个想法是做这样的事情:

sed s/\<xyz\>.*\<\\xyz\>/\<xyz\>replacement\<\\xyz\>/ <original.xml >new.xml

也许我最好将文件的整行替换为我想要的,因为我会知道元素名称和我想要使用的新值?但这假设有问题的元素位于一行,并且没有其他 XML 数据位于同一行。我宁愿有一个命令,它基本上会用我指定的新字符串替换元素 xyz 的值,而不必担心元素是否都在一行上,等等。

如果 sed 不是这项工作的最佳工具,那么请拨通我的电话以找到更好的方法。

如果有人能引导我朝着正确的方向前进,我将不胜感激,您可能会为我节省数小时的反复试验。提前致谢!

--詹姆斯

【问题讨论】:

    标签: xml regex sed


    【解决方案1】:

    sed 不会成为用于多行替换的简单工具。可以使用它的N 命令和一些递归来实现它们,在读取每一行后检查是否找到了标签的结尾......但这并不漂亮,你永远不会记得它。

    当然,实际解析xml并替换标签将是最安全的事情,但如果你知道你不会遇到任何问题,你可以试试这个:

    perl -p -0777 -e 's@<xyz>.*?</xyz>@<xyz>new-value</xyz>@sg' <xml-file>
    

    分解:

    • -p 告诉它循环输入并打印
    • -0777 告诉它使用文件结尾作为输入分隔符,这样它就可以一口气把整个东西都放进去
    • -e 表示我要你做的事情来了

    还有替换本身:

    • 使用@ 作为分隔符,这样您就不必转义/
    • 使用*?,非贪婪版本,尽可能少地匹配,所以我们不会一直到文件中最后一次出现&lt;/xyz&gt;
    • 使用s修饰符让.匹配换行符(获取多行标签值)
    • 使用g修饰符多次匹配模式

    多田!这会将结果打印到标准输出 - 一旦您验证它是否符合您的要求,请添加 -i 选项以告诉它在适当的位置编辑文件。

    【讨论】:

    • 我喜欢这个解决方案,因为它简单而且 perl 在很多 linux 发行版中都是原生的
    • 这很有帮助。我试图用 sed 做类似的事情,但是这个 perl 脚本效果很好。另外,我添加了 -i 标志,以便它会为我写入文件。很高兴看到 perl 也自动生成了备份文件。
    【解决方案2】:

    好的,所以我咬紧牙关,花时间编写了一个 Java 程序来满足我的需求。下面是我的 main() 方法调用的操作方法,以防将来对其他人有所帮助:

    /**
     * Takes an input XML file, replaces the text value of the node specified by an XPath parameter, and writes a new
     * XML file with the updated data.
     * 
     * @param inputXmlFilePathName
     * @param outputXmlFilePathName
     * @param elementXpath
     * @param elementValue
     * @param replaceAllFoundElements
     */
    public static void replaceElementValue(final String inputXmlFilePathName,
                                           final String outputXmlFilePathName,
                                           final String elementXpathExpression,
                                           final String elementValue,
                                           final boolean replaceAllFoundElements)
    {
        try
        {
            // get the template XML as a W3C Document Object Model which we can later write back as a file
            InputSource inputSource = new InputSource(new FileInputStream(inputXmlFilePathName));
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            Document document = documentBuilderFactory.newDocumentBuilder().parse(inputSource);
    
            // create an XPath expression to access the element's node
            XPathFactory xpathFactory = XPathFactory.newInstance();
            XPath xpath = xpathFactory.newXPath();
            XPathExpression xpathExpression = xpath.compile(elementXpathExpression);
    
            // get the node(s) which corresponds to the XPath expression and replace the value
            Object xpathExpressionResult = xpathExpression.evaluate(document, XPathConstants.NODESET);
            if (xpathExpressionResult == null)
            {
                throw new RuntimeException("Failed to find a node corresponding to the provided XPath.");
            }
            NodeList nodeList = (NodeList) xpathExpressionResult;
            if ((nodeList.getLength() > 1) && !replaceAllFoundElements)
            {
                throw new RuntimeException("Found multiple nodes corresponding to the provided XPath and multiple replacements not specified.");
            }
            for (int i = 0; i < nodeList.getLength(); i++)
            {
                nodeList.item(i).setTextContent(elementValue);
            }
    
            // prepare the DOM document for writing
            Source source = new DOMSource(document);
    
            // prepare the output file
            File file = new File(outputXmlFilePathName);
            Result result = new StreamResult(file);
    
            // write the DOM document to the file
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.transform(source, result);
        }
        catch (Exception ex)
        {
            throw new RuntimeException("Failed to replace the element value.", ex);
        }
    }
    

    我是这样运行程序的:

    $ java -cp xmlutility.jar com.abc.util.XmlUtility input.xml output.xml '//name/text()' JAMES
    

    【讨论】:

      【解决方案3】:

      我不想成为反对者,但 XML 绝不是常规的。正则表达式可能比它的价值更麻烦。请参阅此处了解更多信息:Using C# Regular expression to replace XML element content

      毕竟,您对一个简单的 Java 程序的想法可能很好。如果您非常了解 XSLT,则 XSLT 转换可能会更容易。如果你知道 Perl ...那就是恕我直言的方法。

      话虽如此,如果您选择使用正则表达式并且您的 sed 版本支持扩展正则表达式,您可以使用 /g 使其成为多行。换句话说,将 /g 放在正则表达式的末尾,即使它们位于多行上,它也会匹配您的模式。

      还有。您提出的正则表达式是“贪婪的”。它将抓住最大的字符组,因为 "." 将从第一次出现 of 到最后一个 .您可以通过将通配符更改为“.?”来使其“惰性”。将问号放在星号后将告诉它只匹配一组 to 。

      【讨论】:

      • 我很确定sed 中的/g 修饰符使其在行内全局替换,而不是跨行扩展。我也不认为sed 支持这样的惰性正则表达式——当我尝试它时,肯定不支持。
      【解决方案4】:

      我试图做同样的事情,但遇到了实现它的 [gu]awk 脚本。

      BEGIN { FS = "[<|>]" }
      {
          if ($2 == "xyz") {
              sub($3, "replacement")      
          }
          print
      }
      

      【讨论】:

        猜你喜欢
        • 2018-08-22
        • 2018-01-07
        • 2021-12-31
        • 2012-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-25
        相关资源
        最近更新 更多