Template is called where it should not to be - xslt

I've the below two XML cases.
Case1:
<para>Rent is the sum of money paid by the Tenant to the Landlord for the exclusive use of premises. The Landlord and Tenant signs a <page num="4"/>tenancy agreement which has to be stamped with the tax authorities as required under the Stamp Duties Act. The stamping of a tenancy agreement gives it validity but if the tenancy agreement is not stamped that does not mean</para>
Case2:
<para><page num="5"/>The Writ of Distress proceedings is an effective way to recover arrears in rent but regard must be had to the Landlord/Tenant relationship and the effect of publicity of such proceedings to the image of the building amongst other things.</para>
and the below XSLT
<xsl:template match="para">
<xsl:apply-templates select="child::node()[(self::page)]"/>
<li class="item">
<div class="para">
<span class="item-num">
<xsl:value-of select="../#num"></xsl:value-of>
</span>
<xsl:apply-templates select="child::node()[not(self::page)]"/>
</div>
</li>
</xsl:template>
<xsl:template match="page">
<xsl:processing-instruction name="pb">
<xsl:text>label='</xsl:text>
<xsl:value-of select="./#num"/>
<xsl:text>'</xsl:text>
<xsl:text>?</xsl:text>
</xsl:processing-instruction>
<a name="{concat('pg_',./#num)}"/>
<xsl:apply-templates/>
</xsl:template>
What I'm trying to do is check if page is the immediate(first) child of para and print that value first and then do the rest. But in both the cases, the page is printed first.
In the above cases provided, for case1, the page should be called just like any other template in para, since it is not the immediate child of para, but in case2, first the page has to be printed and next the template is to be called, as page num="5" is the immediate child of para Please let me know how I can do this.
A demo is here

I think what you mean is that you want to perform extra processing when page is the first child node under para. Your apply-templates need to look like this
<xsl:apply-templates select="node()[1][self::page]" />
However, it also sounds like you want to perform other processing on page elements regardless. You probably need two templates matching page here, but one with a "mode" to distinguish it from your normal processing.
Call it like this
<xsl:apply-templates select="node()[1][self::page]" mode="first"/>
And match it like this
<xsl:template match="page" mode="first">
This would contain the code to output your processing instruction.
For "normal" processing of the page element, just have another template matching page without the mode
<xsl:template match="page">
<a name="{concat('pg_',./#num)}"/>
<xsl:apply-templates/>
</xsl:template>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="para">
<xsl:apply-templates select="node()[1][self::page]" mode="first"/>
<li class="item">
<div class="para">
<span class="item-num">
<xsl:value-of select="../#num"></xsl:value-of>
</span>
<xsl:apply-templates />
</div>
</li>
</xsl:template>
<xsl:template match="page">
<a name="{concat('pg_',./#num)}"/>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="page" mode="first">
<xsl:processing-instruction name="pb">
<xsl:text>label='</xsl:text>
<xsl:value-of select="./#num"/>
<xsl:text>'</xsl:text>
</xsl:processing-instruction>
</xsl:template>
</xsl:stylesheet>
EDIT: If you don't want both "page" templates to apply to apply to the first page element, then add the following template to ignore it
<xsl:template match="page[not(preceding-sibling::node())]" />
Note, this will only work if you have <xsl:strip-space elements="*" /> present in your document, to strip out white-space only text nodes. Alternatively, you could write this
<xsl:template match="page[not(preceding-sibling::node()[not(self::text()) or normalize-space()])]" />
EDIT 2: The reason you need the extra templates is because of this line
<xsl:apply-templates />
This is will look for templates that match all the child elements under the current para element. So, for a page element, the following template will be matched
<xsl:template match="page">
But you say you don't want the very first page element to be matched in this case. Therefore, you are a more 'specific' template to match it. For example
<xsl:template match="page[not(preceding-sibling::node())]" />
This template matches page elements with no preceding siblings; i.e. the very first element under para.
XSLT has the concept of priority for templates. Where a template matching an element with a condition specified, that template will always be given priority. In this case, the specific template simply ignores the page element, to ensure it doesn't get output.
For other page elements, the other template will be used as normal.

Related

XSLT wrap element and following-sibling text

Kindly help me to wrap the img.inline element with the following sibling text comma (if comma exists):
text <img id="1" class="inline" src="1.jpg"/> another text.
text <img id="2" class="inline" src="2.jpg"/>, another text.
Should be changed to:
text <img id="1" class="inline" src="1.jpg"/> another text.
text <span class="img-wrap"><img id="2" class="inline" src="2.jpg"/>,</span> another text.
Currently, my XSLT will wrap the img.inline element and add comma inside the span, now I want to remove the following comma.
text <span class="img-wrap"><img id="2" class="inline" src="2.jpg"/>,</span>
, <!--remove this extra comma--> another text.
My XSLT:
<xsl:template match="//img[#class='inline']">
<xsl:copy>
<xsl:choose>
<xsl:when test="starts-with(following-sibling::text(), ',')">
<span class="img-wrap">
<xsl:apply-templates select="node()|#*"/>
<xsl:text>,</xsl:text>
</span>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="node()|#*"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
<!-- checking following-sibling::text() -->
<xsl:apply-templates select="following-sibling::text()" mode="commatext"/>
</xsl:template>
<!-- here I want to match the following text, if comma, then remove it -->
<xsl:template match="the following comma" mode="commatext">
<!-- remove comma -->
</xsl:template>
Is my approach is correct? or is this something should be handled differently? pls suggest?
Currently you are copying the img and the embedding the span within that. Also, you do <xsl:apply-templates select="node()|#*"/> which will select child nodes of img (or which there are none). And for the attributes it will end add them to the span.
You don't actually need the xsl:choose here as you can add the condition to the match attribute.
<xsl:template match="//img[#class='inline'][starts-with(following-sibling::node()[1][self::text()], ',')]">
Note I have changed the condition as following-sibling::text() selects ALL text elements that follow the img node. You only want to get the node immediately after the img node, but only if it is a text node.
Also, trying to select the following text node with xsl:apply-templates is probably not the right approach, assuming you have a template that matches the parent node which selects all child nodes (not just img ones). I am assuming you were using the identity template here.
Anyway, try this XSLT instead
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="no" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="//img[#class='inline'][starts-with(following-sibling::node()[1][self::text()], ',')]">
<span class="img-wrap">
<xsl:copy-of select="." />
<xsl:text>,</xsl:text>
</span>
</xsl:template>
<xsl:template match="text()[starts-with(., ',')][preceding-sibling::node()[1][self::img]/#class='inline']">
<xsl:value-of select="substring(., 2)" />
</xsl:template>
</xsl:stylesheet>

Recursively replacing elements in XSLT

I need to replace the <tref> element with other tags from elsewhere in my document. For example, I have:
<tref id="57236"/>
and
<Topic>
<ID>57236</ID>
<Text>
<p id="4">
<cs id="56792">1090-189-01 </cs>
<href id="57237">
<cs id="56792">Document Name</cs>
</href>
</p>
</Text>
</Topic>
Obtaining the following is not a problem:
<p id="4">
<cs id="56792">1090-189-01 </cs>
<href id="57237">
<cs id="56792">Document Name</cs>
</href>
</p>
With this stylesheet:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="tref">
<xsl:variable name="NodeID"><xsl:value-of select="#id"/></xsl:variable>
<xsl:copy-of select="//Topic[ID = $NodeID]/Text/p/node()"/>
</xsl:template>
What I cannot do is replacing trefs nested into other trefs. For example, consider the following:
<tref id="57236"/>
and:
<Topic>
<ID>57236</ID>
<Text>
<p id="251">
<tref id="37287"/>
</p>
</Text>
</Topic>
My stylesheet duly replaces the tref with the content of the tag - which also contains a tref:
<p id="251">
<tref id="37287"/>
</p>
My current solution is to call <xsl:template match="tref"> from two different stylesheets. It does the job, but it is not very elegant, and what if trefs are nested at an even deeper level? And recursion is the bread and butter of XSLT.
Is there a solution to recursively replace all trefs as in XSLT?
Instead of using xsl:copy-of, use xsl:apply-templates
<xsl:apply-templates select="//Topic[ID = $NodeID]/Text/p/node()"/>
Or, to eliminate the use of the varianle
<xsl:apply-templates select="//Topic[ID = current()/#id]/Text/p/node()"/>
Note you can make use of an xsl:key to look-up the Topic elements
<xsl:key name="topic" match="Topic" use="ID" />
Then you can write this
<xsl:apply-templates select="key('topic', #id)/Text/p/node()"/>
Be wary of infinite recursion if you have a tref referring to a Topic that is an ancestor of it.

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.

applying templates of same type but working on different type of declaration

I've below XMLs.
<emphasis type="italic">
varying from ti,e to time<star.page>58</star.page>Starch
</emphasis>
and
<para indent="no">
<star.page>18</star.page> Further to same.
</para>
here i'm trying to apply-templates on the star.page, but the confusion is if i take <xsl:apply-templates select="./*[1][self::star.page]" mode="first"/>, it is working fine for the first case, but for the second case, the star.page is getting duplicated, if i use <xsl:apply-templates select="./node()[1][self::star.page]" mode="first"/>, in case 2 the star.page that is supposed to appear before div is coming inside div and for case 1, the value is getting duplicated.
Here are the DEmos
Case1-enter link description here
Case2- enter link description here
Expected output are as below.
Case 1:
<span class="font-style-italic">
varying from ti,e to time<?pb label='58'?><a name="pg_58"></a></span>2<span class="font-style-italic">Starch
</span>
Case 2:
<?pb label='18'?><a name="pg_18"></a>
<div class="para">
Further to same.
</div>
Here the condition is as below.
If star.page is immediate child of parent node(though it is para or emphasis), the pb label has to be created first followed by the tag(Case 2 output).
If there is text and in between text there is star.page, then the content should come with pb label inside it.(Case 1 output).
please let me know a common solution on how i can fix theses issues.
I am not clear what the bulk of your XSLT is doing, but I would first make use of a named template to avoid repeated code. There is no issue with giving a matched template a name too.
<xsl:template match="star.page" name="page">
<xsl:processing-instruction name="pb">
<xsl:text>label='</xsl:text>
<xsl:value-of select="."/>
<xsl:text>'</xsl:text>
<xsl:text>?</xsl:text>
</xsl:processing-instruction>
<a name="{concat('pg_',.)}"/>
</xsl:template>
Then the template with the mode "first" becomes this
<xsl:template match="star.page" mode="first">
<xsl:call-template name="page" />
</xsl:template>
You can still call this in the same way, or maybe a slightly different condition will also work
<xsl:apply-templates select="star.page[not(preceding-sibling::node())]" mode="first"/>
Then, all you need is a template to ignore "star.page" that are the first child (because they have already been explicitly selected)
<xsl:template match="star.page[not(preceding-sibling::node())]" />
As a simplified example, try this for starers
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="para|emphasis">
<xsl:apply-templates select="star.page[not(preceding-sibling::node())]" mode="first"/>
<div>
<xsl:choose>
<xsl:when test="./#align">
<xsl:attribute name="class"><xsl:text>para align-</xsl:text><xsl:value-of select="./#align"/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="class"><xsl:text>para</xsl:text></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template match="star.page[not(preceding-sibling::node())]" />
<xsl:template match="star.page" name="page">
<xsl:processing-instruction name="pb">
<xsl:text>label='</xsl:text>
<xsl:value-of select="."/>
<xsl:text>'</xsl:text>
<xsl:text>?</xsl:text>
</xsl:processing-instruction>
<a name="{concat('pg_',.)}"/>
</xsl:template>
<xsl:template match="star.page" mode="first">
<xsl:call-template name="page" />
</xsl:template>
</xsl:stylesheet>
Do note the use of strip-space here because strictly speaking, the star.page is not the first node in each example, there is a white-space node before it.
When applied to this XML
<root>
<emphasis type="italic">
varying from ti,e to time<star.page>58</star.page>Starch
</emphasis>
<para indent="no">
<star.page>18</star.page> Further to same.
</para>
</root>
The following is output
<div class="para">
varying from time to time<?pb label='58'??><a name="pg_58"/>Starch
</div>
<?pb label='18'??>
<a name="pg_18"/>
<div class="para"> Further to same.
</div>

How do I check if a tag exists in XSLT?

I have the following template
<h2>one</h2>
<xsl:apply-templates select="one"/>
<h2>two</h2>
<xsl:apply-templates select="two"/>
<h2>three</h2>
<xsl:apply-templates select="three"/>
I would like to only display the headers (one,two,three) if there is at least one member of the corresponding template. How do I check for this?
<xsl:if test="one">
<h2>one</h2>
<xsl:apply-templates select="one"/>
</xsl:if>
<!-- etc -->
Alternatively, you could create a named template,
<xsl:template name="WriteWithHeader">
<xsl:param name="header"/>
<xsl:param name="data"/>
<xsl:if test="$data">
<h2><xsl:value-of select="$header"/></h2>
<xsl:apply-templates select="$data"/>
</xsl:if>
</xsl:template>
and then call as:
<xsl:call-template name="WriteWithHeader">
<xsl:with-param name="header" select="'one'"/>
<xsl:with-param name="data" select="one"/>
</xsl:call-template>
But to be honest, that looks like more work to me... only useful if drawing a header is complex... for a simple <h2>...</h2> I'd be tempted to leave it inline.
If the header title is always the node name, you could simplifiy the template by removing the "$header" arg, and use instead:
<xsl:value-of select="name($header[1])"/>
I like to exercise the functional aspects of XSL which lead me to the following implementation:
<?xml version="1.0" encoding="UTF-8"?>
<!-- test data inlined -->
<test>
<one>Content 1</one>
<two>Content 2</two>
<three>Content 3</three>
<four/>
<special>I'm special!</special>
</test>
<!-- any root since take test content from stylesheet -->
<xsl:template match="/">
<html>
<head>
<title>Header/Content Widget</title>
</head>
<body>
<xsl:apply-templates select="document('')//test/*" mode="header-content-widget"/>
</body>
</html>
</xsl:template>
<!-- default action for header-content -widget is apply header then content views -->
<xsl:template match="*" mode="header-content-widget">
<xsl:apply-templates select="." mode="header-view"/>
<xsl:apply-templates select="." mode="content-view"/>
</xsl:template>
<!-- default header-view places element name in <h2> tag -->
<xsl:template match="*" mode="header-view">
<h2><xsl:value-of select="name()"/></h2>
</xsl:template>
<!-- default header-view when no text content is no-op -->
<xsl:template match="*[not(text())]" mode="header-view"/>
<!-- default content-view is to apply-templates -->
<xsl:template match="*" mode="content-view">
<xsl:apply-templates/>
</xsl:template>
<!-- special content handling -->
<xsl:template match="special" mode="content-view">
<strong><xsl:apply-templates/></strong>
</xsl:template>
Once in the body all elements contained in the test element have header-content-widget applied (in document order).
The default header-content-widget template (matching "*") first applies a header-view then applies a content-view to the current element.
The default header-view template places the current element's name in the h2 tag. The default content-view applies generic processing rules.
When there is no content as judged by the [not(text())] predicate no output for the element occurs.
One off special cases are easily handled.