XSLT skip duplicate element - java

I am a beginner to XSLT.
My Source XML is as below:
<Passengers>
<Passenger type="A" id="P1"/>
<Passenger type="A" id="P2"/>
<Passenger type="B" id="P3"/>
<Passenger type="C" id="P4"/>
</Passengers>
The out-put should be as below:
<Pax_Items>
<Item>
<Type>A</Type>
<Count>2</Count>
</Item>
<Item>
<Type>B</Type>
<Count>1</Count>
</Item>
<Item>
<Type>C</Type>
<Count>1</Count>
</Item>
</Pax_Items>
I have created XSLT as below
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0" exclude-result-prefixes="xmlns">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:variable name="filter" select="'TK,AJ'"/>
<xsl:template match="Passengers">
<xsl:element name="Pax_Items">
<xsl:apply-templates select="Passenger"/>
</xsl:element>
</xsl:template>
<xsl:template match="Passenger">
<xsl:element name="Item">
<xsl:element name="Type">
<xsl:value-of select="#type"/>
</xsl:element>
<xsl:element name="Count">
<xsl:value-of select="count(//Passenger[#type=current()/#type])"/>
</xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
With above XSLT i got the below output:
<Pax_Items>
<Item>
<Type>A</Type>
<Count>2</Count>
</Item>
<Item>
<Type>A</Type>
<Count>2</Count>
</Item>
<Item>
<Type>B</Type>
<Count>1</Count>
</Item>
<Item>
<Type>C</Type>
<Count>1</Count>
</Item>
</Pax_Items>
How can i omit or skip the duplicate element? Please help.

This is actually a good example of a grouping problem. In XSLT1.0, the most efficient way to do grouping is with a technique called "Muenchian Grouping", so it might be worthwhile learning about this.
In this case, you want to group Passenger elements by their #type attribute, so you would define a key to do this
<xsl:key name="Passengers" match="Passenger" use="#type"/>
Then, you need to select the Passenger elements which happen to be the first occurence of that element in the group for their #type attribute. This is done as follows:
<xsl:apply-templates
select="Passenger[generate-id() = generate-id(key('Passengers', #type)[1])]"/>
Note the use of generate-id which generates a unique ID for a node, allowing two nodes to be compared.
Then, to count the number of occurences in the group, it is straight-forward
<xsl:value-of select="count(key('Passengers', #type))"/>
Here is the full XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="Passengers" match="Passenger" use="#type"/>
<xsl:template match="Passengers">
<Pax_Items>
<xsl:apply-templates select="Passenger[generate-id() = generate-id(key('Passengers', #type)[1])]"/>
</Pax_Items>
</xsl:template>
<xsl:template match="Passenger">
<Item>
<Type>
<xsl:value-of select="#type"/>
</Type>
<Count>
<xsl:value-of select="count(key('Passengers', #type))"/>
</Count>
</Item>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<Pax_Items>
<Item>
<Type>A</Type>
<Count>2</Count>
</Item>
<Item>
<Type>B</Type>
<Count>1</Count>
</Item>
<Item>
<Type>C</Type>
<Count>1</Count>
</Item>
</Pax_Items>
Also note there is no real reason to use xsl:element to output static elements. Just write out the element directly.

Update your passenger template as follows; I have added if condition to check duplicate nodes,
<xsl:template match="Passenger">
<xsl:if test="not(preceding-sibling::Passenger[#type = current()/#type])">
<xsl:element name="Item">
<xsl:element name="Type">
<xsl:value-of select="#type"/>
</xsl:element>
<xsl:element name="Count">
<xsl:value-of select="count(//Passenger[#type=current()/#type])"/>
</xsl:element>
</xsl:element>
</xsl:if>
</xsl:template>

Related

How to Read Java List Object in XSL

I am having a Java Object which is ArrayList and i have added it in response of service which is in XML format as below,
<root>
<SubRoot>
<type>A</type>
<mand>Y</mand>
<Section>B</Section>
</SubRoot>
<SubRoot>
<type>A</type>
<mand>Y</mand>
<Section>A</Section>
</SubRoot>
</root>
I am trying to use the some value from above xml for condition.On basis of true condition i will call some template for my purpose.
I am calling my xsl template as below,
<xsl:choose>
<xsl:when test="(/root/SubRoot[Section = 'A'])">
//Call some template
</xsl:when>
<xsl:otherwise>
//some template
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="(/root/SubRoot[Section = 'B'])">
//call some template
</xsl:when>
<xsl:otherwise>
//some template
</xsl:otherwise>
</xsl:choose>
Each time my first when will get executed its not coming inside the second when condition.
Could you please help me how the both condition will get executed?is it right approach in terms of performance?
Any Suggestion approach must be appreciated.
Try this,
XSL
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://locomotive/bypass/docx">
<xsl:output method="xml" version="1.0" 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="SubRoot">
<xsl:choose>
<xsl:when test="Section = 'A'">
<!-- your logic for A -->
<A></A>
</xsl:when>
<xsl:otherwise>
<!-- your logic for B -->
<B></B>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
XML
<root>
<SubRoot>
<type>A</type>
<mand>Y</mand>
<Section>B</Section>
</SubRoot>
<SubRoot>
<type>A</type>
<mand>Y</mand>
<Section>A</Section>
</SubRoot>
Demo link : http://xsltransform.net/ejivdHb/15

Using XSLT to recursively load relative XML files and apply transformation

I have a xml file whose structure looks like this:
<root>
<includes>
<includeFile name="../other/some_xml.xml"/>
</includes>
<itemlist>
<item id="1" >
<selections>
<selection name="one" />
</selections>
</item>
</itemlist>
The xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt">
<xsl:output method="xml" indent="yes" xalan:indent-amount="4" />
<xsl:template match="/">
<xsl:element name="ItemList">
<xsl:if test="root/item">
<xsl:call-template name="templ" />
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template name="templ">
<xsl:element name="ItemList">
<xsl:for-each select="root/itemlist/item">
<xsl:element name="Item">
<xsl:element name="ItemIdentifier">
<xsl:value-of select="#id" />
</xsl:element>
<xsl:element name="Condition">
<xsl:value-of select="selections/selection[1]/#name" />
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I created one XSLT which i am using to filter out items. The problem is that for every file, i have to check if it contains the includefile tag, which is a relative path pointing to a similar xml, and if it does, i need to collect items from that file also, recursively. For now i transformed the xml using my xslt and then i have to parse the xml to look for includefile tag. This solution doesn't look elegant and i was wondering if all of it could be done via xslt.
The XSLT document function in XSLT 1.0 and in XSLT 2.0 additionally the doc function allow you to pull in further documents, processing is then simply possible with matching templates. So consider to move your XSLT coding style to write matching templates and apply-templates, then you can easily do
<xsl:template match="includes/includeFile">
<xsl:apply-templates select="document(#name)/*"/>
<xsl:template>
and then you simply need to make sure the <xsl:template match="root">...</xsl:template> creates the output you want.

Parsing nested tags through XSLT

I have a XML file like this -
<item>
<item>
<tag>value</tag>
<tag2>value</tag2>
</item>
</item>
I'd like to extract out the inner tag so it looks like this -
<item>
<tag>value</tag>
<tag2>value</tag2>
</item>
Is this possible through XSLT?
The outer <item> and </item> are guaranteed to be the first and last lines of the XML document if that helps.
If you want to extract just leaf elements (those that have no child elements of their own) then something as simple as
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/*">
<xsl:copy>
<xsl:copy-of select="descendant::*[not(*)]" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
will do the job.
<xsl:template match="/">
<xsl:copy-of select="//item[not(item)]"/>
</xsl:template>
extracts those item elements that do not contain other item elements.

Transforming XSL with XSL

A question about Martin's answer:
Martin Honnen's answer works great, but not with the root element. Let's say I have "cars" as a root element:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="foo">
<cars>
<car1 />
<car2 />
</cars>
</xsl:template>
</xsl:stylesheet>
And I want to obtain:
<xsl:template match="foo">
<cars>
<car1 />
<car2 />
<TANK />
</cars>
</xsl:template>
For this, I'd use:
<xsl:template match="cars" >
<xsl:copy>
<xsl:apply-templates/>
<TANK />
</xsl:copy>
</xsl:template>
Which outputs the exact input, without changing anything. I can try:
<xsl:template match="/" >
<xsl:copy>
<xsl:apply-templates/>
<TANK />
</xsl:copy>
</xsl:template>
But it will place the TANK node outside the stylesheet, like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="order">
<cars>
<car1/>
<car2/>
</cars>
</xsl:template>
</xsl:stylesheet><TANK/>
How to get the TANK element inside cars?
Original question:
I have a XSL that I use to transform an XML:
XML_format1 -> XSL1 -> XML_format2
I need to transform this first XSL file (using a second XSL) to obtain a third XSL file, which will output an XML with a third format. In short:
XSL1 -> XSL2 -> XSL3
XML_format1 -> XSL3 -> XML_format3
Using the following stylesheet, I am able to copy the first XSL's contents and also skip certain nodes:
<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:template match="//skipThisNode"/>
</xsl:stylesheet>
My problem: in addition to this, I also need to change some nodes' structure (add something), from this:
<car>
<color>green</color>
<fuel>petrol</fuel>
</car>
To this:
<car>
<color>green</color>
<fuel>petrol</fuel>
<topSpeed>99</topSpeed>
</car>
LE: I could create a template to match the specific nodes that I need to add children to, like so:
<xsl:template match="car">
<color>
<!-- existing value-of -->
</color>
<fuel>
<!-- existing value-of -->
</fuel>
<topSpeed>
<!-- * new value-of * -->
</topSpeed>
</xsl:template>
But this seems like going over the top. Is there a simpler way of achieving what I want?
I would rather use
<xsl:param name="newSpeed" select="99"/>
<xsl:template match="car">
<xsl:copy>
<xsl:apply-templates/>
<topSpeed>
<xsl:value-of select="$newSpeed"/>
</topSpeed>
</xsl:copy>
</xsl:template>
that keeps the processing chain up, allowing you to add templates to transform or delete child and descendants of car elements when needed.
[edit]
I am not sure I understand your latest requirement as the input seems to be a complete stylesheet but then as the wanted output you have only shown a single template. So assuming you have the input as
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="order">
<cars>
<car1 />
<car2 />
</cars>
</xsl:template>
</xsl:stylesheet>
and you want to both transform the xsl:template's match attribute as well as the literal result elements in the template body I would use
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xsl:template[#match = 'order']/#match">
<xsl:attribute name="{name()}">
<xsl:text>foo</xsl:text>
</xsl:attribute>
</xsl:template>
<xsl:template match="xsl:template[#match = 'order']/cars">
<xsl:copy>
<xsl:apply-templates/>
<TANK/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
that way I get (tested with Saxon 6.5.5) the result
<?xml version="1.0" encoding="utf-8"?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="foo">
<cars>
<car1/>
<car2/>
<TANK/></cars>
</xsl:template>
</xsl:stylesheet>
which is hopefully what you want (lacking proper indentation perhaps).

Generic XML sorting via XSLT applied by Java?

Basically, I want an exact copy of the XML, except I'd like to sort certain nodes by their ID attribute. Not all elements with the ID attribute should be sorted.
I've kludged together a working stylesheet, but it requires me to hard-code the <xsl:apply-template>'s in for all sibling nodes to the nodes I'm sorting on. I've no formal schema, and I can't be certain that it won't change. I'd like to be able to create a more generic stylesheet which will output the non-sorted nodes without these explicit calls.
Is this possible?
My Java code to transform the XML:
String input = "C:\\presort.xml";
String output = "C:\\postsort.xml";
String xsl = "C:\\sort.xsl"
Document dom;
try
{
dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(input);
Element e = dom.getDocumentElement();
Transformer transform = TransformerFactory.newInstance().newTransformer(
new StreamSource( new File(xsl) ));
StreamResult result = new StreamResult(new File(output));
transform.transform(new DOMSource(dom), result);
}
catch (Exception e)
{
e.printStackTrace();
}
My XML:
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Person id="1">
<Base>true</Base>
<Description>something</Description>
<Name>Vin Vinigar</Name>
<Privileges>
<Privilige id="340">
<Permission>2</Permission>
<Job id="dontsort" />
</Privilige>
<Privilige id="11">
<Permission>3</Permission>
<Job id="dontsort" />
</Privilige>
<Privilige id="1011">
<Permission>1</Permission>
<Job id="2342" />
</Privilige>
</Privileges>
</Person>
<Person id="f32">
<Base>true</Base>
<Description>Here be dragons</Description>
<Name>John Doe</Name>
<Privileges>
<Privilige id="23a">
<Permission>2</Permission>
<Job id="a2a" />
</Privilige>
</Privileges>
</Person>
<Person id="22">
<PossibleUnknownTagHere>something</PossibleUnknownTagHere>
<Name>Han Solo</Name>
<Privileges>
<Privilige id="23a">
<Permission>3</Permission>
<Job id="a2a" />
</Privilige>
</Privileges>
</Person>
</root>
My stylesheet:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root|#*">
<xsl:copy >
<xsl:apply-templates select="#*" />
<xsl:apply-templates select="Person">
<xsl:sort select="#id" order="ascending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="Person">
<xsl:copy >
<xsl:apply-templates select="#*" />
<!-- Works but requies a template for each sibling element -->
<xsl:apply-templates select="Base" />
<xsl:apply-templates select="Description" />
<xsl:apply-templates select="Name" />
<!-- I'd like to do something like this -->
<!--
<xsl:choose>
<xsl:when test="not(Privileges)">
<xsl:apply-templates select="." />
</xsl:when>
</xsl:choose>
-->
<xsl:apply-templates select="Privileges" />
</xsl:copy>
</xsl:template>
<!-- I'd like to remove the need for these 3 explicity copies -->
<xsl:template match="Base">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="Description">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="Name">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="Privileges">
<xsl:copy >
<xsl:apply-templates select="#*" />
<xsl:apply-templates select="Privilege">
<xsl:sort select="#id" order="ascending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="Privilege">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
<xsl:template>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:apply-templates select="Person">
<xsl:sort select="#id" order="ascending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="Privileges">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:apply-templates select="Privilege">
<xsl:sort select="#id" order="ascending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>

Categories

Resources