Find the level of a node in xslt - xslt

I want to find the level of a node using xslt.
Logic
ol/li/ul/li ---> <p type="list_bullet_2>
ol/li/ ---> list_number
ul/li/ ---> list_bullet_2
ul/li/ ---> list_bullet_3
ol/li/ ---> list_number_4
ul/li ---> list_bullet_5
This is also only for li when there is no child p.
How can I do using xslt. I am using xslt 2.0

check this
<xsl:template match="li">
<xsl:copy>
<p type="{concat('list', if(local-name(..) = 'ol') then 'number' else 'bullet', if(ancestor::li) then concat('_',count(ancestor::li)+1) else '')}"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
See transformation at https://xsltfiddle.liberty-development.net/3NJ3919

To find the nesting level of particular node, you can use:
count(ancestor::*)
To find li element with no p child, you can use:
li[not(p)]
either in select or in xsl:template match.

Related

xslt - add elements to text but remove the in between element

I am not worked with XSLT lot. But Somehow, I am struggling to get the expected output for the below items.
Input 1:
<name>xxxx <xsample>dddd</xsample> zzzz</name>
Output for 1:
<p><t>xxxx dddd zzzz</t></p> // here I don't want to wrap the tag
Input 2
<name>xxxx <ysample>dddd</ysample> zzzz</name>
Output for 2:
<p><t>xxxx </t><t>dddd</t><t> zzzz</t></p>
I have tried with the below xslt code:
<xsl:template match="name">
<p>
<xsl:apply-templates select="*|#*|comment()|processing-instruction()|text()"/>
</p>
</xsl:template>
<xsl:template match="name/text()[not(parent::ysample)]">
<t><xsl:value-of select="."/></t>
</xsl:template>
<xsl:template match="name/ysample">
<t><xsl:value-of select="."/></t>
</xsl:template>
Anybody could you please help me with this?
Thanks,
Kumar
I think the problem is with this line
<xsl:template match="name//text()[not(parent::ysample)]">
There are two issues here
name/text() matches text nodes that are direct children of name, and so the condition not(parent::ysample), which applies to the text node, will never be true as the parent will always be name
This is maybe a typo, but you probably want to check for xsample here, to implement your logic, especially because you already have a template matching ysample
Try this line instead:
<xsl:template match="name//text()[not(parent::xsample)]">
You can also check this in XSLT 2.0 with grouping
<xsl:template match="name">
<p>
<xsl:for-each-group select="node()" group-adjacent="self::text() or self::xsample">
<t>
<xsl:value-of select="current-group()"/>
</t>
</xsl:for-each-group>
</p>
</xsl:template>

Attempts to use following-sibling to convert

I try to convert my old html by xslt-script to my new xml stucture.
I have a Problem to converting the folowing source to my needed xml structure.
Source
<p>
<a class="DropDown">Example Text</a>
</p>
<div class="collapsed">
<table>..</table>
<p>..</p>
</div>
xml structure
<lq>
<p>Example Text</p>
<table>..</table>
<p>..</p>
</lp>
I tried the following xls, but the div class="collapsed" is not adopted into the lp tag.
<xsl:template match="p/a[#class='DropDown']">
<lp>
<p><xsl:apply-templates select="text()"/></p>
<xsl:if test="/p/a/following-sibling::*[1][self::div]">
<xsl:apply-templates select="*|text()"/>
</xsl:if>
</lp>
</xsl:template>
Can anyone tell me what I did wrong ore where the mistake is?
Thanks much
IMHO, you want to do:
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p[a/#class='DropDown']">
<lp>
<p>
<xsl:value-of select="a"/>
</p>
<xsl:copy-of select="following-sibling::*[1][self::div]/node()"/>
</lp>
</xsl:template>
<xsl:template match="div[preceding-sibling::*[1][self::p/a/#class='DropDown']]"/>
As for your mistake:
You are testing the existence of some p that is the root element
and contains an a whose following sibling is div. None of these are true in the given example;
xsl:if does not change the context: your <xsl:apply-templates
select="*|text()"/> applies templates to the child nodes of the
current a;
Presumably you don't want the div to appear again in the original place -
so if you have another template to suppress it, you cannot use
<xsl:apply-templates> to insert it at the place you do want it -
at least not without using another mode.

How to make XSLT xsl:if work

I am trying to make this (xml 1.0) code work . I am new to this and already exhausted myself in
trying different ways. Does someone know my mistake?
<xsl:for-each select="News/Sport">
<xsl:if test="local-name()='Basketball'">
<p>
<xsl:text>Basketball Sport</xsl:text>
</p>
<xsl:value-of select="News/Sport/Basketball/Phrases/Phrase"/>
</xsl:if>
</xsl:for-each>
When I transform it into an HTML file the content doesn't show up. When I remove the xsl:for each and the xsl:if statements the content is successfully presented. I only wish that the content is first checked (if it is available in the XML file) and if yes, that it is taken from the XML content.
Thank you in advance for your help!
EDIT:
This is my XML code
<News>
<Sport>
<Basketball>
<Phrases>
<Phrase>Zach Randolph recovered the opening tipoff in Game 1 of the Western Conference Finals, and he didn’t touch the ball again until the possession following the Grizzlies’ first timeout.
</Phrase>
<Phrases>
</Basketball>
</Sport>
</News>
EDIT2:
Could you tell me why I cannot apply a template inside this below function? Only the text works now:(
<xsl:for-each select="News/Sport[Basketball]">
<xsl:apply-templates select="News/Sport/*" />
</xsl:for-each>
<xsl:template match="Basketball">
<p>
<xsl:text>Basketball Sport</xsl:text>
</p>
<xsl:apply-templates select="Phrases/Phrase"/>
</xsl:template>
<xsl:for-each select="News/Sport">
<xsl:if test="local-name()='Basketball'">
In this if test, the context node is a Sport element, so local-name() will always be Sport and will never equal Basketball.
I only wish that the content is first checked (if it is available in the XML file) and if yes, that it is taken from the XML content.
The usual way to handle this sort of thing in XSLT is to define templates matching the various nodes that might be present and then applying templates to all the nodes that are actually found. If there are no nodes of a particular type then the corresponding template will not fire
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" />
<xsl:template match="/">
<html>
<body>
<!-- apply templates that match all elements inside Sport, which may
be Basketball, Football, etc. -->
<xsl:apply-templates select="News/Sport/*" />
</body>
</html>
</xsl:template>
<!-- when we find a Basketball element ... -->
<xsl:template match="Basketball">
<p>
<xsl:text>Basketball Sport</xsl:text>
</p>
<xsl:apply-templates select="Phrases/Phrase"/>
</xsl:template>
<!-- when we find a Football element ... -->
<xsl:template match="Football">
<p>
<xsl:text>Football Sport</xsl:text>
</p>
<!-- whatever you need to do for Football elements -->
</xsl:template>
<xsl:template match="Phrase">
<p><xsl:value-of select="." /></p>
</xsl:template>
</xsl:stylesheet>
That way you don't need any explicit for-each or if, the template matching logic handles it all for you.
You are missing the idea of a context node. Within a xsl:for-each, everything you refer to is about or relative to the selected nodes. So, for instance, within <xsl:for-each select="News/Sport">, the context node is the Sport elements, and a test like <xsl:if test="local-name()='Basketball'"> is always going to be false because the local name is always Sport.
It looks like you want just <xsl:if test="Basketball"> which tests whether there are any Basketball child nodes of the current Sport node.
The same thing applies to <xsl:value-of select="News/Sport/Basketball/Phrases/Phrase"/>. Because everything is relative to the Sport node, XSLT is looking for News/Sport/Basketball/Phrases/Phrase within the Sport element, which never exists.
In addition, you can just put text in literally: there is no need for an xsl:text element here, so your code should look like
<xsl:for-each select="News/Sport">
<xsl:if test="Basketball">
<p>Basketball Sport</p>
<xsl:value-of select="Basketball/Phrases/Phrase"/>
</xsl:if>
</xsl:for-each>
You can refine this further by adding a predicate to the for-each selection, so that it matches only Sport elements with a Basketball child. Like this
<xsl:for-each select="News/Sport[Basketball]">
<p>Basketball Sport</p>
<xsl:value-of select="Basketball/Phrases/Phrase"/>
</xsl:for-each>

Matching different node handles to get a node value with XSLT

XSLT available is 1.0.
I'm working on a dual-language site in an XML-based CMS (Symphony CMS), and need to replace the English version of a category name with the French version.
This is my source XML.
<data>
<our-news-categories-for-list-fr>
<entry id="118">
<title-fr handle="technology">Technologie</title-fr>
</entry>
<entry id="117">
<title-fr handle="healthcare">Santé</title-fr>
</entry>
</our-news-categories-for-list-fr>
<our-news-article-fr>
<entry id="100">
<categories>
<item id="117" handle="healthcare" section-handle="our-news-categories" section-name="Our News categories">Healthcare</item>
<item id="118" handle="technology" section-handle="our-news-categories" section-name="Our News categories">Technology</item>
</categories>
<main-text-fr mode="formatted"><p>Blah blah</p></main-text-fr>
</entry>
</our-news-article-fr>
</data>
This is part of the XSLT that I currently have for the French version.
<xsl:template match="data">
<xsl:apply-templates select="our-news-article-fr/entry"/>
</xsl:template>
<xsl:template match="our-news-article-fr/entry">
<xsl:if test="categories/item">
<p class="category">In:</p>
<ul class="category">
<xsl:for-each select="categories/item">
<li><xsl:value-of select="."/></li>
</xsl:for-each>
</ul>
</xsl:if>
</xsl:template match>
The problem: the visible text of the anchor (<xsl:value-of select="."/>) gives the English version of the category title.
The handles of the following nodes match (all handles are in English), and so I'm thinking I should be able to match one from the other.
/data/our-news-categories-for-list-fr/entry/title-fr/#handle (value of title-fr node is French translation of category title)
/data/our-news-article-fr/entry/categories/item/#handle
I'm new to XSLT and am struggling to find how to do this.
Many thanks.
Add <xsl:key name="k1" match="our-news-categories-for-list-fr/entry" use="#id"/> as a child of your XSLT stylesheet element. Then use e.g. <li><xsl:value-of select="key('k1', #id)/title-fr"/></li>.
../our-news-categories-for-list-fr/entry/title-fr/text() instead of #handle should do it
Your problem is that you are in
<our-news-article-fr>
and need to reference
<our-news-categories-for-list-fr>
so I do a parent .. to walk up the tree and then down the entry nodes
Within the xsl:for-each repetition instruction, the context is our-news-article-fr/entry/categories/item. If you use . you select the current context, that's why you are receiving the english version there.
Another approach (not saying the simplest and the best one) is simply specify an XPath expression which locates the correct node. You can use the ancestor:: axis to go out from the current context to data and then use your test node. The needed predicate must match against the current context using current() function:
<xsl:value-of select="
ancestor::data[1]/
our-news-categories-for-list-fr/
entry/
title-fr
[#handle=current()/#handle]
"/>
If data is the root of your document you can obviously use an absolute location path:
/
data/
our-news-categories-for-list-fr/
entry/
title-fr
[#handle=current()/#handle]

XPath expression to select all child elements that are not some specific element

Given the XML
<blockquote>
<attribution>foo</attribution>
<para>bar</para>
</blockquote>
I have the XSL template
<xsl:template match="dbk:blockquote">
<blockquote>
<xsl:apply-templates select="*[not(dbk:attribution)]" />
<xsl:apply-templates select="dbk:attribution" />
</blockquote>
</xsl:template>
where the first apply-templates should select all child elements of the dbk:blockquote that are not of type dbk:attribution. (This is necessary to move attributions to the bottom.)
However, it in fact matches every node. Why?
You want to use the self axis:
<xsl:apply-templates select="*[not(self::dbk:attribution)]" />
This selects child elements that are not themselves a dbk:attribution element. Your version selects child elements that do not contain a dbk:attribution child.
I am no xpath expert. But I think this should work.
<xsl:template match="dbk:blockquote">
<blockquote>
<xsl:apply-templates select="*[local-name(.) != 'attribution']" />
<xsl:apply-templates select="*[local-name(.) = 'attribution']" />
</blockquote>
</xsl:template>