I have a xml that has so many elements and most of that contain attributes.. for some of the attributes values are same so I need to group them and generate diff xml.
I/p Ex:
<TestNode>
<ABC1 value="10.7" format="$" />
<ABC2 value="10.5" format="$" />
<ABC3 value="20" format="Rs" />
<ABC4 value="50" format="Rs" />
<ABC5 value="10.5" format="$" />
</TestNode>
I need to group the rows by format. Note: Format is not fixed... it may grow ...
O/P Ex:
is it possible to get ? Thanks in advance...
In XSLT 1.0 you would use Muenchian grouping.
Define a key "format", from which we can easily select all elements given a format name. Than apply Muenchian grouping to find the unique formats in the input.
Then it gets simple. The "*" template will be applied once per format, and uses the key() to fetch all entries for that format.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="format" match="TestNode/*" use="#format" />
<xsl:template match="TestNode">
<body>
<xsl:apply-templates select="*[generate-id(.)=generate-id(key('format',#format)[1])]"/>
</body>
</xsl:template>
<xsl:template match="*">
<format format="{#format}">
<xsl:copy-of select="key('format', #format)" />
</format>
</xsl:template>
</xsl:stylesheet>
In XSLT 2.0 you should be able to do it with <xsl:for-each-group>, current-grouping-key() and current-group()
Example:
<xsl:for-each-group
select="TestNode/*"
group-by="#format"
>
<group format="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<xsl:copy-of select="."/>
</xsl:for-each>
</group>
</xsl:for-each-group>
See: http://www.w3.org/TR/xslt20/#grouping
Related
I wanted to replace wildcards in a control XML-file from a third-party software.
Unfortunately these wildcards also used as attribute values in this XML-file.
I will give you an example:
<control>
<some-tag id="$wildcard1$" version="3.14">
<another-tag id="second_level">stackoverflow rocks!</another-tag>
</some-tag>
<some-tag id="foo" version="$wildcard2$"/>
<some-tag id="bar" version="145.31.1"/>
</control>
I tried to write a generic transformation with parameters to replace the wildcards in the attribute values.
My biggest problem was, that i don't know the attribute name. So i need to match every attribute in the XML file. That is easy but how i match every attribute with a specific value (e.g. $wildcard$) ?
The answer to this question was quite easier than I thought it would be.
<xsl:template match="#*[. = $wildcard]">
<xsl:attribute name="{name(.)}">
<xsl:value-of select="$wildcard_value"/>
</xsl:attribute>
</xsl:template>
I hope it helps someone.
P.S: Here is my full XSL-Transformation to replace wildcards in attributes values:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:param name="wildcard" required="yes" />
<xsl:param name="wildcard_value" required="yes" />
<xsl:output method="xml" version="1.0" encoding="UTF-8"
indent="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*[. = $wildcard]">
<xsl:attribute name="{name(.)}">
<xsl:value-of select="$wildcard_value" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Considering this XML,
XML:
<?xml version="1.0" encoding="UTF-8"?>
<items>
<book>
<title>doublebell</title>
<count>available</count>
</book>
<phone>
<brand>nokia</brand>
<model></model>
</phone>
</items>
Mapping Criteria while writing XSLT:
show the newbook/newtitle only if a value is present in input.
show the newbook/newcount only if a value is present in input.
show the newphone/newbrand only if a value is present in input.
show the newphone/newmodel only if a value is present in input.
XSLT:
<?xml version="1.0" encoding="ISO-8859-1"?>
<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="book" select="items/book" />
<xsl:variable name="phone" select="items/phone" />
<xsl:template match="/">
<items>
<newbook>
<xsl:if test="$book/title!=''">
<newtitle>
<xsl:value-of select="$book/title" />
</newtitle>
</xsl:if>
<xsl:if test="$book/count!=''">
<newcount>
<xsl:value-of select="$book/count" />
</newcount>
</xsl:if>
</newbook>
<xsl:if test="$phone/brand!='' or $phone/model!=''"> <!-- not sure if this condition is required for the above mapping criteria -->
<newphone>
<xsl:if test="$phone/brand!=''">
<newbrand>
<xsl:value-of select="$phone/brand" />
</newbrand>
</xsl:if>
<xsl:if test="$phone/model!=''">
<newmodel>
<xsl:value-of select="$phone/model" />
</newmodel>
</xsl:if>
</newphone>
</xsl:if>
</items>
</xsl:template>
</xsl:stylesheet>
This is my concern:- In my actual XSLT, I have almost 70 conditions like
this, and everytime the XPath search is made twice [or thrice.. ] for
each condition [ for eg: <xsl:if test="$phone/brand!=''"> and <xsl:value-of select="$phone/brand" /> and outer if condition].
Is this much performance overhead? I don't feel it when I ran my application.
I like to hear from experienced people if this is correct way of writing the XSLT. Do I need to save the path in a variable and reuse it as done for $book
and $phone ? In such a case there will be 70+variables just to hold this.
You can approach this quite differently using templates. If you define a template that matches any element whose content is empty and does nothing:
<xsl:template match="*[. = '']" />
or possibly use normalize-space() if you want to consider elements to be empty if they contain only whitespace
<xsl:template match="*[not(normalize-space())]" />
Now with this in place add templates for the elements you are interested in
<xsl:template match="book">
<newbook><xsl:apply-templates /></newbook>
</xsl:template>
<xsl:template match="title">
<newtitle><xsl:apply-templates /></newtitle>
</xsl:template>
and so on. Now the book template will create a newbook element and go on to process its children. When it gets to the title it will have two different templates to choose from and will pick the "most specific" match. If the title is empty then the *[. = ''] template will win and nothing will be output, only if the title is non-empty will it create a newtitle element.
This way you let the template matcher do most of the work for you, you don't need any explicit conditional checks using xsl:if.
<?xml version="1.0" encoding="ISO-8859-1"?>
<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="/">
<items><xsl:apply-templates select="items/*" /></items>
</xsl:template>
<!-- ignore empty elements -->
<xsl:template match="*[not(normalize-space())]" />
<xsl:template match="book">
<newbook><xsl:apply-templates /></newbook>
</xsl:template>
<xsl:template match="title">
<newtitle><xsl:apply-templates /></newtitle>
</xsl:template>
<!-- and so on with similar templates for the other elements -->
</xsl:stylesheet>
Building on Ian's answer, you can also make a generic template that will create the "new" elements for you without having to specify each one individually. That would look like the below:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<items><xsl:apply-templates select="items/*" /></items>
</xsl:template>
<xsl:template match="*[not(normalize-space())]" />
<xsl:template match="*">
<xsl:element name="{concat('new',name())}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
That last template just rebuilds the element by concatenating the word "new" to the front of it.
I am quite new to xsl and functional programming, so I'll be grateful for help on this one:
I have a template that transforms some xml and provides an output. The problem is that there are many elements of type xs:date, all in different contexts, that must be localized. I use a concatenation of substrings of these xs:dates to produce a localized date pattern strings.
As you can guess this causes a lot of copy-paste "substring-this and substring-that". How can I write a template that will automatically transform all the elements of type xs:date to localized strings preserving all the context-aware transformations?
My xsl is something like this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/">
...
<input value="{substring(/select/a/date 9,2)}.{substring(/select/a/date, 6,2)}.{substring(/select/a/date 1,4)}">
...
<!-- assume that following examples are also with substrings -->
<div><xsl:value-of select="different-path/to_date"/></div>
...
<table>
<tr><td><xsl:value-of select="path/to/another/date"/></td></tr>
</table>
<apply-templates/>
</xsl:template>
<xsl:template match="something else">
<!-- more dates here -->
</xsl:template>
</xsl:stylesheet>
I hope I managed to make my question clear =)
UPD: Here is an example of xml:
<REQUEST>
<header>
<... />
<ref>
<ref_date type="xs:date">1970-01-01</ref_date>
</ref>
</header>
<general>
<.../>
<info>
<.../>
<date type="xs:date">1970-01-01</date>
<ExpireDate type="xs:date">1970-01-01</ExpireDate>
<RealDate type="xs:date">1970-01-01</RealDate>
<templateDetails>template details</templateDetails>
<effectiveDate type="xs:date">1970-01-01</effectiveDate>
</info>
<party>
<.../>
<date type="xs:date">1970-01-01</date>
</party>
<!-- many other parts of such kind -->
</general>
</REQUEST>
As for the output, there are many different options. The main thing is that these values must be set as a value of different html objects, such as tables, input fields and so on. You can see an example in the xsl listing.
P.S. I'm using xsl 1.0.
If you did a schema-aware XSLT 2.0 transformation, you wouldn't need all those type='xs:date' attributes: defining it in the schema as a date would be enough. You could then match the attributes with
<xsl:template match="attribute(*, xs:date)">
What you could do is add a template to match any element which has an #type attribute of 'xs:date', and do you substring manipulation in there
<xsl:template match="*[#type='xs:date']">
<xsl:value-of select="translate(., '-', '/')" />
</xsl:template>
In this case I am just replacing the hyphens by slashes as an example.
Then, instead of using xsl:value-of....
<div><xsl:value-of select="different-path/to_date"/></div>
You could use xsl:apply-templates
<div><xsl:apply-templates select="different-path/to_date"/></div>
Consider this XSLT as an example
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*[#type='xs:date']">
<xsl:copy>
<xsl:value-of select="translate(., '-', '/')" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In this case, all this XSLT is doing is copying the XML document as-is, but changing the date elements.
If you wanted to use the date template for other elements, or values, you could also make it a named-template, like so
<xsl:template match="*[#type='xs:date']" name="date">
<xsl:param name="date" select="." />
<xsl:value-of select="translate($date, '-', '/')" />
</xsl:template>
This would allow you to also call it much like a function. For example, to format a data and add as an attribute you could do the following:
<input>
<xsl:attribute name="value">
<xsl:call-template name="date">
<xsl:with-param name="date" select="/select/a/date" />
</xsl:call-template>
</xsl:attribute>
</input>
In that, I want to display only the unique fruit entries in it. Here is the XML tag what I'm using for parsing
<main>
<local id="1" type="Primary">
-<summary Date="23-02-12">
-<fruit>apple</fruit>
-<fruit>Orange</fruit>
</summary>
</local>
<local id="2" type="Primary">
-<summary Date="23-02-12">
-<fruit>apple</fruit>
-<fruit>mango</fruit>
</summary>
</local>
</main>
The expected result should be in the below format
<fruit>apple</fruit>
<fruit>Orange</fruit>
<fruit>Mango</fruit>
Here are the code snippet what I'm trying to use
<xsl:for-each select="main/local">
<xsl:for-each select="symbol/fruit">
<xsl:copy-of select="."/>
<xsl:copy-of select="fruit[not(.=$fruit)]"/>
</xsl:for-each>
</xsl:for-each>
But I'm not getting any output display for this, Can you please help me how can I remove this duplicate redundancy from here.? Thank You in advance
To do this in XSLT1.0 you can make use of a technique called 'Meunchian' grouping. First you define a key to 'look-up' the fruit elements based on the value
<xsl:key name="fruit" match="fruit" use="." />
Then, to get the unique fruit names, you match fruit elements that happen to be the first fruit element in the key (and to check two nodes are the same the generate-id() method is used)
<xsl:apply-templates
select="//fruit[generate-id() = generate-id(key('fruit', .)[1])]" />
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="fruit" match="fruit" use="." />
<xsl:template match="/">
<xsl:apply-templates
select="//fruit[generate-id() = generate-id(key('fruit', .)[1])]" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output:
<fruit>apple</fruit>
<fruit>Orange</fruit>
<fruit>mango</fruit>
I'm trying out a sample of look-up tables in XSLT and am not able to get it to work
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" />
<xsl:key name="classification-lookup" match="classification" use="id" />
<xsl:variable name="classification-top" select="document('')/*/classifications" />
<xsl:template match="BusinessListing">
<listing>
<id>
<xsl:value-of select="id" />
</id>
<xsl:apply-templates select="$classification-top">
<xsl:with-param name="curr-label" select="." />
</xsl:apply-templates>
</listing>
</xsl:template>
<xsl:template match="classifications">
<xsl:param name="curr-label" />
<category>
<xsl:value-of select="key('classification-lookup', $curr-label/listingData/classifications/classificationId)/description" />
</category>
</xsl:template>
<classifications>
<classification>
<id>7981</id>
<description>Category1</description>
</classification>
<classification>
<id>7982</id>
<description>Category2</description>
</classification>
<classification>
<id>7983</id>
<description>Category3</description>
</classification>
<classification>
<id>7984</id>
<description>Category4</description>
</classification>
</classifications>
</xsl:stylesheet>
and the source is as below .
<?xml version="1.0"?>
<BusinessListings>
<BusinessListing>
<id>1593469</id>
<listingData>
<classifications>
<classificationId>7982</classificationId>
<classificationId>7983</classificationId>
</classifications>
</listingData>
</BusinessListing>
</BusinessListings>
In the result below , The category is empty but I need the Classification Id from the source to be matched with the id in the classification tag and the category generated .
<?xml version="1.0" encoding="UTF-8"?>
<listing>
<id>1593469</id> -- Empty I need the Category2 and Category3 here
<category/>
</listing>
I know that I may be wide off mark but I've just started off with XSLT and referred the sample here http://www.ibm.com/developerworks/xml/library/x-xsltip.html . Thanks for the help .
Your XSLT stylesheet contains an error -- according to spec, any child-element of xsl:stylesheet (aka top-level element) must be in a non-null namespace:
"*In addition, the xsl:stylesheet
element may contain any element not
from the XSLT namespace, provided that
the expanded-name of the element has a
non-null namespace URI. "
If the XSLT processor you are using doesn't raise an error, then it is non-compliant and buggy and shouldn't be used. Find and use a compliant XSLT processor (I am using .NET XslCompiledTransform, Saxon 6.5.5, ..., etc).
There are other errors, too.
Solution:
Define a new namespace with prefix (say) "x:":
Change the embedded <classifications> to <x:classifications> -- now this conforms to the Spec.
Perform more changes to the code until you get this transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="my:x" exclude-result-prefixes="x">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="classification-lookup" match="classification"
use="id" />
<xsl:template match="BusinessListing">
<listing>
<id>
<xsl:value-of select="id" />
</id>
<xsl:apply-templates/>
</listing>
</xsl:template>
<xsl:template match="classificationId">
<xsl:variable name="vCur" select="."/>
<xsl:for-each select="document('')">
<category>
<xsl:value-of select=
"key('classification-lookup',$vCur)/description" />
</category>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
<x:classifications>
<classification>
<id>7981</id>
<description>Category1</description>
</classification>
<classification>
<id>7982</id>
<description>Category2</description>
</classification>
<classification>
<id>7983</id>
<description>Category3</description>
</classification>
<classification>
<id>7984</id>
<description>Category4</description>
</classification>
</x:classifications>
</xsl:stylesheet>
.4. In the above code notice the line: <xsl:for-each select="document('')"> .
The purpose of this is to make the stylesheet the current document. The key() function operates only on the current document and if you want the embedded classification elements to be indexed and used, you must change the current document (usually in this way). In XSLT 2.0 the key() function allows a 3rd argument which is a node from the document whose index should be used.
When this transformation is applied to the provided XML document:
<BusinessListings>
<BusinessListing>
<id>1593469</id>
<listingData>
<classifications>
<classificationId>7982</classificationId>
<classificationId>7983</classificationId>
</classifications>
</listingData>
</BusinessListing>
</BusinessListings>
the wanted, correct result is produced:
<listing>
<id>1593469</id>
<category>Category2</category>
<category>Category3</category>
</listing>