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>
Related
I am trying to use apply-templates for elements encapsulated within parent element but has different element name. In my example I want to apply it to /Author/Name/ elements and choose either of one whichever has a value. This task is continuation of another question I have asked.
I am using the following XML
<?xml version="1.0" encoding="UTF-8"?>
<Author>
<Info>
<Name>
<FirstName>#3 bb***</FirstName>
<LastName>test</LastName>
</Name>
<Input>
<Item>##### 3 ??***</Item>
</Input>
</Info>
<Custom>Test</Custom>
</Author>
I am applying the following XSLT 1.0 using java. In above example I want template applied to firstName and lastName but show value in firstName since it is the first with a value.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="allowed-start-chars">abcdefghijklmnopqrstuvwxyz</xsl:variable>
<xsl:variable name="allowed-follow-chars">0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ ?abcdefghijklmnopqrstuvwxyz-'.,/#&()!+</xsl:variable>
<xsl:template match="/">
<html>
<body>
<!-- Not sure how to use `xsl:choose` just added to show my intentions-->
<xsl:choose>
<xsl:when test="..">
<div>
<xsl:apply-templates select="Author/Name/FirstName"/>
</div>
</xsl:when>
</xsl:choose>
</body>
</html>
</xsl:template>
<!--used pipe to apply for two different elements but it does not work-->
<xsl:template match="FirstName|LastName">
<!-- find the first character eligible to be starting character -->
<xsl:variable name="start-chars" select="translate(., translate(., $allowed-start-chars, ''), '')"/>
<xsl:variable name="start-char" select="substring($start-chars, 1, 1)"/>
<!-- get text after the chosen starting character -->
<xsl:variable name="tail" select="substring-after(., $start-char)"/>
<!-- remove unwanted characters from tail -->
<xsl:variable name="fo" select="translate($tail, translate($tail, $allowed-follow-chars, ''), '')"/> <xsl:choose>
<!--only show if character remain after removing bad ones, and not empty-->
<xsl:when test="string-length($start-char) > 0 and normalize-space(concat($start-char, $fo))">
<xsl:value-of select="normalize-space(concat($start-char, $fo))"/>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The Java code is Stylizer class from Oracle tuorial page example, please refer the link for full code. I am running command-line sending xml and xslt files.
File stylesheet = new File(argv[0]);
File datafile = new File(argv[1]);
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse(datafile);
// Use a Transformer for output
TransformerFactory tFactory = TransformerFactory.newInstance();
StreamSource stylesource = new StreamSource(stylesheet);
Transformer transformer = tFactory.newTransformer(stylesource);
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(System.out);
transformer.transform(source, result);
I would appreciate again help on this one. As I am learning XSLT and I am also not sure what performance issue using approach
From the context of:
<xsl:template match="/">
you can do:
<xsl:apply-templates select="(Author/Info/Name/FirstName | Author/Info/Name/LastName)[text()][1]"/>
to select the first element of the two that has a child text node. If there are only FirstName and LastName, you can shorten this to:
<xsl:apply-templates select="Author/Info/Name/*[text()][1]"/>
Note that your attempt:
<xsl:apply-templates select="Author/Name/FirstName"/>
cannot work because Name is not a child of Author.
I have written a Java Program that converts XML to CSV. But currently it is converting partial only.
FileUtils.writeByteArrayToFile(new File("src/main/resources/excel/Data.xml"), inputFile);
File stylesheet = new File("src/main/resources/excel/Data.xsl");
File xmlSource = new File("src/main/resources/excel/Data.xml");
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(xmlSource);
StreamSource stylesource = new StreamSource(stylesheet);
Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);
Source source = new DOMSource(document);
String path = "src/main/resources/excel/validatedXmlToCSV.csv";
Result outputTarget = new StreamResult(new File(path));
transformer.transform(source, outputTarget);
This is the Java code that takes XML and XSL and converts it to the CSV.
XML file that I am trying to convert it to CSV is:
<root>
<row>
<TECHNICIANID>AA5263</TECHNICIANID>
<CUID>AA5263</CUID>
<TURFS>
<TURF>
<AREANAME>CA_MILPITAS_ABEL_A</AREANAME>
<DEFAULT>Y</DEFAULT>
<ALTERNATE>Y</ALTERNATE>
</TURF>
<TURF>
<AREANAME>CA_SNJS_WHITE_RD_A</AREANAME>
<DEFAULT>Y</DEFAULT>
<ALTERNATE>Y</ALTERNATE>
</TURF>
</TURFS>
</row>
<row>
<TECHNICIANID>AC1964</TECHNICIANID>
<CUID>AC1964</CUID>
<TURFS>
<TURF>
<AREANAME>CA_MILPITAS_ABEL_A</AREANAME>
<DEFAULT>Y</DEFAULT>
<ALTERNATE>Y</ALTERNATE>
</TURF>
<TURF>
<AREANAME>CA_SNJS_WHITE_RD_A</AREANAME>
<DEFAULT>Y</DEFAULT>
<ALTERNATE>Y</ALTERNATE>
</TURF>
</TURFS>
</row>
</root>
Let me show you how I wrote XSL file that it not working properly:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format" >
<xsl:output method="text" omit-xml-declaration="yes" indent="no"/>
<xsl:template match="/">TECHNICIANID,CUID,TURFS/TURF/0/AREANAME,TURFS/TURF/0/DEFAULT,TURFS/TURF/0/ALTERNATE,TURFS/TURF/1/AREANAME,TURFS/TURF/1/DEFAULT,TURFS/TURF/1/ALTERNATE
<xsl:for-each select="/root/row">
<xsl:value-of select="concat(TECHNICIANID,',',CUID,'
')"/>
<xsl:for-each select="/TURFS/TURF">
<xsl:value-of select="concat(AREANAME,',',DEAFAULT,',',ALTERNATE,',
')"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Currently while converting it is printing row TECHNICIANID and CUID only. Other it is not printing.
Expected Output:
TECHNICIANID,CUID,TURFS/TURF/0/AREANAME,TURFS/TURF/0/DEFAULT,TURFS/TURF/0/ALTERNATE,TURFS/TURF/1/AREANAME,TURFS/TURF/1/DEFAULT,TURFS/TURF/1/ALTERNATE
AA5263,AA5263,CA_MILPITAS_ABEL_A,Y,Y,CA_SNJS_WHITE_RD_A,Y,Y
AC1964,AC1964,CA_MILPITAS_ABEL_A,Y,Y,CA_SNJS_WHITE_RD_A,Y,Y
I guess the inner <xsl:for-each select="/TURFS/TURF"> should be <xsl:for-each select="TURFS/TURF">. And concat(AREANAME,',',DEAFAULT should be concat(AREANAME,',',DEFAULT.
I believe you could do simply:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/root">
<!-- header -->
<xsl:text>TECHNICIANID,CUID,TURFS/TURF/0/AREANAME,TURFS/TURF/0/DEFAULT,TURFS/TURF/0/ALTERNATE,TURFS/TURF/1/AREANAME,TURFS/TURF/1/DEFAULT,TURFS/TURF/1/ALTERNATE
</xsl:text>
<!-- data -->
<xsl:for-each select="row">
<xsl:value-of select=".//*[not(*)]" separator=","/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/bF2MmXG/1
If you want to pick the values explicitly, then change:
<xsl:value-of select=".//*[not(*)]" separator=","/>
to:
<xsl:value-of select="TECHNICIANID, CUID, TURFS/TURF[1]/(AREANAME, DEFAULT, TALTERNATE), TURFS/TURF[2]/(AREANAME, DEFAULT, TALTERNATE)" separator=","/>
Your problem involves extracting data from a multilevel XML file and convert it to a CSV file. The Java code for this is complicated.
It is convenient to perform the conversion using SPL, the open-source Java package. You just need several lines of code:
A
1
=xml(file("Data.xml").read(),"root/row").conj(TECHNICIANID|CUID|TURFS.TURF.conj(~.array()))
2
=create(TECHNICIANID,CUID,TURFS/TURF/0/AREANAME,TURFS/TURF/0/DEFAULT,TURFS/TURF/0/ALTERNATE,TURFS/TURF/1/AREANAME,TURFS/TURF/1/DEFAULT,TURFS/TURF/1/ALTERNATE).record(A1)
3
=file("validatedXmlToCSV.csv").export#ct(A2)
SPL offers JDBC driver to be invoked by Java. Just store the above SPL script as xml2csv.splx and invoke it in Java as you call a stored procedure:
…
Class.forName("com.esproc.jdbc.InternalDriver");
con= DriverManager.getConnection("jdbc:esproc:local://");
st=con.prepareCall("call xml2csv()");
st.execute();
…
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>
I have an XSL file where I want to update my attribute tag value using java code.
This is my XSL file :-
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
version="2.0">
<xsl:attribute-set name="__frontmatter">
<xsl:attribute name="text-align">center</xsl:attribute>
</xsl:attribute-set>
</xsl:stylesheet>
I want to read parent tag "__frontmatter" , then under this child node tag "text-align" and update the value center.
I know to read Node name from XML file , but this is something confusing me, how will I read xsl:attribute-set and name="xyz" from java code?
EDIT:- Adding method.
private static void updateElementValue(Document doc) {
String a="right";
NodeList frontmatterr = doc.getElementsByTagName("text-align");
Element e = null;
for(int i=0; i<frontmatterr.getLength();i++){
e = (Element) frontmatterr.item(i);
Node name = emp.getElementsByTagName("text-align").item(0).getFirstChild();
name.setNodeValue(name.getNodeValue().valueOf(a));
}
}
My java code where I am trying to read xsl node.
Although you say in your comment that you "cannot use another XSLT file" I think XSLT is the right tool to manipulate XSLT so I post a suggestion here, with the XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="new-text-align">right</xsl:param>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xsl:attribute-set[#name = '__frontmatter']/xsl:attribute[#name = 'text-align']">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:value-of select="$new-text-align"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
online at https://xsltfiddle.liberty-development.net/bdxtqo/1, you can transform the original XSLT you have to
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:attribute-set name="__frontmatter">
<xsl:attribute name="text-align">right</xsl:attribute>
</xsl:attribute-set>
</xsl:stylesheet>
within the Java JAXP API you would simply set up a Transformer https://docs.oracle.com/javase/8/docs/api/javax/xml/transform/Transformer.html with above XSLT as the source (https://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newTransformer-javax.xml.transform.Source-) and use the original XSLT as the input Source and would get the new XSLT as the Result of the transform method.
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.