XSLT sequential processing - xslt

Within class xyz only, I want to examine exactly two divs and their classnames.
If classname="yes" then output '1'.
If classname="no" then output '0'.
<div class="xyz">
<div class="no"></div>
<div class="yes"></div>
</div>
Desired output: 0 1
<div class="xyz">
<div class="yes"></div>
<div class="yes"></div>
</div>
Desired output: 1 1
.. etc ..
Finding the first is easy but how do I do it "sequentially"?

Recursive processing can be used as in the XSLT-1.0 code below:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="div[#class='xyz']/div[#class='no']">
<xsl:text>0 </xsl:text>
</xsl:template>
<xsl:template match="div[#class='xyz']/div[#class='yes']">
<xsl:text>1 </xsl:text>
</xsl:template>
<xsl:template match="node()">
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:transform>
The 3rd template processes all the nodes recursively, starting with document node. The first two templates do the desired output for #class with 'yes' and 'no'.

Related

Strip all the text from a specific node and remove all tags from xml using xslt1

I'm trying to strip all tags from a xml doc and i need to strip all text from a specific node only. For more clearity see the below example:
<root>
<p>My 1st Semester Visual</p>
<p>
<b>Self Reflection</b>
</p>
<p>The activity</p>
<content-block>
<div class="imageWrapper" />
</content-block>
<p id="5fce699db97470099ea6c7e6"> </p>
<content-block>
<div class="carousel">
<div class="carouselHeader" />
<div class="carouselNavbar">
<div class="carouselNavbarThumbnails" />
</div>
</div>
My Space Unit Flyer
</content-block>
<div>
<br />
</div>
</root>
Result:
<root><text>My 1st Semester VisualSelf ReflectionThe activity
My Space Unit Flyer
</text><contentBlocks>2</contentBlocks></root>
Expected result: I also need to remove text that is inside the <content-block>.
<root><text>My 1st Semester VisualSelf ReflectionThe activity
</text><contentBlocks>2</contentBlocks></root>
My xslt:
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="no" omit-xml-declaration="yes"/>
<!-- Strip out white space -->
<xsl:strip-space elements="*"/>
<!-- Strip out all html tags, only leaving text contents -->
<xsl:template match="*">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="root">
<root>
<text>
<xsl:apply-templates/>
</text>
<contentBlocks>
<xsl:if test="//content-block">
<xsl:value-of select="count(//content-block)"/>
</xsl:if>
<xsl:if test="figure">
<xsl:value-of select="count(figure)"/>
</xsl:if>
</contentBlocks>
</root>
</xsl:template>
</xsl:transform>
Thanks in advance
<!-- Add this to your code. It suppresses content-block. -->
<xsl:template match="content-block"/>

Removing a div tag in variable of xsl file

I have to remove a div(menu) with an ul tag in it. All the data is stored in a variable $data. I have remove that div in that variable through xslt
Before:
<div id="container>
<div id="menu">
<ul>
</ul>
</div>
</div>
After
<div id="container>
</div>
Well if you know there is only the div id="menu" in that container div then you could make a shallow copy of that container div. In general, with XSLT 1.0, a variable will be a result tree fragment, to process it further with XSLT/XPath (other than outputting it with value-of or copy-of) you need to use exsl:node-set on the variable. Then you could process the elements with the identity transformation and a template for the div[#id = 'menu'] that does not process it to delete it (online at http://xsltransform.net/bFN1y9C):
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:exsl="http://exslt.org/common" exclude-result-prefixes="exsl">
<xsl:output method="html" indent="yes"/>
<xsl:variable name="data">
<div id="container">
<div id="menu">
<ul>
</ul>
</div>
</div>
</xsl:variable>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:variable name="data2">
<xsl:apply-templates select="exsl:node-set($data)/node()"/>
</xsl:variable>
<xsl:template match="div[#id = 'menu']"/>
<xsl:template match="/">
<xsl:copy-of select="$data2"/>
</xsl:template>
</xsl:transform>
If you need to perform other transformation steps you might need to separate the different steps by using modes.

Template declared to match element, but never triggered

I've the following XML.
<?xml version="1.0" encoding="UTF-8"?>
<docs>
<biblos>
<texto xmlns="http://www.aranzadi.es/namespace/contenido/biblos/texto" idioma="spa">
<parrafo>
<en-origen estilo-fuente="cursiva">This is cursive text.</en-origen>
</parrafo>
</texto>
</biblos>
</docs>
and the following XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" doctype-public="XSLT-compat" omit-xml-declaration="yes" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<html>
<body>
<section class="chapter">
<xsl:apply-templates select="docs"/>
</section>
</body>
</html>
</xsl:template>
<xsl:template match="docs">
<div class="chapter">
<xsl:text>Docs Block</xsl:text>
<xsl:apply-templates select="biblos"/>
</div>
</xsl:template>
<xsl:template match="biblos">
<xsl:text>biblos block</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="texto">
<xsl:text>Text To Block</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="parrafo">
<div class="para">
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="parrafo">
<span class="format-smallcaps">
<xsl:apply-templates/>
</span>
</xsl:template>
<xsl:template match="en-origen">
<xsl:variable name="fontStyle">
<xsl:choose>
<xsl:when test="./#estilo-fuente">
<xsl:value-of select="concat('font-style-',#estilo-fuente)"/>
</xsl:when>
<xsl:when test="./#format">
<xsl:value-of select="concat('format-',#format)"/>
</xsl:when>
</xsl:choose>
</xsl:variable>
<span class="{$fontStyle}">
<xsl:value-of select="."/>
<xsl:apply-templates select="para"/>
</span>
</xsl:template>
</xsl:transform>
when i run this, I'm getting the below output.
<!DOCTYPE html
PUBLIC "XSLT-compat">
<html>
<body>
<section class="chapter">
<div class="chapter">Docs Blockbiblos block
This is cursive text.
</div>
</section>
</body>
</html>
Here the problem is, though I've declared texto and child nodes of it in my XSLT, it is not getting called, but the text is directly getting printed.
Please let me know where I'm going wrong and how I can fix it.
Good question (thanks for providing a complete, working example!). Often, if elements are not matched, the cause lies in missing namespaces:
You have the following in your input XML:
<texto xmlns="http://www.aranzadi.es/namespace/contenido/biblos/texto" idioma="spa">
In other words, the texto element is in a namespace. In your XSLT you have the following:
<xsl:template match="texto">
Since no namespace is declared for XPath (xpath-default-namespace on the containing xsl:template or xsl:stylesheet), this will operate on elements texto in no namespace, meaning, as written, it will not match texto from your source.
You can solve this by:
<xsl:transform
xmlns:tto="http://www.aranzadi.es/namespace/contenido/biblos/texto"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
and:
<xsl:template match="tto:texto">
Now your template will be matched.
Remember that element names can be in a namespace if the namespace is declared on that element, but the attributes, unless prefixes, are in no nammespace, so this solution is only required (given your example input) on matching or selecting elements.
Also, it is important to realize that prefixes do not matter, they do not need to match the prefix (or absence thereof) from the source document. What matters, is that the namespace bound to the prefix matches.
If there are child elements, in this case parrafo and en-origen, these inherit the namespace given on their parent element. So if you want to match these elements as well, you should adjust their names to tto:paraffo and tto:en-origin in patterns and XPath expressions.

XSLT Match attribute and then its element

In my source XML, any element can have an #n attribute. If one does, I want to output it before processing the element and all its children.
For example
<line n="2">Ipsum lorem</line>
<verse n="5">The sounds of silence</verse>
<verse>Four score and seven</verse>
<sentence n="3">
<word n="1">Hello</word>
<word n="2">world</word>
</sentence>
I have templates that match "line", "verse", "sentence" and "word". If any of those elements has an #n value, I want to output it in front of whatever the element's template generates.
The above might come out something like
2 <div class="line">Ipsum lorem</span>
5 <span class="verse">The sounds of silence</span>
<span class="verse">Four score and seven</span>
3 <p class="sentence">
1 <span class="word">Hello</span>
2 <span class="word">world</span>
</p>
where the templates for "line", "verse", etc. generated the div, span and p elements.
How should I think of this problem? -- Match the attribute and then apply-templates to its parent? (What would the syntax for that be?) Put a call-template at the beginning of every element's template? (That's unappealing.) Something else? (Probably!)
I tried a few things but got either an infinite loop, or nothing, or processing of the attribute and then its parent's children, but not the parent itself.
To simplify matters, I've placed the mapping from XML to HTML elements in an in-document data structure (accessible via the document() function with no arguments). Now only one template is needed requiring special processing of the #n attribute in only one place.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<map>
<elt xml="line" html="class"/>
<elt xml="verse" html="span"/>
<elt xml="sentence" html="p"/>
<elt xml="word" html="span"/>
</map>
<xsl:template match="line|verse|sentence|word">
<xsl:if test="#n"><xsl:value-of select="#n"/> </xsl:if>
<xsl:element name="{document()/map/elt[#xml=name()]/#html}">
<xsl:attribute name="class"><xsl:value-of select="name()"/></xsl:attibute>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
Here is one simple way to do this:
<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="*/*[#n]">
<xsl:value-of select="concat('
', #n, ' ')"/>
<xsl:apply-templates select="self::*" mode="content"/>
</xsl:template>
<xsl:template match="*/*[not(#*)]">
<xsl:apply-templates select="." mode="content"/>
</xsl:template>
<xsl:template match="line" mode="content">
<div class="line"><xsl:apply-templates/></div>
</xsl:template>
<xsl:template match="verse | word" mode="content">
<span class="{name()}"><xsl:apply-templates mode="content"/></span>
</xsl:template>
<xsl:template match="sentence" mode="content">
<p class="sentence"><xsl:apply-templates/></p>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<t>
<line n="2">Ipsum lorem</line>
<verse n="5">The sounds of silence</verse>
<verse>Four score and seven</verse>
<sentence n="3">
<word n="1">Hello</word>
<word n="2">world</word>
</sentence>
</t>
the wanted, correct result is produced:
2 <div class="line">Ipsum lorem</div>
5 <span class="verse">The sounds of silence</span>
<span class="verse">Four score and seven</span>
3 <p class="sentence">
1 <span class="word">Hello</span>
2 <span class="word">world</span>
</p>
Explanation: Appropriate use of templates and modes.

create HTML from a list of XML nodes using XSLT

I am a noob on XSLT.
I have a XML where t nodes are followed by other nodes, and then another t node might appear again followed by nodes again, and so on
<t />
<n1 />
<n2 />
..
<t/>
<n3 />
<n4 />
...
What I need to turn this XML into is a HTML where t nodes wraps all nodes following it up to the next t node
<div class='t'>
<div class='n1'/>
<div class='n2'/>
...
</div>
<div class='t'>
<div class='n3'/>
<div class='n4'/>
...
</div>
I am having a hard time implementing this.
Any ideas \ hints?
Thanks!
This is grouping adjacents. There are many solutions:
Whit this wellformed input:
<root>
<t />
<n1 />
<n2 />
<t/>
<n3 />
<n4 />
</root>
XSLT 1.0: traversing with following axis
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="node()[1]" mode="group"/>
<xsl:apply-templates select="t"/>
</xsl:copy>
</xsl:template>
<xsl:template match="t">
<div class="t">
<xsl:apply-templates select="following-sibling::node()[1]"
mode="group"/>
</div>
</xsl:template>
<xsl:template match="t" mode="group"/>
<xsl:template match="node()" mode="group">
<xsl:apply-templates select="."/>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="group"/>
</xsl:template>
<xsl:template match="*[starts-with(name(),'n')]">
<div class="{name()}"/>
</xsl:template>
</xsl:stylesheet>
XSLT 1.0: Keys
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:key name="kNodeByMark"
match="node()[../t][not(self::t)]"
use="generate-id((..|preceding-sibling::t[1])[last()])"/>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="key('kNodeByMark',generate-id())"/>
<xsl:for-each select="t">
<div class="t">
<xsl:apply-templates
select="key('kNodeByMark',generate-id())"/>
</div>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(),'n')]">
<div class="{name()}"/>
</xsl:template>
</xsl:stylesheet>
XSLT 2.0: for-each-group instruction
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="node()[../t[1] >> .]"/>
<xsl:for-each-group select="node()" group-starting-with="t">
<div class="t">
<xsl:apply-templates
select="current-group()[position()>1]"/>
</div>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(),'n')]">
<div class="{name()}"/>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<div class="t">
<div class="n1" />
<div class="n2" />
</div>
<div class="t">
<div class="n3" />
<div class="n4" />
</div>
</root>
EDIT: Traversing following axis refactored to look like the others solutions. Stripping identity rules.
See my note on your question, regarding "which XSLT version?". If grouping is supported in your target version, see other answers here, as that is easier to understand and will almost certainly perform better on any XSLT processor. If you aren't certain, I recommend going with a 1.0 solution like this one.
You can do it with the "XML fragment" exactly like you posted with most XSLT processors, but I added a "root" element to your XML, to reduce certain unknowns in answering your question.
In this solution below, I've tried to keep a direct correlation between the shape of the XSLT and the shape of the output you desire. In my opinion that makes it easier to maintain/understand, at least for smaller stylesheets.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root">
<xsl:for-each select="t">
<div class='t'>
<xsl:for-each select="following-sibling::*[count(preceding-sibling::t)=(count(current()/preceding-sibling::t) + 1) and not(self::t)]">
<div class='{name()}' />
</xsl:for-each>
</div>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The right-hand side of "following-sibling::*[count(preceding-sibling::t)=(count(current()/preceding-sibling::t) + 1) and not(self::t)]" could be simplified, I'm sure, using something like "current()::position()" (which isn't valid, fyi), but I'm rusty and couldn't remember some of the alias syntax.
This basically says: 1) Evaluate every T. 2) Select elements with the same quantity of T preceding them, as the index of the T we are currently evaluating.
Note that you've probably tried iterating through procedurally, and found you can't store the last value found in XSLT. Or you've found that you can, but only with nested templates. This same type of pivot you are performing has many XSLT neophytes hitting roadblocks, so don't feel bad.