xmlns attribute not getting set in xslt transformation - java

I am passing a stylesheet and an input stream to Transformer class to get output XML.
The relative variable passed to xmlns is not getting replaced in the result.
Input Stream:
"http://www.abc.com/foo/bar"
+"EventCommon"
+"application,xsd:string"
+"companyId,xsd:string"
+"operator,xsd:string"
+"today,xsd:date"
+"transactionStage,xsd:string"
Stylesheet:
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<xsl:variable name="var_main_namespace" select="array/data[#attribute=1][#value=1][#subvalue=1]" />
<xsd:schema xmlns="($var_main_namespace)" targetNamespace="{$var_main_namespace}" elementFormDefault="qualified" />
</xsl:template>
</xsl:stylesheet>
Output XML:
<xsd:schema
xmlns="($var_main_namespace)"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.abc.com/foo/bar"
elementFormDefault="qualified"
/>
The code snippet:
TransformerFactory tFactory = TransformerFactory.newInstance();
StreamSource sc = new StreamSource(new ByteArrayInputStream(new String(c).getBytes()));
Transformer transformer = tFactory.newTransformer(sc);
ByteArrayOutputStream outputxml = new ByteArrayOutputStream();
transformer.transform(
new StreamSource(
new ByteArrayInputStream(new String(c2).getBytes())
),
new StreamResult(outputxml)
);
Any help is appreciated

Leaving aside the ($...) vs. {$...} typo, this won't work because xmlns is not an attribute, it's a namespace declaration.
Not being attributes, namespace declarations in literal result elements are not treated as attribute value templates and you can't create them using <xsl:attribute>. But as you're apparently using XSLT 2.0 you can create them using <xsl:namespace>
<xsl:template match="/">
<xsl:variable name="var_main_namespace" select="array/data[#attribute=1][#value=1][#subvalue=1]" />
<xsd:schema targetNamespace="{$var_main_namespace}" elementFormDefault="qualified">
<xsl:namespace name="" select="$var_main_namespace" />
</xsd:schema>
</xsl:template>
For this to work with the Java code you've given your default TransformerFactory needs to be one that supports XSLT 2.0, such as Saxon 9.

Related

Java XML and XSLT transformation error: Namespace for prefix 'm' has not been declared

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>

XSLT read from two different sources (XML file and Excel sheet)

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>

Exclude processing instruction from XSLT

I have a simple plain-text-generating XSLT that I am applying like this (using the reference implementation):
StreamSource schemasource = new StreamSource(getClass().getResourceAsStream("source.xml"));
StreamSource stylesource = new StreamSource(getClass().getResourceAsStream("transform.xslt"));
Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);
StringWriter writer = new StringWriter();
transformer.transform(schemasource, new StreamResult(domWriter));
System.out.println("XML after transform:");
System.out.println(writer.toString());
The "transform" call always inserts an processing instruction in the output. Is there any way to configure it not to do so?
(For example for a very simple node identity transform
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="*">
<xsl:copy/>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
when applied to
<hello/>
I get
<?xml version="1.0" encoding="UTF-8"?>
<hello/>
)
Many thanks, Andy
Strictly speaking the <?xml declaration is not a processing instruction. However, you should be able to use the xsl:output command here to specify that no xml declaration should be output:
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" />
This should be placed as a direct child of the xsl:stylesheet element.

Incorrect behaviour of xsl:for-each in Java

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.

Create xmlns attribute in the XML using XSLT Transformation

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.

Categories