Sort data in the xml alphabetical order - xslt

Input XML :
<?xml version="1.0" encoding="utf-8" ?>
<infoset>
<info>
<title>Bill</title>
<group>
<code>state</code>
</group>
</info>
<info>
<title>Auto</title>
<group>
<code>state</code>
</group>
</info>
<info>
<title>Auto2</title>
</info>
<info>
<title>Auto3</title>
</info>
<info>
<title>Auto5</title>
</info>
<info>
<title>Certificate</title>
<group>
<code>Auto4</code>
</group>
</info>
</infoset>
Expected output :
A
Auto2
Auto3
Auto4
Certificate
Auto5
S
state
Auto
Bill
I need to arrange the title and code in alphabetical order.If the info has group the tile should come under the group. I am using visual studio2010 , xslt1.0 Processor and xml editor.

Sorting in XSLT is straight-forward. What you are actually really needing to know is how to 'group' items. As Michael Kay commented, this is much easier in XSLT 2.0 than in XSLT 1.0. In XSLT 1.0 you tend to use the Muenchian Grouping method, which appears confusing when you first see it, but is generally the most efficient way of doing it.
From the looks of your output, you are doing two lots of grouping. Firstly, by the first letter, then by either group/code (if it exists), or title.
Muenchian Grouping works by defining a key, to enable quick look up of all items in a 'group'. For the first letter of eithe group/code or title, you would define it like so
<xsl:key name="letter" match="info" use="substring(concat(group/code, title), 1, 1)"/>
(Note: This is case sensitive, so you may need to use the 'translate' function if you can have a mix of lower and upper case start letters).
If group/code exists, it will use the first letter of that, otherwise it will pick up the first letter of the title.
For the group/code or title itself, the key would be as follows
<xsl:key name="info" match="info" use="title[not(../group)]|group/code"/>
So, it only uses "title" elements where there is no "group" element present.
To get the distinct first letters for your first grouping, you select all the info elements and check whether they are the first element in the key for their given letter. This is done like so
<xsl:apply-templates
select="info
[generate-id()
= generate-id(key('letter', substring(concat(group/code, title), 1, 1))[1])]"
mode="letter">
<xsl:sort select="substring(concat(group/code, title), 1, 1)" />
</xsl:apply-templates>
The 'mode' is used here because the final XSLT will have to templates matching info.
Within the matching template, to group by either code/group or title you can then do this
<xsl:apply-templates
select="key('letter', $letter)
[generate-id() = generate-id(key('info', title[not(../group)]|group/code)[1])]">
<xsl:sort select="title[not(../group)]|group/code" />
</xsl:apply-templates>
And finally, to output all the elements within the final group, you would just use the key again
<xsl:apply-templates select="key('info', $value)[group/code=$value]/title">
Here is the full XSLT.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes"/>
<xsl:key name="letter" match="info" use="substring(concat(group/code, title), 1, 1)"/>
<xsl:key name="info" match="info" use="title[not(../group)]|group/code"/>
<xsl:template match="/*">
<xsl:apply-templates select="info[generate-id() = generate-id(key('letter', substring(concat(group/code, title), 1, 1))[1])]" mode="letter">
<xsl:sort select="substring(concat(group/code, title), 1, 1)" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="info" mode="letter">
<xsl:variable name="letter" select="substring(concat(group/code, title), 1, 1)" />
<xsl:value-of select="concat($letter, '
')" />
<xsl:apply-templates select="key('letter', $letter)[generate-id() = generate-id(key('info', title[not(../group)]|group/code)[1])]">
<xsl:sort select="title[not(../group)]|group/code" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="info">
<xsl:variable name="value" select="title[not(../group)]|group/code" />
<xsl:value-of select="concat($value, '
')" />
<xsl:apply-templates select="key('info', $value)[group/code=$value]/title">
<xsl:sort select="." />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="title">
<xsl:value-of select="concat(' ', ., '
')" />
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
A
Auto2
Auto3
Auto4
Certificate
Auto5
s
state
Auto
Bill

Related

XSLT 2.0 3.0 for-each context error when tokenizing attributes

Given this XML
<dmodule>
<content>
<warningsAndCautionsRef>
<warningRef id="w001" warningIdentNumber="warning-001">
</warningRef>
<warningRef id="w002" warningIdentNumber="warning-002">
</warningRef>
<cautionRef id="c001" cautionIdentNumber="caution-001">
</cautionRef>
<cautionRef id="c002" cautionIdentNumber="caution-002">
</cautionRef>
</warningsAndCautionsRef>
<faultReporting>
<preliminaryRqmts>
<reqSafety>
<safetyRqmts cautionRefs="c001 c002" warningRefs="w001 w002"/>
</reqSafety>
</preliminaryRqmts>
</faultReporting>
</content>
</dmodule>
I would like to tokenize the attributes #cautionRefs (and #warningRefs) and then find the cautionRef element that matches its #id to the tokenized value:
<xsl:template match="#cautionRefs">
<xsl:for-each select="tokenize(.,'\s')">
<xsl:apply-templates select="//*[#id=.]"/>
</xsl:for-each>
</xsl:template>
but the apply-templates fails: Fatal error during transformation Leading '/' selects nothing: the context item is not a node. It works if I don't tokenize and use string functions instead but that is not desirable.
Desired result:
Tokenize #cautionRefs="c001 c002" (which has multiple parent elements)
So each value is passed to the <cautionRef>template that will retrieve the caution and warning statements, to be displayed in a PDF:
<xsl:apply-templates select="//*[#id='c001']"/>
<xsl:apply-templates select="//*[#id='c002']"/>
I tried using <xsl:key name="id" match="*" use="#id"/> with
<xsl:for-each select="key('id',tokenize(.,'\s'))">
but the for-each is blank.
The above apply-templates will match with this <cautionRef> template, which retrieves the caution and warning statements correctly. I just need help with the context of the #cautionRefs template:
<xsl:template match="cautionRef">
<xsl:variable name="IdentNumber" select="#cautionIdentNumber"/>
<xsl:apply-templates select="//cautionSpec[cautionIdent/#cautionIdentNumber=$IdentNumber]"/>
</xsl:template>
You could use a variable and use that for context:
<xsl:template match="#cautionRefs|#warningRefs">
<xsl:variable name="ctx" select="/"/>
<xsl:for-each select="tokenize(.,'\s')">
<xsl:apply-templates select="$ctx//*[#id=.]"/>
</xsl:for-each>
</xsl:template>
but I would use a key like you hinted at (updated to include context based on comments)...
<xsl:key name="by_id" match="*[#id]" use="#id"/>
<xsl:variable name="root" select="/"/>
<xsl:template match="#cautionRefs|#warningRefs">
<xsl:for-each select="tokenize(.,'\s')">
<xsl:apply-templates select="key('by_id',.,$root)"/>
</xsl:for-each>
</xsl:template>
Here's a full working example. NB it's best to have this level of detail in the actual question; i.e. a sample input file, the XSLT, and output, along with an example of what you want the output to look like.
Input:
<test>
<safetyRqmts cautionRefs="c001 c002" warningRefs="w001"/>
<cautionRef id="c001" cautionIdentNumber="caution-001"/>
<cautionRef id="c002" cautionIdentNumber="caution-001"/>
</test>
Stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:key name="by_id" match="*[#id]" use="#id"/>
<xsl:template match="#cautionRefs|#warningRefs">
<xsl:apply-templates select="key('by_id', tokenize(.))"/>
</xsl:template>
</xsl:stylesheet>
Output:
<test>
<safetyRqmts><cautionRef id="c001" cautionIdentNumber="caution-001"/><cautionRef id="c002" cautionIdentNumber="caution-001"/></safetyRqmts>
<cautionRef id="c001" cautionIdentNumber="caution-001"/>
<cautionRef id="c002" cautionIdentNumber="caution-001"/>
</test>

XSLT: Copying node data to another node by matching attribute value, Efficiently?

I coded the XSLT to copy one node data to another by validating the attribute value, I got the desired output but I'm curious to know whether there is an efficient way to do this or if this is the only way to do it. [I'm not an XSLT expert] Can someone help !!!
Please use this link to check instantly.
https://xsltfiddle.liberty-development.net/pNvtBH2/3
Actual XML:
<?xml version="1.0" encoding="utf-8" ?>
<section>
<p>note 1 : 1</p>
<p>note 2 : 2</p>
<p>note 3 : 3</p>
<note id="test1">hello one</note>
<note id="test2">hello two</note>
<note id="test3">hello <i>three</i></note>
<note id="test4">hello <i>four</i></note>
</section>
Output:
<?xml version="1.0" encoding="UTF-8"?><section>
<p>note 1 : <a>hello one</a></p>
<p>note 2 : <a>hello two</a></p>
<p>note 3 : <a>hello <i>three</i></a></p>
<note id="test4">hello <i>four</i></note>
</section>
XSLT Code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="xml" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a">
<a>
<xsl:variable name="href" select="#href" />
<xsl:choose>
<xsl:when test="$href = //note/#id">
<xsl:copy-of select="//note[#id=$href]/node()" />
</xsl:when>
</xsl:choose>
</a>
</xsl:template>
<xsl:template match="note">
<xsl:choose>
<xsl:when test="#id = //a/#href">
<xsl:apply-templates select="node" />
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Whether it's efficient or not depends on how smart the optimizer in your XSLT processor is. Saxon-EE will do a pretty good job on this, Saxon-HE less so.
If you want to make it efficient on all processors, use keys. Replace an expression like //note[#id=$href] with a call on the key() function. Declare the key as
<xsl:key name="k" match="note" use="id"/>
and then you can get the matching nodes using key('k', $href).
The xsl:when test="$href = //note/#id" is redundant, as xsl:copy-of will do nothing if nothing is selected.
In the note template, I'm not sure what
<xsl:when test="#id = //a/#href">
<xsl:apply-templates select="node" />
</xsl:when>
is trying to achieve, because you don't have any elements named "node". If the aim is to avoid processing a note element at this stage if it was already processed from an a element, then you could build another index with
<xsl:key name="a" match="a" use="1"/>
and replace test="#id = //a/#href" with test="key('a', #href)"

How to fold by a tag a group of selected (neighbor) tags with XSLT1?

I have a set of sequential nodes that must be enclosed into a new element. Example:
<root>
<c>cccc</c>
<a gr="g1">aaaa</a> <b gr="g1">1111</b>
<a gr="g2">bbbb</a> <b gr="g2">2222</b>
</root>
that must be enclosed by fold tags, resulting (after XSLT) in:
<root>
<c>cccc</c>
<fold><a gr="g1">aaaa</a> <b gr="g1">1111</b></fold>
<fold><a gr="g2">bbbb</a> <b gr="g2">2222</b></fold>
</root>
So, I have a "label for grouping" (#gr) but not imagine how to produce correct fold tags.
I am trying to use the clues of this question, or this other one... But I have a "label for grouping", so I understand that my solution not needs the use of key() function.
My non-general solution is:
<xsl:template match="/">
<root>
<xsl:copy-of select="root/c"/>
<fold><xsl:for-each select="//*[#gr='g1']">
<xsl:copy-of select="."/>
</xsl:for-each></fold>
<fold><xsl:for-each select="//*[#gr='g2']">
<xsl:copy-of select="."/>
</xsl:for-each></fold>
</root>
</xsl:template>
I need a general solution (!), looping by all #gr and coping (identity) all context that not have #gr... perhaps using identity transform.
Another (future) problem is to do this recursively, with fold of foldings.
In XSLT 1.0 the standard technique to handle this sort of thing is called Muenchian grouping, and involves the use of a key that defines how the nodes should be grouped and a trick using generate-id to extract just the first node in each group as a proxy for the group as a whole.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*" />
<xsl:output indent="yes" />
<xsl:key name="elementsByGr" match="*[#gr]" use="#gr" />
<xsl:template match="#*|node()" name="identity">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<!-- match the first element with each #gr value -->
<xsl:template match="*[#gr][generate-id() =
generate-id(key('elementsByGr', #gr)[1])]" priority="2">
<fold>
<xsl:for-each select="key('elementsByGr', #gr)">
<xsl:call-template name="identity" />
</xsl:for-each>
</fold>
</xsl:template>
<!-- ignore subsequent ones in template matching, they're handled within
the first element template -->
<xsl:template match="*[#gr]" priority="1" />
</xsl:stylesheet>
This achieves the grouping you're after, but just like your non-general solution it doesn't preserve the indentation and the whitespace text nodes between the a and b elements, i.e. it will give you
<root>
<c>cccc</c>
<fold>
<a gr="g1">aaaa</a>
<b gr="g1">1111</b>
</fold>
<fold>
<a gr="g2">bbbb</a>
<b gr="g2">2222</b>
</fold>
</root>
Note that if you were able to use XSLT 2.0 then the whole thing becomes one for-each-group:
<xsl:template match="root">
<xsl:for-each-group select="*" group-adjacent="#gr">
<xsl:choose>
<!-- wrap each group in a fold -->
<xsl:when test="#gr">
<fold><xsl:copy-of select="current-group()" /></fold>
</xsl:when>
<!-- or just copy as-is for elements that don't have a #gr -->
<xsl:otherwise>
<xsl:copy-of select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>

XSLT - how to apply a template to every node of the type?

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>

XSLT matching PAGEID to an element ID

How would I match two separate numbers in an XML document? There are multiple <PgIndexElementInfo> elements in my XML document, each representing a different navigation element, each with a unique <ID>. Later in the document a <PageID> specifies a number that sometimes matches an <ID> used above. How could I go about matching the <PageID> to the <ID> specified above?
<Element>
<Content>
<PgIndexElementInfo>
<ElementData>
<Items>
<PgIndexElementItem>
<ID>1455917</ID>
</PgIndexElementItem>
</Items>
</ElementData>
</PgIndexElementInfo>
</Content>
</Element>
<Element>
<Content>
<CustomElementInfo>
<PageID>1455917</PageID>
</CustomElementInfo>
</Content>
</Element>
EDIT:
I added the solution below to my code. The xsl:apply-templates that is present is used to recreate the nested lists that are lost between HTML and XML. What I now need to do is match the PageID to the ID of a <PgIndexElementItem> and add a CSS class to the <ul> it is a part of. I hope that makes sense.
<xsl:key name="kIDByValue" match="ID" use="."/>
<xsl:template match="PageID[key('kIDByValue',.)]">
<xsl:apply-templates select="//PgIndexElementItem[not(contains(Description, '.'))]" />
</xsl:template>
<xsl:template match="PgIndexElementItem">
<li>
<xsl:value-of select="Title"/>
<xsl:variable name="prefix" select="concat(Description, '.')"/>
<xsl:variable name="childOptions"
select="../PgIndexElementItem[starts-with(Description, $prefix)
and not(contains(substring-after(Description, $prefix), '.'))]"/>
<xsl:if test="$childOptions">
<ul>
<xsl:apply-templates select="$childOptions" />
</ul>
</xsl:if>
</li>
</xsl:template>
The XSLT way for dealing with cross references is with keys.
Matching: A rule matching every PageID element that it has been referenced by an ID element.
<xsl:key name="kIDByValue" match="ID" use="."/>
<xsl:template match="PageID[key('kIDByValue',.)]">
<!-- Template content -->
</xsl:template>
Selecting: A expression selecting every PageID element with specific value.
<xsl:key name="kPageIDByValue" match="PageID" use="."/>
<xsl:template match="ID">
<xsl:apply-templates select="key('kPageIDByValue',.)"/>
</xsl:template>