我有一堆XSL。其中一个碰巧使用base-uri()。
直接针对文件运行时,它会显示文档的systemId。 当在另一个XSL之后运行时,它显示XSL的systemId。
我无法控制的事情
另外,我更喜欢流媒体解决方案。这可以通过将每个中间结果写入磁盘并将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);
}
}
答案 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;
}
}