get first element whose preceding - xslt

I've the below sample xml tree.
-root
-para
-page
-footnum
-para
-footnum
-para
-footnum
-para
-page
-footnum
I want to apply templates on the first footnum whose preceding is page. From inside the footnum template. here below para there will be even more nodesbut here i want to apply-template only to the first footnum followed by page,, whether it is in same para or different.
The code i tried is as below.
<xsl:template match="footnote" mode="footnote">
<xsl:variable name="rt">
<xsl:value-of select="//page/#num/following::footnote[1]/#num"/>
</xsl:variable>
<xsl:value-of select="$rt"/>
<xsl:if test="contains($rt,#num)">
<xsl:variable name="op"><</xsl:variable>
<xsl:variable name="apos">'</xsl:variable>
<xsl:variable name="cl">></xsl:variable>
<xsl:value-of select="concat($op,'?pb label=',$apos,./#num,$apos,'?',$cl)"/>
</xsl:if>
<div class="tr_footnote">
<div class="footnote">
<sup>
<a>
<xsl:attribute name="name"><xsl:text>ftn.</xsl:text><xsl:value-of select="#num"/></xsl:attribute>
<xsl:attribute name="href"><xsl:text>#f</xsl:text><xsl:value-of select="#num"/></xsl:attribute>
<xsl:attribute name="class"><xsl:text>tr_ftn</xsl:text></xsl:attribute>
<xsl:value-of select="#num"/>
</a>
</sup>
<xsl:apply-templates/>
</div>
</div>
</xsl:template>
Update:
here when i'm running this template, it is giving me before which footnotes the number is to be placed, but here i'm getting the footnote number, but not the pagenumber. please let me know how i can retrieve the page number.
here it is applying templates for all the footnums. please let me know where am i going wrong.
Thanks

You are already in the footnote context. To get the first preceding page node, you must use
<xsl:variable name="rt" select="preceding-sibling::*[1][local-name()='page']/#num"/>

If (as I think you are saying) you want to call apply-templates to the first footnum with a preceding page element, then your first problem is that the apply-templates instruction in your sample code applies to the page element, not to the footnum element.
Your second problem (whether the error is in your code or in your problem description) is that you say you wish to do this work only in some cases (it's not clear to me what conditions you have in mind -- one footnum per document, or once per page element on the first footnum following that page element, or something else), but you don't seem to have any test in your code that tests for the condition you have in mind. The test in your conditional is preceding::page[1], which is synonymous (as a Boolean) with preceding::page, and which excludes all those footnum elements which appear before any page element in the document, and includes all the others.
Assuming that your page elements mark page breaks, and your footnum elements mark footnote numbers, and what you want is to do something special for the first footnote (if any) on each page [if that's not right, you'll have to make the appropriate adjustments to the solution], then you need to find an XPath expression that returns true for the first footnote on each page, and not for the others.
What is the crucial characteristic here of the first footnum on each page? That there is no other footnum element between it and the immediately preceding page. So we could write (not tested):
<xsl:variable name="mypage" as="element(page)?"
select="preceding::page[1]"/>
<xsl:variable name="preceding-footnum-on-same-page"
as="element(footnum)?"
select="preceding::footnum[preceding::page[. is $mypage]]"/>
<xsl:if test="$mypage and not($preceding-footnum-on-same-page)">
... do your stuff here ...
</xsl:if>
Another way to formulate it is: the current node is the first footnum on the current page if, when we find the current page and then find its first footnum, it's the current node.
<xsl:if test=". is ./preceding::page[1]/following::footnum[1]">
...
</xsl:if>
I hope this helps.

Related

Modify attributes of certain tags adding a value

like this example https://plone-theming-with-diazo.readthedocs.org/en/latest/snippets_diazo/recipes/index.html#add-attributes-on-the-fly
I need to modify the class of all of specific tag, adding a value (on the content side).
This rule does'nt work:
<xsl:template css:match="ul.navTreeLevel0 li">
<xsl:attribute name="class"><xsl:value-of select="./#class"/> no-bullet</xsl:attribute>
</xsl:template>
I want to add the value "no-bullet" on any li tag inside a ul with "navTreeLevel0" class.
Diazo doesn't raise exceptions.
In the same rule file I've a similar situation, but in this case works :
<replace content="//div[contains(#class,'cell')]/#class">
<xsl:attribute name="class">
<xsl:if test='contains(current(),"width-3:4")'>nine large-9 columns</xsl:if>
<xsl:if test='contains(current(),"width-2:3")'>height large-8 columns</xsl:if>
<xsl:if test='contains(current(),"width-1:2")'>six large-6 columns</xsl:if>
<xsl:if test='contains(current(),"width-1:3")'>four large-4 columns</xsl:if>
<xsl:if test='contains(current(),"width-1:4")'>three large-3 columns</xsl:if>
<xsl:if test='contains(current(),"width-full")'>twelve large-12 columns</xsl:if>
</xsl:attribute>
</replace>
What's the matter?
Vito
It may be the same reason as in this similar question I read today: diazo xsl:template not applying when inside secondary rules file
Quoting the Diazo docs from there: "Inline XSL directives must be placed directly inside the root tag and are applied unconditionally."
Well, apparently some xsl works outside of the root rules tag too, seeing that the other part of your code works.
If you replace the css:match="..." in your code with match="obviously wrong[xsl" does Diazo then raise an exception? If not, then the likely reason is that your xsl is ignored and needs to be moved to the root roles tag.

variable inside second for loop

I am trying to print variable value in second for loop of xslt but not getting displayed
here is my code
<xsl:for-each select="CampaignData/Mailer/CustomerInfo/CustInfFld/Coupons/Cust_Sel_Coupons">
<xsl:sort select="#CouponPosition"/>
<xsl:variable name="CouponID">
<xsl:value-of select="#CouponID"/>
</xsl:variable>
<xsl:for-each select="CampaignData/Mailer/Coupons/Cntr_Sel_Coupons">
<div style='color:red;'><xsl:value-of select='$CouponID'/></div>
</xsl:for-each>
</xsl:for-each>
I think one potential problem is that the outer for-each changes the context node so the inner for-each needs to take that into account:
<xsl:for-each select="CampaignData/Mailer/CustomerInfo/CustInfFld/Coupons/Cust_Sel_Coupons">
<xsl:sort select="#CouponPosition"/>
<xsl:variable name="CouponID" select="#CouponID"/>
<xsl:for-each select="ancestor::CampaignData/Mailer/Coupons/Cntr_Sel_Coupons">
<div style='color:red;'><xsl:value-of select='$CouponID'/></div>
</xsl:for-each>
</xsl:for-each>
I have also changed to code for the variable to simply write the expression you want to select in the select attribute instead of nesting a value-of.
If you still have problems then post a sample of the XML you want to process.
The select attribute of the nested xsl:for-each doesn't select anything and this is why its body isn't executed even once.
Inside the inner xsl:for-each your current node is of the form:
CampaignData/Mailer/CustomerInfo/CustInfFld/Coupons/Cust_Sel_Coupons
The select attribute of the inner xsl:for-each specifies:
CampaignData/Mailer/Coupons/Cntr_Sel_Coupons
this is a relative XPath expression and it is resolved off the current node. Thus, the specified select for the inner xsl:for-each is actually (from outside of the outer xsl:for-each):
CampaignData/Mailer/CustomerInfo/CustInfFld/Coupons/Cust_Sel_Coupons
/CampaignData/Mailer/Coupons/Cntr_Sel_Coupons
It is highly unlikely there are any such nodes in the source XML document that you haven't shown.
Without having the source XML it isn't possible to say what exactly the relative expression should be -- most likely:
Change:
<xsl:for-each select=
"CampaignData/Mailer/Coupons/Cntr_Sel_Coupons">
to:
<xsl:for-each select=
"../../../../Coupons/Cntr_Sel_Coupons">

Next Previous navigation in news article for umbraco

This is driving me a little crazy (been trying to do this for quite some time)
I'm trying to get some xpath code working that will display next and previous links, which will take you to the next newsItem and once it gets to newsItem4, then the next button disappears.
<newsArea>
<newsItem1></newsItem1> <----- currentPage
<newsItem2></newsItem2>
<newsItem3></newsItem3>
<newsItem4></newsItem4>
</newsArea>
I can list al the newsItems whilst on a news item with
<xsl:for-each select="$currentPage/../*">
<h2><xsl:value-of select="#nodeName"/></h2>
</xsl:for-each>
and i can count how many items i have
<xsl:value-of select="count($currentPage/../*)-1"/>
but i dont know how to move to the next news item, or how to tell it when to stop showing the next node when it comes to the end of the newsItems (one example just jummped to another leve)
Any help would be really really appreciated.
Thanks
Tim
Take a look at the paging sample from this blog post: http://www.nibble.be/?p=11
That should give you an idea of how it works. You could always set your page size to 1.
Also, there is a package based on that method: http://our.umbraco.org/projects/developer-tools/paging-xslt
That should get you on the right path.
You could easily create a "Page Navigation" macro to add to the bottom of any template you want it, XSLT would be as follows:
<xsl:template match="/">
<!-- if there's a previous page -->
<xsl:if test="$currentPage/preceding-sibling::*[1]">
< Prev
</xsl:if>
<!-- if there's next and previous, show divider -->
<xsl:if test="$currentPage/preceding-sibling::*[1] and $currentPage/following-sibling::*[1]">
<xsl:text> | </xsl:text>
</xsl:if>
<!-- if there's a next page -->
<xsl:if test="$currentPage/following-sibling::*[1]">
Next >
</xsl:if>
</xsl:template>

I need to build a string from a list of attributes (not elements) in XSLT

want to make a comma-delimited string from a list of 3 possible attributes of an element.
I have found a thread here:
XSLT concat string, remove last comma
that describes how to build a comma-delimited string from elements. I want to do the same thing with a list of attributes.
From the following element:
<myElement attr1="Don't report this one" attr2="value1" attr3="value2" attr4="value3" />
I would like to produce a string that reads: "value1,value2,value3"
One other caveat: attr2 thru attr4 may or may not have values but, if they do have values, they will go in order. So, attr4 will not have a value if attr3 does not. attr3 will not have a value if attr2 does not. So, for an attribute to have a value, the one before it in the attribute list must have a value.
How can I modify the code in the solution to the thread linked to above so that it is attribute-centric instead of element-centric?
Thanks in advance for whatever help you can provide.
This is easy in principe, but only if it is really clear which attribute you want to exclude. Since attributes are not by definition ordered in XML (in contrast the elements), you need to say how the attribute(s) to skip can be identified.
Edit: Regarding the attribute order, XML section 3.1 says:
Note that the order of attribute specifications in a start-tag or empty-element tag is not significant.
That said, something like this should do the trick (adjust the [] condition as you see fit):
<xsl:template match="myElement">
<xsl:for-each select="#*[position()!=1]">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
In XSLT 2.0 it is as easy as
<xsl:template match="myElement">
<xsl:value-of select="#* except #att1" separator=","/>
</xsl:template>
I would appreciate Lucero's answer .. He definitely has nailed it ..
Well, Here is one more code which truncates attribute attr1, which appears at any positions other than 1 in attribute list.
scenario like this::
<myElement attr2="value1" attr3="value2" attr4="value3" attr1="Don't report this one" />
Here is the XSLT code .. ::
<xsl:template match="myElement">
<xsl:for-each select="#*[name()!='attr1']">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
The output will be:
value1,value2,value3
If you wish to omit more than one attribute say .. attr1 and attr2 ..
<xsl:template match="myElement">
<xsl:for-each select="#*[name()!='attr1' and name()!='attr2']">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
The corresponding output will be:
value2,value3

XSLT: pass value from one for-each match to the next

I'm using the following to match all <section>s with a revision attribute set. <section>can appear at many different levels of the document tree, always contained within <chapter>s.
<xsl:for-each select="//section[#revision]">
<!-- Do one thing if this is the first section
matched in this chapter -->
<!-- Do something else if this section is in the same
chapter as the last section matched -->
</xsl:for-each>
As the comments say, I need to make each for-each iteration aware of the chapter to which the previous matched section belonged. I know that <xsl:variable>s are actually static once set, and that <xsl:param> only applies to calling templates.
This being Docbook, I can retreive a section's chapter number with:
<xsl:apply-templates select="ancestor::chapter[1]" mode="label.markup" />
but I think it can be done with purely XPath.
Any ideas? Thanks!
Not sure if I unterstood your requirements 100%, but…
<xsl:variable name="sections" select="//section[#revision]" />
<xsl:for-each select="$sections">
<xsl:variable name="ThisPos" select="position()" />
<xsl:variable name="PrevMatchedSection" select="$sections[$ThisPos - 1]" />
<xsl:choose>
<xsl:when test="
not($PrevMatchedSection)
or
generate-id($PrevMatchedSection/ancestor::chapter[1])
!=
generate-id(ancestor::chapter[1])
">
<!-- Do one thing if this is the first section
matched in this chapter -->
</xsl:when>
<xsl:otherwise>
<!-- Do something else if this section is in the same
chapter as the last section matched -->
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
However, I suspect this whole thing can be solved more elegantly with a <xsl:template> / <xsl:apply-templates> approach. But without seeing your input and expected output this is hard to say.
position() will return your position within the current for-each iteration. The first iteration, IIRC, will return 0, so testing for "position()=0" will tell you if you are in the first iteration.