xsl picking text value of following siblings - xslt

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>

Related

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>

XSLT: in a <p> element, how to replace a line break (<br/>) with successive <alinea> element?

I am trying to transform a <p> element with <br/> elements within into several <alinea>subtext</alinea>. For instance :
<p>subtext<br/>some more text<br/> some more subtext</p>
From the previous <p> I was hoping to simply replace all <br/> into </alinea><alinea> since the template for <p> opens a <alinea> element already.
<xsl:template match="p">
<para><alinea><xsl:apply-templates/></alinea></para>
</xsl:template>
<xsl:template match="br">
</alinea><xsl:apply-templates/><alinea>
</xsl:template>
But it is doesn't validate.
Expected result :
<para>
<alinea>
subtext
</alinea>
<alinea>
some more text
</alinea>
<alinea>
some more subtext
</alinea>
</para>
This is quite easy to do in XSLT 2.0:
<xsl:template match="p">
<para>
<xsl:for-each-group select="node()" group-starting-with="br">
<alinea>
<xsl:copy-of select="current-group()[not(self::br)]" />
</alinea>
</xsl:for-each-group>
</para>
</xsl:template>
Demo: https://xsltfiddle.liberty-development.net/6r5Gh2Q
Prob you can use something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="p">
<para>
<xsl:apply-templates/>
</para>
</xsl:template>
<xsl:template match="text()">
<aline>
<xsl:value-of select="."/>
</aline>
</xsl:template>
<xsl:template match="br"/>
<xsl:template match="div">
<blockquote>
<xsl:value-of select="."/>
</blockquote>
</xsl:template>
</xsl:stylesheet>
I found a (dirty?) way to replace <br> with </alinea><alinea> :
<xsl:template match="br">
<xsl:value-of disable-output-escaping="yes">
</alinea><alinea>
</xsl:value-of>
</xsl:template>
Anything prettier ?

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>

Assign CSS class to element in an XSL

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.

Show the header only once in a for-each

Say I have the following XML:
<a>
<b>1</b>
<b>2</b>
<b>3</b>
</a>
... and require:
Header
1
2
3
... but an xslt like:
<xsl:template match = "/" >
<xsl:variable name="headed" select="false()"/>
<xsl:for-each select = "a/*" >
<xsl:if test="not($headed)">
<xsl:text>Header</xsl:text>
<!--
this next line causes a problem due to
the attempted reassignment of $headed
-->
<xsl:variable name="headed" select="true()"/>
</xsl:if>
<xsl:value-of select="." />
<xsl:value-of select="'
'"/>
</xsl:for-each>
</xsl:template>
is invalid, can anybody recommend a brief and readable solution? and perhaps a good book to learn a functional mindset from :)
Cheers
Simon
------------------------------ addendum --------------------------
After pondering the answers I've been presented with I realised I've lost some of the key components of the problem I was trying to tackle.
my data is more like:
<address>
<line1>street</line1>
<line2>town</line2>
<line3>city</line3>
<country>uk</country>
</address>
and my desired output is more like:
<table>
<tr><td rowspan="6" valign="top">Address</td><td>street</td></tr>
<tr><td>town</td></tr>
<tr><td>city</td></tr>
<tr><td>uk</td></tr>
</table>
any further help would be greatly appreciated.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="a">Header
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="a/b">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Is practically what you want, and simpler than what you have now.
Just take <xsl:text>Header</xsl:text> out of for-each...
The end result was more like:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version = "1.0"
xmlns:xsl = "http://www.w3.org/1999/XSL/Transform" >
<xsl:template match="/a">
<html><body><table>
<xsl:apply-templates select="*"/>
</table></body></html>
</xsl:template>
<xsl:template match="a/*">
<xsl:if test="not(.='')">
<TR>
<xsl:if test="position()=1">
<!--
<TD rowspan="6" valign="top">Address</TD>
improved based on Tomalak's suggestion
-->
<xsl:element name="TD">
<xsl:attribute name="rowspan" >
<!--
<cough />
<xsl:value-of select="count(*)"/>
-->
<xsl:value-of select="count(*[not(.='')]"/>
</xsl:attribute>
<xsl:attribute name="valign" >
<xsl:text>top</xsl:text>
</xsl:attribute>
<xsl:text>Address</xsl:text>
</xsl:element>
</xsl:if>
<TD>
<xsl:value-of select="."/>
</TD>
</TR>
</xsl:if>
</xsl:template>
</xsl:stylesheet>