How this value use comma in xsl - xslt

xml file like below I want concat value of name, add1, city, add2 with separated by comma
<Details>
<name>abc</name>
<profile>
<address>
<add1>ccc</add1>
<add2>bbb</add2>
<city>CA</city>
</address>
</profile>
</Details>
I want Output like below:-
abc, ccc, CA, bbb
(I mean city will come first before add2 and if any value is blank then it will adjust accordingly)

If you are looking to output all the text nodes within the Details element, you simply iterate over them all using xsl:for-each and use the position() function to output a comma if the node is not the first one
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Details">
<xsl:for-each select="//text()">
<xsl:if test="position() > 1">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:value-of select="." />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
So, if one of your elements has no text in it, it will not get output or have an extra comma.

<xsl:variable name="name">
<xsl:value-of select="Details/name"/>
</xsl:variable>
<xsl:variable name="add1">
<xsl:value-of select="Details/profile/address/add1"/>
</xsl:variable>
<xsl:variable name="add2">
<xsl:value-of select="Details/profile/address/add2"/>
</xsl:variable>
<xsl:variable name="city">
<xsl:value-of select="Details/profile/address/city"/>
</xsl:variable>
<xsl:value-of select="concat($name,',',$add1,',',$city,',',$add2)"/><br>
It will display the O/P like this abc, ccc, CA, bbb if add1 returns null then it will display like this abc, , CA, bbb

If you're using XSLT 2.0 you can use the () operator to construct a sequence in the order you want and then use the separator attribute on xsl:value-of to output the whole sequence with commas:
<xsl:template match="Details">
<xsl:value-of select="(name, profile/address/add1, profile/address/city,
profile/address/add2)" separator=", " />
</xsl:template>
If you want to filter out elements with an empty value (e.g. if the document contains <city/>) then you can do that with a predicate on the select expression:
(name, profile/address/add1, profile/address/city,
profile/address/add2)[normalize-space()]
The predicate removes from the sequence any nodes whose value is empty or consists entirely of whitespace.

Related

XSLT discrepancy with how variable is used

My question is about xsl:variable and the syntax for a predicate in an Xpath. I've boiled down my question to the point where this short XML can help me demonstrate:
<root>
<tabular>
<col halign="left"/>
<col halign="right"/>
<row>
<cell>Some content</cell>
<cell>Some content</cell>
</row>
</tabular>
</root>
In my application, when I am applying a template on a cell, I need to access the #halign of the corresponding col. In doing so, I have encountered a discrepancy between Xpath expressions that I thought should be equivalent. I would like to understand why this happens. To demonstrate, I apply the XSL at the end of this post using XSLT 1.0.
The cell template in my XSLT here is silly but it lays out the discrepancy I don't understand. Basically it repeatedly tries to print the #halign value corresponding to the second cell. First, using the $col variable that has value 2. Then using [position()=$col]. Then using [number($col)]. Then simply using [2], hard coded. Lastly, using a separate $colsel variable that was defined using a #select attribute.
I expect to see:
ancestor::tabular/col[...]/#halign
[2] makes right
[position()=2] makes right
[number(2)] makes right
(hard 2) [2] makes right
(var #select) [2] makes right
but instead I see:
ancestor::tabular/col[...]/#halign
[2] makes left
[position()=2] makes right
[number(2)] makes right
(hard 2) [2] makes right
(var #select) [2] makes right
Is anyone able to offer an explanation for why using [$col] behaves differently?
Here is the XSL:
<xsl:template match="/">
<xsl:apply-templates select="root/tabular"/>
</xsl:template>
<xsl:template match="tabular">
<xsl:apply-templates select="row"/>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="cell"/>
</xsl:template>
<?xml version='1.0'?> <!-- As XML file -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="root/tabular"/>
</xsl:template>
<xsl:template match="tabular">
<xsl:apply-templates select="row"/>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="cell[2]"/>
</xsl:template>
<xsl:template match="cell[2]">
<xsl:variable name="col">
<xsl:value-of select="2"/>
</xsl:variable>
<xsl:variable name="colsel" select="2"/>
<xsl:text>ancestor::tabular/col[...]/#halign</xsl:text>
<xsl:text>
</xsl:text>
<xsl:text> [</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[$col]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text> [position()=</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[position()=$col]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text> [number(</xsl:text>
<xsl:value-of select="$col"/>
<xsl:text>)] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[number($col)]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>(hard 2) [2] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[2]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>(var #select) [</xsl:text>
<xsl:value-of select="$colsel"/>
<xsl:text>] makes </xsl:text>
<xsl:value-of select="ancestor::tabular/col[$colsel]/#halign"/>
<xsl:text>
</xsl:text>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Let us use a more convenient example:
XML
<root>
<item>first</item>
<item>second</item>
</root>
XSLT 1.0
<xsl:stylesheet version="2.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="num" select="2"/>
<xsl:variable name="str" select="string(2)"/>
<xsl:variable name="rtf">2</xsl:variable>
<xsl:template match="/root">
<results>
<num>
<xsl:copy-of select="item[$num]"/>
</num>
<str>
<xsl:copy-of select="item[$str]"/>
</str>
<rtf>
<xsl:copy-of select="item[$rtf]"/>
</rtf>
</results>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<results>
<num>
<item>second</item>
</num>
<str>
<item>first</item>
<item>second</item>
</str>
<rtf>
<item>first</item>
<item>second</item>
</rtf>
</results>
Now you ask why the difference in the results. The answer can be found in the XPath specification that prescribes how a predicate is to be evaluated:
A PredicateExpr is evaluated by evaluating the Expr and converting the
result to a boolean. If the result is a number, the result will be
converted to true if the number is equal to the context position and
will be converted to false otherwise; if the result is not a number,
then the result will be converted as if by a call to the boolean
function.
In the first instance the value of the $num variable is the number 2. Therefore the result of evaluating the expression within the predicate is a number, and the predicate will be true when the number is equal to the context position - which is only true for the item in the second position.
In the second instance, the value of the $str variable is the string "2". Therefore the expression within the predicate does not evaluate to a number and will be converted to boolean by doing:
boolean("2")
which returns true() for all items, regardless of their position.
In the third instance, the value of the $rtf variable is a result tree fragment that contains a text node that consists of the character "2". When placed in a predicate, the outcome will be similar to the previous instance: the result of evaluating the expression is not a number, and converting it to a boolean will produce a value of true(). Note that your:
<xsl:variable name="col">
<xsl:value-of select="2"/>
</xsl:variable>
does exactly the same thing.
Note also that in XSLT 1.0 the xsl:value-of instruction returns the value of the first node in the selected node-set. Therefore, if we change our template to:
<xsl:template match="/root">
<results>
<num>
<xsl:value-of select="item[$num]"/>
</num>
<str>
<xsl:value-of select="item[$str]"/>
</str>
<rtf>
<xsl:value-of select="item[$rtf]"/>
</rtf>
</results>
</xsl:template>
the result will be:
<results>
<num>second</num>
<str>first</str>
<rtf>first</rtf>
</results>
but still both items are selected by item[$str] and by item[$rtf].
Change the variable declaration to:
<xsl:variable name="col" select="2"/>
and it will behave as you expect and select the second col.
You had declared the variable using xsl:value-of: <xsl:value-of select="2"/>, which creates a computed text() node.
When you use that $col variable by itself in a predicate, that string value "2" it is evaluated as true() in the predicate test, rather than if it were a number() and would then be interpreted as short-hand for position() = 2.

XSLT - replace specific content of the text() node with a new node

I have a xml like this,
<doc>
<p>Biological<sub>89</sub> bases<sub>4456</sub> for<sub>8910</sub> sexual<sub>4456</sub>
differences<sub>8910</sub> in<sub>4456</sub> the brain exist in a wide range of
vertebrate species, including chickens<sub>8910</sub> Recently<sub>8910</sub> the
dogma<sub>8910</sub> of<sub>4456</sub> hormonal dependence for the sexual
differentiation of the brain has been challenged.</p>
</doc>
As you can see there are <sub> nodes and text() node contains inside the <p> node. and every <sub> node end, there is a text node, starting with a space. (eg: <sub>89</sub> bases : here before 'bases' text appear there is a space exists.) I need to replace those specific spaces with nodes.
SO the expected output should look like this,
<doc>
<p>Biological<sub>89</sub><s/>bases<sub>4456</sub><s/>for<sub>8910</sub><s/>sexual<sub>4456</sub>
<s/>differences<sub>8910</sub><s/>in<sub>4456</sub><s/>the brain exist in a wide range of
vertebrate species, including chickens<sub>8910</sub><s/>Recently<sub>8910</sub><s/>the
dogma<sub>8910</sub><s/>of<sub>4456</sub><s/>hormonal dependence for the sexual
differentiation of the brain has been challenged.</p>
</doc>
to do this I can use regular expression like this,
<xsl:template match="p/text()">
<xsl:analyze-string select="." regex="( )">
<xsl:matching-substring>
<xsl:choose>
<xsl:when test="regex-group(1)">
<s/>
</xsl:when>
</xsl:choose>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
But this adds <s/> nodes to every spaces in the text() node. But I only need thi add nodes to that specific spaces.
Can anyone suggest me a method how can I do this..
If you only want to match text nodes that start with a space and are preceded by a sub element, you can put the condition in your template match
<xsl:template match="p/text()[substring(., 1, 1) = ' '][preceding-sibling::node()[1][self::sub]]">
And if you just want to remove the space at the start of the string, a simple replace will do.
<xsl:value-of select="replace(., '^\s+', '')" />
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="no" />
<xsl:template match="p/text()[substring(., 1, 1) = ' '][preceding-sibling::node()[1][self::sub]]">
<s />
<xsl:value-of select="replace(., '^\s+', '')" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Just change the regex like so ^( ): it will match only the spaces at the beginning of the text part.
With this XSL snipped:
<xsl:analyze-string select="." regex="^( )">
Here is the result I obtain:
<p>Biological<sub>89</sub><s></s>bases<sub>4456</sub><s></s>for<sub>8910</sub><s></s>sexual<sub>4456</sub>
differences<sub>8910</sub><s></s>in<sub>4456</sub><s></s>the brain exist in a wide range of
vertebrate species, including chickens<sub>8910</sub><s></s>Recently<sub>8910</sub><s></s>the
dogma<sub>8910</sub><s></s>of<sub>4456</sub><s></s>hormonal dependence for the sexual
differentiation of the brain has been challenged.
</p>

XSLT select all text except one child node text

I have xml like this:
<article>
<title> Test title - <literal> Compulsory - </literal> <fn> ABC </fn>
<comments> a comment</comments>
</title>
</article>
I want to get all child node + self text in a variable
e.g.
$full_title = "Test title - Compulsory - ABC"
Except comments node text.
Following is my unsuccessful try where i miss title node text.
<xsl:template name="test">
<xsl:variable name="full_title" select="article/title/*[not(self::comments)][1]" />
<xsl:variable name="width" select="45" />
<xsl:choose>
<xsl:when test="string-length($full_title) > $width">
<xsl:value-of select="concat(substring($full_title,1,$width),'..')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$full_title"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Change * to node(). That will select both elements and text nodes that are children of the <title> element. Then take out the [1] since you want all children of <title>:
<xsl:variable name="full_title"
select="string-join(article/title/node()[not(self::comments)], '')" />
A more reliable way to do it, so that you won't get tripped up if you have multiple levels under <title> and <comments> elements occur as grandchildren, would be this:
<xsl:variable name="full_title"
select="string-join(article/title//text()[not(ancestor::comments)], '')" />
Update:
Since you want the variable to hold a string value, and since you're passing it to functions like concat() and string-length() which cannot take a sequence of multiple nodes as a first argument, using string-join(..., '') around the sequence converts it to a string by concatenating the string values of each node.
Try this:
<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:template match="/">
<xsl:variable name="full-text">
<xsl:apply-templates select="//*[not(self::comments)]"
mode="no-comments"/>
</xsl:variable>
<xsl:value-of select="$full-text"/><!-- just for debug-->
</xsl:template >
<xsl:template match="*" mode="no-comments">
<xsl:value-of select="text()"/>
</xsl:template>
</xsl:stylesheet>
attribute mode used only for clarity

XSLT Transformation (help)

I newby to XSLT and having some trouble to solve this problem.
The input is coming from an XML Excel document and has this format :
<Row>
<Cell><Data ss:Type="String">ToE.3</Data></Cell>
<Cell ss:Index="15"><Data ss:Type="String">Maintain</Data></Cell>
<Cell><Data ss:Type="Number">3</Data></Cell>
<Cell><Data ss:Type="String">Other</Data></Cell>
<Cell ss:Index="131"><Data ss:Type="String">Windows 2003</Data></Cell>
<Cell><Data >Microsoft SQL Server 2005</Data></Cell>
</Row>
..more rows (note the excel sheet has 132 columns)
I need to convert this to a standard text file, something like (with the right column) separator :
Col1 Col2 Col3 ..To.. Col15 Col16 ..To.. Col131
ToE.3 Maintain 3 Windows 2003
The problem is how to insert the empty row values that are skipt with the Index attribute.
The transformation without the empty, index handling looks like :
<xsl:for-each select="Row">
<xsl:for-each select="Cell/Data">
<xsl:value-of select="current()"/>
<xsl:text>\</xsl:text>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
Some help would be warmly appreciated
step1: you need to declare output format, ie, "text" and not "xml"..
step2: you need to get rid of additional whitespace. use Strip-space with element='*', that means 'all'!
step3: you need to write header row first ie, col1, col2 etc..
so using template match select an element row that is first in your XML.. assuming that all the rows have same number of columns, you need to write "COL+ NUMBER" .. column numbers = no of cells you have in first row.
step4: if the cell is last then insert 'enter character'..
step5: call the generic function
step6: explaining generic function:
this function copies data under each cells separated by \. Only for the first row, we would be calling it manually, otherwise template match will take care of it.
Here is the code:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template name="Header" match="Row[not(preceding-sibling::Row)]">
<xsl:for-each select="Cell">
<xsl:value-of select="'Col'"/>
<xsl:value-of select="position()"/>
<xsl:if test="position()!=last()">
<xsl:value-of select="'\'"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<xsl:call-template name="CopyData"/>
</xsl:template>
<xsl:template name="CopyData" match="Row">
<xsl:for-each select="Cell">
<xsl:for-each select="Data">
<xsl:apply-templates select="."/>
</xsl:for-each>
<xsl:if test="position()!=last()">
<xsl:value-of select="'\'"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
corresponding sample output:
Col1\Col2\Col3\Col4\Col5\Col6
ToE.3\Maintain\3\Other\Windows 2003\Microsoft SQL Server 2005
ToE.3\Maintain\3\Other\Windows 2003\Microsoft SQL Server 2005
This is tricky because as you are seeing Excel skips columns in which no data appears, then provides an ss:Index attribute for the subsequent non-blank column. You have to reconstruct the "missing" cell positions on your own. That is, if you wish to retain the original column position like "15" or "131" in your example, with intervening blanks.
Agreeing with InfantProgrammer above, but suggest you'd add some logic to the "CopyData" template above to (a) determine the number of missing cells, then (b) call a recursive named template to write 'em to output.
<xsl:template name="WriteBlanks">
<xsl:param name="Count" select="0"/>
<xsl:if test="Count > 0">
<xsl:value-of select="'\'"/>
<xsl:call-template name="WriteBlanks">
<xsl:with-param name="Count" select="$Count - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
You could do something similar to generate the first row of column headers.
Given the simplicity of your need to just write backslash characters as column separator, a more succinct approach of just creating a long string of them, then lopping off however many are needed with XPath substring() could be in reach. However a recursive template may be suitable for more complex outputs.

xslt substring question

I am new to XSLT.
I have a input XML file which needs to be shown as a different output XML. I am using the xslt for transformation.
Input XML:
<Row>
<Column>abc.xyz.ijm</Column>
<Row>
Output XML:
<abc>
<xyz>
<ijm>String</ijm>
</xyz>
</abc>
I tried using xsl:when along with substring-before and substring-after functions but the result xml is not close to what I want.
How to know the last occurence of '.' so that <ijm>String</ijm> is constructed followed by the end tags of the words that are found before each of the previous occurences of the '.' so that </xyz> and </abc> can be added as shown in the output xml above?
Any code snippet is not at all appreciated.
Thanks in advance.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Column/text()" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length()">
<xsl:choose>
<xsl:when test="not(contains($pText,'.'))">
<xsl:element name="{$pText}">String</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{substring-before($pText,'.')}">
<xsl:call-template name="tokenize">
<xsl:with-param name="pText"
select="substring-after($pText,'.')"/>
</xsl:call-template>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document (corrected to be well-formed):
<Row>
<Column>abc.xyz.ijm</Column>
</Row>
produces the wanted, correct result:
<abc>
<xyz>
<ijm>String</ijm>
</xyz>
</abc>
Explanation:
Recursively called named template with stop condition: the $pText parameter is either the empty string or a string that doesn't contain the period character.
Intermediate action: Create an element whose name is the substring-before the '.' character, then call yourself recursively with the text after the first period character as parameter.
Stop action: Create an element with name -- the whole string in the parameter, and value: the string "String".