XSLT Convert generic XML - xslt

I have an XML file generated using javax.sql.rowset.WebRowSet.writeXml which looks like:
<metadata>
This section has column properties like name / label etc
</metadata>
<data>
<currentRow>
<columnValue>Ken</columnValue>
<columnValue>12</columnValue>
<columnValue>USA</columnValue>
</currentRow>
</data>
I want to convert this to look like:
<Class>
<Student>
<name>Ken</name>
<ID>12</ID>
<location>USA</location>
</Student>
</Class>
How can I do the conversion? I need this to transform the XML to a HTML table.

The following stylesheet produces the desired result:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<Class>
<xsl:apply-templates select="/*/data/currentRow" />
</Class>
</xsl:template>
<xsl:template match="currentRow">
<Student>
<xsl:apply-templates select="columnValue" />
</Student>
</xsl:template>
<xsl:template match="columnValue[1]">
<name><xsl:apply-templates/></name>
</xsl:template>
<xsl:template match="columnValue[2]">
<ID><xsl:apply-templates/></ID>
</xsl:template>
<xsl:template match="columnValue[3]">
<location><xsl:apply-templates/></location>
</xsl:template>
</xsl:stylesheet>
Note: Added a root node to the given source to make it well-formed.

Related

Generate XSL (Denormalise format) for Nested XML

I need to flatten my XML as the file size very huge and nested level, I am creating the XSL file manually. Below is my sample scenario of content of the XML file-
<StudentDetail>
<SchoolName>SSHPS</SchoolName>
<SchoolEstablishedYear>1990</SchoolEstablishedYear>
<ClassDetails>
<ClassDetail>
<ClassStartedYear>1990</ClassStartedYear>
<Section ID="12345">
<SectioName>Section A</SectioName>
<Students>
<Student ID="1">
<StudentName>John</StudentName>
<Address>
<HomeNumber>10</HomeNumber>
<StreetName>Avenue</StreetName>
</Address>
</Student>
<Student ID="2">
<StudentName>Steve</StudentName>
</Student>
</Students>
</Section>
<Section ID="123456">
<SectioName>Section B</SectioName>
<Students>
<Student ID="100">
<StudentName>Dia</StudentName>
<Age>6</Age>
</Student>
<Student ID="101">
<StudentName>Kevin</StudentName>
</Student>
</Students>
</Section>
</ClassDetail>
<ClassDetail>
<ClassStartedYear>1995</ClassStartedYear>
<Section ID="543466">
<SectioName>Section A</SectioName>
<Students>
<Student ID="200">
<StudentName>Dia</StudentName>
<Muncipality>
<AreaCode>100</AreaCode>
<Areaname>GRAND</Areaname>
</Muncipality>
</Student>
<Student ID="201">
<StudentName>Liva</StudentName>
</Student>
</Students>
</Section>
<Section ID="7543466">
<SectioName>Section A</SectioName>
<Students>
<Student ID="300">
<StudentName>Zane</StudentName>
</Student>
<Student ID="301">
<StudentName>Susan</StudentName>
</Student>
</Students>
</Section>
</ClassDetail>
</ClassDetails>
</StudentDetail>
Below is the required format of the XML-
<StudentDetail>
<Student>
<SchoolName>SSHPS</SchoolName>
<SchoolEstablishedYear>1990</SchoolEstablishedYear>
<ClassStartedYear>1990</ClassStartedYear>
<SectionID>12345</SectionID>
<SectioName>Section A</SectioName>
<StudentID>1</StudentID>
<StudentName>John</StudentName>
<Address_HomeNumber>10</Address_HomeNumber>
<Address_StreetName>Avenue</Address_StreetName>
<Age> </Age>
<Muncipality_AreaCode></Muncipality_AreaCode>
<Muncipality_Areaname></Muncipality_Areaname>
</Student>
.
.
.
<Student>
<SchoolName>SSHPS</SchoolName>
<SchoolEstablishedYear>1990</SchoolEstablishedYear>
<ClassStartedYear>1995</ClassStartedYear>
<SectionID>7543466</SectionID>
<SectioName>Section A</SectioName>
<StudentID>100</StudentID>
<StudentName>Dia</StudentName>
<Address_HomeNumber></Address_HomeNumber>
<Address_StreetName></Address_StreetName>
<Age></Age>
<Muncipality_AreaCode>100</Muncipality_AreaCode>
<Muncipality_Areaname>GRAND</Muncipality_Areaname>
</Student>
</StudentDetail>
I have generated the XSL template, I am not able to load this as there is some error within this-
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<StudentDetail>
<xsl:for-each select="StudentDetail/ClassDetails">
<Student>
<SchoolName><xsl:value-of select="StudentDetail/SchoolName"/></SchoolName>
<SchoolEstablishedYear><xsl:value-of select="StudentDetail/SchoolEstablishedYear"/></SchoolEstablishedYear>
<ClassStartedYear><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/ClassStartedYear"/></ClassStartedYear>
<StudentID><xsl:value-of select="StudentDetail/ClassDetails/Section/#ID"/></StudentID>
<SectioName><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/SectionName"/></SectioName>
<StudentID><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/#ID"/></StudentID>
<StudentName><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student"/></StudentName>
<Address_HomeNumber><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/Address/HomeNumber"/></Address_HomeNumber>
<Address_StreetName><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/Address/StreetName"/></Address_StreetName>
<Age><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/Age"/></Age>
<Muncipality_AreaCode><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/Muncipality/AreaCode"/></Muncipality_AreaCode>
<Muncipality_Areaname><xsl:value-of select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student/Muncipality/Areaname"/></Muncipality_Areaname>
</Student>
</xsl:for-each>
</StudentDetail>
</xsl:template>
I am new to handling the XML, I am stuck with handling the nested XML
The main problem with your stylesheet is that you are creating a Student for each ClassDetails instead of for each Student.
Instead of:
<xsl:for-each select="StudentDetail/ClassDetails">
<Student>
<!-- data -->
</Student>
</xsl:for-each>
you should be doing:
<xsl:for-each select="StudentDetail/ClassDetails/ClassDetail/Section/Students/Student">
<Student>
<!-- data -->
</Student>
</xsl:for-each>
Then, within the Student element, you need to retrieve data from the ancestor School, ClassDetail and Section parts, as well as from the child elements of the current Student node.
In order to minimize the need to repeatedly navigate up and down the tree, I would suggest putting the ancestor parts details in variables and access them from there:
XSLT 1.0
<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:template match="/StudentDetail">
<xsl:copy>
<xsl:variable name="school-details" select="SchoolName | SchoolEstablishedYear"/>
<xsl:for-each select="ClassDetails/ClassDetail">
<xsl:variable name="class-details" select="ClassStartedYear"/>
<xsl:for-each select="Section">
<xsl:variable name="section-details">
<SectionID>
<xsl:value-of select="#ID"/>
</SectionID>
<xsl:copy-of select="SectioName"/>
</xsl:variable>
<xsl:for-each select="Students/Student">
<xsl:copy>
<xsl:copy-of select="$school-details | $class-details"/>
<xsl:copy-of select="$section-details"/>
<StudentID>
<xsl:value-of select="#ID"/>
</StudentID>
<xsl:copy-of select="StudentName"/>
<Address_HomeNumber>
<xsl:value-of select="Address/HomeNumber"/>
</Address_HomeNumber>
<Address_StreetName>
<xsl:value-of select="Address/StreetName"/>
</Address_StreetName>
<Age>
<xsl:value-of select="Age"/>
</Age>
<Muncipality_AreaCode>
<xsl:value-of select="Muncipality/AreaCode"/>
</Muncipality_AreaCode>
<Muncipality_Areaname>
<xsl:value-of select="Muncipality/Areaname"/>
</Muncipality_Areaname>
</xsl:copy>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Firstly, I'm surprised that anyone would want to turn this nicely structured input into this badly structured output. But we're not here to discuss that.
Secondly, I'm confused by the statement that you are "generating the XSL manually". I would have thought you were either creating it manually, or you were generating it programmatically, and it's not clear which of those is the case here.
Third, you've told us there is "some error" in your generated XSL, but you haven't told us what that error is. The only error I can see is a missing close tag for the xsl:stylesheet element, and that's presumably a typo. If you're getting an error, tell us what the error is.
Fourth, there seems to be a much simpler approach. As far as I can see, you can achieve the desired output by applying three rules:
If an element has child elements, just process its children
If an element has an ID attribute, change <X ID="x"/> to <XID>x</XID> and then process its children
If an element has a text node child, copy it unchanged.
The first rule corresponds to the default XSLT processing rule; the other two rules can be simply expessed in XSLT as:
<xsl:template match="*[#ID]">
<xsl:element name="{name()}ID">
<xsl:value-of select="#ID"/>
</xsl:element>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*[text()]">
<xsl:copy-of select="."/>
</xsl:template>

Is there a way to replace the for-each with apply-templates in an XSLT Transform?

Environment: XSLT 1.0
The transform will take each element in partOne section and lookup #field attribute in partTwo section using #find attribute and then output #value attribute.
I'm using a for-each loop and was wondering if apply-templates could work?
xml
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="file.xslt"?>
<xml>
<partOne>
<target field="hello"/>
<target field="world"/>
</partOne>
<partTwo>
<number input="2" find="hello" value="valone" />
<number input="2" find="world" value="valtwo" />
<number input="2" find="hello" value="valthree" />
<number input="2" find="world" value="valfour" />
</partTwo>
</xml>
xsl
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="/xml/partOne/target">
,<xsl:value-of select="#field"/>
<xsl:for-each select="/xml/partTwo/number[#find=current()/#field]">
,<xsl:value-of select="#value"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
,hello
,valone
,valthree
,world
,valtwo
,valfour
Well, it seems straight-forward to change
<xsl:for-each select="/xml/partTwo/number[#find=current()/#field]">
,<xsl:value-of select="#value"/>
</xsl:for-each>
to
<xsl:apply-templates select="/xml/partTwo/number[#find=current()/#field]"/>
with a template
<xsl:template match="partTwo/number">
,<xsl:value-of select="#value"/>
</xsl:template>
As your root template so far processes all elements you need to change it to
<xsl:template match="/">
<xsl:apply-templates select="xml/partOne"/>
</xsl:template>
to avoid processing the partTwo element(s) twice.
For the cross-reference you might want to use a key in both versions:
<xsl:key name="ref" match="partTwo/number" use="#find"/>
and then select="key('ref', #field)" instead of select="/xml/partTwo/number[#find=current()/#field]" for the apply-templates or for-each.

Transforming xml with namespaces using XSLT

I have the following xml
<?xml version="1.0" encoding="UTF-8"?>
<typeNames xmlns="http://www.dsttechnologies.com/awd/rest/v1" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<typeName recordType="case" href="awdServer/awd/services/v1/businessareas/SAMPLEBA/types/SAMPLECASE">SAMPLECASE</typeName>
<typeName recordType="folder" href="awdServer/awd/services/v1/businessareas/SAMPLEBA/types/SAMPLEFLD">SAMPLEFLD</typeName>
<typeName recordType="source" href="awdServer/awd/services/v1/businessareas/SAMPLEBA/types/SAMPLEST">SAMPLEST</typeName>
<typeName recordType="transaction" href="awdServer/awd/services/v1/businessareas/SAMPLEBA/types/SAMPLEWT">SAMPLEWT</typeName>
</typeNames>
I want to transform above xml as below by using XSLT:
<response>
<results>
<source>
SAMPLEST
</source>
</results>
</response>
</xsl:template>
I just want to get the source from the input xml to the output xml.
I am trying with the following xml, but couldn't get the required output xml:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:v="http://www.dsttechnologies.com/awd/rest/v1" version="2.0" exclude-result-prefixes="v">
<xsl:output method="xml" version="1.0" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="typeNames">
<response>
<results>
<source>
<xsl:value-of select="source" />
</source>
</results>
</response>
</xsl:template>
</xsl:stylesheet>
I. Namespace in input xml
<typeNames xmlns="http://www.dsttechnologies.com/awd/rest/v1"...
xmlns puts self + all child nodes into a namespace. This namespace does not need any prefix.
II. Namespace in XSLT
... xmlns:v="http://www.dsttechnologies.com/awd/rest/v1"...
You prefixed the namespace (same uri as source) with v, so you have to write this prefix in your xpath as well.
<xsl:template match="v:typeNames">
[XSLT 2.0: you also can add xpath-default-namespace="uri" in the stylesheet section, to define a default namespace for all xpath-expressions. Therefore you dont have to prefix the namespace.]
III. Guessing on given input xml
<xsl:value-of select="source" /> -> <typeName recordType="source"..>SAMPLEST</typeName>
If you want to select the shown xml-node, you have to write one of the following:
absolute, without any context node:
/v:typeNames/v:typeName[#recordType = 'source']
on context-node typeNames:
v:typeName[#recordType = 'source']
[<xsl:value-of select="..."/> will return the text-node(s), e.g. "SAMPLEST"]
EDIT:
What if there are two tags.
First things first: <xsl:value-of in XSLT 1 can only work with 1 node! If the xpath expression matches more than one node, it will just process the first one!
Solve it like this way:
...
<results>
<xsl:apply-templates select="v:typeName[#recordType = 'source']"/>
</results>
...
<xsl:template match="v:typeName[#recordType = 'source']">
<source>
<xsl:value-of select="."/>
</source>
</xsl:template>
The apply-templates within results searches for all typeName..source. The matching template listens to that node and creates the xml <source>....

XSLT To Bring Child Elements into Parent As Attributes And Keep Existing Parent Attribute

I'm stumped and am very new to XSLT. A little direciton would be greatly appreciated. My ultimate objective is to bring the firstName, lastName, and gender elements under the student element up into that element as attributes, but also want to retain the StudentID attribute that already exists there. I'd also like that "year_Collection" to disappear.
I started with this XML document
<?xml version="1.0" encoding="UTF-8"?>
<Report xmlns="Upload" Name="Upload">
<student StudentID="123456">
<firstName firstName="John"/>
<lastName lastName="Johnson"/>
<gender gender="M"/>
<year_Collection>
<year value="2013">
<term hoursEarned="18.00" hoursAttempted="18.00" termCode="S1"/>
</year>
</year_Collection>
</student>
</Report>
My desired output looks like this
<?xml version="1.0" encoding="UTF-8"?>
<Report xmlns="Upload" Name="Upload">
<student gender="M" lastName="Johnson" firstName="John" StudentID="123456">
<year value="2013">
<term hoursEarned="18.00" hoursAttempted="18.00" termCode="S1"/>
</year>
</student>
</Report>
I was able to use this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:r="Upload" exclude-result-prefixes="r" extension-element-prefixes="r">
<xsl:template match="r:student">
<xsl:copy>
<xsl:for-each select="*">
<xsl:attribute name="{local-name(.)}">
<xsl:value-of select="."/>
</xsl:attribute>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
To get here:
<?xml version="1.0" encoding="UTF-8"?>
<Report xmlns="Upload" Name="Upload">
<student year_Collection="" gender="M" lastName="Johnson" firstName="John">
<year value="2013">
<term hoursEarned="18.00" hoursAttempted="18.00" termCode="S1"/>
</year>
</student>
</Report>
But it overwrites the StudentID attribute that already exists. How do I get the elements to come up as attributes, but not overwrite StudentID?
Also, that pesky year_collection element won't disappear with this, but I can split that into a separate question if that's a better way to go about it.
<xsl:template match="r:year_Collection">
<xsl:apply-templates/>
</xsl:template>
But it overwrites the StudentID attribute that already exists.
No, it doesn't. The problem is you're not copying it. You should start your template with:
<xsl:template match="r:student">
<xsl:copy>
<xsl:copy-of select="#StudentID"/>
or, if you prefer:
<xsl:template match="r:student">
<xsl:copy>
<xsl:copy-of select="#*"/>
to copy any and all attributes the Student has.
Regarding the problem with<year_Collection>, you should try to turn only leaf nodes into attributes, for example:
<xsl:template match="r:student">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="*/#*"/>
<xsl:copy-of select="*/*[#*]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Of course, if your structure is known, naming the required nodes explicitly would be much better, e.g;
<xsl:template match="r:student">
<xsl:copy>
<xsl:copy-of select="#StudentID | r:firstName/#firstName | r:lastName/#lastName | r:gender/#gender"/>
<xsl:copy-of select="r:year_Collection/r:year"/>
</xsl:copy>
</xsl:template>

Overriding match="*" template from DocBook XSL

DocBook XSL includes a template that matches all element
<xsl:template match="*">
<xsl:message> .... </xsl:message>
</xsl:template>
I need to override it with another template because my source XML tree contains more that just the DoocBook XML. If I specify such a template in the file it overrides all templates in DocBook XSL. It seems like that all imported templates, are prioritized on the order of import only, and NOT according to how specific the template is.
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:db="http://docbook.org/ns/docbook" version="1.0">
<xsl:import href="docbook-xsl-ns/xhtml/docbook.xsl" />
<xsl:import href="copy.xsl"/>
<xsl:template match="/">
<xsl:apply-templates select="//db:book"/>
</xsl:template>
</xsl:stylesheet>
copy.xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<!-- go process attributes and children -->
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Sample XML source
<?xml version="1.0" encoding="UTF-8"?>
<root>
<http-host>localhost</http-host>
<book xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:svg="http://www.w3.org/2000/svg" xmlns:m="http://www.w3.org/1998/Math/MathML" xml:id="course.528" xml:lang="en" version="5.0">
<info>
<title>Postoperative Complications</title>
</info>
<chapter xml:id="chapter.1">
<title>INTRODUCTION</title>
<para>Postoperative complications are a constant threat to the millions ....</para>
</chapter>
</book>
<errors></errors>
</root>
This is true for both Xalan and xsltproc processors. How do I override this template without having to change the DocBook XSL source. I tried messing with priorities but that did not work.
From what I understand, you want to apply the copy.xsl's template only for non-docbook elements. Try to be more specific in your copy.xsl - by being more specific in your copy.xsl, that template will get selected for all non-docbook elements.
copy.xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform>
<xsl:template match="*[not(namespace-uri() = 'http://docbook.org/ns/docbook')]">
<xsl:element name="{local-name()}">
<!-- go process attributes and children -->
<xsl:apply-templates select="#*|node()" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Depending on the presence of DocBook elements within non-Docbook nodes, you might need to restrict the nodeset for which you apply at the apply-templates part as well(based on the namespace) and maybe mess around the apply-templates flow to make sure it handles it predictably. Hope this is of some use to you..