1

我有一堆 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);
  }
}
4

2 回答 2

1

我的第一个想法是向树添加一个 xml:base 属性;这将确定 base-uri() 函数的结果。但是鉴于您描述的限制,也许这太具有破坏性了。

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

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

于 2013-07-24T21:58:20.857 回答
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;
  }
}
于 2013-07-26T02:30:28.060 回答