I want to group elements with #class="warning" and with #class="warninglistbullet" into a single .
Input:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Title</title>
</head>
<body>
<section>
<h1 class="heading1">A section</h1>
<p class="bodytext">Some bodytext.</p>
<p class="parts">1234</p>
<p class="parts">23456</p>
<p class="parts">2341</p>
<p class="bodytext">Some bodytext.</p>
<p class="warning">Eletrical hazard</p>
<p class="warning">Do not:</p>
<ul class="warninglistbullet">
<li class="warninglistbullet">
<p class="warninglistbullet">Take a bath and</p>
</li>
<li class="warninglistbullet">
<p class="warninglistbullet">use power tools at the same time.</p>
</li>
</ul>
</section>
</body>
</html>
Desired output:
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>Title</title>
</head>
<body>
<section>
<h1 class="heading1">A section</h1>
<p class="bodytext">Some bodytext.</p>
<div class="parts">
<p class="parts">1234</p>
<p class="parts">23456</p>
<p class="parts">2341</p>
</div>
<p class="bodytext">Some bodytext.</p>
<div class="warning">
<p class="warning">Eletrical hazard</p>
<p class="warning">Do not:</p>
<ul class="warninglistbullet">
<li class="warninglistbullet">
<p class="warninglistbullet">Take a bath and</p>
</li>
<li class="warninglistbullet">
<p class="warninglistbullet">use power tools at the same time.</p>
</li>
</ul>
</div>
</section>
</body>
</html>
Current, wrong template:
<xsl:template match="section">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#*"/>
<xsl:for-each-group select="*[#class!='']" group-adjacent="#class">
<xsl:choose>
<xsl:when test="current-grouping-key = 'parts'">
<div class="parts">
<xsl:apply-templates select="current-group()"/>
</div>
</xsl:when>
<xsl:when test="(current-grouping-key = 'warning') or (current-grouping-key = 'warninglistbullet')">
<div class="warning">
<xsl:apply-templates select="current-group()"/>
</div>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:element>
</xsl:template>
I am able to group together elements with the same class, but how to group elements with different classes?
--
Apparently, Stackoverflow will not let me post the question because there is too much code and too little explanation.
So I will ramble on just to please the script that is preventing me from posting.
I tried both the "boolean" pattern and the "#class" pattern in the for-each-group element. I was not able to get either to work:
I do not know how to write the boolean pattern to match "parts" and "warning" and "warninglistbullet".
If I try to group-by class and evaluate the current-grouping-key() as "warning" OR "warningbulletitem", then the two elements never end up being grouped together.
How about:
<xsl:template match="section">
<xsl:copy>
<xsl:for-each-group select="*[#class!='']" group-adjacent="replace(#class, 'warninglistbullet', 'warning')">
<xsl:choose>
<xsl:when test="current-grouping-key() = ('parts', 'warning')">
<div class="{current-grouping-key()}">
<xsl:apply-templates select="current-group()"/>
</div>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
Related
Note sure how to make one of your online test environments for this issue.
Test XSL script:
<?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" indent="yes" version="4.01"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
doctype-public="//W3C//DTD XHTML 1.0 Transitional//EN"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
<xsl:attribute name="lang">
<xsl:value-of select="//Settings//LanguageCode"/>
</xsl:attribute>
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<link rel="stylesheet" type="text/css">
<xsl:attribute name="href">
<xsl:value-of select="//Settings/Template/#Style"/>
</xsl:attribute>
</link>
<title>
<!--<xsl:value-of select="//Labels/ReportTitleWorksheets"/>-->
Assignment Slips
</title>
<style type="text/css">
#import url('<xsl:text>2020/</xsl:text><xsl:value-of select="//Settings/Template/#Style"/>');
</style>
</head>
<body>
<xsl:for-each select="AssignmentSlips/Page">
<div class="containerPage">
<xsl:if test="#PageBreakBefore=1">
<br style="page-break-before: always;"/>
</xsl:if>
<xsl:for-each select="StudentSlip">
<div class="containerSlip">
<img alt="s89" width="323px" height="429px">
<xsl:attribute name="src">
<xsl:text>2020\</xsl:text>
<xsl:value-of select="//Settings/Template"/>
</xsl:attribute>
</img>
<div class="fieldName">
<xsl:attribute name="dir">
<xsl:value-of select="//Settings/Direction"/>
</xsl:attribute>
<xsl:value-of select="Student"/>
</div>
<div class="fieldAssisant">
<xsl:attribute name="dir">
<xsl:value-of select="//Settings/Direction"/>
</xsl:attribute>
<xsl:value-of select="Assistant"/>
</div>
<div class="fieldDate">
<xsl:attribute name="dir">
<xsl:value-of select="//Settings/Direction"/>
</xsl:attribute>
<xsl:value-of select="Date"/>
</div>
<div class="fieldCounsel">
<xsl:choose>
<xsl:when test="#ItemPosition='1' and Assistant!=''">
<xsl:text>1st: </xsl:text>
</xsl:when>
<xsl:when test="#ItemPosition='2'">
<xsl:text>2nd: </xsl:text>
</xsl:when>
<xsl:when test="#ItemPosition='3'">
<xsl:text>3rd: </xsl:text>
</xsl:when>
</xsl:choose>
<xsl:attribute name="dir">
<xsl:value-of select="//Settings/Direction"/>
</xsl:attribute>
<xsl:value-of select="StudyPoint"/>
</div>
<xsl:choose>
<xsl:when test="Assignment=1">
<div class="checkBibleReading">✓</div>
</xsl:when>
<xsl:when test="Assignment=2">
<div class="checkInitialCall">✓</div>
<div class="fieldInitialCallIndex">
<xsl:choose>
<xsl:when test="#ItemPosition='1'">
<xsl:text>#1</xsl:text>
</xsl:when>
<xsl:when test="#ItemPosition='2'">
<xsl:text>#2</xsl:text>
</xsl:when>
<xsl:when test="#ItemPosition='3'">
<xsl:text>#3</xsl:text>
</xsl:when>
</xsl:choose>
</div>
</xsl:when>
<xsl:when test="Assignment=3">
<div class="checkFirstReturnVisit">✓</div>
<div class="fieldFirstReturnVisitIndex">
<xsl:choose>
<xsl:when test="#ItemPosition='1'">
<xsl:text>#1</xsl:text>
</xsl:when>
<xsl:when test="#ItemPosition='2'">
<xsl:text>#2</xsl:text>
</xsl:when>
<xsl:when test="#ItemPosition='3'">
<xsl:text>#3</xsl:text>
</xsl:when>
</xsl:choose>
</div>
</xsl:when>
<xsl:when test="Assignment=4">
<div class="checkSecondReturnVisit">✓</div>
<div class="fieldSecondReturnVisitIndex">
<xsl:choose>
<xsl:when test="#ItemPosition='1'">
<xsl:text>#1</xsl:text>
</xsl:when>
<xsl:when test="#ItemPosition='2'">
<xsl:text>#2</xsl:text>
</xsl:when>
<xsl:when test="#ItemPosition='3'">
<xsl:text>#3</xsl:text>
</xsl:when>
</xsl:choose>
</div>
</xsl:when>
<xsl:when test="Assignment=5">
<div class="checkThirdReturnVisit">✓</div>
</xsl:when>
<xsl:when test="Assignment=6">
<div class="checkBibleStudy">✓</div>
</xsl:when>
<xsl:when test="Assignment=7">
<div class="checkTalk">✓</div>
</xsl:when>
<xsl:when test="Assignment=0">
<div class="checkOther">✓</div>
<div class="fieldOther">
<xsl:attribute name="dir">
<xsl:value-of select="//Settings/Direction"/>
</xsl:attribute>
<xsl:value-of select="Other"/>
</div>
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="Location=1">
<div class="checkMainHall">✓</div>
</xsl:when>
<xsl:when test="Location=2">
<div class="checkAuxClass1">✓</div>
</xsl:when>
<xsl:when test="Location=3">
<div class="checkAuxClass2">✓</div>
</xsl:when>
</xsl:choose>
</div>
</xsl:for-each>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Test XML data:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="AssignmentSlips-2020-v1.xsl"?>
<AssignmentSlips Version="230800">
<Settings>
<LanguageCode>zu</LanguageCode>
<Template Style="s-89-zul.css">s-89-zul.jpg</Template>
<Direction>ltr</Direction>
<ForeignGroupMode>0</ForeignGroupMode>
</Settings>
<Page PageBreakBefore="0">
<StudentSlip ItemPosition="0">
<Student></Student>
<StudyPoint></StudyPoint>
<Material></Material>
<Assignment ItemCount="1">1</Assignment>
<Location>1</Location>
<Date>ULwesine 9 Februwari 2023</Date>
</StudentSlip>
<StudentSlip ItemPosition="1">
<Student></Student>
<StudyPoint></StudyPoint>
<Assignment ItemCount="1">2</Assignment>
<Location>1</Location>
<Date>ULwesine 9 Februwari 2023</Date>
</StudentSlip>
<StudentSlip ItemPosition="2">
<Student></Student>
<StudyPoint></StudyPoint>
<Assignment ItemCount="1">3</Assignment>
<Location>1</Location>
<Date>ULwesine 9 Februwari 2023</Date>
</StudentSlip>
<StudentSlip ItemPosition="3">
<Student></Student>
<StudyPoint></StudyPoint>
<Material></Material>
<Assignment ItemCount="1">6</Assignment>
<Location>1</Location>
<Date>ULwesine 9 Februwari 2023</Date>
</StudentSlip>
</Page>
</AssignmentSlips>
The referenced CSS / JPG files are in a 2020 sub folder:
https://www.dropbox.com/s/vbbqr10gbgmil8s/2020.zip?dl=0
When I open the XML file with Microsoft Edge the display is blank.
When I did manage to view the source it was cropped:
<!DOCTYPE html PUBLIC "//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="zu" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<link rel="stylesheet" type="text/css" href="s-89-zul.css" />
<title>
Assignment Slips
</title>
<style type="text/css">
#import url('2020/s-89-zul.css');
</style>
</head>
<body>
<div class="containerPage">
<div class="containerSlip">
<img alt="s89" width="323px" height="429px" src="2020\s-89-zul.jpg" />
<div class="fieldName" dir="ltr">
</div>
<div class="fieldAssisant" dir="ltr">
</div>
<div class="fieldDate" dir="ltr">ULwesine 9 Februwari 2023</div>
<div class="fieldCounsel" dir="ltr">
</div>
<div class="checkBibleReading">✓</div>
<div class="checkMainHall">✓</div>
</div>
<div class="containerSlip">
<img alt="s89" width="323px" height="429px" src="2020\s-89-zul.jpg" />
<div class="fieldName" dir="ltr">
</div>
<div class="fieldAssisant" dir="ltr">
</div>
<div class="fieldDate" dir="ltr">ULwesine 9 Februwari 2023</div>
<div class="fieldCounsel" dir="ltr">
</div>
<div class="checkInitialCall">✓</div>
<div class="fieldInitialCallIndex">#1</div>
<div class="checkMainHall">✓</div>
</div>
<div class="containerSlip">
<img alt="s89" width="323px" height="429px" src="2020\s-89-zul.jpg" />
<div class="fieldName" dir="ltr">
</div>
<div class="fieldAssisant" dir="ltr">
</div>
<div class="fieldDate" dir="ltr">ULwesine 9 Februwari 2023</div>
<div class="fieldCounsel">2nd:
What have I done wrong here?
Your first problem is that you are testing this code in an environment that gives very poor diagnostics. Ideally, test your code in an XSLT development tool such as Oxygen. If you can only test it in the browser, use a browser like Chrome with a decent developer's console.
When I run it in XSLTfiddle (https://www.xsltfiddle.liberty-development.net) I get a clear error message:
Error executing XSLT at line 70 : An attribute node (dir) cannot be
created after a child of the containing element. Most recent element
start tag was output at line 58 of module
If you look at the surrounding code:
<div class="fieldCounsel">
<xsl:choose>
<xsl:when test="#ItemPosition='1' and Assistant!=''">
<xsl:text>1st: </xsl:text>
</xsl:when>
<xsl:when test="#ItemPosition='2'">
<xsl:text>2nd: </xsl:text>
</xsl:when>
<xsl:when test="#ItemPosition='3'">
<xsl:text>3rd: </xsl:text>
</xsl:when>
</xsl:choose>
<xsl:attribute name="dir">
<xsl:value-of select="//Settings/Direction"/>
</xsl:attribute>
<xsl:value-of select="StudyPoint"/>
</div>
It's perfectly clear that the xsl:choose is creating a text node, and you then follow this with an instruction that creates an attribute. You need to create the attributes of an element before you create the children.
I need to convert a HTML file to XML format using XSLT 2.0. The HTML file contains only <p> tag with classes h1, h2, h3, . . .
<body>
<p class='h1'>the fisr A</p>
<p class='txt'>one</p>
<p>tow</p>
<p class='h2'>the sec B</p>
<p class='txt'>theree</p>
<p class='h2'>the sec sec B</p>
<p class='txt'>the next text</p>
<p class='h3'>the fisr C</p>
<p class='txt'>four</p>
<p class='txt'>five</p>
<p class='h1'>the seccond A</p>
<p class='txt'>the seccond txt</p>
<p class='h2'>the second B</p>
<p class='txt'>six</p>
<p class='txt'>seven</p>
<p class='h1'>the third A</p>
<p class='txt'>eight</p>
<p class='txt'>nine</p>
</body>
I need the XML output as shown below
<book>
<sectionA>
<title>the fisr A</title>
<p class="txt">one</p>
<p>tow</p>
<sectionB>
<title>the sec B</title>
<p class="txt">theree</p>
</sectionB>
<sectionB>
<title>the sec sec B</title>
<p class="txt">the next text</p>
<sectionC>
<title>the fisr C</title>
<p class="txt">four</p>
<p class="txt">five</p>
</sectionC>
</sectionB>
</sectionA>
<sectionA>
<title>the seccond A</title>
<p class="txt">the seccond txt</p>
<sectionB>
<title>the second B</title>
<p class="txt">six</p>
<p class="txt">seven</p>
</sectionB>
</sectionA>
<sectionA>
<title>the third A</title>
<p class="txt">eight</p>
<p class="txt">nine</p>
</sectionA>
</book>
Can anyone help me to get the desired output?
You can try this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="body">
<book>
<xsl:for-each-group select="p" group-starting-with="p[#class='h1']">
<sectionA>
<title>
<xsl:value-of select="node()"/>
</title>
<xsl:for-each-group select="current-group() except ." group-starting-with="p[#class='h2']">
<xsl:choose>
<xsl:when test="self::p[#class='h2']">
<sectionB>
<title>
<xsl:value-of select="node()"/>
</title>
<xsl:for-each-group select="current-group() except ." group-starting-with="p[#class='h3']">
<xsl:choose>
<xsl:when test="self::p[#class='h3']">
<sectionC>
<title>
<xsl:value-of select="node()"/>
</title>
<xsl:apply-templates select="current-group() except ."></xsl:apply-templates>
</sectionC>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"></xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</sectionB>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"></xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</sectionA>
</xsl:for-each-group>
</book>
</xsl:template>
<xsl:template match="p">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet> <!-- added by edit -->
I am an amateur and have started XML and XSLT recently.
I've been asked to make a XSLT file from the below XML.
<?xml version="1.0" encoding="UTF-8" ?>
<event>
<title>Test 1</title>
<description>The first test</description>
<location>
<postalcode>A1A 1A1</postalcode>
<city>Vancouver</city>
<province>BC</province>
<streetaddress>Arina street east</streetaddress>
</location>
<attendees>
<name>John</name>
<email>example#gmail.com</email>
<phone>778777777</phone>
</attendees>
</event>
I made this XSLT file
<?xml version="1.0" encoding="utf-8"?>
<!-- event.xsl -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<head><title><xsl:value-of select="title"/></title></head>
<body>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="event">
<h2><xsl:value-of select="title"/></h2>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="description">
<p><xsl:value-of select="description"/></p>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="location">
<p>
<xsl:value-of select="streetaddress"/>
<xsl:value-of select="city"/>
<xsl:value-of select="province"/>
<xsl:value-of select="postalcode"/>
</p>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="attendees">
<xsl:for-each select="event/attendees">
<p>
<xsl:value-of select="name"/>
<xsl:value-of select="email"/>
<xsl:value-of select="phone"/>
</p>
</xsl:for-each>
<xsl:apply-templates />
</xsl:template>
</xsl:stylesheet>
this is the generated HTML
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-16">
<title></title></head>
<body>
<h2>Test 1</h2>
Test 1
<p></p>The first test
<p>Arina street eastVancouverBCA1A 1A1</p>
A1A 1A1
Vancouver
BC
Arina street east
John
example#gmail.com
778777777
</body>
</html>
this the desired html that I am looking for
<html>
<head>
<title>Test 1</title>
<body>
<h2>Test 1</h2>
<p>The first test</p>
<p>
Ariana Street East<br>
Vancouver<br>
BC , A1A 1A1<br>
</p>
<!-- repeat-->
<p>
Name:john<br>
Email:example#gmail.com<br>
Phone:77877777
</p>
<p>
Name:john2<br>
Email:example2#gmail.com<br>
Phone:77877778
</p>
</body>
</html>
when I make a HTML file it is kind of messed up.
would you let me know where are my mistakes?
do you have any easy explained article?
thank you
You may try this slightly adapted version:
<?xml version="1.0" encoding="utf-8"?>
<!-- event.xsl -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<head>
<title>
<xsl:value-of select="event/title"/>
</title>
</head>
<body>
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="title" />
<xsl:template match="event">
<h2>
<xsl:value-of select="title"/>
</h2>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="description">
<p>
<xsl:value-of select="description"/>
</p>
</xsl:template>
<xsl:template match="location">
<p>
<xsl:value-of select="streetaddress"/>
<br/>
<xsl:value-of select="city"/>
<br/>
<xsl:value-of select="province"/>
<br/>
<xsl:value-of select="postalcode"/>
<br/>
</p>
</xsl:template>
<xsl:template match="attendees">
<p>
Name: <xsl:value-of select="name"/><br/>
Email: <xsl:value-of select="email"/><br/>
Phone: <xsl:value-of select="phone"/><br/>
</p>
</xsl:template>
</xsl:stylesheet>
Which will generate the following output:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Test 1</title>
</head>
<body>
<h2>Test 1</h2>
<p></p>
<p>Arina street east<br>Vancouver<br>BC<br>A1A 1A1<br></p>
<p>
Name: John<br>
Email: example#gmail.com<br>
Phone: 778777777<br></p>
</body>
</html>
I think you have an error in "attendess" pattern match:
<xsl:template match="attendees">
<xsl:for-each select="event/attendees">
<p>...
The "for-each" instruction is redundant, attendees template is applied from de "apply-templates" instruction in the event template.
The first title section wont work, update it to:
<title><xsl:value-of select="event/title"/></title>
add field names at the begining and elements at the end of the lines on attendees and location
<xsl:template match="location">
<p>
address:<xsl:value-of select="streetaddress"/><br>
city:<xsl:value-of select="city"/><br>
province:<xsl:value-of select="province"/><br>
postalcode:<xsl:value-of select="postalcode"/> <br>
</p>
<xsl:apply-templates />
</xsl:template>
I need wrapping each word with a tag (e. span) in a HTML document, like:
<html>
<head>
<title>It doesnt matter</title>
</head>
<body>
<div> Text in a div </div>
<div>
Text in a div
<p>
Text inside a p
</p>
</div>
</body>
</html>
To result something like this:
<html>
<head>
<title>It doesnt matter</title>
</head>
<body>
<div> <span>Text </span> <span> in </span> <span> a </span> <span> div </span> </div>
<div>
<span>Text </span> <span> in </span> <span> a </span> <span> div </span>
<p>
<span>Text </span> <span> in </span> <span> a </span> <span> p </span>
</p>
</div>
</body>
</html>
It's important to keep the structure of the body...
Any help?
All of the three different solutions below use the XSLT design pattern of overriding the identity rule to generally preserve the structure and contents of the XML document, and only modify specific nodes.
I. XSLT 1.0 solution:
This short and simple transformation (no <xsl:choose> used anywhere):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" 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="*[not(self::title)]/text()"
name="split">
<xsl:param name="pText" select=
"concat(normalize-space(.), ' ')"/>
<xsl:if test="string-length(normalize-space($pText)) >0">
<span>
<xsl:value-of select=
"substring-before($pText, ' ')"/>
</span>
<xsl:call-template name="split">
<xsl:with-param name="pText"
select="substring-after($pText, ' ')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied to the provided XML document:
<html>
<head>
<title>It doesnt matter</title>
</head>
<body>
<div> Text in a div </div>
<div>
Text in a div
<p>
Text inside a p
</p>
</div>
</body>
</html>
produces the wanted, correct result:
<html>
<head>
<title>It doesnt matter</title>
</head>
<body>
<div>
<span>Text</span>
<span>in</span>
<span>a</span>
<span>div</span>
</div>
<div>
<span>Text</span>
<span>in</span>
<span>a</span>
<span>div</span>
<p>
<span>Text</span>
<span>inside</span>
<span>a</span>
<span>p</span>
</p>
</div>
</body>
</html>
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" 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="*[not(self::title)]/text()">
<xsl:for-each select="tokenize(., '[\s]')[.]">
<span><xsl:sequence select="."/></span>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied to the same XML document (above), again the correct, wanted result is produced:
<html>
<head>
<title>It doesnt matter</title>
</head>
<body>
<div>
<span>Text</span>
<span>in</span>
<span>a</span>
<span>div</span>
</div>
<div>
<span>Text</span>
<span>in</span>
<span>a</span>
<span>div</span>
<p>
<span>Text</span>
<span>inside</span>
<span>a</span>
<span>p</span>
</p>
</div>
</body>
</html>
III Solution using FXSL:
Using the str-split-to-words template/function of FXSL one can easily implement much more complicated tokenization -- in any version of XSLT:
Let's have a more complicated XML document and tokenization rules:
<html>
<head>
<title>It doesnt matter</title>
</head>
<body>
<div> Text: in a div </div>
<div>
Text; in; a. div
<p>
Text- inside [a] [p]
</p>
</div>
</body>
</html>
Here there is more than one delimiter that indicates the start or end of a word. In this particular example the delimiters can be: " ", ";", ".", ":", "-", "[", "]".
The following transformation uses FXSL for this more complicated tokenization:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext">
<xsl:import href="strSplit-to-Words.xsl"/>
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[not(self::title)]/text()">
<xsl:variable name="vwordNodes">
<xsl:call-template name="str-split-to-words">
<xsl:with-param name="pStr" select="normalize-space(.)"/>
<xsl:with-param name="pDelimiters"
select="' ;.:-[]'"/>
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates select="ext:node-set($vwordNodes)/*"/>
</xsl:template>
<xsl:template match="word[string-length(normalize-space(.)) > 0]">
<span>
<xsl:value-of select="."/>
</span>
</xsl:template>
</xsl:stylesheet>
and produces the wanted, correct result:
<html>
<head>
<title>It doesnt matter</title>
</head>
<body>
<div>
<span>Text</span>
<span>in</span>
<span>a</span>
<span>div</span>
</div>
<div>
<span>Text</span>
<span>in</span>
<span>a</span>
<span>div</span>
<p>
<span>Text</span>
<span>inside</span>
<span>a</span>
<span>p</span>
<word/>
</p>
</div>
</body>
</html>
You could achieve this by extending the identity transform to include a recursive template which checks for spaces in a piece of text, and if so puts a span tag around the first word. It can then recursively calls itself for the remaining portion of the text.
Here is it in action...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Don't split the words in the title -->
<xsl:template match="title">
<xsl:copy-of select="." />
</xsl:template>
<!-- Matches a text element. Given a name so it can be recursively called -->
<xsl:template match="text()" name="wrapper">
<xsl:param name="text" select="." />
<xsl:variable name="new" select="normalize-space($text)" />
<xsl:choose>
<xsl:when test="contains($new, ' ')">
<span><xsl:value-of select="concat(substring-before($new, ' '), ' ')" /></span>
<xsl:call-template name="wrapper">
<xsl:with-param name="text" select="substring-after($new, ' ')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<span><xsl:value-of select="$new" /></span>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When called on your sample HTML, the output is as follows:
<html>
<head>
<title>It doesnt matter</title>
</head>
<body>
<div>
<span>Text </span>
<span>in </span>
<span>a </span>
<span>div</span>
</div>
<div>
<span>Text </span>
<span>in </span>
<span>a </span>
<span>div</span>
<p>
<span>Text </span>
<span>inside </span>
<span>a </span>
<span>p</span>
</p>
</div>
</body>
</html>
I wasn't 100% sure how important the spaces within the span elements are for you though.
So I have a XML document generated by my application like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE AddressBook>
<?xml-stylesheet type="text/xsl" href="stylesheet.xsl"?>
<AddressBook>
<Item>
<UserGeneratedElementName1 class="info">Whatever blah blah</UserGeneratedElementName1>
<UserGeneratedElementName2 class="info">Whatever blah blah</UserGeneratedElementName2>
</Item>
<Item>
<UserGeneratedElementName3 class="info">Whatever blah blah</UserGeneratedElementName3>
</Item>
...
...
Other Items with user-generated elements with user-generated content...
</AddressBook>
And I want to turn it into a HTML document similar to this:
<html>
<head>
<title>AddressBook</title>
</head>
<body>
<div class="root">
<div class="item">
<b>UserGeneratedElementName1:</b> Whatever blah blah
<b>UserGeneratedElementName2:</b> Whatever blah blah
</div>
<div class="item">
<b>UserGeneratedElementName3:</b> Whatever blah blah
</div>
...
...
Other transformed items...
</div>
</body>
</html>
I have tried to get a grasp of the XSLT syntax, but all the guides were either too vague to help me with this or too deep. Also XSLT syntax seems quite confusing.
Thanks in advance.
Take a look at this question here
Is there an XSLT name-of element?
You can use
<xsl:value-of select ="name(.)"/>
or
<xsl:value-of select ="local-name()"/>
to get the name of a node, depending on if you want to include the full prefixed name, or just the local portion.
You should be able to piece those together with xsl:for-each blocks to iterate through the first 3 levels of items and generate the HTML you're looking for.
Something like this would work for a fixed number of levels.
<xsl:for-each select="*">
<html>
<head>
<title><xsl:value-of select="local-name()" /></title>
</head>
<body>
<div class="root">
<xsl:for-each select="*">
<div>
<xsl:attribute name="class">
<xsl:value-of select="local-name()" />
</xsl:attribute>
<xsl:for-each select="*">
<b><xsl:value-of select="local-name()" />:</b> <xsl:value-of select="." />
</xsl:for-each>
</div>
</xsl:for-each>
</div>
</body>
</html>
</xsl:for-each>
A more generic approach would look something more like:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes" version="4.0"/>
<xsl:template match="/">
<xsl:for-each select="*">
<html>
<head>
<title><xsl:value-of select="local-name()" /></title>
</head>
<body>
<div class="root">
<xsl:call-template name="recurseElement">
<xsl:with-param name="element" select="." />
</xsl:call-template>
</div>
</body>
</html>
</xsl:for-each>
</xsl:template>
<xsl:template name="recurseElement">
<xsl:param name="element" />
<xsl:for-each select="$element/*">
<xsl:choose>
<xsl:when test="count(child::*)>0">
<div>
<xsl:attribute name="class">
<xsl:value-of select="local-name()" />
</xsl:attribute>
<xsl:call-template name="recurseElement">
<xsl:with-param name="element" select="." />
</xsl:call-template>
</div>
</xsl:when>
<xsl:otherwise>
<b><xsl:value-of select="local-name()" />:</b> <xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This complete XSLT transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="AddressBook">
<html>
<head>
<title>AddressBook</title>
</head>
<body>
<div class="root">
<xsl:apply-templates/>
</div>
</body>
</html>
</xsl:template>
<xsl:template match="Item">
<div class="item"><xsl:apply-templates/></div>
</xsl:template>
<xsl:template match="Item/*">
<b><xsl:value-of select="name()"/>:</b> <xsl:text/>
<xsl:value-of select="concat(.,'
')"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<AddressBook>
<Item>
<UserGeneratedElementName1 class="info">Whatever blah blah</UserGeneratedElementName1>
<UserGeneratedElementName2 class="info">Whatever blah blah</UserGeneratedElementName2>
</Item>
<Item>
<UserGeneratedElementName3 class="info">Whatever blah blah</UserGeneratedElementName3>
</Item> ... ... Other Items with user-generated elements with user-generated content...
</AddressBook>
produces the wanted, correct result:
<html>
<head>
<title>AddressBook</title>
</head>
<body>
<div class="root">
<div class="item">
<b>UserGeneratedElementName1:</b>Whatever blah blah
<b>UserGeneratedElementName2:</b>Whatever blah blah
</div>
<div class="item">
<b>UserGeneratedElementName3:</b>Whatever blah blah
</div> ... ... Other Items with user-generated elements with user-generated content...
</div>
</body>
</html>
Explanation:
Templates matching any Item element and any element child of an Item element.
Use of the standard XPath name() function.