使用groovy更新xml文件时保留格式

时间:2013-12-19 19:56:15

标签: xml regex groovy

我有大量包含网址的XML文件。我正在编写一个groovy实用程序来查找每个URL并将其替换为更新版本。

给出example.xml:

<?xml version="1.0" encoding="UTF-8"?>
<page>
    <content>
        <section>
            <link>
                <url>/some/old/url</url>
            </link>
            <link>
                <url>/some/old/url</url>
            </link>
        </section>
        <section>
            <link>
                <url>
                    /a/different/old/url?with=specialChars&amp;escaped=true
                </url>
            </link>
        </section>
    </content>
</page>

脚本运行后,example.xml应包含:

<?xml version="1.0" encoding="UTF-8"?>
<page>
    <content>
        <section>
            <link>
                <url>/a/new/and/improved/url</url>
            </link>
            <link>
                <url>/a/new/and/improved/url</url>
            </link>
        </section>
        <section>
            <link>
                <url>
                    /a/different/new/and/improved/url?with=specialChars&amp;stillEscaped=true
                </url>
            </link>
        </section>
    </content>
</page>

使用groovy优秀的xml支持很容易做到这一点,除了我想更改URL以及其他任何关于文件。

我的意思是:

  • 空格不得更改(文件可能包含空格,制表符或两者)
  • 必须保留评论
  • 必须保留
  • windows与unix样式的行分隔符
  • 不得添加或删除顶部的xml声明
  • 标签中的属性必须保留其订单

到目前为止,在尝试了XmlParser,DOMBuilder,XmlNodePrinter,XmlUtil.serialize()等许多组合之后,我已经逐行阅读每个文件,并应用了一个丑陋的xml实用程序和正则表达式。

读取和写入每个文件:

files.each { File file ->
    def lineEnding = file.text.contains('\r\n') ? '\r\n' : '\n'
    def newLineAtEof = file.text.endsWith(lineEnding)
    def lines = file.readLines()
    file.withWriter { w ->
        lines.eachWithIndex { line, index ->
            line = update(line)
            w.write(line)
            if (index < lines.size-1) w.write(lineEnding)
            else if (newLineAtEof) w.write(lineEnding)
        }
    }
}

搜索和更新一行中的网址:

def matcher = (line =~ urlTagRegexp) //matches a <url> element and its contents
matcher.each { groups ->
    def urlNode = new XmlParser().parseText(line)
    def url = urlNode.text()
    def newUrl = translate(url)
    if (newUrl) {
        urlNode.value = newUrl
        def replacement = nodeToString(urlNode)
        line = matcher.replaceAll(replacement)
    }
}

def nodeToString(node) {
    def writer = new StringWriter()
    writer.withPrintWriter { printWriter ->
        def printer = new XmlNodePrinter(printWriter)
        printer.preserveWhitespace = true
        printer.print(node)
    }
    writer.toString().replaceAll(/[\r\n]/, '')
}

这主要是有效的,除了它无法处理多行分割的标签,并且在将文件写回时弄乱换行很麻烦。

我是groovy的新手,但我觉得必须有更加时髦的方式来做这件事。

2 个答案:

答案 0 :(得分:10)

我刚刚在https://gist.github.com/akhikhl/8070808创建了gist,以演示如何使用Groovy和JDOM2完成此类转换。

重要说明:

  1. Groovy在技术上允许使用任何java库。如果有的话 无法使用Groovy JDK完成,可以使用其他库完成。
  2. 应该明确地包含jaxen库(实现XPath)(通过@Grab或通过maven / gradle),因为它是JDOM2的可选依赖项。
  3. @ Grab / @ GrabExclude指令的序列修复了jaxen对JDOM-1.0的古怪依赖。
  4. XPathFactory.compile还支持变量绑定和过滤器(参见在线javadoc)。
  5. XPathExpression(由compile返回)支持两个主要功能 - evaluate和evaluateFirst。 evaluate总是返回所有XML节点的列表,满足指定的谓词,而evaluateFirst只返回第一个匹配的XML节点。
  6. <强>更新

    以下代码:

    new XMLOutputter().with {
      format = Format.getRawFormat()
      format.setLineSeparator(LineSeparator.NONE)
      output(doc, System.out)
    }
    

    解决了保留空格和行分隔符的问题。 getRawFormat构造一个保留空格的格式对象。 LineSeparator.NONE指示格式对象,它不应该转换行分隔符。

    上面提到的要点也包含这个新代码。

答案 1 :(得分:7)

有一个没有任何第三方库的解决方案。

def xml = file.text
def document = groovy.xml.DOMBuilder.parse(new StringReader(xml))
def root = document.documentElement
use(groovy.xml.dom.DOMCategory) {
    // manipulate the XML here, i.e. root.someElement?.each { it.value = 'new value'}
}

def result = groovy.xml.dom.DOMUtil.serialize(root)

file.withWriter { w ->
    w.write(result)
}

取自http://jonathan-whywecanthavenicethings.blogspot.de/2011/07/keep-your-hands-off-of-my-whitespace.html