Java xsl transform produces weird content after xsl-copy - java

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>.

Related

How to determine if two XML files have the same structure even if the tags have different values?

I wish to compare two XML files and determine if they have the same structure i.e. The same type and number of tags with preferably the same attributes. The value of the tags and attributes may be different.
This code detects ALL the differences. Even if the structure is the same but values are different. I want to refine this to detect only the structural differences.
public static List compareXML(Reader source, Reader target) throws
SAXException, IOException{
//creating Diff instance to compare two XML files
Diff xmlDiff = new Diff(source, target);
//for getting detailed differences between two xml files
DetailedDiff detailXmlDiff = new DetailedDiff(xmlDiff);
return detailXmlDiff.getAllDifferences();
}
Try this XSLT 3.0:
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="text()"/>
<xsl:template match="#*">
<xsl:attribute name="name()"/>
</xsl:template>
<xsl:variable name="doc1">
<xsl:apply-templates select="doc('one.xml')"/>
</xsl:variable>
<xsl:variable name="doc2">
<xsl:apply-templates select="doc('two.xml')"/>
</xsl:variable>
<xsl:template name="xsl:initial-template">
<xsl:value-of select="deep-equal($doc1, $doc2)"/>
</xsl:template>

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.

ArrayIndexOutOfBoundsException when transforming XML using XSLT

I am using the Java javax.xml.transform library in my Scala Play application to perform a simple XSLT transformation on some XML. I am trying to remove the namespace from one of the elements, but I am getting an exception when I POST XML to the endpoint which does the transformation.
The method I have written to do the transformation is below:
def transformXml(xml: String, xslName: String): Try[String] = {
Try {
// Create transformer factory
val factory: TransformerFactory = TransformerFactory.newInstance()
// Use the factory to create a template containing the xsl file
val template: Templates = factory.newTemplates(new StreamSource(new FileInputStream(s"app/xsl/$xslName.xsl")))
// Use the template to create a transformer
val xformer: Transformer = template.newTransformer()
// Prepare the input for transformation
val input: Source = new StreamSource(new StringReader(xml))
// Prepare the output for transformation result
val outputBuffer: Writer = new StringWriter
val output: javax.xml.transform.Result = new StreamResult(outputBuffer)
// Apply the xslt transformation to the input and store the result in the output
xformer.transform(input, output)
// Return the transformed XML
outputBuffer.toString
}
}
Through putting printlns in my code, I have deduced that it is in fact failing at the xformer.transform(input, output) line. The XML I am passing in and the XSL file I am using to transform are below:
<?xml version="1.0"?>
<Message xmlns="http://foo.bar" xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance">
<EnvelopeVersion>2.0</EnvelopeVersion>
<Header>
<MessageDetails>
...
...
...
</MessageDetails>
<SenderDetails/>
</Header>
<OtherDetails>
<Keys/>
</OtherDetails>
<Body>
</Body>
</Message>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:param name="ancestralNamespace" select="namespace-uri(/*[1])"/>
<xsl:copy>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="ancestralNamespace" select="$ancestralNamespace"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="*[contains(namespace-uri(),'foo.bar')]">
<xsl:param name="ancestralNamespace" select="namespace-uri(..)"/>
<xsl:element name="{local-name()}" namespace="">
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="ancestralNamespace" select="$ancestralNamespace"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
My expected output is this:
<?xml version="1.0"?>
<Message>
<EnvelopeVersion>2.0</EnvelopeVersion>
<Header>
<MessageDetails>
...
...
...
</MessageDetails>
<SenderDetails/>
</Header>
<OtherDetails>
<Keys/>
</OtherDetails>
<Body>
</Body>
</Message>
The error I get back from sending a POST request to my endpoint is this:
{
"statusCode": 500,
"message": "javax.xml.transform.TransformerException: java.lang.ArrayIndexOutOfBoundsException: -1"
}
I do not have much experience with XSLT and have inherited this code from someone else to try to debug, so if anyone with XML/XSLT experience could give me some help I would greatly appreciate it. The perplexing thing is that the person I got this problem from had written Unit Tests using this method (send in my example XML and get out the expected XML) and they passed so I don't know where to look next.
Right so after a few hours of debugging and fretting over this, I found the solution!
The default transformer which my Play application was using handles XSLT differently, and was getting confused at the line <xsl:param name="ancestralNamespace" select="namespace-uri(/*[1])"/>. What solved my issue was to use a different transformer. The one I found to work was Xalan (version 2.7.2), and after importing that into my project build file I hit the endpoint and the transformation was successful.
To import the version I found to work, add the following to your build:
"xalan" % "xalan" % "2.7.2" % "runtime"
I believe that the "runtime" section is the most important part, as it seems to overwrite what the application would normally use. I would guess that the reason my tests passed but my endpoint failed is that Scala Test runs with different configuration to runtime. Nothing else about my code had to be changed.
I hope this helps to stop anyone else from encountering this (admittedly rather unique) error! I ended up trawling through countless forums from as far back as 2002 before resorting to trying a different runtime configuration.

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">

Replace an attribute in xml with xpath

I want to take an attribute found thru xpath and replace it in the Document.
This is the xml:
<MineX STATE="add">
<Desc F_CREATOR="admin" F_ENTRYDATE="2010-12-24" F_HEIGHT="0.875" F_ID="1" F_LEFT="1.15625" F_LINE_COLOR="255" F_FORECOLOR="0">
<F_CUSTOM_BYTES></F_CUSTOM_BYTES>
</Desc>
</MineX>
With Java, I can retrieve the value like this:
org.w3c.dom.Document xmlDoc = getDoc(path);
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
XPathExpression myExp = xpath.compile("//MineX/Desc/#F_LINE_COLOR");
System.out.println("Line color:" + (String)myExp.evaluate(xmlDoc, XPathConstants.STRING) + "\n");
This prints out: 255
So, what XPath function will allow me to replace the 255, for another string?
Or do I need something other than XPath for this?
So, what XPath function will allow me
to replace the 255, for another
string? Or do I need something other
than XPath for this?
XPath is the query language for XML and as such cannot modify an XML document.
In order to modify an XML document one needs to use the programming language (such as XSLT, C#, JS, PHP, ..., etc) that is hosting XPath.
Here is a solution, where the hosting language is XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pNewLineColor" select="123"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#F_LINE_COLOR">
<xsl:attribute name="{name()}">
<xsl:value-of select="$pNewLineColor"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<MineX STATE="add">
<Desc F_CREATOR="admin"
F_ENTRYDATE="2010-12-24"
F_HEIGHT="0.875"
F_ID="1"
F_LEFT="1.15625"
F_LINE_COLOR="255"
F_FORECOLOR="0">
<F_CUSTOM_BYTES></F_CUSTOM_BYTES>
</Desc>
</MineX>
the wanted, correct result is produced:
<MineX STATE="add">
<Desc F_CREATOR="admin"
F_ENTRYDATE="2010-12-24"
F_HEIGHT="0.875"
F_ID="1"
F_LEFT="1.15625"
F_LINE_COLOR="123"
F_FORECOLOR="0">
<F_CUSTOM_BYTES></F_CUSTOM_BYTES>
</Desc>
</MineX>
XPath is a query language for extracting information out of an XML file. As far as I know it is not suited for replacing or editing data in an XML. One way to transform an XML is via XSLT.

Categories

Resources