如何使用 groovy 在 XML 中进行搜索+替换?
题
如何使用 groovy 在 XML 中进行搜索+替换?
我需要尽可能短/简单的东西,因为我将将此代码提供给测试人员用于他们的 SoapUI 脚本。
更具体地说,我该如何转向:
<root><data></data></root>
进入:
<root><data>value</data></root>
解决方案
您可以使用 XSLT 执行的某些操作也可以使用某种形式的“搜索和替换”执行。这完全取决于您的问题有多复杂以及您想要实施解决方案的“通用性”程度。为了使您自己的示例更加通用:
xml.replaceFirst("<Mobiltlf>[^<]*</Mobiltlf>", '<Mobiltlf>32165487</Mobiltlf>')
您选择的解决方案取决于您。根据我自己的经验(对于非常简单的问题),使用简单的字符串查找比使用正则表达式更快,而正则表达式又比使用成熟的 XSLT 转换更快(实际上是有意义的)。
其他提示
经过一些疯狂的编码后,我看到了曙光,并这样做了
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 文档中的注释和元数据等,所以我必须找到另一种方法
我用 DOMCategory 做了一些测试,它几乎可以工作了。我可以进行替换,但一些与信息路径相关的评论消失了。我正在使用这样的方法:
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>
结果中唯一缺少的是结果中的 <?mso- 行。有人对此有想法吗?
到目前为止,这是最好的答案,它给出了正确的结果,因此我将接受答案:)但是,这对我来说太大了。我想我最好解释一下替代方案是:
xml.replace("<Mobiltlf></Mobiltlf>", <Mobiltlf>32165487</Mobiltlf>")
但这不是很 xml'y,所以我想我应该寻找替代方案。另外,我不能确定第一个标签始终为空。
要保留属性,只需像这样修改您的小程序(我已经包含了一个示例源来测试它):
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", "bob@email.com" )
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">bob@email.com</E-mail-adresse>
</application:FA_Ansoegning>
请注意,匹配的正则表达式现在包含 3 个捕获组:(1) 开始标记(包括属性),(2) 标记的“旧”内容是什么,以及 (3) 结束标记。替换字符串通过 $i 语法引用这些捕获的组(使用反斜杠在 GString 中转义它们)。只是一个提示:正则表达式是非常强大的动物,熟悉它们确实值得;-)。
页面上描述了三种更新 XML 的“官方”常规方法 http://groovy.codehaus.org/Processing+XML, ,“更新 XML”部分。
在这三种方式中,似乎只有 DOMCategory 方式保留了 XML 注释等。
对我来说,实际的复制、搜索和替换似乎是 XSLT 样式表的完美工作。在 XSLT 中,您完全可以复制所有内容(包括您遇到问题的项目),然后将数据插入到需要的位置。您可以通过 XSL 参数传递数据的特定值,也可以动态修改样式表本身(如果您将其作为字符串包含在 Groovy 程序中)。在 Groovy 中调用此 XSLT 来转换文档非常简单。
我很快将以下 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', 'john.doe@foobar.com')
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>john.doe@foobar.com</E-mail-adresse>
</application:FA_Ansoegning>
杰出的!非常感谢您的帮助:)
这以一种更干净、更简单的方式解决了我的问题。它最终看起来像这样:
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", "bob@email.com" )
println input
由于我将其提供给我们的测试人员在他们的测试工具 SoapUI 中使用,因此我尝试“包装”它,以便他们更容易复制和粘贴。
这对于我的目的来说已经足够好了,但如果我们能再添加一个“扭曲”那就更完美了
假设输入中有这个......
<Mobiltlf type="national" anotherattribute="value"></Mobiltlf>
...即使我们替换了值,我们也希望保留这两个属性。有没有办法使用正则表达式来实现这一点?