Numbered bookmarks in PDF output from DITA bookmaps with and without parts - xslt

I am trying to create PDF output with numbered headings using the DITA OT and a custom plugin. By default, the output contains part numbers, chapter numbers and appendix number in the headings and the TOC, but no numbers in the bookmarks. So far, I have managed to number all the remaining topics in the headings and the TOC, like so (the chapter numbers restart in every part):
bookmap
part I
chapter 1
topic 1.1
topic 1.2
chapter 2
part II
chapter 1
However, I cannot get the same numbers for the bookmarks.
I am using the following code (or override) to select the bookmarks that must be numbered:
<xsl:template match="*[contains(#class, ' topic/topic ')]" mode="bookmark">
<xsl:variable name="mapTopicref" select="key('map-id', #id)[1]"/>
<xsl:variable name="topicTitle">
<xsl:call-template name="getNavTitle"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$mapTopicref[#toc = 'yes' or not(#toc)] or
not($mapTopicref)">
<fo:bookmark>
<xsl:attribute name="internal-destination">
<xsl:call-template name="generate-toc-id"/>
</xsl:attribute>
<xsl:if test="$bookmarkStyle!='EXPANDED'">
<xsl:attribute name="starting-state">hide</xsl:attribute>
</xsl:if>
<fo:bookmark-title>
<xsl:choose>
<xsl:when test="contains($mapTopicref/#class, ' bookmap/part ')">
<xsl:call-template name="getChapterPrefix"/>
<xsl:text> </xsl:text>
</xsl:when>
<xsl:when test="contains($mapTopicref/#class, ' bookmap/appendix ')">
<xsl:call-template name="getChapterPrefix"/>
<xsl:text> </xsl:text>
</xsl:when>
<xsl:when test="contains($mapTopicref/#class, ' bookmap/chapter ')">
<xsl:call-template name="getChapterPrefix"/>
<xsl:text> </xsl:text>
</xsl:when>
</xsl:choose>
<xsl:value-of select="normalize-space($topicTitle)"/>
</fo:bookmark-title>
<xsl:apply-templates mode="bookmark"/>
</fo:bookmark>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates mode="bookmark"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I am using the following code to create the numbers (derived from an example in DITA for Print):
<xsl:template name="getChapterPrefix">
<xsl:variable name="topicType">
<xsl:call-template name="determineTopicType"/>
</xsl:variable>
<xsl:variable name="partsCount">
<xsl:value-of select="count($map//*[contains(#class, ' bookmap/part')])"/>
</xsl:variable>
<xsl:variable name="containingChapter" select="ancestor-or-self::*[contains(#class, ' topic/topic')][position()=1]"/>
<xsl:variable name="id" select="$containingChapter/#id"/>
<xsl:variable name="topicChapters">
<xsl:copy-of select="$map//*[contains(#class, ' bookmap/chapter')]"/>
</xsl:variable>
<xsl:variable name="topicAppendices">
<xsl:copy-of select="$map//*[contains(#class, ' bookmap/appendix')]"/>
</xsl:variable>
<xsl:variable name="topicParts">
<xsl:copy-of select="$map//*[contains(#class, ' bookmap/part')]"/>
</xsl:variable>
<xsl:variable name="chapterNumber">
<xsl:choose>
<xsl:when test="$topicChapters/*[#id = $id]">
<xsl:choose>
<xsl:when test="$partsCount=0"> <!-- Bookmaps without parts work fine -->
<xsl:number format="1" value="count($topicChapters/*[#id =$id]/preceding-sibling::*) + 1"/>
</xsl:when>
<xsl:otherwise> <!-- This does not work yet. -->
<xsl:number format="1" value="count($topicChapters/*[#id =$id]/preceding-sibling::*) + 1"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="$topicAppendices/*[#id = $id]">
<xsl:number format="A" value="count($topicAppendices/*[#id =$id]/preceding-sibling::*) + 1"/>
</xsl:when>
<xsl:when test="$topicParts/*[#id = $id]">
<xsl:number format="I" value="count($topicParts/*[#id =$id]/preceding-sibling::*) + 1"/>
</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="$chapterNumber != ''">
<xsl:value-of select="$chapterNumber"/>
</xsl:when>
</xsl:choose>
</xsl:template>
With this code, parts, appendices and bookmaps without parts are numbered correctly. However, for bookmaps with parts, chapters are numbered consecutively, which is inconsistent.
bookmap
part I
chapter 1
topic 1.1
topic 1.2
chapter 2
part II
chapter 3
Can anybody help me to correct this?

I just found a way to obtain the result I want. The piece of code that calculates the chapter number for bookmaps with parts was modified als follows:
<!-- If there's something in $topicChapters with an id that matches the id of the
context node, then I'm inside a chapter. -->
<xsl:when test="$topicChapters/*[#id = $id]">
<xsl:choose>
<xsl:when test="$partsCount=0"> <!-- Bookmaps without parts -->
<xsl:number format="1" value="count($topicChapters/*[#id =$id]/preceding-sibling::*) + 1"/>
</xsl:when>
<xsl:otherwise> <!-- Bookmaps with parts. -->
<xsl:number format="1" value="count(//*[contains(#class,' bookmap/chapter ')][#id =$id]/preceding-sibling::*)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
This is probably everything but elegant, but then, I'm a tech writer ...

Related

incorporating string into the when contidtion in xsl

I am trying to incorporate string into the when condition, but it does not work.
I have tried this:
<xsl:text>Salary:</xsl:text>
<xsl:for-each select="z:SalaryRecord">
<xsl:choose>
<xsl:when test="z:SalaryRecord = 'agreement'">
<xsl:value-of select="concat(' ',position(),' ',z:Type,'
')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(' ',position(),' ',' ',z:Type,'(','from ',z:AmountFrom, ' to ',z:AmountTo,')','
')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
and this:
<xsl:when test="z:SalaryRecord::text() = 'agreement'">
and this
<xsl:when test="z:SalaryRecord == 'agreement'">
My XML source code:
<RequiredSalary>
<SalaryRecord>
<Type>monthly</Type>
<AmountFrom>1000</AmountFrom>
<AmountTo>2000</AmountTo>
</SalaryRecord>
<SalaryRecord>
<Type>agreement</Type>
</SalaryRecord>
</RequiredSalary>
Any idea pls?
thanks
One fix to your situation may be the following template. However, you'd have to define a namespace for the z prefix in your stylesheet. Try adding xmlns:z="http://whatever.is.your.namespace" to your XSLT stylesheet element.
<xsl:template match="z:RequiredSalary">
<xsl:for-each select="z:SalaryRecord">
<xsl:text>Salary:</xsl:text>
<xsl:choose>
<xsl:when test="z:Type = 'agreement'">
<xsl:value-of select="concat(' ',position(),' ',z:Type,'
')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(' ',position(),' ',' ',z:Type,'(','from ',z:AmountFrom, ' to ',z:AmountTo,')','
')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:value-of select="'
'" />
</xsl:template>

How to convert a negative decimal into hexadecimal using xslt 1

I would like to convert negative and positive decimal into hexadecimal using xslt 1.0.
There's already a topic related to this question here but the answer is given using xslt 2.0.
I tried to reproduce the template using xslt 1.0 but it always returns an empty value.
<xsl:template name="convertDecToHex">
<xsl:param name="pInt" />
<xsl:variable name="vMinusOneHex64"><xsl:number>18446744073709551615</xsl:number></xsl:variable>
<xsl:variable name="vCompl">
<xsl:choose>
<xsl:when test="$pInt > 0">
<xsl:value-of select="$pInt" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$vMinusOneHex64 + $pInt + 1" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="vCompl = 0">
<xsl:text>0</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="vCompl > 16">
<xsl:variable name="result">
<xsl:call-template name="convertDecToHex">
<xsl:with-param name="pInt" select="$vCompl div 16" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat($result,substring('0123456789ABCDEF',($vCompl div 16) + 1,1))" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('',substring('0123456789ABCDEF',($vCompl div 16) + 1,1))" />
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Could you help me making it work?
To convert a decimal number to 32-bit signed hexadecimal in pure XSLT 1.0:
<xsl:template name="dec2signedhex">
<xsl:param name="decimal"/>
<xsl:variable name="n">
<xsl:choose>
<xsl:when test="$decimal < 0">
<xsl:value-of select="$decimal + 4294967296"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$decimal"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="q" select="floor($n div 16)"/>
<xsl:if test="$q">
<xsl:call-template name="dec2signedhex">
<xsl:with-param name="decimal" select="$q"/>
</xsl:call-template>
</xsl:if>
<xsl:value-of select="substring('0123456789ABCDEF', $n mod 16 + 1, 1)"/>
</xsl:template>

reformatting dates with square brackets

When transferring my XML-files to TeX I try to reformat dates – my publisher said I had to use a smaller horizontal space between dates – and can't manage to go beyond the first step.
My input file is this
<a>
<date>January 1900</date>
<date>2. 2. 1902</date>
<date>3. [3]. 1903</date>
<date>[4. 4. 1904]</date>
</a>
where brackets mean that the date is not certain. There are all possible combination of brackets, e.g. second number of the year: 1[9]00. I created a command \mini which makes small space in TeX:
\newcommand{\mini}{\,}
The result after the xslt should be:
January 1900
2.{\mini}2.{\mini}1902
3.{\mini}[3].{\mini}1903
[4.{\mini}4.{\mini}1904]
I wrote a function, which tries to extract the square brackets and store their position to a variable and afterwards concat them back again. But as I don't manage to get the variables to show correct positions, I'm stuck:
<xsl:function name="foo:date-translate">
<xsl:param name="date-string" as="xs:string"/>
<xsl:variable name="opening-square-bracket" as="xs:integer" select="count(substring-before($date-string,'['))"/>
<xsl:variable name="closing-square-bracket" as="xs:integer" select="count(substring-before($date-string,'['))"/>
<xsl:variable name="date-string-without-square-brackets" as="xs:string" select="replace(replace($date-string,'\[',''),'\]','')"/>
<xsl:choose>
<xsl:when test="matches($date-string-without-square-brackets,'\d{1,2}. \d{1,2}. \d{4}')">
<xsl:choose>
<xsl:when test="not(contains($date-string,'['))">
<xsl:value-of select="replace($date-string,'(\d{1,2}). (\d{1,2}). (\d{4})','$1\\mini$2\\mini$3')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(substring(replace($date-string-without-square-brackets,'(\d{1,2}). (\d{1,2}). (\d{4})','$1\\mini$2\\mini$3'),0,$opening-square-bracket),'[',substring(replace($date-string-without-square-brackets,'(\d{1,2}). (\d{1,2}). (\d{4})','$1\\mini$2\\mini$3'),$opening-square-bracket, $closing-square-bracket))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$date-string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
I finally managed to solve it with recursion. Basically I have the test whether it suits the regex dd. mm. yyyy when the brackets are removed. as this works i can now rebuild the whole string.
<xsl:function name="foo:date-repeat">
<xsl:param name="date-string" as="xs:string"/>
<xsl:param name="amount" as="xs:integer"/>
<xsl:param name="counter" as="xs:integer"/>
<xsl:choose>
<xsl:when test="substring($date-string,$counter,1) =' '">
<xsl:text>\mini</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($date-string,$counter,1)"/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="$counter <= $amount">
<xsl:value-of select="foo:date-repeat($date-string, $amount,$counter+1)"/>
</xsl:if>
</xsl:function>
<xsl:function name="foo:date-translate">
<xsl:param name="date-string" as="xs:string"/>
<xsl:variable name="date-string-without-square-brackets" as="xs:string" select="replace(replace($date-string,'\[',''),'\]','')"/>
<xsl:choose>
<xsl:when test="matches($date-string-without-square-brackets,'\d{1,2}. \d{1,2}. \d{4}')">
<xsl:choose>
<xsl:when test="not(contains($date-string,'['))"> <!-- Daten ohne eckige Klammer -->
<xsl:value-of select="replace($date-string,'(\d{1,2}). (\d{1,2}). (\d{4})','$1\\mini$2\\mini$3')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="foo:date-repeat($date-string, string-length($date-string),1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$date-string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
I have even expanded my solution, now the function checks tests every space ' ' if before is a dot or a dot and a bracket and afterwards is a number or a bracket and a number:
<xsl:function name="foo:date-repeat">
<xsl:param name="date-string" as="xs:string"/>
<xsl:param name="amount" as="xs:integer"/>
<xsl:param name="counter" as="xs:integer"/>
<xsl:choose>
<xsl:when test="substring($date-string,$counter,1) =' ' and ((substring($date-string,$counter -1,1) = '.' and number(substring($date-string,$counter -2,1)) = number(substring($date-string,$counter -2,1))) or (substring($date-string,$counter -2,2) = '.]' and number(substring($date-string,$counter -3,1)) = number(substring($date-string,$counter -3,1))))">
<xsl:choose>
<xsl:when test="number(substring($date-string,$counter +1,1)) = number(substring($date-string,$counter +1,1))">
<xsl:text>\mini</xsl:text>
</xsl:when>
<xsl:when test="substring($date-string,$counter +1,1) ='[' and number(substring($date-string,$counter +2,1)) = number(substring($date-string,$counter +2,1))">
<xsl:text>\mini</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($date-string,$counter,1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="substring($date-string,$counter,1) ='['">
<xsl:text>{[}</xsl:text>
</xsl:when>
<xsl:when test="substring($date-string,$counter,1) =']'">
<xsl:text>{]}</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($date-string,$counter,1)"/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="$counter <= $amount">
<xsl:value-of select="foo:date-repeat($date-string, $amount,$counter+1)"/>
</xsl:if>

XSLT - Replace multiple new line with single new line

I'm looking to replace multiple consecutive new line characters with a single new line character in the result of an XSLT.
Within an xml node I could have the following:
<description>
A description that goes over multiple lines, because:
- someone inputted the data in another system
- and there are gaps between lines
Another gap above this
</description>
I'd like the text out of this node to appear as so:
A description that goes over multiple lines, because:
- someone inputted the data in another system
- and there are gaps between lines
Another gap above this
Is there a way to do this with an XSLT?
Using XSLT 1.0 (libxslt)
How about:
<xsl:template match="description">
<xsl:call-template name="normalize-returns">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="normalize-returns">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text, '
')">
<!-- recursive call -->
<xsl:call-template name="normalize-returns">
<xsl:with-param name="text">
<xsl:value-of select="substring-before($text, '
')"/>
<xsl:text>
</xsl:text>
<xsl:value-of select="substring-after($text, '
')"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

How to transform a number (1,2,3, etc) into an ordinal number (1st, 2nd, 3rd, etc) using xslt

Pretty simple question, how can I transform a number (1, 2, 3, etc) into a print friendly ordinal number (1st, 2nd, 3rd, etc) using xslt?
Currently the following works for 1-20 but we may be seeing larger sets of entities getting ranked soon:
<xsl:template name="FormatRanking">
<xsl:param name="Value"></xsl:param>
<xsl:choose>
<xsl:when test="$Value = '1'">
<xsl:value-of select="$Value"/>st
</xsl:when>
<xsl:when test="$Value = '2'">
<xsl:value-of select="$Value"/>nd
</xsl:when>
<xsl:when test="$Value = '3'">
<xsl:value-of select="$Value"/>rd
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Value"/>th
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The only way I would know how to do this would be to change the xsl:when's:
<xsl:when test="$Value = '1'">
<xsl:when test="$Value = '2'">
<xsl:when test="$Value = '3'">
to (not even sure if this is correct):
<xsl:when test="$Value = '1' or $Value = '21' or $Value = '31' ...">
<xsl:when test="$Value = '2' or $Value = '22' or $Value = '33' ...">
<xsl:when test="$Value = '3' or $Value = '22' or $Value = '33' ...">
I'd like to do something similar to this Is there an easy way to create ordinals in C#? but I'm not sure if it's possible in Xslt.
At this point we only need an English solution.
Here's the solution from "Is there an easy way to create ordinals in C#?", translated to XSLT:
<xsl:template name="FormatRanking">
<xsl:param name="Value" select="0" />
<xsl:value-of select="$Value"/>
<!-- a little parameter sanity check (integer > 0) -->
<xsl:if test="
translate($Value, '0123456789', '') = ''
and
$Value > 0
">
<xsl:variable name="mod100" select="$Value mod 100" />
<xsl:variable name="mod10" select="$Value mod 10" />
<xsl:choose>
<xsl:when test="$mod100 = 11 or $mod100 = 12 or $mod100 = 13">th</xsl:when>
<xsl:when test="$mod10 = 1">st</xsl:when>
<xsl:when test="$mod10 = 2">nd</xsl:when>
<xsl:when test="$mod10 = 3">rd</xsl:when>
<xsl:otherwise>th</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
I'm not saying it's a good idea to do this in xslt, but...
<xsl:template name="FormatRanking">
<xsl:param name="Value"></xsl:param>
<xsl:choose>
<xsl:when test="substring($Value,string-length($Value)-1) = '11'">
<xsl:value-of select="$Value"/>th
</xsl:when>
<xsl:when test="substring($Value,string-length($Value)-1) = '12'">
<xsl:value-of select="$Value"/>th
</xsl:when>
<xsl:when test="substring($Value,string-length($Value)-1) = '13'">
<xsl:value-of select="$Value"/>th
</xsl:when>
<xsl:when test="substring($Value,string-length($Value)) = '1'">
<xsl:value-of select="$Value"/>st
</xsl:when>
<xsl:when test="substring($Value,string-length($Value)) = '2'">
<xsl:value-of select="$Value"/>nd
</xsl:when>
<xsl:when test="substring($Value,string-length($Value)) = '3'">
<xsl:value-of select="$Value"/>rd
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Value"/>th
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Edit: or a bit neater solution:
<xsl:template name="FormatRanking">
<xsl:param name="Value"></xsl:param>
<xsl:variable name="penultimateChar" select="substring($Value,string-length($Value)-1, 1)"/>
<xsl:variable name="lastChar" select="substring($Value,string-length($Value))"/>
<xsl:value-of select="$Value"/>
<xsl:choose>
<xsl:when test="$penultimateChar= '1'">
<xsl:text>th </xsl:text>
</xsl:when>
<xsl:when test="$lastChar = '1'">
<xsl:text>st </xsl:text>
</xsl:when>
<xsl:when test="$lastChar = '2'">
<xsl:text>nd </xsl:text>
</xsl:when>
<xsl:when test="$lastChar = '3'">
<xsl:text>rd </xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>th </xsl:text>
</xsl:otherwise>
</xsl:choose>
IMO, by using regular expressions (XSLT 2.0), this can be done more concisely:
<xsl:template name="FormatRanking">
<xsl:param name="Value"/>
<xsl:choose>
<xsl:when test="matches(string($Value), '.+[^1][123]$')">
<xsl:value-of select="replace(replace(replace(string($Value), '1$', '1st'), '2$', '2nd'), '3$', '3rd')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(string($Value), 'th')" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This checks for applicability of the special rule before using a regular expression which recognises number strings ending on 1/2/3, excluding 11/12/13. Only then special handling is applied. Otherwise the "th" is just appended.
Please note that the $Value is converted explicitly to a string datatype before applying string operations to avoid potentially funny situations. The return value is anyway implicitly a string.
However, just as a side comment, this only works for the English language. I'd be interested in a true multilingual approach....