如何在XSL链中保留原始文档的systemId?

时间:2013-07-24 18:35:48

标签: java xslt saxon

我有一堆XSL。其中一个碰巧使用base-uri()。

直接针对文件运行时,它会显示文档的systemId。 当在另一个XSL之后运行时,它显示XSL的systemId。

我无法控制的事情

  • XSL内容
  • XSL的顺序
  • 必须使用XSLT2(saxon)

另外,我更喜欢流媒体解决方案。这可以通过将每个中间结果写入磁盘并将systemId伪造到原始文档的结果来解决,但效率非常低。

这是我到目前为止所尝试的内容。

public class BadSystemIdDemo {
  private static final SAXTransformerFactory XSLT2 =
      new net.sf.saxon.TransformerFactoryImpl();

  public static void main(String[] args) throws Exception {
    Result to = new StreamResult(System.out);

    // outputs: "file:///one.xsl"
    usingXMLFilter(to);
    System.out.println();

    // also outputs: "file:///one.xsl"
    usingTransformerHandler(to);
    System.out.println();

    // wanted: "file:///in.xml"
  }

  private static void usingTransformerHandler(Result to) throws Exception {
    TransformerHandler first = XSLT2.newTransformerHandler(Inputs.xsl1());
    TransformerHandler second = XSLT2.newTransformerHandler(Inputs.xsl2());

    first.setResult(new SAXResult(second));
    second.setResult(to);

    XSLT2.newTransformer().transform(Inputs.in(), new SAXResult(first));
  }

  private static void usingXMLFilter(Result to) throws Exception {
    XMLReader r = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
    XMLFilter first = XSLT2.newXMLFilter(Inputs.xsl1());
    XMLFilter second = XSLT2.newXMLFilter(Inputs.xsl2());

    first.setParent(r);
    second.setParent(first);

    XSLT2.newTransformer().transform(Inputs.in(second), to);
  }
}

仅举例,真实的东西显然更复杂。

public class Inputs {
  private static final String IN_SYSTEM_ID = "file:///in.xml";
  private static final String XSL1_SYSTEM_ID = "file:///one.xsl";
  private static final String XSL2_SYSTEM_ID = "file:///two.xsl";

  static Source in() {
    return new StreamSource(new StringReader("<root/>"), IN_SYSTEM_ID);
  }

  static Source in(XMLReader using) {
    return new SAXSource(using, SAXSource.sourceToInputSource(in()));
  }

  static Source xsl1() {
    String contents = ""
        + "<xsl:stylesheet version=\"2.0\""
        + "                xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">"
        + "  <xsl:template match=\"@*|node()\">"
        + "    <xsl:copy>"
        + "      <xsl:apply-templates select=\"@*|node()\"/>"
        + "    </xsl:copy>"
        + "  </xsl:template>"
        + "</xsl:stylesheet>";
    return new StreamSource(new StringReader(contents), XSL1_SYSTEM_ID);
  }

  static Source xsl2() {
    String contents = ""
        + "<xsl:stylesheet version=\"2.0\""
        + "                xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">"
        + "  <xsl:template match=\"*\">"
        + "    <xsl:value-of select=\"base-uri(.)\"/>"
        + "  </xsl:template>"
        + "</xsl:stylesheet>";
    return new StreamSource(new StringReader(contents), XSL2_SYSTEM_ID);
  }
}

2 个答案:

答案 0 :(得分:1)

我的第一个想法是在树中添加一个xml:base属性;这将决定base-uri()函数的结果。但考虑到你所描述的限制,也许这太具破坏性了。

说实话,我并不相信这些限制。如果您可以控制Java代码,那么您可以创建一个导入xsl2的样式表并覆盖调用base-uri()的模板,将其替换为对样式表参数的引用。

但是,如果你准备从JAXP接口转移到Saxon的s9api API,那么它可能就完成了。要在s9api中设置转换管道,可以使用一个XsltTransformer作为另一个XsltTransformer的Destination,并通过在第二个XsltTransformer上调用setBaseUri()来影响在该样式表中调用的base-uri()的结果。

答案 1 :(得分:0)

通过覆盖XMLReader #setDocumentLocator()来管理以实现此功能。这是相当hackish,如果输入文档使用XInclude,可能会破坏。

  private static void usingTransformerHandler(Result to) throws Exception {
    TransformerHandler first = XSLT2.newTransformerHandler(Inputs.xsl1());
    TransformerHandler second = XSLT2.newTransformerHandler(Inputs.xsl2());

    LocatorFixer fixer = new LocatorFixer();
    first.setResult(new SAXResult(fixer.wrap(second)));
    second.setResult(to);

    XSLT2.newTransformer().transform(Inputs.in(), new SAXResult(fixer.wrap(first)));
  }

  private static void usingXMLFilter(Result to) throws Exception {
    XMLReader r = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
    XMLFilter first = XSLT2.newXMLFilter(Inputs.xsl1());
    XMLFilter second = XSLT2.newXMLFilter(Inputs.xsl2());

    LocatorFixer fixer = new LocatorFixer();
    first.setParent(fixer.wrap(r));
    second.setParent(fixer.wrap(first));

    XSLT2.newTransformer().transform(Inputs.in(second), to);
  }

辅助

class LocatorFixer {
  private Locator copied;

  XMLFilterImpl wrap(XMLReader delegate) {
    return new XMLFilterImpl(delegate) {
      @Override
      public void setDocumentLocator(Locator real) {
        if (copied != null) {
          super.setDocumentLocator(copied);
        } else {
          copied = new LocatorImpl(real);
          super.setDocumentLocator(real);
        }
      }
    };
  }

  ContentHandler wrap(ContentHandler delegate) {
    XMLFilterImpl fixed = wrap((XMLReader) null);
    fixed.setContentHandler(delegate);
    return fixed;
  }
}