如何使用Javax.xml.transformer API将XML文档传递给XSL文件?

7

我正在使用 javax.xml.transform API 进行 XSL 转换。该 API 只允许将一个 XML 文档作为输入应用于转换,如下所示。

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    StringWriter stringWriter = new StringWriter();
    File xml = new File("C:\\abc");
    File xsl = new File("C:\\def.xsl");
    factory.setNamespaceAware(true);
    DocumentBuilder builder = factory.newDocumentBuilder();
    document = builder.parse(xml);
    TransformerFactory transformerFactory = 
    TransformerFactory.newInstance();
    StreamSource style = new StreamSource(xsl);
    Transformer transformer = transformerFactory.newTransformer(style);
    DOMSource source = new DOMSource(document);

此外,您可以像下面这样传递简单的字符串参数,而不会出现任何问题:
transformer.setParameter("mode", "CREATE");

但是,我希望将XML文档作为参数传递给XSL文件。我尝试了以下代码,这是在SO页面上建议的:

DocumentBuilder builder = factory.newDocumentBuilder();
 final Document documentFile = builder.parse(xml2);
 Map<String, Document> docs = new HashMap<String, Document>();
 docs.put("lookup", documentFile);
 transformer.setURIResolver(new DocumentURIResolver(docs));

我设置了XML中的标签,用以下方式接收值:

<xsl:variable name="lookup" select="('documentFile')/>  . 

但是对我来说这并不起作用。有人可以帮我通过javax.xml.transform API正确传递多个XML文档到任何XSL文件吗?

更新

仍然困在这个问题上,有人可以告诉我如何将XML对象作为参数传递到XSLT 2.0样式表中吗?我已经尝试了不同的方法,但仍然没有成功。我需要知道通过JAVA xsl transform API的解决办法。


1
如果你在标记问题为[tag:xslt-2.0]时询问java.xml.tansform,你会使用哪个XSLT处理器?你是否知道自从1.0版本以来,XSLT就拥有了强大的“document”函数,它允许直接从XSLT中加载/访问其他XML文档,甚至可以一次性加载多个文档? - Martin Honnen
嗨Martin,我没有特定使用任何处理器,所以我假设默认的XSLT处理器正在被使用。我知道XSLT有强大的文档功能,但我想通过transformer.setParameter()函数传递该文档,类似于传递字符串或整数参数的方式,因此我需要知道我们是如何实现的? - Saurabh Chaturvedi
任何帮助都将不胜感激!! :) - Saurabh Chaturvedi
嗯,Oracle Java JRE中的默认XSLT处理器通常是某个内部版本的Xalan,而Xalan肯定不是XSLT 2处理器,所以我不确定为什么你会将问题标记为那个版本。我想一个单一参数可能可以作为W3C DOM文档或者一般节点传递。 - Martin Honnen
谢谢Martin!!也许我之前不知道这个。但是如果我将它设置为W3C DOM文档,我不知道在XSLT方面应该使用什么正确的表达式来接收它。 - Saurabh Chaturvedi
显示剩余9条评论
5个回答

3

我认为你的问题在于XSLT。请更改:

<xsl:variable name="lookup" select="('documentFile')/>  . 

为了

<xsl:variable name="lookup" select="document('lookup')/>

这将导致变压器使您文档的DOM在变量lookup中可访问。键lookup来自docs.put("lookup", documentFile);

通过URIResolver动态传递多个XML源到XSL转换。

完整工作示例:

有三个XML文件:repo.xmlbooks.xmlarticles.xmlrepo.xml包含有关书籍和文章的状态信息。文件articles.xmlbooks.xml包含每个项目的标题信息。目标是打印所有书籍和文章的状态信息以及标题信息。所有文件中的条目都通过id键连接。

GitHub上找到完整示例或复制/粘贴下面的列表。

repo.xml

<repository>
    <book>
        <id>1</id>
        <status>available</status>
    </book>
    <book>
        <id>2</id>
        <status>lost</status>
    </book>
    <article>
        <id>1</id>
        <status>in transit</status>
    </article>
</repository>

books.xml

<books>
    <book id="1">
        <title>Book One</title>
    </book>
    <book id="2">
        <title>Book Two</title>
    </book>
    <book id="3">
        <title>Book Three</title>
    </book>
</books>

articles.xml

<articles>
    <article id="1">
         <title>Article One</title>
    </article>
    <article id="2">
        <title>Article Two</title>
    </article>
    <article id="3">
        <title>Article Three</title>
    </article>
</articles>

join.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">


<xsl:output method="xml" encoding="UTF-8" indent="yes" />

<xsl:template match="/">
    <titleStatusJoin>
        <xsl:for-each select="//book">
            <xsl:variable name="myId" select="id" />
            <book>
                <status>
                    <xsl:value-of select="status" />
                </status>
                <title>
                    <xsl:for-each select="document('bookFile')//book">
                        <xsl:variable name="bookId" select="@id" />
                        <xsl:choose>
                            <xsl:when test="$myId = $bookId">
                                <xsl:value-of select="title" />
                            </xsl:when>
                        </xsl:choose>
                    </xsl:for-each>
                </title>
            </book>
        </xsl:for-each>
        <xsl:for-each select="//article">
            <xsl:variable name="myId" select="id" />
            <article>
                <status>
                    <xsl:value-of select="status" />
                </status>
                <title>
                    <xsl:for-each select="document('articleFile')//article">
                        <xsl:variable name="bookId" select="@id" />
                        <xsl:choose>
                            <xsl:when test="$myId = $bookId">
                                <xsl:value-of select="title" />
                            </xsl:when>
                        </xsl:choose>
                    </xsl:for-each>
                </title>
            </article>
        </xsl:for-each>

    </titleStatusJoin>
</xsl:template>
</xsl:stylesheet>

请使用以下Java代码...
@Test
public void useMultipleXmlSourcesInOneXsl3() {
    InputStream xml = Thread.currentThread().getContextClassLoader().getResourceAsStream("stack54335576/repo.xml");
    InputStream xsl = Thread.currentThread().getContextClassLoader().getResourceAsStream("stack54335576/join3.xsl");
    InputStream booksXml = Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream("stack54335576/books.xml");
    InputStream articlesXml = Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream("stack54335576/articles.xml");
    Document booksDom = readXml(booksXml);
    Document articlesDom = readXml(articlesXml);
    Map<String, Document> parameters = new HashMap<>();
    parameters.put("bookFile", booksDom);
    parameters.put("articleFile", articlesDom);
    xslt(xml, xsl, parameters);
}

public final void xslt(InputStream xml, InputStream xsl, Map<String, Document> parameters) {
    try {
        TransformerFactory factory = TransformerFactory.newInstance();
        Transformer transformer = factory.newTransformer(new StreamSource(xsl));
        transformer.setURIResolver((href, base) -> new DOMSource(parameters.get(href)));
        transformer.transform(new StreamSource(xml), new StreamResult(System.out));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

private Document readXml(InputStream xmlin) {
    try {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        DocumentBuilder db = dbf.newDocumentBuilder();
        return db.parse(xmlin);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

…生成此输出

<?xml version="1.0" encoding="UTF-8"?>
<titleStatusJoin>
   <book>
      <status>available</status>
      <title>Book One</title>
   </book>
   <book>
     <status>lost</status>
     <title>Book Two</title>
   </book>
   <article>
     <status>in transit</status>
     <title>Article One</title>
   </article>
</titleStatusJoin>

1
使用默认的非命名空间感知DocumentBuilder和DOM Level 1,以及非命名空间感知createElement来操作带有命名空间的XML(XSLT)似乎是在自找麻烦。我强烈建议将DocumentBuilder设置为命名空间感知,并使用createElementNS与XSLT命名空间一起使用。 - Martin Honnen

1

你已经尝试过这个了吗?

org.w3c.dom.Document doc = ... // Your xml document
transformer.setParameter("demo", doc.getDocumentElement());

1

(回答扩展以通过URIResolver传递解析后的W3C DOM文档)

可以使用JRE提供的Xalan XSLT处理器的版本在纯XSLT / XPath中完成此操作。

例如,假设输入文档的名称作为参数传递给Transformer

    File parentDir = new File("c:\\dir");

    StringWriter stringWriter = new StringWriter();
    File xml = new File(parentDir, "input.xml");
    File xsl = new File(parentDir, "xslt-document-param.xslt");
    Source xsltSource = new StreamSource(xsl);
    Source xmlSource = new StreamSource(xml);

    TransformerFactory transformerFactory = TransformerFactory
            .newInstance();
    Transformer transformer = transformerFactory.newTransformer(xsltSource);
    transformer.setParameter("doc-name", "basic.xml");
    transformer.transform(xmlSource, new StreamResult(stringWriter));

    System.out.println(stringWriter);

然后,可以将此参数传递到XPath中的document()函数中,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
 xmlns:xalan="http://xml.apache.org/xalan"
 version="1.0"
 exclude-result-prefixes="xalan">

 <xsl:output 
  method="xml"
  indent="yes" 
  xalan:indent-amount="2"/>

 <xsl:param name="doc-name"/>

 <xsl:template match="/">
  <xsl:variable name="doc-content" select="document($doc-name)"/>
  <parent>
   <xsl:for-each select="$doc-content/basic/*">
    <child>
     <xsl:value-of select="name(.)"/>
    </child>
   </xsl:for-each>
  </parent>
 </xsl:template>

</xsl:stylesheet>

这个可以读取参数中的 basic.xml 文件:

<basic>
 <one/>
 <two/>
 <three/>
</basic>

并将其转换为:

<parent>
  <child>one</child>
  <child>two</child>
  <child>three</child>
</parent>
document() 函数的参数是一个 URI。相对路径是相对于 XSL 文件解析的。同样,这可以是一个完整的 URL,或者通过一个自定义的 transformer.setURIResolver() 解析,就像这个问题中的情况一样。
(从这里开始编辑...)
为了使用已解析的 Document 对象传递给 XSLT,URIResolver 方法能够处理将其传递回 document() 函数。
例如,以问题中的查找为例:
    File lookupXml = new File(parentDir, "basic.xml");
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = factory.newDocumentBuilder();
    Document document = builder.parse(lookupXml);
    Map<String, Document> docs = new HashMap<>();
    docs.put("lookup", document);

    transformer.setURIResolver((href, base) -> new DOMSource(docs.get(href)));

这个XSL能够遍历与上述相同的basic.xml文件...
 <xsl:template match="/">
  <xsl:variable name="doc-content" select="document('lookup')"/>
  <parent>
   <xsl:for-each select="$doc-content/basic/*">
    <child>
     <xsl:value-of select="name(.)"/>
    </child>
   </xsl:for-each>
  </parent>
 </xsl:template>

...并输出相同的结果。


0

尝试将您的<xsl:variable name="lookup" select="('documentFile')"/>指令替换为<xsl:variable name="documentFile" select="document($lookup)"/>,并使用transformer.setParameter("lookup", "myfile.xml");将您的XML文档作为参数传递,这意味着:将由lookup参数引用的文档加载到documentFile变量中。

另请参见使用XSL从外部XML文件提取数据


0

关于您的问题,如果您提供用例并询问如何解决,将比询问如何修复代码更有帮助,因为我们无法完全看到您的代码和XML。

以下可能是解决方案:

1)我们可以将XML转换为字符串

try {
    StringReader _reader = new StringReader("<xml>vkhan</xml>");
    StringWriter _writer = new StringWriter();
    TransformerFactory tFactory = TransformerFactory.newInstance();
    Transformer transformer = tFactory.newTransformer(
            new javax.xml.transform.stream.StreamSource("styler.xsl"));//ur xsl

    transformer.transform(
            new javax.xml.transform.stream.StreamSource(_reader), 
            new javax.xml.transform.stream.StreamResult(_writer));

    String result = writer.toString();
} catch (Exception e) {
    e.printStackTrace();
}

2) 根据您的要求修改以下代码,将其作为调用对象列表传递给for循环。

public class Data {

    public static final Document transformXmlDocument(Document sourceDocument, InputStream xsltFile) {
        DOMSource xmlSource = new DOMSource(sourceDocument);
        StreamSource xsltSource = new StreamSource(xsltFile);

        Document transformedData = null;

        try {        
            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer(xsltSource);

            ByteArrayOutputStream output = new ByteArrayOutputStream();
            StreamResult result = new StreamResult(output);

            transformer.transform(xmlSource, result);

            DocumentBuilder resultBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            transformedData = resultBuilder.parse(
                new InputSource(
                    new StringReader(
                        new String(output.toByteArray())
                    )
                )
            );
        } catch (Exception e) {
            Log.e("XSLT Transformation", e.getMessage());
        }

        return transformedData;
    }
}

如果这个答案没有帮到您,请更新您的完整Java代码、示例XML和要求。 - vaquar khan
嗨vaquar,感谢您的回复。我不是让您修复我的代码。我已经能够提供一个XML作为输入,就像您上面展示的那样。我想要将其他XML文件传递给我的XSL文件进行转换。这就是为什么我想要传递一个XML文件作为附加参数的原因,这是我的使用情况。我尝试了不同的方法,但没有一个方法能够很好地工作。如果您能帮忙,请告诉我,谢谢!! - Saurabh Chaturvedi
请更新您问题中的示例JSON,因为我们需要输入JSON来解决问题。 - vaquar khan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接