XML Transformation from Java changes namespace prefix to ns1 from nsi - java

I have below xslt element which I am using for XML transformation.
<xsl:attribute name="{name()}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" namespace="http://www.w3.org/2001/XMLSchema-instance">
<xsl:value-of select="." />
</xsl:attribute>
However after transformation the XML looks as below
<TEST xmlns:ns1="http://www.w3.org/2001/XMLSchema-instance" ns1:nil="true"/>
instead of xmlns:nsi it changes it to xmlns:ns1 and for other elements xmlns:ns0
Has anyone faced this issue before. when I transform though eclipse output is correct but if I transform it using java code it changes the xmlns prefix with ns0 ns1 etc...
Below is my java code for transformation.
StringWriter sw = new StringWriter();
javax.xml.transform.Result result = new javax.xml.transform.stream.StreamResult(sw);
TransformerFactory transFact = TransformerFactory.newInstance();
Transformer trans = transFact.newTransformer(xsltSource);
trans.transform(xmlSource, result);
After further analysis, when I transform using standalone java program the output is as below(as expected)
<TEST xmlns:nsi="http://www.w3.org/2001/XMLSchema-instance" nsi:nil="true"/>
but if I run it on server the output is as below
<TEST xmlns:ns1="http://www.w3.org/2001/XMLSchema-instance" ns1:nil="true"/>
xmlns:xsi is getting replaced with xmlns:xs1

You could try to create the attribute QName explicitely using the desired prefix instead of using the namespace attribute:
<xsl:attribute name="xsi:{local-name()}"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
select="."/>

Related

Change xml namespace url in Java

I have a java REST API and we recently changed domain. The api is versioned although up to now this has involved adding removing elements across the versions.
I would like to change the namespaces if someone goes back to previous versions but I am struggling. I have realised now, after some hacking about, that it is probably because I am changing the namespace of the xml that is actually being referenced. I was thinking of it as a text document but I guess the tool is not ?
So looking at this xml with the n#namespace url veg.com ->
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ns2:apple xmlns:ns2="http://veg.com/app/api/apple" xmlns:ns1="http://veg.com/app/api" xmlns:ns3="http://veg.com/app/api/apple/red"
xmlns:ns4="http://veg.com/app/banana" xmlns:ns5="http://veg.com/app/api/pear" xmlns:ns6="http://veg.com/app/api/orange"
ns1:created="2016-05-23T16:47:55+01:00" ns1:href="http://falseserver:8080/app/api/apple/1" ns1:id="1">
<ns2:name>granny smith</ns2:title>
<ns2:flavour>sweet</ns2:status>
<ns2:origin>southwest region</ns2:grantCategory>
...
</ns2:apple>
I would like to change the namespaces to fruit.com. This is a very hacky unit test which shows the broad approach that I have been trying...
#Test
public void testNamespaceChange() throws Exception {
Document appleDoc = load("apple.xml");
XPath xpath = XPathFactory.newInstance().newXPath();
org.w3c.dom.Node node = (org.w3c.dom.Node) xpath.evaluate("//*[local-name()='apple']", appleDoc , XPathConstants.NODE);
NamedNodeMap nodeMap = node.getAttributes();
for (int i = 0; i < nodeMap.getLength(); i++) {
if (nodeMap.item(i).getNodeName().startsWith("xmlns:ns")) {
nodeMap.item(i).setTextContent( nodeMap.item(i).getNodeValue().replace( "veg.com", "fruit.com"));
}
}
//Check values have been set
for (int i = 0; i < nodeMap.getLength(); i++) {
System.out.println(nodeMap.item(i).getNodeName());
System.out.println(nodeMap.item(i).getNodeValue());
System.out.println("----------------");
}
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform(new DOMSource(node), result);
System.out.println("XML IN String format is: \n" +
writer.toString());
}
So the result of this is that the loop of nodeMap items shows the updates taking hold
i.e. all updated along these lines
xmlns:ns1
http://fruit.com/app/api
-------------------------------------------
xmlns:ns2
http://fruit.com/app/api/apple
-------------------------------------------
xmlns:ns3
http://fruit.com/app/api/apple/red
-------------------------------------------
...
but when I print out the transfomed document I get what I see in the api response...
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ns2:apple xmlns:ns2="http://veg.com/app/api/apple" xmlns:ns1="http://veg.com/app/api" xmlns:ns3="http://fruit.com/app/api/apple/red"
xmlns:ns4="http://fruit.com/app/banana" xmlns:ns5="http://fruit.com/app/api/pear" xmlns:ns6="http://fruit.com/app/api/orange"
ns1:created="2016-05-23T16:47:55+01:00" ns1:href="http://falseserver:8080/app/api/apple/1" ns1:id="1">
The sibling (and further down the hierarchy) namespaces have been changed but ns1 and ns2 have remained unchanged.
Can anyone tell me why and whether there is a simple way for me to update them ? I guess the next step for me might be to stream the xml doc into a string, update them as text and then reload it as an xml document but I'm hoping I'm being defeatist and there is a more elegant solution ?
I would solve it with an XSLT like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*[namespace-uri()='http://veg.com/app/api/apple']" priority="1">
<xsl:element name="local-name()" namespace="http://fruit.com/app/api/apple">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This stylesheet combines the identity transform with a template which changes namespace of elements in http://veg.com/app/api/apple to http://fruit.com/app/api/apple.
I think it is much simpler that Java code that you have. You'd be also more flexible, should you find out you have more differences between version of you XML apart just namespaces.
Please consider this to be a rough sketch. I wrote a book on XSLT some 15 years ago, but did not use XSLT for more than 6 or 7 years.

Define a list variable in xslt

In my xsl transform the filename is passed as a parameter to the stylesheet. I want to do a certain set of actions if it is in a certain filelist. Right now I'm doing it this way;
<xsl:param name="specialFiles" select="'|a.xml|b.xml|'"/>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="contains($specialFiles,concat('|',$FILENAME,'|'))" >
<xsl:apply-templates select="abc" />
</xsl:when>
.....
.....
This works, but quickly becomes messy when the specialFiles list grows. Is there a way to declare it like an array, and lookup quickly?
EDIT: This is the code I'm using to transform, I just print everything to stdout
TransformerFactory factory = TransformerFactory.newInstance();
Source xslt = new StreamSource(new File("1.xsl"));
Transformer transformer = factory.newTransformer(xslt);
File xmlFile = new File(args[0]);
String baseName = xmlFile.getName();
transformer.setParameter("FILENAME", baseName); // pass the basename of the file
transformer.transform(new StreamSource(xmlFile ), new StreamResult(System.out));
EDIT 2:
I just managed to do this in a slightly different way, I embed an xml fragment inside the stylesheet and use an xpath expression on it in
<specialFiles>
<name>abcdef.xml</name>
<name>sadfk32.xml</name>
</specialFiles>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="document('')/xsl:stylesheet/specialFiles/name/text()[contains(.,$fileName)]">
....
....
I used a xml fragment embedded within the stylesheet:
<specialFiles>
<name>abcdef.xml</name>
<name>sadfk32.xml</name>
</specialFiles>
So a simple xpath on it can be used to verify certain name is in the list or not,
<xsl:when test="document('')/xsl:stylesheet/specialFiles/name= $filename">

Doing DOM Node-to-String transformation, but with namespace issues

So we have an XML Document with custom namespaces. (The XML is generated by software we don't control. It's parsed by a namespace-unaware DOM parser; standard Java7SE/Xerces stuff, but also outside our effective control.) The input data looks like this:
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<MainTag xmlns="http://BlahBlahBlah" xmlns:CustomAttr="http://BlitherBlither">
.... 18 blarzillion lines of XML ....
<Thing CustomAttr:gibberish="borkborkbork" ... />
.... another 27 blarzillion lines ....
</MainTag>
The Document we get is usable and xpath-queryable and traversable and so on.
Converting this Document into a text format for writing out to a data sink uses the standard Transformer approach described in a hundred SO "how do I change my XML Document into a Java string?" questions:
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter stringwriter = new StringWriter();
transformer.transform (new DOMSource(theXMLDocument), new StreamResult(stringwriter));
return stringwriter.toString();
and it works perfectly.
But now I'd like to transform individual arbitrary Nodes from that Document into strings. A DOMSource constructor accepts Node pointers just the same as it accepts a Document (and in fact Document is just a subclass of Node, so it's the same API as far as I can tell). So passing in an individual Node in the place of "theXMLDocument" in the snippet above works great... until we get to the Thing.
At that point, transform() throws an exception:
java.lang.RuntimeException: Namespace for prefix 'CustomAttr' has not been declared.
at com.sun.org.apache.xml.internal.serializer.SerializerBase.getNamespaceURI(Unknown Source)
at com.sun.org.apache.xml.internal.serializer.SerializerBase.addAttribute(Unknown Source)
at com.sun.org.apache.xml.internal.serializer.ToUnknownStream.addAttribute(Unknown Source)
......
That makes sense. (The "com.sun.org.apache" is weird to read, but whatever.) It makes sense, because the namespace for the custom attribute was declared at the root node, but now the transformer is starting at a child node and can't see the declarations "above" it in the tree. So I think I understand the problem, or at least the symptom, but I'm not sure how to solve it though.
If this were a String-to-Document conversion, we'd be using a DocumentBuilderFactory instance and could call .setNamespaceAware(false), but this is going in the other direction.
None of the available properties for transformer.setOutputProperty() affect the namespaceURI lookup, which makes sense.
There is no such corresponding setInputProperty or similar function.
The input parser wasn't namespace aware, which is how the "upstream" code got as far as creating its Document to hand to us. I don't know how to hand that particular status flag on to the transforming code, which is what I really would like to do, I think.
I believe it's possible to (somehow) add a xmlns:CustomAttr="http://BlitherBlither" attribute to the Thing node, the same as the root MainTag had. But at that point the output is no longer identical XML to what was read in, even if it "means" the same thing, and the text strings are eventually going to be compared in the future. We wouldn't know if it were needed until the exception got thrown, then we could add it and try again... ick. For that matter, changing the Node would alter the original Document, and this really ought to be a read-only operation.
Advice? Is there some way of telling the Transformer, "look, don't stress your dimwitted little head over whether the output is legit XML in isolation, it's not going to be parsed back in on its own (but you don't know that), just produce the text and let us worry about its context"?
Given your quoted error message "Namespace for prefix 'CustomAttr' has not been declared.",
I'm assuming that your pseudo code is along the lines of:
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<MainTag xmlns="http://BlahBlahBlah" xmlns:CustomAttr="http://BlitherBlither">
.... 18 blarzillion lines of XML ....
<Thing CustomAttr:attributeName="borkborkbork" ... />
.... another 27 blarzillion lines ....
</MainTag>
With that assumption, here's my suggestion:
So you want to extract the "Thing" node from the "big" XML. The standard approach is to use a little XSLT to do that. You prepare the XSL transformation with:
Transformer transformer = transformerFactory.newTransformer(new StreamSource(new File("isolate-the-thing-node.xslt")));
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setParameter("elementName", stringWithCurrentThing); // parameterize transformation for each Thing
...
EDIT: #Ti, please note the parameterization instruction above (and below in the xslt).
The file 'isolate-the-thing-node.xslt' could be a flavour of the following:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:custom0="http://BlahBlahBlah"
xmlns:custom1="http://BlitherBlither"
version="1.0">
<xsl:param name="elementName">to-be-parameterized</xsl:param>
<xsl:output encoding="utf-8" indent="yes" method="xml" omit-xml-declaration="no" />
<xsl:template match="/*" priority="2" >
<!--<xsl:apply-templates select="//custom0:Thing" />-->
<!-- changed to parameterized selection: -->
<xsl:apply-templates select="custom0:*[local-name()=$elementName]" />
</xsl:template>
<xsl:template match="node() | #*" priority="1">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Hope that gets you over the "Thing" thing :)
I have managed to parse the provided document, get the Thing node and print it without issues.
Take a look at the Working Example:
Node rootElement = d.getDocumentElement();
System.out.println("Whole document: \n");
System.out.println(nodeToString(rootElement));
Node thing = rootElement.getChildNodes().item(1);
System.out.println("Just Thing: \n");
System.out.println(nodeToString(thing));
nodeToString:
private static String nodeToString(Node node) {
StringWriter sw = new StringWriter();
try {
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.transform(new DOMSource(node), new StreamResult(sw));
} catch (TransformerException te) {
System.out.println("nodeToString Transformer Exception");
}
return sw.toString();
}
Output:
Whole document:
<?xml version="1.0" encoding="UTF-8"?><MainTag xmlns="http://BlahBlahBlah" xmlns:CustomAttr="http://BlitherBlither">
<Thing CustomAttr="borkborkbork"/>
</MainTag>
Just Thing:
<?xml version="1.0" encoding="UTF-8"?><Thing CustomAttr="borkborkbork"/>
When I try the same code with CustomAttr:attributeName as suggested by #marty it fails with the original exception, so it looks like somewhere in your original XML you are prefixing a attribute or node with that custom CustomAttr namespace.
In the latter case you can leverage the problem with setNamespaceAware(true), which will include the namespace information on the Thing node itself.
<?xml version="1.0" encoding="UTF-8"?><Thing xmlns:CustomAttr="http://BlitherBlither" CustomAttr:attributeName="borkborkbork" xmlns="http://BlahBlahBlah"/>

Setting xslt hidden value from java

New to xslt, I wanted to set a value of string from java to this variable
<xsl:element name="input">
<xsl:attribute name="type">hidden</xsl:attribute>
<xsl:attribute name="name">trackId</xsl:attribute>
<xsl:attribute name="value"><xsl:value-of select="trackValue"/></xsl:attribute>
</xsl:element>
Is it in same manner as html or is it different apprach? Thanks for help and time.
Yes, you can pass values into your XSLT using parameters. What you would do is define a parameter near the top of your XSLT file:
<xsl:param name="trackValue" />
And then you would pass in a value for this when you run the transform:
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer xsltTransformer = transformerFactory.newTransformer(xsltSource);
xsltTransformer.setParameter("trackValue", parameterValue);
Then you can use it wherever you want to (note the use of the $ sign):
<xsl:attribute name="value"><xsl:value-of select="$trackValue"/></xsl:attribute>
XSL Transformation in Java with parameters

Java xsl transform produces weird content after xsl-copy

I am using the following xsl to retrieve the schema types from wsdl
<?xml version="1.0" encoding="UTF-8"?><xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="//*[local-name()='schema' and namespace-uri()='http://www.w3.org/2001/XMLSchema']">
<xsl:copy-of select="."></xsl:copy-of>
</xsl:template></xsl:transform>
and the following java snippet
tFactory = TransformerFactory.newInstance();
xslSource = new StreamSource("resources/input/wsdl2xsd.xsl");
xmlSource = new StreamSource(wsdlURI.toString());
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
outStream = new BufferedOutputStream(byteArrayOutputStream);
transformer = tFactory.newTransformer(xslSource);
transformer.transform(xmlSource, new StreamResult(outStream));
System.out.print(new String(byteArrayOutputStream.toByteArray()));
but for some reason I am getting a lot for empty space with some weird string after the end of my 'schema' element?. It works for some wslds though. I tried eBays public wsdl which is huge:
http://developer.ebay.com/webservices/741/eBaySvc.wsdl
which outputs strings after the 'schema' element.
Well your current stylesheet code relies on the built-in templates to reach your own template and on that way you might get output from text nodes.
As you don't seem to want that you can either add <xsl:template match="text()"/> to your code or you might want a different approach where you simply do e.g. <xsl:template match="/"><xsl:copy-of select="//xs:schema" xmlns:xs="http://www.w3.org/2001/XMLSchema"/></xsl:template>.

Categories

Resources