Java 如何从一组XPath表达式生成XML文件?
我希望能够在给定一组XPath映射的情况下生成完整的XML文件 输入可以在两个映射中指定:(1)一个映射列出XPath表达式和值;以及(2)定义适当名称空间的另一个Java 如何从一组XPath表达式生成XML文件?,java,xml,xslt,xpath,xquery,Java,Xml,Xslt,Xpath,Xquery,我希望能够在给定一组XPath映射的情况下生成完整的XML文件 输入可以在两个映射中指定:(1)一个映射列出XPath表达式和值;以及(2)定义适当名称空间的另一个 /create/article[1]/id => 1 /create/article[1]/description => bar /create/article[1]/name[1] => foo /create/article[1]/price[
/create/article[1]/id => 1
/create/article[1]/description => bar
/create/article[1]/name[1] => foo
/create/article[1]/price[1]/amount => 00.00
/create/article[1]/price[1]/currency => USD
/create/article[2]/id => 2
/create/article[2]/description => some name
/create/article[2]/name[1] => some description
/create/article[2]/price[1]/amount => 00.01
/create/article[2]/price[1]/currency => USD
对于名称空间:
/create => xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/
/create/article => xmlns:ns1='http://predic8.com/material/1/‘
/create/article/price => xmlns:ns1='http://predic8.com/common/1/‘
/create/article/id => xmlns:ns1='http://predic8.com/material/1/'
还要注意,我还需要处理XPath属性表达式,这一点很重要。例如:我还应该能够处理属性,例如:
/create/article/@type => richtext
最后的输出应该如下所示:
<ns1:create xmlns:ns1='http://predic8.com/wsdl/material/ArticleService/1/'>
<ns1:article xmlns:ns1='http://predic8.com/material/1/‘ type='richtext'>
<name>foo</name>
<description>bar</description>
<ns1:price xmlns:ns1='http://predic8.com/common/1/'>
<amount>00.00</amount>
<currency>USD</currency>
</ns1:price>
<ns1:id xmlns:ns1='http://predic8.com/material/1/'>1</ns1:id>
</ns1:article>
<ns1:article xmlns:ns1='http://predic8.com/material/2/‘ type='richtext'>
<name>some name</name>
<description>some description</description>
<ns1:price xmlns:ns1='http://predic8.com/common/2/'>
<amount>00.01</amount>
<currency>USD</currency>
</ns1:price>
<ns1:id xmlns:ns1='http://predic8.com/material/2/'>2</ns1:id>
</ns1:article>
</ns1:create>
有趣的问题。让我们假设XPath表达式的输入集满足一些合理的约束,例如,如果有一个X/article[2],那么(在它之前)也有一个X/article[1]。让我们暂时把问题的名称空间部分放在一边
让我们来看一个XSLT2.0解决方案:我们将从表单中的输入开始
<paths>
<path value="1">/create/article[1]/id</path>
<path value="bar">/create/article[1]/description</path>
</paths>
/创建/article[1]/id
/创建/文章[1]/说明
然后我们会把它变成
<paths>
<path value="1"><step>create</step><step>article[1]</step><step>id</step></path>
...
</paths>
createarticle[1]id
...
现在,我们将调用一个函数,该函数在第一步进行分组,并递归调用自身在下一步进行分组:
<xsl:function name="f:group">
<xsl:param name="paths" as="element(path)*"/>
<xsl:param name="step" as="xs:integer"/>
<xsl:for-each-group select="$paths" group-by="step[$step]">
<xsl:element name="{replace(current-grouping-key(), '\[.*', '')}">
<xsl:choose>
<xsl:when test="count(current-group) gt 1">
<xsl:sequence select="f:group(current-group(), $step+1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="current-group()[1]/@value"/>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:for-each-group>
</xsl:function>
这是未经测试的,可能有一些细节需要调整才能使其正常工作。但我认为基本方法应该奏效
问题的名称空间部分最好通过预处理路径列表来解决,以便为每个步骤元素添加名称空间属性;然后可以在xsl:element指令中使用它将元素放在正确的名称空间中。如果建立在以下基础上,这个问题有一个简单的解决方案:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kNSFor" match="namespace" use="@of"/>
<xsl:variable name="vStylesheet" select="document('')"/>
<xsl:variable name="vPop" as="element()*">
<item path="/create/article/@type">richtext</item>
<item path="/create/article/@lang">en-us</item>
<item path="/create/article[1]/id">1</item>
<item path="/create/article[1]/description">bar</item>
<item path="/create/article[1]/name[1]">foo</item>
<item path="/create/article[1]/price[1]/amount">00.00</item>
<item path="/create/article[1]/price[1]/currency">USD</item>
<item path="/create/article[1]/price[2]/amount">11.11</item>
<item path="/create/article[1]/price[2]/currency">AUD</item>
<item path="/create/article[2]/id">2</item>
<item path="/create/article[2]/description">some name</item>
<item path="/create/article[2]/name[1]">some description</item>
<item path="/create/article[2]/price[1]/amount">00.01</item>
<item path="/create/article[2]/price[1]/currency">USD</item>
<namespace of="create" prefix="ns1:"
url="http://predic8.com/wsdl/material/ArticleService/1/"/>
<namespace of="article" prefix="ns1:"
url="xmlns:ns1='http://predic8.com/material/1/"/>
<namespace of="@lang" prefix="xml:"
url="http://www.w3.org/XML/1998/namespace"/>
<namespace of="price" prefix="ns1:"
url="xmlns:ns1='http://predic8.com/material/1/"/>
<namespace of="id" prefix="ns1:"
url="xmlns:ns1='http://predic8.com/material/1/"/>
</xsl:variable>
<xsl:template match="/">
<xsl:sequence select="my:subTree($vPop/@path/concat(.,'/',string(..)))"/>
</xsl:template>
<xsl:function name="my:subTree" as="node()*">
<xsl:param name="pPaths" as="xs:string*"/>
<xsl:for-each-group select="$pPaths" group-adjacent=
"substring-before(substring-after(concat(., '/'), '/'), '/')">
<xsl:if test="current-grouping-key()">
<xsl:choose>
<xsl:when test=
"substring-after(current-group()[1], current-grouping-key())">
<xsl:variable name="vLocal-name" select=
"substring-before(concat(current-grouping-key(), '['), '[')"/>
<xsl:variable name="vNamespace"
select="key('kNSFor', $vLocal-name, $vStylesheet)"/>
<xsl:choose>
<xsl:when test="starts-with($vLocal-name, '@')">
<xsl:attribute name=
"{$vNamespace/@prefix}{substring($vLocal-name,2)}"
namespace="{$vNamespace/@url}">
<xsl:value-of select=
"substring(
substring-after(current-group(), current-grouping-key()),
2
)"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$vNamespace/@prefix}{$vLocal-name}"
namespace="{$vNamespace/@url}">
<xsl:sequence select=
"my:subTree(for $s in current-group()
return
concat('/',substring-after(substring($s, 2),'/'))
)
"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="current-grouping-key()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:for-each-group>
</xsl:function>
</xsl:stylesheet>
richtext
美国英语
1.
酒吧
福
0
美元
11.11
澳元
2.
某个名字
一些描述
0.01
美元
将此转换应用于任何XML文档(未使用)时,将生成所需的正确结果:
<ns1:create xmlns:ns1="http://predic8.com/wsdl/material/ArticleService/1/">
<ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/" type="richtext"
xml:lang="en-us"/>
<ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/">
<ns1:id>1</ns1:id>
<description>bar</description>
<name>foo</name>
<ns1:price>
<amount>00.00</amount>
<currency>USD</currency>
</ns1:price>
<ns1:price>
<amount>11.11</amount>
<currency>AUD</currency>
</ns1:price>
</ns1:article>
<ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/">
<ns1:id>2</ns1:id>
<description>some name</description>
<name>some description</name>
<ns1:price>
<amount>00.01</amount>
<currency>USD</currency>
</ns1:price>
</ns1:article>
</ns1:create>
1.
酒吧
福
0
美元
11.11
澳元
2.
某个名字
一些描述
0.01
美元
说明:
<ns1:create xmlns:ns1="http://predic8.com/wsdl/material/ArticleService/1/">
<ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/" type="richtext"
xml:lang="en-us"/>
<ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/">
<ns1:id>1</ns1:id>
<description>bar</description>
<name>foo</name>
<ns1:price>
<amount>00.00</amount>
<currency>USD</currency>
</ns1:price>
<ns1:price>
<amount>11.11</amount>
<currency>AUD</currency>
</ns1:price>
</ns1:article>
<ns1:article xmlns:ns1="xmlns:ns1='http://predic8.com/material/1/">
<ns1:id>2</ns1:id>
<description>some name</description>
<name>some description</name>
<ns1:price>
<amount>00.01</amount>
<currency>USD</currency>
</ns1:price>
</ns1:article>
</ns1:create>
合理的假设是,在整个生成的文档中,具有相同local-name()
的任何两个元素都属于同一名称空间——这涵盖了现实世界中绝大多数XML文档
命名空间规范遵循路径规范。nsmespace规范的格式为:
在使用xsl:element
生成元素之前,使用xsl:key
创建的索引选择适当的名称空间规范。根据该名称空间规范,其前缀
和url
属性的值用于在xsl:element
指令中指定完整元素名称和元素名称空间uri的值
我遇到了类似的情况,我必须将一组XPath/FQN值映射转换为XML。通用的简单解决方案可以使用以下代码,这些代码可以增强到特定的需求
public class XMLUtils {
static public String transformToXML(Map<String, String> pathValueMap, String delimiter)
throws ParserConfigurationException, TransformerException {
DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentFactory.newDocumentBuilder();
Document document = documentBuilder.newDocument();
Element rootElement = null;
Iterator<Entry<String, String>> it = pathValueMap.entrySet().iterator();
while (it.hasNext()) {
Entry<String, String> pair = it.next();
if (pair.getKey() != null && pair.getKey() != "" && rootElement == null) {
String[] pathValuesplit = pair.getKey().split(delimiter);
rootElement = document.createElement(pathValuesplit[0]);
break;
}
}
document.appendChild(rootElement);
Element rootNode = rootElement;
Iterator<Entry<String, String>> iterator = pathValueMap.entrySet().iterator();
while (iterator.hasNext()) {
Entry<String, String> pair = iterator.next();
if (pair.getKey() != null && pair.getKey() != "" && rootElement != null) {
String[] pathValuesplit = pair.getKey().split(delimiter);
if (pathValuesplit[0].equals(rootElement.getNodeName())) {
int i = pathValuesplit.length;
Element parentNode = rootNode;
int j = 1;
while (j < i) {
Element child = null;
NodeList childNodes = parentNode.getChildNodes();
for (int k = 0; k < childNodes.getLength(); k++) {
if (childNodes.item(k).getNodeName().equals(pathValuesplit[j])
&& childNodes.item(k) instanceof Element) {
child = (Element) childNodes.item(k);
break;
}
}
if (child == null) {
child = document.createElement(pathValuesplit[j]);
if (j == (i - 1)) {
child.appendChild(
document.createTextNode(pair.getValue() == null ? "" : pair.getValue()));
}
}
parentNode.appendChild(child);
parentNode = child;
j++;
}
} else {
// ignore any other root - add logger
System.out.println("Data not processed for node: " + pair.getKey());
}
}
}
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource domSource = new DOMSource(document);
// to return a XMLstring in response to an API
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
StreamResult resultToFile = new StreamResult(new File("C:/EclipseProgramOutputs/GeneratedXMLFromPathValue.xml"));
transformer.transform(domSource, resultToFile);
transformer.transform(domSource, result);
return writer.toString();
}
public static void main(String args[])
{
Map<String, String> pathValueMap = new HashMap<String, String>();
String delimiter = "/";
pathValueMap.put("create/article__1/id", "1");
pathValueMap.put("create/article__1/description", "something");
pathValueMap.put("create/article__1/name", "Book Name");
pathValueMap.put("create/article__1/price/amount", "120" );
pathValueMap.put("create/article__1/price/currency", "INR");
pathValueMap.put("create/article__2/id", "2");
pathValueMap.put("create/article__2/description", "something else");
pathValueMap.put("create/article__2/name", "Book name 1");
pathValueMap.put("create/article__2/price/amount", "2100");
pathValueMap.put("create/article__2/price/currency", "USD");
try {
XMLUtils.transformToXML(pathValueMap, delimiter);
} catch (ParserConfigurationException | TransformerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}}
这将完成清理工作您的要求太模糊了:“我还需要处理任何类型的XPath表达式,这一点很重要”->,它们通常是一组无法解决的方程。如果您对映射设置了很多限制,使它们基本上都与您的示例中的相同,那么只需循环它们并填充DOM即可。我们只限于节点路径和属性如何。我会更新这个问题。那么@Larry,你对我的解决方案有什么问题?@SeanB.Durkin它似乎不是通用的,我想我忘了在问题中明确提到这一点。例如,您似乎有硬编码的表达式,如/s11:Envelope/s11:Body/ns1:create/article
。另外,我不确定为什么会有这样的限制,即匹配总是以abc[n]开始,即以方括号表达式等。不过,在这种情况下,我确实需要一个通用的解决方案。不过还是要谢谢你,如果你还有其他想法,请贡献。还有“模板”文档吗?如果是这样的话,它的任何部分都是固定的吗?非常感谢你的解决方案。但是,我在尝试处理时遇到以下错误:第8行FODC0002上的可恢复错误:org.xml.sax.SAXParseException:prolog中不允许包含内容。第52行XPTY0004出错:在my:subTree()(#35)
中,不允许将空序列作为key()的第三个参数。你知道怎么回事吗?另一个问题是:上述解决方案是否也支持在XML文档中创建属性。例如,在问题中,这就是我所说的拥有一个规范的意思,比如/create/article/@type=>abcde
,等等。这意味着,就像我可以创建节点一样,我也应该能够创建属性,等等。@Larry:[Re:attribute creation}这可以在您显示和解释attibute创建规则的那一刻完成,目前还没有任何规则。@Larry,至于您在运行转换时遇到的问题,有几个可能的原因:a)您更改了代码;b)您没有使用符合XSLT 2.0的处理器。I a
resultString = resultString.replaceAll("(__[0-9][0-9])|(__[0-9])", "");