I have an issue when generating PDF from Java objects using XSLFO and XSLT:
I have this code:
TransformerFactory.newInstance("org.apache.xalan.processor.TransformerFactoryImpl",
Thread.currentThread().getContextClassLoader());
templates = factory.newTemplates(new StreamSource(PdfGenerator.class.getResourceAsStream(ORDERS_XSL)));
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
Source src = getSourceForCommandList(commandeList);
try {
Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
Result res = new SAXResult(fop.getDefaultHandler());
templates.newTransformer().transform(src, res);
} finally {
out.flush();
}
My xslt uses fo namespace:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="orders">
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="simpleA4"
page-height="29.7cm" page-width="21cm" margin-top="2cm"
margin-bottom="1cm" margin-left="0.5cm" margin-right="0.5cm">
<fo:region-body />
</fo:simple-page-master>
</fo:layout-master-set>
<xsl:apply-templates select="order" />
</fo:root>
</xsl:template>
...
</xsl:template>
</xsl:stylesheet>
It works fine and generates a PDF from source Objects using Apache FOP and XSLT.
However, when I add this line to respect best practices regarding security:
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
It breaks because fo namespace is not loaded, I have those warning on template parsing:
SystemId Unknown; Line #13; Column #67; "master-name" attribute is not allowed on the fo:simple-page-master element!
SystemId Unknown; Line #13; Column #67; "page-height" attribute is not allowed on the fo:simple-page-master element!
SystemId Unknown; Line #13; Column #67; "page-width" attribute is not allowed on the fo:simple-page-master element!
SystemId Unknown; Line #13; Column #67; "margin-top" attribute is not allowed on the fo:simple-page-master element!
Issue was due to xalan TransformerFactory implementation being loaded instead of JDK's embedded one com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
I excluded xalan from dependencies :
<exclusions>
<exclusion>
<artifactId>xalan</artifactId>
<groupId>xalan</groupId>
</exclusion>
</exclusions>
And it worked
Related
I am generating a PDF document with XML file as input using Apache FOP 2.4.
To prevent XXE-Attacks I need to set the secure processing feature (FEATURE_SECURE_PROCESSING) in TransformerFactory:
InputStream xslTransformer = getClass().getClassLoader().getResourceAsStream("foo.xsl");
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setFeature(FEATURE_SECURE_PROCESSING, true);
Transformer transformer = transformerFactory.newTransformer(new StreamSource(xslTransformer));
transformer.transform(new DOMSource(), new SAXResult(fop.getDefaultHandler()));
After setting this feature I can't generate any PDF document and I'm getting warnings:
SystemId Unknown; Line #49; Column #99; "master-name" attribute is not allowed on the fo:simple-page-master element!
SystemId Unknown; Line #49; Column #99; "initial-page-number" attribute is not allowed on the fo:simple-page-master element!
SystemId Unknown; Line #49; Column #99; "page-height" attribute is not allowed on the fo:simple-page-master element!
SystemId Unknown; Line #49; Column #99; "page-width" attribute is not allowed on the fo:simple-page-master element!
etc ...
Here is a section of XSL file (foo.xsl):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:pdf="http://xmlgraphics.apache.org/fop/extensions/pdf">
<xsl:template match="/">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master master-name="A4-portrait" initial-page-number="1"
page-height="29.7cm" page-width="21.0cm" margin-top="0cm"
margin-left="1cm" margin-right="1.3cm" margin-bottom="0cm">
<fo:region-body margin-top="2.2cm" margin-bottom="1.2cm" margin-left="1.3cm"/>
<fo:region-before region-name="xsl-region-before" extent="2.2cm"/>
<fo:region-after region-name="xsl-region-after" extent="1.2cm"/>
<fo:region-start region-name="xsl-region-start" extent="1.3cm"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="A4-portrait" font-family="Consolas" font-size="11">
<fo:flow flow-name="xsl-region-body">
<fo:block linefeed-treatment="preserve" font-weight="bold">
foo
</fo:block>
<fo:block linefeed-treatment="preserve">
bar
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>
How should I use this feature and make it work? Java version is 8.
This is due to xalan-2.7.2.
Here is the bug in Xalan-J
Switching to xalan-2.7.1 or earlier will solve your problem.
You may have to force exclusions for xalan on an Apache-FO dependency.
You can also overwrite with 2.7.2_3, which patches this problem.
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.xalan</artifactId>
<version>2.7.2_3</version><!--$NO-MVN-MAN-VER$-->
</dependency>
Use of <!--$NO-MVN-MAN-VER$--> prevents overrides.
I'm trying to transform an XML document with the javax.xml.transform.Transformer and XSLT, and am having trouble with a namespace prefix that is not recognized when I call the transform method.
Here is the XML document which has the "m" namespace prefix defined in it:
<?xml version="1.0" encoding="UTF-8"?>
<edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0">
<edmx:DataServices xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:DataServiceVersion="1.0">
<Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" Namespace="SITUATION">
</Schema>
</edmx:DataServices>
</edmx:Edmx>
Here is the XSL that currently just copies the whole XML document (later I'd like to extend it to merge in another XML document, similar to what is described here: https://stackoverflow.com/a/5706319/208011):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Copy everything including attributes as default action -->
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="#*" />
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Here is the java code:
Source xsltSource = new StreamSource(new File(getClass().getClassLoader().getResource("merge-metadata.xsl").getFile()));
transformer = transFact.newTransformer(xsltSource);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
Document input = dbf.newDocumentBuilder().parse(new InputSource(new StringReader(s1)));
StringWriter out = new StringWriter();
transformer.transform(new DOMSource(input), new StreamResult(out));
Here's the stack trace:
Caused by: java.lang.RuntimeException: Namespace for prefix 'm' has not been declared.
at com.sun.org.apache.xml.internal.serializer.SerializerBase.getNamespaceURI(SerializerBase.java:915)
at com.sun.org.apache.xml.internal.serializer.SerializerBase.addAttribute(SerializerBase.java:431)
at com.sun.org.apache.xml.internal.serializer.ToUnknownStream.addAttribute(ToUnknownStream.java:316)
Eventually I want to merge this XML document with another, but I can't get past the namespace issue to just copy this one.
If I don't use the xsl stylesheet in the transformer factory newTransformer() method then I don't get the namespace error and the output of the transformation is exactly the same as the original XML document.
If I set the DocumentBuilderFactory to not be namespace aware then I don't get the exception, but the output of the transformation is missing the namespaces.
Thanks for your help.
Your hand-crafted, pseudo-identity transform is losing the namespace declarations and failing when it tries to name an element with an undeclared namespace prefix:
<xsl:element name="{name()}">
Use the standard identity transformation instead:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.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>
Is there a way I can make my xslt file get input from two different sources? One source is a XML file and the other is an Excel sheet. I need to get data from both of them. For example, I have this xslt:
<?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" indent="yes"/>
<xsl:template match="Data/AAA">
<xsl:for-each select=".">
<Data xmlns="MyProtocol.xsd">
<BBB>
<Id><xsl:value-of select="Id"/></Id>
<Timestamp><xsl:value-of select="Timestamp"/></Timestamp>
<xsl:if test="Transfer">
<Transfer><xsl:value-of select="Transfer"/></Transfer>
</xsl:if>
<Code>0</Code>
<xsl:for-each select="Sequence/Number">
<Result>
<Number><xsl:value-of select="."/></Number>
<Code>0</Code>
</Result>
</xsl:for-each>
</BBB>
</Data>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Excel Sheet:
A B
1 Number | Code
-----------------------
2 5556667777 1
3 5559877890 1
4 5552835291 0
. ...
. ...
. ...
The xml:
<Data xmlns="MyProtocol.xsd">
<AAA>
<Id>2</Id>
<Timestamp>2016-31-12</Timestamp>
<Sequence>
<Number>5556667777</Number>
<Number>5559877890</Number>
<Number>5552835291</Number>
</Sequence>
</AAA>
</Data>
From here, I want to change the xslt for the <xsl:for-each select="Sequence/Number"> part. I would like to to the following:
Read the <Number> from the xml file
Check if that number is in the Excel sheet
Assign the correct <Code> number for that number in the xslt
I have code where I can pass the XML file so that the xslt can make a new XML file according to the xslt; however, I can't figure out how to do the above.
Here's the code I have:
File stylesheet = new File(xsltFilePath);
InputSource inputSource = new InputSource(new ByteArrayInputStream(xmlString.getBytes()));
Document document = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(inputSource);
StreamSource stylesource = new StreamSource(stylesheet);
Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);
StringWriter stringWriter = new StringWriter();
transformer.transform(new DOMSource(document), new StreamResult(stringWriter));
return stringWriter.toString();
The above code works fine if I just use a xml file. How can I achieve what I described above? Thanks in advance!
Update: Got it to work by turning the Excel sheet into a xml file (through Java code) and then following Martin Honnen's answer below.
Assuming you have the XML data exported as an XML document you can pull in the the second document with e.g.
<xsl:param name="sheet-uri" select="'sheet.xml'"/>
<xsl:param name="sheet-doc" select="document($sheet-uri)"/>
and then
<xsl:for-each select="Sequence/Number">
<Result>
<Number><xsl:value-of select="."/></Number>
<Code>
<xsl:variable name="referenced-code" select="$sheet-doc//Sequence[Number = current()]/Code"/>
<xsl:choose>
<xsl:when test="$referenced-code">
<xsl:value-of select="$referenced-code"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</Code>
</Result>
</xsl:for-each>
I'm seeing inconsistent behaviour when applying my XSLT with a Java program compared do testing with XML Spy. Specifically, when using Java, the xsl:for-each directive does not seem to iterate over the entire list. This is not the case when testing with XML Spy.
My XML document:
<metadata xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<maps>
<geographicCoverages>
<coverage key="ON">Ontario</coverage>
<coverage key="MB">Manitoba</coverage>
<coverage key="SK">Saskatchewan</coverage>
<coverage key="AB">Alberta</coverage>
<coverage key="BC">British Columbia</coverage>
<coverage key="YT">Yukon</coverage>
<coverage key="NU">Nunavut</coverage>
</geographicCoverages>
</maps>
<entry>
<string>GEO_COVERAGE</string>
<list>
<string>BC</string>
<string>ON</string>
</list>
</entry>
...
</metadata>
My XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="text" version="1.0" indent="yes"/>
<xsl:key name="geoCoverageKey" match="metadata/maps/geographicCoverages/coverage" use="#key" />
<xsl:template match="/metadata">
<xsl:for-each select="entry[string='GEO_COVERAGE']/list">
<xsl:value-of select="key('geoCoverageKey', string)" separator=", " />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When I test the above XSLT with XMLSpy, I get my expected output:
Ontario, British Columbia
My Java test program:
public static void main(String[] args) throws TransformerException {
String xsltPath = "test_migration.xslt";
String xmlPath = "test-migration.xml";
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer(new StreamSource(xsltPath));
StreamResult result = new StreamResult( new StringWriter() );
transformer.transform(new StreamSource(xmlPath), result );
System.out.println( result.getWriter().toString() );
}
However, when I run the above Java test program, the output I get back consists of one value only; Ontario. I'm at a loss as to why I'm getting this difference.
The default processor in the JDK is Xalan. Xalan is an XSLT 1.0 processor. When an XSLT 1.0 processor is given a stylesheet that specifies version="2.0", it is required (under the rules for XSLT 1.0 processors) to ignore attributes that are not defined in XSLT 1.0. The "separator" attribute falls into this category. In XSLT 1.0, xsl:value-of outputs the first node in a node-set and ignores the rest.
The answer is to install Saxon, which provides XSLT 2.0 processing in Java.
I am trying to add the xmlns attribute to the resulting XML with a value passed by parameter during XSLT transformation using JDK Transformer (Oracle XML v2 Parser or JAXP) but it always defaults to http://www.w3.org/2000/xmlns/
My source XML
<test/>
My XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://example.com">
<xsl:param name="myNameSpace" select="'http://neilghosh.com'"/>
<xsl:template match="/">
<process>
<xsl:attribute name="xmlns:neil">
<xsl:value-of select="$myNameSpace"/>
</xsl:attribute>
</process>
</xsl:template>
</xsl:stylesheet>
My Result
<?xml version="1.0"?>
<process xmlns="http://www.w3.org/2000/xmlns/" xmlns:neil="neilghosh.com">
</process>
My Desired Result
<?xml version="1.0"?>
<process xmlns="http://example.com" xmlns:neil="neilghosh.com">
</process>
Firstly, in the XSLT data model, you don't want to create an attribute node, you want to create a namespace node.
Namespace nodes are usually created automatically: if you create an element or attribute in a particular namespace, the requisite namespace node (and hence, when serialized, the namespace declaration) are added automatically by the processor.
If you want to create a namespace node that isn't necessary (because it's not used in the name of any element or attribute) then in XSLT 2.0 you can use xsl:namespace. If you're stuck with XSLT 1.0 then there's a workaround, that involves creating an element in the relevant namespace and then copying its namespace node:
<xsl:variable name="ns">
<xsl:element name="neil:dummy" namespace="{$param}"/>
</xsl:variable>
<process>
<xsl:copy-of select="$ns/*/namespace::neil"/>
</process>
Michael Kay provided you with the correct answer, but based on your comments, you aren't sure how to use it in your transformation.
Here is a complete transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pNamespace" select="'neilghosh.com'"/>
<xsl:variable name="vDummy">
<xsl:element name="neil:x" namespace="{$pNamespace}"/>
</xsl:variable>
<xsl:template match="/*">
<xsl:element name="process" namespace="http://example.com">
<xsl:copy-of select="namespace::*"/>
<xsl:copy-of select="ext:node-set($vDummy)/*/namespace::*[.=$pNamespace]"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<test/>
the wanted, correct result is produced:
<process xmlns="http://example.com" xmlns:neil="neilghosh.com" />
Namespace declarations in XML are not attributes even though they look like attributes. In XSLT 2.0 you can use <xsl:namespace name="neil" select="$myNameSpace" /> to add a namespace declaration to the result tree dynamically but that feature is not available in XSLT 1.0.
Don't try to create "xmlns" attributes yourself. Create the namespaces in the XSLT and they will be done automatically.
This XSLT works (tested with Saxon 9.4):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:neil="neilghosh.com"
xpath-default-namespace="http://example.com"
xmlns="http://example.com" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="myDynamicNamespace" select="'http://neilghosh.com'"/>
<xsl:template match="/">
<xsl:element name="process">
<xsl:namespace name="neil" select="$myDynamicNamespace"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
And gives the following output:
<?xml version="1.0" encoding="UTF-8"?>
<process xmlns="http://example.com" xmlns:neil="http://neilghosh.com"/>
Finally got an workaround which worked with my XSLT Processor (Oracle XML V2 Parser)
I had to transform it to a DOM Document and then persist that DOM to filesystem instead of outputting directly to StreamResult
I used DOMResult in the transform method
Following XSLT fragment worked but there was an extra xmlns:xmlns="http://www.w3.org/2000/xmlns/" which was probably absorbed by Document and did not appear in the final output when I persisted to file system.
<process>
<xsl:attribute name="xmlns">
<xsl:value-of select="'http://example.com'"/>
</xsl:attribute>
<process>
I know this is not the best way to do but given the parse constraint this is the only choice I have now.