XSLT to convert XML to text - xslt

I have a requirement where i need to generate a text file from XML document, generated text file should be in some particular format based on some rules. My XML looks something shown below:
<info>
<Tag1>
<Tag2>
<Tag3>
<PartNo>12 </PartNo>
</Tag3>
<DBOMInf1> 111 </DBOMInf1>
<DBOMInf2> sring </DBOMInf2>
</Tag2>
<Tag2>
<Tag3>
<PartNo>12 </PartNo>
</Tag3>
<DBOMInf1> 555 </DBOMInf1>
<DBOMInf2> abcd </DBOMInf2>
</Tag2>
</Tag1>
<Tag4>
<Tag5>
<Description>1200 liter </Description>
<No>12</No>
<Name>Engine</Name>
<Id>700</Id>
</Tag5>
</Tag4>
<action>
<actionId>700</actionId>
</action>
</info>
Expected output format in text:
ACTIONID|NO|DESCRIPTION|NAME|DBOMInf1|DBOMInf2
700|12|Engine|1200 liter| 111|sring
700|12|Engine|1200 liter| 555|abcd
I am new to XSLT programming can any body share some info or example on how can i achieve this , i am familiar with basis of XSLT like templates matching, value of select.
Any link, or example will be very helpful.
Thanks

It looks like you want a row for each Tag2 element, in which case these are easily matching by doing the following (assuming you are currently positioned on the info element
<xsl:apply-templates select="Tag1/Tag2" />
But it also looks like you want to look up information from Tag5 elements. In this case you could use a key to look up such values, based on the No element. The key would be defined as follows:
<xsl:key name="Tags" match="Tag5" use="No" />
And to look up the tags for a given Tag2 element, you could do the following:
<xsl:apply-templates select="key('Tags', normalize-space(Tag3/PartNo))" />
(Note, normalize-space with remove the excess white-space from the element)
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="Tags" match="Tag5" use="No" />
<xsl:template match="/info">
<xsl:text>ACTIONID|NO|DESCRIPTION|NAME|DBOMInf1|DBOMInf2</xsl:text>
<xsl:value-of select="'
'" />
<xsl:apply-templates select="Tag1/Tag2" />
</xsl:template>
<xsl:template match="Tag2">
<xsl:apply-templates select="key('Tags', normalize-space(Tag3/PartNo))" />
<xsl:value-of select="concat(DBOMInf1, '|', DBOMInf2, '
')" />
</xsl:template>
<xsl:template match="Tag5">
<xsl:value-of select="concat(Id, '|', No, '|', Name, '|', Description, '|')" />
</xsl:template>
</xsl:stylesheet>
When applied to your input XML, the following text is output
ACTIONID|NO|DESCRIPTION|NAME|DBOMInf1|DBOMInf2
700|12|Engine|1200 liter | 111 | sring
700|12|Engine|1200 liter | 555 | abcd

Related

Sort data in the xml alphabetical order

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

replace substrings using lookup table with xslt

I have a few strings containing a variant of Hexadecimal strings (source is framemaker if one would care). Strings could therefore look like
this is some sentence with some hex code\x27 s , and we need that
fixed.
and will need to be changed to
this is some sentence with some hex code's , and we need that fixed.
In reality there can be a few of these in a single string, so I'm looking on the best way to walk through the text, capture all hex codes (looking like \x## ) and replace all of these codes with the correct character. I have made a xml list / lookup table containing all the characters as follows :
<xsl:param name="reflist">
<Code Value="\x27">'</Code>
<Code Value="\x28">(</Code>
<Code Value="\x29">)</Code>
<Code Value="\x2a">*</Code>
<Code Value="\x2b">+</Code>
<!-- much more like these... -->
</xsl:param>
For now I used a simple replace argument but there are simply too many characters to make this workable.
What's the best way to do this?
One can completely avoid using any "reference table" -- like this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="my xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()[matches(., '\\x(\d|[a-f])+')]">
<xsl:analyze-string select="." regex="\\x(\d|[a-f])+" >
<xsl:matching-substring>
<xsl:value-of select=
"codepoints-to-string(my:hex2dec(substring(.,3), 0))"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:function name="my:hex2dec" as="xs:integer">
<xsl:param name="pStr" as="xs:string"/>
<xsl:param name="pAccum" as="xs:integer"/>
<xsl:sequence select=
"if(not($pStr))
then $pAccum
else
for $char in substring($pStr, 1, 1),
$code in
if($char ge '0' and $char le '9')
then xs:integer($char)
else
string-to-codepoints($char) - string-to-codepoints('a') +10
return
my:hex2dec(substring($pStr,2), 16*$pAccum + $code)
"/>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<p>this is some sentence with some hex code\x27 s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code\x28 s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code\x29 s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code\x2a s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code\x2b s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code\x2c s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code\x2d s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code\x2e s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code\x2f s ,
and we need that fixed.</p>
</t>
the wanted, correct result is produced:
<t>
<p>this is some sentence with some hex code' s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code( s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code) s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code* s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code+ s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code, s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code- s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code. s ,
and we need that fixed.</p>
<p>this is some sentence with some hex code/ s ,
and we need that fixed.</p>
</t>
Do note:
This transformation is generic and can correctly process any hexadecimal unicode code.
For example, if the same transformation is applied on this XML document:
<t>
<p>this is some sentence with some hex code\x0428\x0438\x0448 s ,
and we need that fixed.</p>
</t>
the correct result (containing the Bulgarian word for "grill" in Cyrillic) is produced:
<t>
<p>this is some sentence with some hex codeШиш s ,
and we need that fixed.</p>
</t>
Use analyze-string as in
<xsl:template match="text()">
<xsl:analyze-string select="." regex="\\x[0-9a-f]{{2}}" flags="i">
<xsl:matching-substring>
<xsl:value-of select="$reflist/Code[#Value = .]"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
I would also suggest to use a key e.g.
<xsl:param name="reflist" as="document-node()">
<xsl:document>
<Root>
<Code Value="\x27">'</Code>
<Code Value="\x28">(</Code>
<Code Value="\x29">)</Code>
<Code Value="\x2a">*</Code>
<Code Value="\x2b">+</Code>
<!-- much more like these... -->
</Root>
</xsl:document>
</xsl:param>
<xsl:key name="code-by-value" match="Code" use="#Value"/>
then the lookup can be improved to
<xsl:template match="text/text()">
<xsl:analyze-string select="." regex="\\x[0-9a-f]{{2}}" flags="i">
<xsl:matching-substring>
<xsl:value-of select="key('code-by-value', ., $reflist)"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
I have found some time to morph the suggestions made into working code, with the input being
<root>
<text>this is some sentence with some hex code\x27 s , and we need that \x28and this\x29 fixed.</text>
</root>
and the complete stylesheet being
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:param name="reflist" as="document-node()">
<xsl:document>
<Root>
<Code Value="\x27">'</Code>
<Code Value="\x28">(</Code>
<Code Value="\x29">)</Code>
<Code Value="\x2a">*</Code>
<Code Value="\x2b">+</Code>
<!-- much more like these... -->
</Root>
</xsl:document>
</xsl:param>
<xsl:key name="code-by-value" match="Code" use="#Value"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text/text()">
<xsl:analyze-string select="." regex="\\x[0-9a-f]{{2}}" flags="i">
<xsl:matching-substring>
<xsl:value-of select="key('code-by-value', ., $reflist)"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
Saxon 9.4 transforms the input as follows:
<root>
<text>this is some sentence with some hex code' s , and we need that (and this) fixed.</text>
</root>

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>

Storing into a variable and displaying the unique entries using XSL

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>

How to use group by in xslt

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