i've the below xml
<para>A number of the offences set out in the Companies Ordinance are expressed to apply to "officers" of the company. "Officer" includes directors, managers and the company secretary: Companies Ordinance, s.2(1).</para>
here actually the in the input the quotes given are " but i want to convert it to smart quotes. i used the below xslt for this.
<xsl:template match="para/text()">
<xsl:when test="contains(.,$quot)">
<xsl:value-of select="translate(.,$quot,'“')"/>
<xsl:value-of select="translate(substring-after(.,$quot),$quot,'”')"/>
</xsl:when>
</xsl:template>
but i am getting below ooutput.
A number of the offences set out in the Companies Ordinance are expressed to apply to “officers“ of the company. “Officer“ includes directors, managers and the company secretary: Companies Ordinance, s.2(1).
but i want to get it as below.
A number of the offences set out in the Companies Ordinance are expressed to apply to “officers” of the company. “Officer” includes directors, managers and the company secretary: Companies Ordinance, s.2(1).
please let me know how do i solve this.
Thanks
The main problem with your approach is that the translate function will replace all occurrences of the quotation mark with your smart-opening quote. Therefore, the subsequent substring-after function won't actually find anything because there won't be any more quotation marks to find.
What you really need in this case is a recursive template, together with a combination of both substring-before and substring-after. The named template could actually be combined with your existing template, with default parameters to be used in the initial match
<xsl:template match="para/text()" name="replace">
<xsl:param name="text" select="."/>
<xsl:param name="usequote" select="$openquote"/>
($openquote will be a variable containing the opening quote)
You would then check if the selected text contains a quotation mark
<xsl:when test="contains($text,$quot)">
If so, you would first output the text before the quotation mark, following by the new open quote
<xsl:value-of select="concat(substring-before($text, $quot), $usequote)"/>
The template would then be recursively called with the text after the quote, and with the close quote as a parameter, so the next quote find will be closed.
<xsl:call-template name="replace">
<xsl:with-param name="text" select="substring-after($text,$quot)"/>
<xsl:with-param name="usequote"
select="substring(concat($openquote, $closequote), 2 - ($usequote=$closequote), 1)"/>
</xsl:call-template>
Note: the selecting of the usequote will basically switch between open and close quotes. It takes advantage of the fact 'true' evaluates to 1 in a numeric expression, and 'false' to 0.
Try this XSLT. Note, I am using open and close brackets in this case, just to make the output clearer, but you can easily change the variables to your smart quotes as required. (You may have to specify an encoding for the output in this case though).
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:variable name="quot">"</xsl:variable>
<xsl:variable name="openquote">[</xsl:variable>
<xsl:variable name="closequote">]</xsl:variable>
<xsl:template match="para/text()" name="replace">
<xsl:param name="text" select="."/>
<xsl:param name="usequote" select="$openquote"/>
<xsl:choose>
<xsl:when test="contains($text,$quot)">
<xsl:value-of select="concat(substring-before($text, $quot), $usequote)"/>
<xsl:call-template name="replace">
<xsl:with-param name="text" select="substring-after($text,$quot)"/>
<xsl:with-param name="usequote"
select="substring(concat($openquote, $closequote), 2 - ($usequote=$closequote), 1)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your XML, the following is output
A number of the offences set out in the Companies Ordinance are expressed to apply to [officers] of the company. [Officer] includes directors, managers and the company secretary: Companies Ordinance, s.2(1).
Related
I have a parameterignoreAttributes which is a comma separated list of things to look for. I want to set a variable copyAttrib to be equal to whether any of them are exactly matched by name().
If xsl were a procedural language where variables could be reassigned, I'd use something like this:
<xsl:variable name="copyAttrib" select="true()">
<xsl:for-each select="tokenize($ignoreAttributes,',')">
<xsl:if test="compare(., name()) != 0">
<xsl:variable name="copyAttrib" select="false()"/>
</xsl:if>
</xsl:for-each>
Unfortunately, I can't do that, because xsl is functional (so says this other answer). So variables can only be assigned once.
I think the solution would look something like:
<vsl:variable name="copyAttrib">
<xsl:choose>
<xsl:when>
<xsl:for-each select="tokenize($ignoreAttributes, ',')">
<xsl:if test="compare(., name()) != 0"/>
</xsl:for-each>
<xsl:otherwise>
<xsl:value-of select="false()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Obviously not exactly that (otherwise I wouldn't be asking.)
I know that I could bypass the tokenize and for-each loop by just using replaces on ignoreAttributes and changing all the , to | and then using matches, but I'd like to avoid that if possible because then I need to deal with the possibility that ignoreAttributes (which the user provides) might contain some special characters that will change the regex pattern and escape them all.
I have a parameterignoreAttributes which is a comma separated list of things to look for. I want to set a variable copyAttrib to be equal to whether any of them are exactly matched by name().
That sounds to me like
<xsl:variable name="copyAttrib" as="xs:boolean"
select="tokenize($parameterignoreAttributes, ',') = name()"/>
You say:
Unfortunately, I can't do that, because xsl is functional
when what you mean is: "Fortunately, I don't need to do that, because XSLT is functional".
An XSLT-1.0 way of doing this is by using a recursive, named template:
<xsl:template name="copyAttrib">
<xsl:param name="attribs" />
<xsl:choose>
<xsl:when test="normalize-space(substring-before($attribs,',')) = normalize-space(name(.))">
<xsl:value-of select="'true'" />
</xsl:when>
<xsl:when test="normalize-space($attribs) = ''">
<xsl:value-of select="'false'" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="copyAttrib">
<xsl:with-param name="attribs" select="substring-after($attribs,',')" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Apply this template onto the current, the selected, node and wrap it in a <xsl:variable>:
<xsl:variable name="copyAttribResult">
<xsl:call-template name="copyAttrib">
<xsl:with-param name="attribs" select="'a,b,c,...commaSeparatedValues...'" />
</xsl:call-template>
</xsl:variable>
to get either true or false as a result.
I have following xml
<xml>
<xref>
is determined “in prescribed manner”
</xref>
</xml>
I want to see if we can process xslt 2 and return the following result
<xml>
<xref>
is
</xref>
<xref>
determined
</xref>
<xref>
“in prescribed manner”
</xref>
</xml>
I tried few options like replace the space and entities and then using for-each loop but not able to work it out. May be we can use tokenize function of xslt 2.0 but don't know how to use it. Any hint will be helpful.
# JimGarrison: Sorry, I couldn't resist. :-) This XSLT is definitely not elegant but it does (I assume) most of the job:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:variable name="left_quote" select="'<'"/>
<xsl:variable name="right_quote" select="'>'"/>
<xsl:template name="protected_tokenize">
<xsl:param name="string"/>
<xsl:variable name="pattern" select="concat('^([^', $left_quote, ']+)(', $left_quote, '[^', $right_quote, ']*', $right_quote,')?(.*)')"/>
<xsl:analyze-string select="$string" regex="{$pattern}">
<xsl:matching-substring>
<!-- Handle the prefix of the string up to the first opening quote by "normal" tokenizing. -->
<xsl:variable name="prefix" select="concat(' ', normalize-space(regex-group(1)))"/>
<xsl:for-each select="tokenize(normalize-space($prefix), ' ')">
<xref>
<xsl:value-of select="."/>
</xref>
</xsl:for-each>
<!-- Handle the text between the quotes by simply passing it through. -->
<xsl:variable name="protected_token" select="normalize-space(regex-group(2))"/>
<xsl:if test="$protected_token != ''">
<xref>
<xsl:value-of select="$protected_token"/>
</xref>
</xsl:if>
<!-- Handle the suffix of the string. This part may contained protected tokens again. So we do it recursively. -->
<xsl:variable name="suffix" select="normalize-space(regex-group(3))"/>
<xsl:if test="$suffix != ''">
<xsl:call-template name="protected_tokenize">
<xsl:with-param name="string" select="$suffix"/>
</xsl:call-template>
</xsl:if>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="xref">
<xsl:call-template name="protected_tokenize">
<xsl:with-param name="string" select="text()"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Notes:
There is the general assumption that white space only serves as a token delimiter and need not be preserved.
“ and rdquo; seem to be invalid in XML although they are valid in HTML. In the XSLT there are variables defined holding the quote characters. They will have to be adapted once you find the right XML representation. You can also eliminate the variables and put the characters right into the regular expression pattern. It will be significantly simplified by this.
<xsl:analyze-string> does not allow a regular expression which may evaluate into an empty string. This comes as a little problem since either the prefix and/or the proteced token and/or the suffix may be empty. I take care of this by artificially adding a space at the beginning of the pattern which allows me to search for the prefix using + (at least one occurence) instead of * (zero or more occurences).
In one of my xml message I have a particular tag in which some (unknown) of last positions are filled with a particular character(/). But during XSLT I want to remove this character and generate another string free from that characters in last positions.
Your question is very vague, but you would create an identity transform that copied every element recursively, with a special template for the elements you want to modify, like this
<xsl:template match="particular-tag">
<xsl:copy>
<xsl:value-of select="substring-before(., '/')"/>
</xsl:copy>
</xsl:template>
This will remove all characters from the first slash onwards from every <particular-tag> element.
If I understand the question correctly, you have a string which may end with zero or more slashes; you want a function or a template to strip the trailing slashes -- but not other slashes -- from the string. In XSLT 2.0, the simplest thing to do is write a function to do this. There are many ways to write the function; a straightforward one is this one (which generalizes the problem to stripping any given character off the end of a string, not just slash):
<xsl:function name="my:strip-trailing-char">
<xsl:param name="s" required="yes"/>
<xsl:param name="c" required="yes"/>
<xsl:choose>
<xsl:when test="ends-with($s,$c)">
<xsl:value-of select="my:strip-trailing-char(
substring($s,1,string-length($s) - 1),
$c
)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$s"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
In XSLT 1.0, you can do the same thing with a named template:
<xsl:template name="my:strip-trailing-char">
<xsl:param name="s" required="yes"/>
<xsl:param name="c" required="yes"/>
<xsl:choose>
<xsl:when test="ends-with($s,$c)">
<xsl:call-template name="my:strip-trailing-char">
<xsl:with-param name="s"
select="substring($s,1,string-length($s) - 1)"/>
<xsl:with-param name="c" select="$c"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$s"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I have a template with a parameter. How can I insert a tab character n times?
n is the value of the parameter.
In XSLT 2.0:
<xsl:for-each select="1 to $count"> </xsl:for-each>
(Sadly though, I suspect that if you were using XSLT 2.0 you wouldn't need to ask the question).
Another technique often used with XSLT 1.0 is the hack:
<xsl:for-each select="//*[position() <= $count]"> </xsl:for-each>
which works provided the number of elements in your source document is greater than the number of tab characters you want to output.
Just call it recursively; output a tab, then call the same template again with n-1 passed in, if n > 1.
<xsl:template name="repeat">
<xsl:param name="output" />
<xsl:param name="count" />
<xsl:if test="$count > 0">
<xsl:value-of select="$output" />
<xsl:call-template name="repeat">
<xsl:with-param name="output" select="$output" />
<xsl:with-param name="count" select="$count - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
As has been pointed out, this example will actually output a minimum of one. In my experience where the output is whitespace, it's usually needed. You can adapt the principle of a recursive template like this any way you see fit.
This seems the simplest and most flexible to me.
For XSLT 1.0 (or perhaps 1.1).
<xsl:variable name="count">10</xsl:variable>
<xsl:variable name="repeat"><xsl:text> </xsl:text></xsl:variable>
<xsl:sequence select="string-join((for $i in 1 to $count return $repeat),'')"/>
Of course the count variable is where you assign your n parameter.
I used the variable repeat to hold the tab character, but you could just replace the $repeat with the tab character in single quotes in the sequence element. Note: This variable can be of a length greater than 1, which creates a whole bunch of possibilities.
It does not use recursion, so it won't run into a recursion limit.
I don't know the maximum value you can use for count, but I tested it up to 10,000.
Globally define a long enough array of tabs:
<xsl:variable name="TABS" select="' '" />
Then use like this:
<xsl:value-of select="fn:substring($TABS, 1, fn:number($COUNT))" />
(XSLT 1.0)
<xsl:template name="tabs">
<xsl:param name="n"/>
<xsl:if test="$n > 0"> <!-- When n = 0, output nothing. -->
<xsl:call-template name="tabs"> <!-- Recursive call: call same template... -->
<xsl:with-param name="n" select="$n - 1"/> <!-- ... for writing n - 1 tabs. -->
</xsl:call-template>
<xsl:text> </xsl:text> <!-- Add one tab character. -->
</xsl:if>
</xsl:template>
Example usage:
<xsl:call-template name="tabs">
<xsl:with-param name="n" select="3"/>
</xsl:call-template>
I've discovered an LGPL-licensed library for doing this called functx, as I was sure someone had to have already done this... This is a "standard library" type XSLT library, which contains a function called repeat-string. From the docs:
The functx:repeat-string function returns a string consisting of a given number of copies of $stringToRepeat concatenated together.
Where I use it like this in my code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:functx="http://www.functx.com">
<xsl:import href="../buildlib/functx-1.0.xsl"/>
<xsl:output omit-xml-declaration="yes" />
<xsl:variable name="INDENT" select="' '" />
....
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="data-pusher-properties">
<xsl:for-each select="property">
<xsl:choose>
...
<xsl:when test="boolean(#value = '${pusher.notifications.server}')">
<xsl:value-of select="functx:repeat-string($INDENT, #indent)" />
<xsl:text>"</xsl:text>
<xsl:value-of select="#name" />
<xsl:text>": </xsl:text>
<xsl:text>"</xsl:text>
<xsl:value-of select="$pusher.notifications.email.server" />
<xsl:text>"\
</xsl:text>
</xsl:when>
...
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
So for printing a tab character n times, call it like this:
<xsl:value-of select="functx:repeat-string(' ', n)" />
I know this question is old, but I hope this can still help someone.
Documentation for the repeat-string function
I have an xml file in which there is tag namely, <Gender/> It carries either 'M' or 'F' as data, now my work is to test the value and write <Gender_Tag>Male</Gender_Tag> or <Gender_Tag>Female</Gender_Tag> according to the values M or F respectively .. I tried this code .. It used to work in other circumstances..
All relative paths expressed in a template are evaluated against the current node. Your template match Gender elements, so Gender='M' returns true if there is any Gender's child named 'Gender' with the value 'M'. I guess this is not the case...
Use the dot to express the current node (here a Gender element):
<xsl:template match="root/details/Gender">
<Gender_Tag>
<xsl:choose>
<xsl:when test=".='M'">
<xsl:text>Male</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Female</xsl:text>
</xsl:otherwise>
</xsl:choose>
</Gender_Tag>
</xsl:template>
EDIT: You may use two templates too
<xsl:template match="root/details/Gender[.='M']">
<Gender_Tag>Male</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender[.='F']">
<Gender_Tag>Female</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender">
<xsl:choose>
<xsl:when test="normalize-space(text())='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:when>
<xsl:otherwise>
<Gender_Tag>Female</Gender_Tag>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
My example differs in two points from Scoregraphic's:
It uses xsl:choose to ensure, that only one Gender_Tag element is created (that also means, that if the text is not 'M', it is always a Female)
Use of normalize-space() strips white space around the text content of the element.
Untested, but may work...
<xsl:template match="root/details/Gender">
<xsl:if test="text()='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:if>
<xsl:if test="text()='F'">
<Gender_Tag>Female</Gender_Tag>
</xsl:if>
</xsl:template>
Without seeing XML its hard to be certain, but I think your sample XSLT should be:
<xsl:template match="root/details/Gender">
<xsl:if test=".='M'">
<Gender_Tag><xsl:text>Male</xsl:text></Gender_Tag>
</xsl:if>
<xsl:if test=".='F'">
<Gender_Tag><xsl:text>Female</xsl:text></Gender_Tag>
</xsl:if>
</xsl:template>
Use of choose as per another answer would be better (though I think it should be two explicit when clauses rather than a when and an otherwise)