【问题标题】:How to pass a XML document to XSL file using Javax.xml.transformer API?如何使用 Javax.xml.transformer API 将 XML 文档传递给 XSL 文件?
【发布时间】:2019-06-17 12:50:45
【问题描述】:

我正在使用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 转换 API 的出路。

【问题讨论】:

  • 如果您询问java.xml.tansform 但将问题标记为xslt-2.0,您使用哪个XSLT 处理器?如您所知,XSLT 从 1.0 版开始具有强大的document 功能,允许直接从 XSLT 中加载/访问其他 XML 文档,甚至可以一步完成?
  • 嗨 Martin,我没有专门使用任何处理器,所以我假设正在使用默认 XSLT 处理器。我知道 XSLT 有强大的文档功能,但我想通过 transformer.setParameter() 函数传递该文档,类似于传递字符串或整数参数的方式,所以我需要知道我们是如何做到的?
  • 任何帮助将不胜感激! :)
  • 嗯,Oracle Java JRE 中的默认 XSLT 处理器通常是 Xalan 的某个内部化版本,而 Xalan 肯定不是 XSLT 2 处理器,所以不知道您为什么将问题标记为该版本。我认为单个参数可能可以作为 W3C DOM 文档或一般的节点传递。
  • 谢谢马丁!可能是我没有意识到这一点。但是,如果我将它设置为 W3C DOM 文档,我正在努力在 XSLT 端接收它的正确表达式应该是什么。

标签: java xml xslt-2.0 transformation


【解决方案1】:

我认为您的问题出在 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>

【讨论】:

  • 操作 XSLT,它是带有默认命名空间的 XML,不知道命名空间的 DocumentBuilder 和 DOM 级别 1,不知道命名空间的 createElement 似乎是在自找麻烦。我强烈建议让 DocumentBuilder 命名空间知道并将createElementNS 与 XSLT 命名空间一起使用。
【解决方案2】:

(答案扩展为处理通过 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>

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

【讨论】:

    【解决方案3】:

    你已经试过了吗?

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

    【讨论】:

      【解决方案4】:

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

      另见Extract data from External XML file using XSL

      【讨论】:

        【解决方案5】:

        不确定您的问题,如果您提供用例并询问如何解决将更有帮助,因为我们没有您的代码和 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,感谢您的回复。我不是要你修复我的代码。如上所示,我已经能够提供一个 XML 作为输入。我想将其他 XML 文件传递​​给我的 XSL 文件以执行转换。这就是为什么我想将 XML 文件作为附加参数传递的原因,准确地说,这是我的用例。我尝试了不同的方法,但没有一个适合我。如果你能帮忙,请告诉我,谢谢!!
        • 请更新您问题中的示例 json,因为需要输入 json 来解决问题
        猜你喜欢
        • 2012-11-03
        • 2010-12-31
        • 1970-01-01
        • 2011-01-01
        • 2011-10-17
        • 1970-01-01
        • 2020-12-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多