> How do I have to escape > quotes to get a well-formed XPath string?
First just consider the xpath syntax.
You can use " or ' to delimit a string literal, so if you only want one then you can delimit with the other.
"'" or '"'
if you want both then you can not do it directly in a string literal but you can construct the string '" using
translate('a"','a',"'")
or
concat("'",'"')or if you drop out of xpath, to xslt
<xsl:variable name="x">'"</xsl:variable>
then use $x as this result tree fragment will coerce to a string.
Then you need to get one of those expressions into an XML attribute If you use " to delimit the attribute value then you need to quote " so you end up with
select="translate('a"','a',"'")"which looks a bit odd but the XML parser eats that and gives the xpath system translate('a"','a',"'") which takes the string a" and replaces the a by '.
Can you think of a way to decompose a URL? I want to take something like: http://www.agilic.com/purchase.htm and end up with purchase.htm. ...
You can do this by using a recursive named template, e.g.
<xsl:template name="filename">
<xsl:param name="x"/>
<xsl:choose>
<xsl:when test="contains($x,'/')">
<xsl:call-template name="filename">
<xsl:with-param name="x" select="substring-after($x,'/')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$x"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
and call it somehow like :
<xsl:call-template name="filename"> <xsl:with-param name="x" select="."/> </xsl:call-template>
Jens Lautenbacher completes the picture with
This template get's a filename and gives back the directory part of it. Giving back the filename part is achieved along the same line
You call it somehow like
<xsl:call-template name="strip"> <xsl:with-param name="relfile">foo/bar/baz.xml</xsl:with-param> </xsl:call-template>
<xsl:template name="strip">
<xsl:param name="reldir"/>
<xsl:param name="relfile"/>
<xsl:choose>
<xsl:when test="contains($relfile, '/')">
<xsl:call-template name="strip">
<xsl:with-param name="relfile">
<xsl:value-of select="substring-after($relfile,'/')"/>
</xsl:with-param>
<xsl:with-param name="reldir">
<xsl:value-of
select="concat($reldir, substring-before($relfile,'/'), '/')"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$reldir"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This seems to me to be a good instance to use xsl:key to identify the nodes that are uniquely identified through the 'Uuid' attribute. First, set up the key:
* name - a name for the key, anything you like * match - an XPath matching the nodes that you want to identify * use - an XPath (relative to the 'match' node) that identifies the node
In your case:
<xsl:key name="objects" match="*[@Uuid]" use="@Uuid" />
Note that I haven't named the (element) nodes that are identified by the key because it isn't clear to me whether your 'Class' and 'AnotherObject' elements are indicative of a whole range of possible element names in your input, but we can guarantee at least that they will have a 'Uuid' attribute if they're worth identifying!
Then you can access a particular node through its 'Uuid' attribute using the key() function, so try:
<xsl:template match ="Class">
Class:
Name="<xsl:value-of select="@Name" />"
TargetRef="<xsl:value-of select="key('objects', @TargetRef)/@Name" />"
</xsl:template>How do you put ' or " into a string?
<xsl:variable name="apos">'</xsl:variable> <xsl:variable name="quot">"</xsl:variable> <xsl:variable name="foo" select="concat($quot,'Hello, world!',$quot)"/>
How do you test for the presence of ' or " in a string?
<!-- same $apos and $quot assignments, then... --> <xsl:if test="contains($foo,$apos) or contains($foo,$quot)"> ... </xsl:if>
Jeni Tennison rounds out the discussion..
Good question! XML defines entities for ' and " (&apos; and &quot;, somewhat unsurprisingly). In certain situations, it is possible to use these. Your first example, for instance, could also be given as:
<xsl:variable name="foo" select="'&quot;Hello, world!&quot;'"/>
When this is parsed by the XML parser, the value of the 'select' attribute is set to (no extra quotes included): '"Hello, world!"'
When the XSLT Processor sees this, it recognises the external quotes as designating a string value, and so sets the variable $foo to the string (no extra quotes included): "Hello, world!"
The thing to remember is that you are escaping the " and ' *for the XML parser* and not for the XSLT processor. So your second example:
>How do you test for the presence of ' or " in a string? > ><!-- same $apos and $quot assignments, then... --> ><xsl:if test="contains($foo,$apos) or contains($foo,$quot)"> > ... ></xsl:if>
can be escaped as:
<xsl:if test="contains($foo, &quot;'&quot;) or contains($foo '&quot;')"> ... </xsl:if>
As there are no unescaped "s within the attribute value, the XML parser can parse this and emerges with the value of the 'test' attribute as:
contains($foo, "'") or contains($foo, '"')
The XSLT processor can again recognise that "'" designates a string with the value of the single character ' and that '"' designates a string with the value of the single character ".
Similarly, if you wanted single quotes rather than double quotes around your Hello, World!, then you should do:
<xsl:variable name="foo" select=""'Hello, world!'"" />
(-> "'Hello, world!'" att. value -> 'Hello, world!' string)
[Or, alternatively:
<xsl:variable name="foo" select='"'Hello, world!'"' />
(-> "'Hello, world!'" att. value -> 'Hello, world!' string)]
So, for fairly simple situations like this, it is enough to use the normal XML escaping to get the XSLT processor to see something that it can understand. However, difficulties arise when the quote nesting goes deeper than this. For example, if you wanted to see whether a string contains the string (no extra quotes): "You're here"
There is no way to wrap quotes around that string, and no way that I know of within XSLT/XPath to escape internal quotes like this (the XSLT processor is not an XML parser - it won't detect and recognise "/' itself). In these cases, your method, using variables, is the only solution.
(BTW, I'd personally declare the variables as:
<xsl:variable name="apos" select='"'"' /> <xsl:variable name="quot" select="'"'" />
so that they are set as strings rather than result tree fragments.)
To me that was not rudimentary ( taking into account that there could be exotic situations when some long word is really > 60, so space-scanning could fail ). Anyway.
The snippet below takes into account the whitespace not splitting the word in the middle. It looks back to the first space to prevent that. if it fails - it just splits. tune-width does it.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/doc">
<HTML><BODY><PRE>
<xsl:call-template name="format">
<xsl:with-param select="normalize-space(para)" name="txt" />
<xsl:with-param name="width">30</xsl:with-param>
</xsl:call-template>
</PRE></BODY></HTML>
</xsl:template>
<xsl:template name="format">
<xsl:param name="txt" />
<xsl:param name="width" />
<xsl:if test="$txt">
<xsl:variable name="real-width">
<xsl:call-template name="tune-width">
<xsl:with-param select="$txt" name="txt" />
<xsl:with-param select="$width" name="width" />
<xsl:with-param select="$width" name="def" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="substring($txt, 1, $real-width)" />
<xsl:text>
</xsl:text>
<xsl:call-template name="format">
<xsl:with-param select="substring($txt,$real-width + 1)" name="txt" />
<xsl:with-param select="$width" name="width" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="tune-width">
<xsl:param name="txt" />
<xsl:param name="width" />
<xsl:param name="def" />
<xsl:choose>
<xsl:when test="$width = 0">
<xsl:value-of select="$def" />
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="substring($txt, $width, 1 ) = ' '">
<xsl:value-of select="$width" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="tune-width">
<xsl:with-param select="$txt" name="txt" />
<xsl:with-param select="$width - 1" name="width" />
<xsl:with-param select="$def" name="def" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Input:
<doc> <para> 123456 2345 343434 545454 43434 343 12345 343434 545454 43434 343 32345645 343434 545454 43434 343 3422222225 343434 545454 43434 343 llllllllllllllllllllllooooooooooooooonnnnnnnnnnnggggggggg 345 343434 545454 43434 343 </para> </doc>
Output:
<HTML> <BODY> <PRE>123456 2345 343434 545454 43434 343 12345 343434 545454 43434 343 32345645 343434 545454 43434 343 3422222225 343434 545454 43434 343 lllllllllllllllllllllloooooooo ooooooonnnnnnnnnnnggggggggg 345 343434 545454 43434 343 </PRE> </BODY> </HTML>
I had need to split out an element content into cross references
<doc> <elem>5,6,7</elem> </doc>
to produce <a href="#id5>5</a> etc.
<xsl:template match="doc/elem">
<body>
<xsl:call-template name="links">
<xsl:with-param name="str" select="."/>
</xsl:call-template>
</body>
</xsl:template>
<xsl:template name="links">
<xsl:param name="str"/>
<xsl:choose>
<xsl:when test="contains($str,',')">
<a href="#id{substring-before($str,',')}"><xsl:value-of
select="substring-before($str,',')"/></a>
<xsl:text> </xsl:text>
<xsl:call-template name="links">
<xsl:with-param name="str" select="substring-after($str,',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<a href="#id{$str}"><xsl:value-of select="$str"/></a>
<xsl:text> </xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
These templates insert "<br/>"s between the lines of an address. Adapt at will :-)
Test input
<address> some text on multiple lines which is required to be split up into verbatim lines, for html output </address>
Stylesheet.
<xsl:template match="address//text()">
<xsl:call-template name="make-verbatim">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="make-verbatim">
<xsl:param name="text" select="''"/>
<xsl:variable name="starts-with-space"
select="substring($text, 1, 1) = ' '"/>
<xsl:variable name="starts-with-nl"
select="substring($text, 1, 1) = '
'"/>
<xsl:variable name="before-space">
<xsl:if test="contains($text, ' ')">
<xsl:value-of select="substring-before($text, ' ')"/>
</xsl:if>
</xsl:variable>
<xsl:variable name="before-nl">
<xsl:if test="contains($text, '
')">
<xsl:value-of
select="substring-before($text, '
')"/>
</xsl:if>
</xsl:variable>
<xsl:choose>
<xsl:when test="$starts-with-space">
<xsl:text> </xsl:text>
<xsl:call-template name="make-verbatim">
<xsl:with-param name="text"
select="substring($text,2)"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$starts-with-nl">
<br/><xsl:text>
</xsl:text>
<xsl:call-template name="make-verbatim">
<xsl:with-param name="text"
select="substring($text,2)"/>
</xsl:call-template>
</xsl:when>
<!-- if the string before a space
is shorter than the string before
a newline, fix the space...-->
<xsl:when test="$before-space != ''
and ((string-length($before-space)
< string-length($before-nl))
or $before-nl = '')">
<xsl:value-of select="$before-space"/>
<xsl:text> </xsl:text>
<xsl:call-template name="make-verbatim">
<xsl:with-param name="text"
select="substring-after($text, ' ')"/>
</xsl:call-template>
</xsl:when>
<!-- if the string before a newline
is shorter than the string before
a space, fix the newline...-->
<xsl:when test="$before-nl != ''
and ((string-length($before-nl)
< string-length($before-space))
or $before-space = '')">
<xsl:value-of select="$before-nl"/>
<br/><xsl:text>
</xsl:text>
<xsl:call-template name="make-verbatim">
<xsl:with-param name="text"
select="substring-after($text, '
')"/>
</xsl:call-template>
</xsl:when>
<!-- the string before the newline and the string before the
space are the same; which means they must both be empty -->
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
test.xml
<?xml version="1.0"?>
<Text>
This is where the actual article starts. The article contains
several paragraphs.
Paragaraphs are separated by Carriage returns or Linefeeds, not by Tags
</Text>
test.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:call-template name="break" />
</xsl:template>
<xsl:template name="break">
<xsl:param name="text" select="."/>
<xsl:choose>
<xsl:when test="contains($text, '
')">
<xsl:value-of select="substring-before($text, '
')"/>
<br/>
<xsl:call-template name="break">
<xsl:with-param name="text" select="substring-after($text,'
')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output.
<?xml version="1.0" encoding="utf-8"?><Text><br/> This is where the actual article starts. The article contains<br/> several paragraphs.<br/> <br/> Paragaraphs are separated by Carriage returns or Linefeeds, not by Tags<br/></Text>
>Is there away to force string length? For example, my >output contains a string that must contain 10 characters but the >XML source is not guaranteed to supply that number of characters.
In order to trim a long string to 10, or pad a short one with spaces:
substring(concat(string(.), ' '), 1, 10)
<xsl:variable name="x"><xsl:copy-of select="[nodeset]"/></xsl:variable> <xsl:value-of select="string-length(string($x))"/>
probably does what you want.
> So is there some way to construct a equivalent of sum(), but one that works > on string values of a nodeset?
simple cases you can get by as above, but usually you have to use a node-set extension function for this sort of thing (until xslt 1.1)
for instance if you wanted to apply normalize-space to each of your nodes in the node set before computing your average, you'd do something like
<xsl:variable name="x"> <xsl:for-each select="[nodeset]" > <x><xsl:value-of select="string-length(normalize-space(.))"/></x> </xsl:for-each> </xsl:variable>
<xsl:value-of select="sum(xt:node-set($x)/x)"/>
| I have an element that looks like this: | | <xyz> | this is the first line | this is the second line | this is the third line | </xyz> | | I'd like to transform it so that it could be outputted to html with | the same line breaks, i.e. change all \n to <br />
Here are a couple of templates I use for this purpose. The "br-replace" replaces carriage returns with <br/> tags. The "sp-replace" replaces *pairs* of leading spaces with *pairs* of leading non-breaking spaces. By combining the two in series, you can achieve the affect of keeping code listings (e.g. XML or Java source code examples in a book) properly formatted without using the <pre> tag which tends to mess up the formatting of table cells (often pushing them wider than you'd like).
To use the templates, add them (or import them) into the stylesheet you're building, then at the right moment, just do:
<!-- Call the "br-replace" template -->
<xsl:call-template name="br-replace">
<!--
| Passing the result of calling the sp-replace template
| as the value of the parameter named "text"
+-->
<xsl:with-param name="text">
<!-- Call the "sp-replace" template -->
<xsl:call-template name="sp-replace">
<!-- Passing the value of the current node -->
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
Here are the templates...
<!-- Replace new lines with html <br> tags -->
<xsl:template name="br-replace">
<xsl:param name="text"/>
<xsl:variable name="cr" select="'
'"/>
<xsl:choose>
<!-- If the value of the $text parameter contains carriage ret -->
<xsl:when test="contains($text,$cr)">
<!-- Return the substring of $text before the carriage return -->
<xsl:value-of select="substring-before($text,$cr)"/>
<!-- And construct a <br/> element -->
<br/>
<!--
| Then invoke this same br-replace template again, passing the
| substring *after* the carriage return as the new "$text" to
| consider for replacement
+-->
<xsl:call-template name="br-replace">
<xsl:with-param name="text" select="substring-after($text,$cr)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Replace two consecutive spaces w/ 2 non-breaking spaces -->
<xsl:template name="sp-replace">
<xsl:param name="text"/>
<!-- NOTE: There are two spaces ** here below -->
<xsl:variable name="sp"><xsl:text> </xsl:text></xsl:variable>
<xsl:choose>
<xsl:when test="contains($text,$sp)">
<xsl:value-of select="substring-before($text,$sp)"/>
<xsl:text>  </xsl:text>
<xsl:call-template name="sp-replace">
<xsl:with-param name="text" select="substring-after($text,$sp)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
> I read about the substring-after()-function, which can extract a substring > after the FIRST occurance of a specified string. > > My problem: Have you got an idea, how I can get the substring-after of a > string after the LAST occurance of a specified substring?? > > For example: I want the substring after the last ".". > > String: "A.B.C.0.1.1.hgk" > > The solution should return: "hgk"
The answer is recursion:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="substring-after-last">
<xsl:param name="input" />
<xsl:param name="marker" />
<xsl:choose>
<xsl:when test="contains($input,$marker)">
<xsl:call-template name="substring-after-last">
<xsl:with-param name="input"
select="substring-after($input,$marker)" />
<xsl:with-param name="marker" select="$marker" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$input" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="FOO">
<xsl:call-template
name="substring-after-last">
<xsl:with-param name="input" select="'1.2.3.4.5.6.7'" />
<xsl:with-param name="marker" select="'.'" />
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
> i'm looking for a xslt method to identify the last iteration of a
> char into a string. For example, to extract automatically the name
> of the html page into the url.
>
> string : "h ttp://www.thesite.com/directory1/dir2/dir3../pageindex.htm"
>
> there are the functions substrings-before() et substring-after(),
> but they work on the first occurence of the marker-string. Is there
> a Xslt function which gives the last occurence of a marker-string
> (like lastIndexOf('/',"string")) into a string?
No, there isn't.
You can achieve what you want through recursion. Walk through the string, taking bits off the front of it until you get to a string which has no '/' in it whatsoever.
<!-- define a lastIndexOf named template -->
<xsl:template name="lastIndexOf">
<!-- declare that it takes two parameters
- the string and the char -->
<xsl:param name="string" />
<xsl:param name="char" />
<xsl:choose>
<!-- if the string contains the character... -->
<xsl:when test="contains($string, $char)">
<!-- call the template recursively... -->
<xsl:call-template name="lastIndexOf">
<!-- with the string being the string after the character
-->
<xsl:with-param name="string"
select="substring-after($string, $char)" />
<!-- and the character being the same as before -->
<xsl:with-param name="char" select="$char" />
</xsl:call-template>
</xsl:when>
<!-- otherwise, return the value of the string -->
<xsl:otherwise><xsl:value-of select="$string" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
To get the filename of a URL held in the URL child of the current node, you can call this template like:
<xsl:call-template name="lastIndexOf">
<xsl:with-param name="string" select="URL" />
<xsl:with-param name="char" select="/" />
</xsl:call-template>
It's pretty verbose, but I'm afraid that's the only way to do it in XSLT at the moment.
Dimitre Novatchev offers
I compared times from the three templates on a 800MHz 128Mb RAM Pentium, running each test 10 times, averaging the times reported by MSXML run from the command line, and rounding to the nearest millisecond. Here are the results:
The tail recursive template is always substantially faster than the simple algorithm, but it suffers from the same problem in the end - the time taken increases exponentially rather than linearly based on the length of the string, so for really long strings the least recursive algorithm works best. I haven't taken detailed timings, but there's a similar pattern in Saxon (although Saxon bugs out with the simple algorithm and long strings, I guess a stack overflow). A processor that doesn't optimise tail recursion would probably have similar performance from both the simple and tail-recursive templates.