Assign CSS class to element in an XSL - xslt

My XML file is as follows:
<worksheet>
<row>
<rowTitle>RT1</rowTitle>
<rowType>yesorno</rowType>
<subLine>subLine1Content</subLine>
<subLine>subLine2Content</subLine>
</row>
<row>
<rowTitle>RT2</rowTitle>
<rowType>operation</rowType>
<subLine>subLine1Content</subLine>
<subLine>subLine2Content</subLine>
<subLine>subLine3Content</subLine>
</row>
.
.
</worksheet>
in my xsl, while displaying contents of a particular row, i'd like to add a class to the html element that'll specify the type of the row it is. I tried using xsl:choose and assigning value to a xsl:variable, but that doesn't work.
I'm trying to display the row as
<ol>
<li class="rowTypeBoolean">
RT1
<ul><li>subLineContent1</li>
<li>subLineContent2</li></ul>
</li>
<li class="rowTypeOptions">
RT2
<ul><li>subLineContent1</li>
<li>subLineContent2</li>
<li>subLineContent3</li></ul>
</li>
.
.
</ol>
XSL file snippet
<xsl:template match="row">
<li class="rowClass ${className}">
<xsl:choose>
<xsl:when test="type = 'YESORNO'">
<xsl:variable name="className" select="rowTypeBoolean"/>
</xsl:when>
<xsl:when test="type = 'OPTIONS'">
<xsl:variable name="className" select="rowTypeOptions"/>
</xsl:when>
<xsl:when test="type = 'OPERATION'">
<xsl:variable name="className" select="rowTypeOperation"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="className" select="rowTypeOther"/>
</xsl:otherwise>
</xsl:choose>
<span class="rowTitleClass">
<xsl:value-of select="rowtitle"/>
</span>
<br/>
<ul class="subLineListClass">
<xsl:apply-templates select="subLine"/>
</ul>
</li>
</xsl:template>

You need to add it as an attribute to the element:
<li>
<xsl:choose>
<xsl:when test="type = 'YESORNO'">
<xsl:attribute name="className">rowTypeBoolean</xsl:attribute>
</xsl:when>
<xsl:when test="type = 'OPTIONS'">
<xsl:attribute name="className">rowTypeOptions</xsl:attribute>
</xsl:when>
<xsl:when test="type = 'OPERATION'">
<xsl:attribute name="className">rowTypeOperation"</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="className">rowTypeOther"</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</li>

The most naive solution would be to use xsl:choose instruction like this:
<li>
<xsl:attribute name="className">
<xsl:choose>
<xsl:when test="type = 'YESORNO'">rowTypeBoolean</xsl:when>
<xsl:when test="type = 'OPTIONS'">rowTypeOptions</xsl:when>
<xsl:when test="type = 'OPERATION'">rowTypeOperation</xsl:when>
<xsl:otherwise>rowTypeOther</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</li>
Other way would be to have an inline map (or via fn:document()) like:
<li class="{$map[#type = current()/type]|$map[not(#type)]}"/>
With this as top level element
<map:map xmlns:map="map">
<item type="YESORNO">rowTypeBoolean</item>
<item type="OPTIONS">rowTypeOption</item>
<item type="OPERATIONS">rowTypeOperation</item>
<item>rowTypeOther</item>
</map:map>
<xsl:variable name="map" select="document('')/*/map:map/*" xmlns:map="map"/>

It isn't necessary at all to use <xsl:choose> in the transformation:
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="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="worksheet">
<ol>
<xsl:apply-templates/>
</ol>
</xsl:template>
<xsl:template match="row[rowType='yesorno']">
<li class="rowTypeBoolean">
<xsl:apply-templates/>
</li>
</xsl:template>
<xsl:template match="row[rowType='operation']">
<li class="rowTypeOperation">
<xsl:apply-templates/>
</li>
</xsl:template>
<xsl:template match="row[rowType='options']">
<li class="rowTypeOptions">
<xsl:apply-templates/>
</li>
</xsl:template>
<xsl:template match="row">
<li class="rowTypeOther">
<xsl:apply-templates/>
</li>
</xsl:template>
<xsl:template match="subLine[1]">
<ul>
<xsl:apply-templates select="../subLine" mode="process"/>
</ul>
</xsl:template>
<xsl:template match="subLine" mode="process">
<li><xsl:apply-templates/></li>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<worksheet>
<row>
<rowTitle>RT1</rowTitle>
<rowType>yesorno</rowType>
<subLine>subLine1Content</subLine>
<subLine>subLine2Content</subLine>
</row>
<row>
<rowTitle>RT2</rowTitle>
<rowType>operation</rowType>
<subLine>subLine1Content</subLine>
<subLine>subLine2Content</subLine>
<subLine>subLine3Content</subLine>
</row>
</worksheet>
produces the wanted, correct result:
<ol>
<li class="rowTypeBoolean">
<rowTitle>RT1</rowTitle>
<rowType>yesorno</rowType>
<ul>
<li>subLine1Content</li>
<li>subLine2Content</li>
</ul>
<subLine>subLine2Content</subLine>
</li>
<li class="rowTypeOperation">
<rowTitle>RT2</rowTitle>
<rowType>operation</rowType>
<ul>
<li>subLine1Content</li>
<li>subLine2Content</li>
<li>subLine3Content</li>
</ul>
<subLine>subLine2Content</subLine>
<subLine>subLine3Content</subLine>
</li>
</ol>
Do note:
Only simple templates and <xsl:apply-templates> are used. Therefore, the chances of committing an error are minimized.
This code is inherently extensible and maintainable. If a new row type is introduced none of the existing templates will need to be altered -- just a new short and simple template will need to be added, that matches a row element having a rowType child with the new value.
The mapping between rowType and the corresponding value of the class attribute can be specified in a special global-level namespaced element and/or variable (as done in Alejandro's solution), or even in a separate XML document. Then we can have just one single template, matching row.

Related

convert xml to html list with xslt

I have a xslt code to convert an xml file to html list. The input and output samples are as follows:
Input:
<Beverages>
<Water/>
<Coffee/>
<Tea>
<BlackTea/>
<WhiteTea id="cti" value="ctv" >Camomile Tea</WhiteTea>
<GreenTea id="gti" value="gtv">
<Sencha/>
<Gyokuro/>
<Matcha/>
<PiLoChun/>
</GreenTea>
</Tea>
</Beverages>
and the Output:
<ul>
<li>
<span class="caret"><Beverages></span>
<ul class="nested">
<li><span><Water/></span></li>
<li><span><Coffee/></span></li>
<li>
<span class="caret"><Tea></span>
<ul class="nested">
<li><span><BlackTea/></span></li>
<li><span><WhiteTea id="cti" value="ctv"></span>Camomile Tea<span></WhiteTea></span></li>
<li>
<span><GreenTea id="gti" value="gtv"></span>
<ul class="nested">
<li><span><Sencha/></span></li>
<li><span><Gyokuro/></span></li>
<li><span><Matcha/></span></li>
<li><span><PiLoChun/></span></li>
</ul>
<span></GreenTea></span>
</li>
</ul>
</li>
</ul>
<span></Beverages></span>
</li>
</ul>
Here is my xslt but it is not exactly the same as what I want:
<xsl:template match="/">
<ul><xsl:apply-templates/></ul>
</xsl:template>
<xsl:template match="*">
<li> <span class="caret"><xsl:value-of select="concat('<',name())" />
<xsl:for-each select="#*">
<xsl:value-of select="concat(' ',name())"/>=<xsl:value-of select="concat('"',.,'"')" />
</xsl:for-each>></span>
<xsl:if test="text()">
<xsl:apply-templates select="text()" />
</xsl:if>
<xsl:if test="*">
<ul class="nested"><xsl:apply-templates/></ul>
</xsl:if>
<span><xsl:value-of select="concat('<','/' ,name() , '>')" /></span></li>
</xsl:template>
The problem is that this code can not differentiate between parent elements and non parent element. for parent elements we need to add class="caret" attribute, but for non parent elements we shouldn't add this class.
This should work for you.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<ul>
<xsl:apply-templates/>
</ul>
</xsl:template>
<xsl:template match="*">
<li>
<span>
<xsl:if test="*">
<xsl:attribute name="class">
<xsl:value-of select="'caret'"/>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="concat('<',name())"/>
<xsl:for-each select="#*">
<xsl:value-of select="concat(' ', name(), '=', '"', ., '"')"/>
</xsl:for-each>
<xsl:choose>
<xsl:when test="text() or *">
<xsl:value-of select="'>'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'\>'"/>
</xsl:otherwise>
</xsl:choose>
</span>
<xsl:if test="text()">
<xsl:value-of select="text()"/>
</xsl:if>
<xsl:if test="*">
<ul class="nested">
<xsl:apply-templates/>
</ul>
</xsl:if>
<xsl:if test="text() or *">
<span>
<xsl:value-of select="concat('<','/' ,name() , '>')"/>
</span>
</xsl:if>
</li>
</xsl:template>
</xsl:stylesheet>

xsl numbering with same elements next to

I need to format some xml data with the following structure
<list>
<item>
Test
</item>
<item>
Testt
</item>
<or-item>
TestOr
</or-item>
<or-item>
TestOrr
</or-item>
<item>
Testtt
</item>
<or-item>
TestOrrr
</or-item>
<item>
Testttt
</item>
</list>
with xsl:number the or-item must be formatted with the second level count on that position. I know it would be better to structure the or-item inside that item but the data is given like that.
I need a way to count the or-item next to the current or-item to calculate the numbering for xsl:number
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.1"
xmlns:axf="http://www.antennahouse.com/names/XSL/Extensions" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output encoding="UTF-8" method="html" indent="yes"/>
<xsl:template match="list">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number count="item"/>
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number value="count(//or-item)" format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
</xsl:stylesheet>
Edit
I am using XSLT 1.1 with xsltproc on linux but 2.0 whould be possible if neccessary
As the target format is HTML, it seems you could rely on creating the appropriate nested HTML ordered lists by using xsl:for-each-group and group-starting-with="item":
<xsl:template match="list">
<ol>
<xsl:for-each-group select="*" group-starting-with="item">
<li>
<xsl:value-of select="."/>
<xsl:where-populated>
<ol>
<xsl:apply-templates select="tail(current-group())"/>
</ol>
</xsl:where-populated>
</li>
</xsl:for-each-group>
</ol>
</xsl:template>
<xsl:template match="or-item">
<li>
<xsl:value-of select="."/>
</li>
</xsl:template>
https://xsltfiddle.liberty-development.net/ejivJrM
That example uses some XSLT/XPath 3 stuff like were-populated and tail but in case that XSLT 2 compatility is needed then it could be replaced by <xsl:if test="subsequence(current-group(), 2)"><ol><xsl:apply-templates select="subsequence(current-group(), 2)"/></xsl:if>.
And of course the use of HTML ordered lists is not necessary, if needed/wanted you could just transform the input to nested divs with the used grouping approach and then in a second step use format-number as you seem to want to do:
<xsl:template match="list">
<xsl:variable name="nested-list">
<xsl:for-each-group select="*" group-starting-with="item">
<xsl:copy>
<xsl:value-of select="."/>
<xsl:copy-of select="tail(current-group())"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:variable>
<div>
<xsl:apply-templates select="$nested-list"/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number/>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
https://xsltfiddle.liberty-development.net/ejivJrM/1
You can produce the expected output by simply adjusting the xsl:number instruction:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<xsl:template match="/list">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="item">
<div>
<xsl:number/>
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="or-item">
<div style="padding-left: 10px">
<xsl:number level="any" from="item" format="a) "/>
<xsl:value-of select="."/>
</div>
</xsl:template>
</xsl:stylesheet>

xsl picking text value of following siblings

How can I get only "Your friend" or only "Mickey Mouse" from these nodes?
<span>
<lb/>
Your Friend
<lb/>
<name> Mickey Mouse </name>
</span>
My desired output from XSL would be this:
<p> Your Friend </p>
<p> Mickey Mouse </p>
I have tried with:
<xsl:template match="/">
<xsl:for-each select="./lb">
<p>
<xsl:if test="./following-sibling::text()[1]">
<xsl:value-of select="./following-sibling::*[text()][1]"/>
</xsl:if>
<xsl:if test="./following-sibling::*[name]//text()">
-test-
</xsl:if>
</p>
</xsl:for-each>
</xsl:template>
but I know I'm completely wrong since I never get to -test-
I am mostly guessing here, but it seems you want:
<xsl:template match="span">
<xsl:for-each select="lb">
<p>
<xsl:value-of select="(following-sibling::text()|following-sibling::name)[1]"/>
</p>
</xsl:for-each>
</xsl:template>
use code:
<xsl:strip-space elements="*"/>
<xsl:template match="text()">
<p>
<xsl:value-of select="normalize-space(.)"/>
</p>
</xsl:template>

xsl check if a root node contains any child nodes

Is there a way to determine if a a root level node contains any child nodes?
I have this code file that builds up a navigation menu for a drop-down menu, but for the root nodes that have no nodes below them I want to apply a different template to them:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/Home">
<xsl:apply-templates select="root" />
</xsl:template>
<xsl:template match="root">
<ul class="nav navbar-nav">
<xsl:apply-templates select="node">
<xsl:with-param name="level" select="0"/>
</xsl:apply-templates>
</ul>
</xsl:template>
<xsl:template match="node">
<xsl:param name="level" />
<xsl:choose>
<xsl:when test="$level=0">
<li>
<xsl:attribute name="class">
<xsl:if test="#breadcrumb = 1">active</xsl:if>
<xsl:if test="node">
<xsl:text> dropdown</xsl:text>
</xsl:if>
</xsl:attribute>
<xsl:choose>
<xsl:when test="#enabled = 1">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<xsl:attribute name="class">
<xsl:if test="node">
<xsl:text>dropdown-toggle</xsl:text>
</xsl:if>
</xsl:attribute>
<xsl:if test="node">
<xsl:attribute name="data-toggle">dropdown</xsl:attribute>
</xsl:if>
<xsl:value-of select="#text" />
<xsl:if test="node">
<b class="caret"></b>
</xsl:if>
</a>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#text" />
</xsl:otherwise>
</xsl:choose>
<xsl:if test="node">
<ul class="dropdown-menu">
<xsl:apply-templates select="node">
<xsl:with-param name="level" select="$level + 1" />
</xsl:apply-templates>
</ul>
</xsl:if>
</li>
</xsl:when>
<xsl:otherwise>
<li>
<xsl:attribute name="class">
<xsl:if test="#breadcrumb = 1">active</xsl:if>
<xsl:if test="node">
<xsl:text> dropdown</xsl:text>
</xsl:if>
</xsl:attribute>
<xsl:choose>
<xsl:when test="#enabled = 1">
<a href="{#url}">
<xsl:value-of select="#text" />
</a>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#text" />
</xsl:otherwise>
</xsl:choose>
</li>
<xsl:if test="node">
<!-- no extra level in default bootstrap -->
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Your question is not clear. If the root node does not have any child nodes, then the XML document is empty. Perhaps you meant the root element; there will be exactly one element like that and it's easy to see if it has any child nodes by using:
test="/*/node()"
in an xsl:if or xsl:when instruction.
Alternatively, you could use two templates - one matching a root element with child nodes:
<xsl:template match="/*[node()]">
and one for the other case:
<xsl:template match="/*[not(node())]">
You can use this XSLT-file:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" />
<xsl:template match="/*[count(descendant::*) = 0]">
No siblings
</xsl:template>
<xsl:template match="/*[count(descendant::*) > 0]">
Has siblings
</xsl:template>
</xsl:stylesheet>
With an input XML file with one or more siblings of the root element like this
<?xml version="1.0"?>
<root>
<a />
</root>
it will output "Has siblings".
And with an input file with an empty root tag like this
<?xml version="1.0"?>
<root>
</root>
it will output "No siblings".
If I understand the question correctly, try adding the template rule
<xsl:template match="root[not(*)]"/>

Change text using XSLT

I am trying to change some text from XML. However it did not work.
I have to display all of [intro] text from XML
some Path(text) should change before display [intro].
For example
<a href="3DD3D025-2236-49C9-A169-DD89A36DA0E6/eee.pdf"> --> wrong path
I would like to change to
<a href="Content\3\D\D\3DD3D025-2236-49C9-A169-DD89A36DA0E6/eee.pdf">
Any help would be greatly appreciated.
Sample XML
<?xml version="1.0"?>
<root>
<intro xml:lang="en">
<div class="blueBar">
<h2>Highlights</h2>
<ul>
<li>aaaa</li>
<li>bbbb</li>
<li>ccc</li>
<li>pdf</li>
</ul>
</div>
</intro>
<intro> .....</intro>
</root>
Sample XSLT
<xsl:param name="language"/>
<xsl:template match="root">
<xsl:for-each select="intro[lang($language)]//#href">
<xsl:choose>
<xsl:when test="contains(.,'pdf') and not(contains(.,'Content'))">
<xsl:variable name="pdfGuid">
<xsl:value-of select="substring(.,0,36)"/>
</xsl:variable>
<xsl:variable name="pdfPath">
<xsl:value-of select="concat('/','Content')"/>
<xsl:value-of select="concat('/', substring($pdfGuid, 1,1))"/>
<xsl:value-of select="concat('/', substring($pdfGuid, 2,1))"/>
<xsl:value-of select="concat('/', substring($pdfGuid, 3,1))"/>
<xsl:value-of select="concat('/', $pdfGuid)"/>
</xsl:variable>
<xsl:value-of select="strJS:replace(string(.),string($pdfGuid),string($pdfPath))" disable-output-escaping="yes"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<div class="greenBox">
<xsl:value-of select="intro[lang($language)]" disable-output-escaping="yes"/>
</div>
</xsl:template>
When you ask a question here, please clearly explain the output you want and what the problem is that you are encountering.
There is a discrepancy between your description of the problem and your attempt, wherein you are using forward slashes, and one at the beginning of the path in your XSLT, and backslashes in your description. I am going to assume you want forward slashes, but these can easily be changed. I suspect what you want is something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="language" select="'en'" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<div class="greenBox">
<xsl:apply-templates select="intro[lang($language)]/node()" />
</div>
</xsl:template>
<xsl:template match="#href[contains(., 'pdf') and not(contains(., 'Content'))]">
<xsl:attribute name="href">
<xsl:value-of select="concat('/Content/',
substring(., 1, 1), '/',
substring(., 2, 1), '/',
substring(., 3, 1), '/',
.)"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, the result is:
<div class="greenBox">
<div class="blueBar">
<h2>Highlights</h2>
<ul>
<li>
aaaa
</li>
<li>
bbbb
</li>
<li>
ccc
</li>
<li>
pdf
</li>
</ul>
</div>
</div>