In my Xsl document I need to create a link with text containing line break, creating two sentences.
I'm using tokenize to add words by splitting on the whitespace character.
<xsl:variable name="tokens" select="tokenize($sentece, '\s+')"></xsl:variable>
The first sentence will contain three words, by simply selecting $tokens[1] $tokens[2] $tokens[3] <br />
Now, how do I select the arbitrary amount of remaining words in the tokenize array? Is it possible to do a for-loop on the $tokens array?
You can do a for-each and check position() > 3 like this...
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="sentance">The first sentance. The second...</xsl:variable>
<xsl:variable name="tokens" select="tokenize($sentance, '\s+')"/>
<xsl:for-each select="$tokens">
<xsl:if test="position() > 3">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output...
The second...
Related
I am trying to make an XSLT transformation from XML, I want to transform font style tags into HTML tags, but my I am doing something wrong.
My XML file is like this one :
<root>
<p>
<span>
<i/>
italic
</span>
<span>
<i/>
<b/>
bold-italic
</span>
<span>
normal
</span>
</p>
</root>
What I want is HTML with the same tags but my XSLT transformation does not work:
HTML:
<p>
<i>italic</i>
<i><b>bold-italic</b></i>
normal
<p>
I was trying xsl:if condition but it does not work,i do not know what I am doing wrong:
XSLT:
<xsl:template match="p">
<p>
<xsl:for-each select="span">
<xsl:if test="i">
<i>
<xsl:value-of select="."/>
</i>
</xsl:if>
<xsl:if test="b">
<b>
<xsl:value-of select="."/>
</b>
</xsl:if>
</xsl:for-each>
</p>
</xsl:template>
Do you know how to repair my code ?
Can you have more than just b and i elements? It may be possible to do this with a generic solution, that creates a nested element for each child element of a span element.
This solution uses a recursive template, that matches span, but with a parameter contain the index number of the child element that needs to be output. When this index exceeds the number of child elements, the text is output.
Try this XSLT too:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span">
<xsl:param name="num" select="1"/>
<xsl:variable name="childElement" select="*[$num]"/>
<xsl:choose>
<xsl:when test="$childElement">
<xsl:element name="{local-name($childElement)}">
<xsl:apply-templates select=".">
<xsl:with-param name="num" select="$num + 1"/>
</xsl:apply-templates>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This does assume that all the span element only contain elements you want to nest, in addition to the text.
You can test the contents of span element using an XPath expression with a predicate which tests for its contents, and match different templates for each situation. Since you need b and i for bold-italic, you should use that expression in one of your predicates.
The stylesheet below does the transformation using only templates (without the need of a for-each). I'm assuming the contents of your <span> elements is text (not mixed content):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:strip-space elements="*"/>
<xsl:template match="p">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="span[i]">
<i><xsl:value-of select="."/></i>
</xsl:template>
<xsl:template match="span[b]">
<b><xsl:value-of select="."/></b>
</xsl:template>
<xsl:template match="span[i and b]">
<i><b><xsl:value-of select="."/></b></i>
</xsl:template>
</xsl:stylesheet>
I have a fairly simple xsl stylesheet for transforming an xml doc that defines our html into an html format (please don't ask why, it's just the way we have to do it...)
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="HtmlElement">
<xsl:element name="{ElementType}">
<xsl:apply-templates select="Attributes"/>
<xsl:value-of select="Text"/>
<xsl:apply-templates select="HtmlElement"/>
</xsl:element>
</xsl:template>
<xsl:template match="Attributes">
<xsl:apply-templates select="Attribute"/>
</xsl:template>
<xsl:template match="Attribute">
<xsl:attribute name="{Name}">
<xsl:value-of select="Value"/>
</xsl:attribute>
</xsl:template>
The issue came up when I ran across this little bit of HTML requiring transformation:
<p>
Send instant ecards this season <br/> and all year with our ecards!
</p>
the <br/> in the middle breaks the logic of the transformation and gives me only the first half of the paragraph block: Send instant ecards this season <br></br>. The XML attempting to be transformed looks like:
<HtmlElement>
<ElementType>p</ElementType>
<Text>Send instant ecards this season </Text>
<HtmlElement>
<ElementType>br</ElementType>
</HtmlElement>
<Text> and all year with our ecards!</Text>
</HtmlElement>
Suggestions?
You can simply add a new rule for Text elements and then match both HTMLElements and Texts:
<xsl:template match="HtmlElement">
<xsl:element name="{ElementName}">
<xsl:apply-templates select="Attributes"/>
<xsl:apply-templates select="HtmlElement|Text"/>
</xsl:element>
</xsl:template>
<xsl:template match="Text">
<xsl:value-of select="." />
</xsl:template>
You could make the stylesheet a bit more generic in order to handle additional elements by adjusting the template for HtmlElement to ensure that it applies templates first to the Attributes element, and then to all elements except for the Attributes and HtmlElement elements by using a predicate filter in the select attribute of the xsl:apply-templates.
The built-in templates will match the Text element and will copy the text() to the output.
Also, the template for the root node that you currently have declared (i.e. match="/") can be removed. It simply re-defines what is already handled by the built-in template rules and does not do anything to change behavior, just clutters your stylesheet.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<!-- <xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>-->
<xsl:template match="HtmlElement">
<xsl:element name="{ElementType}">
<xsl:apply-templates select="Attributes"/>
<!--apply templates to all elements except for ElementType and Attributes-->
<xsl:apply-templates select="*[not(self::Attributes|self::ElementType)]"/>
</xsl:element>
</xsl:template>
<xsl:template match="Attributes">
<xsl:apply-templates select="Attribute"/>
</xsl:template>
<xsl:template match="Attribute">
<xsl:attribute name="{Name}">
<xsl:value-of select="Value"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
I have problems with the Xpath expression test="$roles/roles/role='HOBSCS1GB'" . Can anyone help in solving. Thanks
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<xsl:variable name="roles">
<roles>
<role>HOBSCS1ROI</role>
<role>HOBSCS1GB</role>
<role>HOBSCS1FT</role>
</roles>
</xsl:variable>
<xsl:if test="$roles/roles/role='HOBSCS1GB'">
<xsl:value-of select="'YES'"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Assuming you want to find if a the roles element has one or more role elements with text = 'HOBSCS1GB': (Works in Saxon)
<xsl:if test="$roles/roles[role='HOBSCS1GB']">
<xsl:value-of select="'YES'"/>
</xsl:if>
Note that certain parsers like Microsoft may require you to tell the parser that $roles is a result tree fragment, by using node-set(), like so: (Works in msxsl)
<xsl:stylesheet ... xmlns:msxsl="urn:schemas-microsoft-com:xslt" ... />
<xsl:if test="msxsl:node-set($roles)/roles[role='HOBSCS1GB']">
<xsl:value-of select="'YES'"/>
</xsl:if>
Or in xsltproc:
<xsl:stylesheet ... xmlns:exsl="http://exslt.org/common" ... />
<xsl:if test="exsl:node-set($roles)/roles[role='HOBSCS1GB']">
<xsl:value-of select="'YES'"/>
</xsl:if>
I want to create multiple html table pages using XML as the input and xsl as the transformation language.
Now these tables should always have a fixed height, whether it's just one row or ten.
I can't get it to work with CSS (min-height).
So I was wondering, if it is possible to get xsl to always output ten rows and add empty rows in case there are less then ten rows or adding rows in case there are more then ten rows existent in the XML and therefore resizing the table.
Any ideas how this can be achieved?
You sure can do that. I can show you how you would split your data into tables each having ten rows stuffing up the last one (or maybe the only one) with dummy rows when you don't have enough. It should help you get going where you need to go (without an example XML input and desired HTML output this is as much as I can do)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:apply-templates select="data/row[position() mod 10 = 1]" mode="newtable"/>
</xsl:template>
<xsl:template match="row" mode="newtable">
<table>
<xsl:apply-templates select="."/>
<xsl:apply-templates select="following-sibling::row[position() < 10]"/>
<xsl:call-template name="dummy-rows">
<xsl:with-param
name="how-many"
select="9 - count(following-sibling::row[position() < 10])"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template match="row">
<tr><td><xsl:value-of select="."/></td></tr>
</xsl:template>
<xsl:template name="dummy-rows">
<xsl:param name="how-many" select="0"/>
<xsl:if test="$how-many > 0">
<tr><td>dummy</td></tr>
<xsl:call-template name="dummy-rows">
<xsl:with-param name="how-many" select="$how-many - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The idea is that you start your table with the "first" node of each set of 10. That's the [position() mod 10 = 1] predicate. When you get a hold of the starting point of your table you create the table boundaries and process that node again in a normal mode. Then you get the next nine data rows that follow it. Finally, you add as many dummy nodes as you need to make sure you got the 10 total in each table. The dummy-rows template is a recursion. So two techniques here: splitting the set by position() mod and using a recursion to implement iteration.
UPDATE If you only need to make sure you have at least ten rows in your table then you don't need the split logic:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:template match="/">
<table>
<xsl:apply-templates select="data/row"/>
<xsl:call-template name="dummy-rows">
<xsl:with-param
name="how-many"
select="10 - count(data/row)"/>
</xsl:call-template>
</table>
</xsl:template>
<xsl:template match="row">
<tr><td><xsl:value-of select="."/></td></tr>
</xsl:template>
<xsl:template name="dummy-rows">
<xsl:param name="how-many" select="0"/>
<xsl:if test="$how-many > 0">
<tr><td>dummy</td></tr>
<xsl:call-template name="dummy-rows">
<xsl:with-param name="how-many" select="$how-many - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
You can try this with an input like this:
<data>
<row>1</row>
<row>1</row>
<row>3</row>
</data>
or an input like this:
<data>
<row>1</row>
<row>2</row>
<row>3</row>
<row>4</row>
<row>5</row>
<row>6</row>
<row>7</row>
<row>8</row>
<row>9</row>
<row>10</row>
<row>11</row>
<row>12</row>
</data>
In both cases the result was as expected. Try it. You should be able to take it from here.
I have an xml that has a description node:
<config>
<desc>A <b>first</b> sentence here. The second sentence with some link The link. The <u>third</u> one.</desc>
</config>
I am trying to split the sentences using dot as separator but keeping in the same time in the HTML output the eventual HTML tags.
What I have so far is a template that splits the description but the HTML tags are lost in the output due to the normalize-space and substring-before functions.
My current template is given below:
<xsl:template name="output-tokens">
<xsl:param name="sourceText" />
<!-- Force a . at the end -->
<xsl:variable name="newlist" select="concat(normalize-space($sourceText), ' ')" />
<!-- Check if we have really a point at the end -->
<xsl:choose>
<xsl:when test ="contains($newlist, '.')">
<!-- Find the first . in the string -->
<xsl:variable name="first" select="substring-before($newlist, '.')" />
<!-- Get the remaining text -->
<xsl:variable name="remaining" select="substring-after($newlist, '.')" />
<!-- Check if our string is not in fact a . or an empty string -->
<xsl:if test="normalize-space($first)!='.' and normalize-space($first)!=''">
<p><xsl:value-of select="normalize-space($first)" />.</p>
</xsl:if>
<!-- Recursively apply the template for the remaining text -->
<xsl:if test="$remaining">
<xsl:call-template name="output-tokens">
<xsl:with-param name="sourceText" select="$remaining" />
</xsl:call-template>
</xsl:if>
</xsl:when>
<!--If no . was found -->
<xsl:otherwise>
<p>
<!-- If the string does not contains a . then display the text but avoid
displaying empty strings
-->
<xsl:if test="normalize-space($sourceText)!=''">
<xsl:value-of select="normalize-space($sourceText)" />.
</xsl:if>
</p>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
and I am using it in the following manner:
<xsl:template match="config">
<xsl:call-template name="output-tokens">
<xsl:with-param name="sourceText" select="desc" />
</xsl:call-template>
</xsl:template>
The expected output is:
<p>A <b>first</b> sentence here.</p>
<p>The second sentence with some link The link.</p>
<p>The <u>third</u> one.</p>
A good question, and not an easy one to solve. Especially, of course, if you're using XSLT 1.0 (you really need to tell us if that's the case).
I've seen two approaches to the problem. Both involve breaking it into smaller problems.
The first approach is to convert the markup into text (for example replace <b>first</b> by [b]first[/b]), then use text manipulation operations (xsl:analyze-string) to split it into sentences, and then reconstitute the markup within the sentences.
The second approach (which I personally prefer) is to convert the text delimiters into markup (convert "." to <stop/>) and then use positional grouping techniques (typically <xsl:for-each-group group-ending-with="stop"/> to convert the sentences into paragraphs.)
Here is one way to implement the second approach suggested by Michael Kay using XSLT 2.
This stylesheet demonstrates a two-pass transformation where the first pass introduces <stop/> markers after each sentence and the second pass encloses all groups ending with a <stop/> in a paragraph.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- two-pass processing -->
<xsl:template match="/">
<xsl:variable name="intermediate">
<xsl:apply-templates mode="phase-1"/>
</xsl:variable>
<xsl:apply-templates select="$intermediate" mode="phase-2"/>
</xsl:template>
<!-- identity transform -->
<xsl:template match="#*|node()" mode="#all" priority="-1">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<!-- phase 1 -->
<!-- insert <stop/> "milestone markup" after each sentence -->
<xsl:template match="text()" mode="phase-1">
<xsl:analyze-string select="." regex="\.\s+">
<xsl:matching-substring>
<xsl:value-of select="regex-group(0)"/>
<stop/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<!-- phase 2 -->
<!-- turn each <stop/>-terminated group into a paragraph -->
<xsl:template match="*[stop]" mode="phase-2">
<xsl:copy>
<xsl:for-each-group select="node()" group-ending-with="stop">
<p>
<xsl:apply-templates select="current-group()" mode="#current"/>
</p>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<!-- remove the <stop/> markers -->
<xsl:template match="stop" mode="phase-2"/>
</xsl:stylesheet>
This is my humble solution, based on the second suggestion of #Michael Kay answer.
Differently from #Jukka answer (which is very elegant indeed) I'm not using xsl:analyse-string, as XPath 1.0 functions contains and substring-after are enough to accomplish the split. I've also started the match pattern from the config.
Here's the transform:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- two pass processing -->
<xsl:template match="config">
<xsl:variable name="pass1">
<xsl:apply-templates select="node()"/>
</xsl:variable>
<xsl:apply-templates mode="pass2" select="$pass1/*"/>
</xsl:template>
<!-- 1. Copy everything as is (identity) -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- 1. Replace "text. text" with "text<dot/> text" -->
<xsl:template match="text()[contains(.,'. ')]">
<xsl:value-of select="substring-before(.,'. ')"/>
<dot/>
<xsl:value-of select="substring-after(.,'. ')"/>
</xsl:template>
<!-- 2. Group by examining in population order ending with dot -->
<xsl:template match="desc" mode="pass2">
<xsl:for-each-group select="node()"
group-ending-with="dot">
<p><xsl:apply-templates select="current-group()" mode="pass2"/></p>
</xsl:for-each-group>
</xsl:template>
<!-- 2. Identity -->
<xsl:template match="node()|#*" mode="pass2">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="pass2"/>
</xsl:copy>
</xsl:template>
<!-- 2. Replace dot with mark -->
<xsl:template match="dot" mode="pass2">
<xsl:text>.</xsl:text>
</xsl:template>
</xsl:stylesheet>
Applied on the input shown in your question, produces:
<p>A <b>first</b> sentence here.</p>
<p>The second sentence with some link The link.</p>
<p>The <u>third</u> one.</p>
this might do the trick:
http://symphony-cms.com/download/xslt-utilities/view/20816/
/J