如何使用groovy在XML中进行搜索和替换?

时间:2020-03-06 14:20:58  来源:igfitidea点击:

如何使用Groovy在XML中进行搜索和替换?

我需要尽可能简短的内容,因为我会将这段代码提供给测试人员进行SoapUI脚本编写。

更具体地说,我该如何转向:

<root><data></data></root>

进入:

<root><data>value</data></root>

解决方案

检查一下:
http://today.java.net/pub/a/today/2004/08/12/groovyxml.html?page=2

经过疯狂的编码后,我看到了光,像这样

import org.custommonkey.xmlunit.Diff
import org.custommonkey.xmlunit.XMLUnit

def input = '''<root><data></data></root>'''
def expectedResult = '''<root><data>value</data></root>'''

def xml = new XmlParser().parseText(input)

def p = xml.'**'.data
p.each{it.value="value"}

def writer = new StringWriter()
new XmlNodePrinter(new PrintWriter(writer)).print(xml)
def result = writer.toString()

XMLUnit.setIgnoreWhitespace(true)
def xmlDiff = new Diff(result, expectedResult)
assert xmlDiff.identical()

不幸的是,这不会保留原始xml文档中的注释和元数据等,因此我将不得不寻找另一种方法

在http://groovy.codehaus.org/Processing+XML的"更新XML"部分中介绍了三种更新XML的"常规"常规方法。

在这三个中,似乎只有DOMCategory方式保留XML注释等。

我使用DOMCategory做了一些测试,并且几乎可以正常工作。我可以进行替换,但是一些与infopath相关的注释消失了。我正在使用这样的方法:

def rtv = { xml, tag, value ->
    def doc     = DOMBuilder.parse(new StringReader(xml))
    def root    = doc.documentElement
    use(DOMCategory) { root.'**'."$tag".each{it.value=value} }
    return DOMUtil.serialize(root)    
}

在这样的来源上:

<?xml version="1.0" encoding="utf-8"?>
<?mso-infoPathSolution name="urn:schemas-microsoft-com:office:infopath:FA_Ansoegning:http---ementor-dk-application-2007-06-22-" href="manifest.xsf" solutionVersion="1.0.0.14" productVersion="12.0.0" PIVersion="1.0.0.0" ?>
<?mso-application progid="InfoPath.Document" versionProgid="InfoPath.Document.2"?>
<application:FA_Ansoegning xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:application="http://corp.dk/application/2007/06/22/"
xmlns:xd="http://schemas.microsoft.com/office/infopath/2003"
xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/200    8-04-14T14:31:48">
    <Mobiltlf></Mobiltlf>
  <E-mail-adresse></E-mail-adresse>
</application:FA_Ansoegning>

结果中唯一缺少的是结果中的<?msolines。有人对此有想法吗?

对我来说,实际的复制,搜索和替换对于XSLT样式表来说似乎是完美的工作。在XSLT中,我们只需复制所有内容(包括遇到问题的项目)并在需要的地方插入数据就完全没有问题。我们可以通过XSL参数传递数据的特定值,也可以动态修改样式表本身(如果在Groovy程序中包含为字符串)。调用此XSLT从Groovy内转换文档非常简单。

我很快将以下Groovy脚本拼凑在一起(但我毫不怀疑它可以更简单/紧凑地编写):

import javax.xml.transform.TransformerFactory
import javax.xml.transform.stream.StreamResult
import javax.xml.transform.stream.StreamSource

def xml = """
<?xml version="1.0" encoding="utf-8"?>
<?mso-infoPathSolution name="urn:schemas-microsoft-com:office:infopath:FA_Ansoegning:http---ementor-dk-application-2007-06-22-" href="manifest.xsf" solutionVersion="1.0.0.14" productVersion="12.0.0" PIVersion="1.0.0.0" ?>
<?mso-application progid="InfoPath.Document" versionProgid="InfoPath.Document.2"?>
<application:FA_Ansoegning xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:application="http://ementor.dk/application/2007/06/22/"
xmlns:xd="http://schemas.microsoft.com/office/infopath/2003"
xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/200    8-04-14T14:31:48">
    <Mobiltlf></Mobiltlf>
  <E-mail-adresse></E-mail-adresse>
</application:FA_Ansoegning>
""".trim()

def xslt = """
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:param name="mobil" select="'***dummy***'"/>
    <xsl:param name="email" select="'***dummy***'"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="Mobiltlf">
        <xsl:copy>
            <xsl:value-of select="$mobil"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="E-mail-adresse">
        <xsl:copy>
            <xsl:value-of select="$email"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>
""".trim()

def factory = TransformerFactory.newInstance()
def transformer = factory.newTransformer(new StreamSource(new StringReader(xslt)))

transformer.setParameter('mobil', '1234567890')
transformer.setParameter('email', '[email protected]')

transformer.transform(new StreamSource(new StringReader(xml)), new StreamResult(System.out))

运行此脚本将产生:

<?xml version="1.0" encoding="UTF-8"?><?mso-infoPathSolution name="urn:schemas-microsoft-com:office:infopath:FA_Ansoegning:http---ementor-dk-application-2007-06-22-" href="manifest.xsf" solutionVersion="1.0.0.14" productVersion="12.0.0" PIVersion="1.0.0.0" ?>
<?mso-application progid="InfoPath.Document" versionProgid="InfoPath.Document.2"?>
<application:FA_Ansoegning xmlns:application="http://ementor.dk/application/2007/06/22/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xd="http://schemas.microsoft.com/office/infopath/2003" xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/200    8-04-14T14:31:48">
    <Mobiltlf>1234567890</Mobiltlf>
  <E-mail-adresse>[email protected]</E-mail-adresse>
</application:FA_Ansoegning>

到目前为止,这是最好的答案,并且给出了正确的结果,所以我将接受答案:)
但是,对我来说太大了。我想我最好解释一下替代方案是:

xml.replace("<Mobiltlf></Mobiltlf>", <Mobiltlf>32165487</Mobiltlf>")

但这不是xml'y,所以我想我会寻找替代方法。另外,我不能确定第一个标签始终是空的。

我们可以使用XSLT进行的某些操作,也可以使用某种形式的"搜索并替换"进行的操作。这完全取决于问题的复杂程度以及实施解决方案的"通用性"。为了使我们自己的示例更为通用:

xml.replaceFirst("<Mobiltlf>[^<]*</Mobiltlf>", '<Mobiltlf>32165487</Mobiltlf>')

我们选择的解决方案取决于我们。以我自己的经验(对于非常简单的问题),使用简单的字符串查找要比使用正则表达式快,而使用正则表达式要比使用成熟的XSLT转换要快(实际上很有意义)。

杰出的!非常感谢协助:)

那以更清洁,更轻松的方式解决了我的问题。最终看起来像这样:

def rtv = { xmlSource, tagName, newValue ->
    regex = "<$tagName>[^<]*</$tagName>"
    replacement = "<$tagName>${newValue}</$tagName>"
    xmlSource = xmlSource.replaceAll(regex, replacement)
    return xmlSource
}

input = rtv( input, "Mobiltlf", "32165487" )
input = rtv( input, "E-mail-adresse", "[email protected]" )
println input

由于我将其提供给测试人员以在他们的测试工具SoapUI中使用,因此我试图对其进行"包装",以使其更易于复制和粘贴。

就我的目的而言,这已经足够好了,但是如果我们可以再添加一个"扭曲",那将是完美的

假设输入中包含此内容...

<Mobiltlf type="national" anotherattribute="value"></Mobiltlf>

...并且即使我们替换了值,我们仍希望保留这两个属性。有没有办法使用正则表达式呢?

要保留属性,只需像这样修改小程序(我已经提供了一个示例源对其进行测试):

def input = """
<?xml version="1.0" encoding="utf-8"?>
<?mso-infoPathSolution name="urn:schemas-microsoft-com:office:infopath:FA_Ansoegning:http---ementor-dk-application-2007-06-22-" href="manifest.xsf" solutionVersion="1.0.0.14" productVersion="12.0.0" PIVersion="1.0.0.0" ?>
<?mso-application progid="InfoPath.Document" versionProgid="InfoPath.Document.2"?>
<application:FA_Ansoegning xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:application="http://ementor.dk/application/2007/06/22/"
xmlns:xd="http://schemas.microsoft.com/office/infopath/2003"
xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/200    8-04-14T14:31:48">
    <Mobiltlf  type="national" anotherattribute="value"></Mobiltlf>
  <E-mail-adresse attr="whatever"></E-mail-adresse>
</application:FA_Ansoegning>
""".trim()

def rtv = { xmlSource, tagName, newValue ->
    regex = "(<$tagName[^>]*>)([^<]*)(</$tagName>)"
    replacement = "$1${newValue}$3"
    xmlSource = xmlSource.replaceAll(regex, replacement)
    return xmlSource
}

input = rtv( input, "Mobiltlf", "32165487" )
input = rtv( input, "E-mail-adresse", "[email protected]" )
println input

运行此脚本将产生:

<?xml version="1.0" encoding="utf-8"?>
<?mso-infoPathSolution name="urn:schemas-microsoft-com:office:infopath:FA_Ansoegning:http---ementor-dk-application-2007-06-22-" href="manifest.xsf" solutionVersion="1.0.0.14" productVersion="12.0.0" PIVersion="1.0.0.0" ?>
<?mso-application progid="InfoPath.Document" versionProgid="InfoPath.Document.2"?>
<application:FA_Ansoegning xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:application="http://ementor.dk/application/2007/06/22/"
xmlns:xd="http://schemas.microsoft.com/office/infopath/2003"
xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/200    8-04-14T14:31:48">
    <Mobiltlf  type="national" anotherattribute="value">32165487</Mobiltlf>
  <E-mail-adresse attr="whatever">[email protected]</E-mail-adresse>
</application:FA_Ansoegning>

请注意,匹配的正则表达式现在包含3个捕获组:(1)起始标签(包括属性),(2)标签的"旧"内容是什么,以及(3)结束标签。替换字符串通过$ i语法引用这些捕获的组(使用反斜杠在GString中进行转义)。提示:正则表达式是非常强大的动物,熟悉它们真的很值得;-)。