Can I apply a character map to a given node? - xslt-2.0

If I look at the xslt specs it seems a character map applies to the whole document, bit is it also possible to use it on a given node, or within a template ?
Example : I have a node containing look up values, but they might contain characters that don't play well with regular expressions when using it in another template. For now I use a replace functionwhich works well,, but after a few characters that becomes pretty hard to read or maintain. So if I have something like this :
<xsl:variable name="myLookup" select="
replace(
replace(
replace(
replace(
string-join(/*/lookup/*, '|'),
'\[','\\['),
'\]','\\]'),
'\(','\\('),
'\)','\\)')
"/>
is there a way to achieve something like below fictitious example ?
<xsl:character-map name="escapechar">
<xsl:output-character character="[" string="\[" />
<xsl:output-character character="]" string="\]" />
<xsl:output-character character="(" string="\(" />
<xsl:output-character character=")" string="\)" />
</xsl:character-map>
<xsl:variable name="myLookup" select="string-join(/*/lookup/*, '|')" use-character-map="escapechar"/>
I know this is not working at all, it is just to make my request a bit visual.
Any idea ?

I think character maps in XSLT 2.0 are a serialization feature to be applied when a result tree is serialized to a file or stream so I don't see how you could apply one to a certain string or certain node during a transformation.
As for escaping meta characters of regular expression patterns, maybe http://www.xsltfunctions.com/xsl/functx_escape-for-regex.html helps.

Character maps is only a serialization feature, which means that it is only executed when the final output of a transformation is produced. However, you can significantly simplify your current code.
Just use:
replace($pStr, '(\[|\]|\(|\))','\\$1')
Here is a complete example:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:value-of select="my:escape(.)"/>
</xsl:template>
<xsl:function name="my:escape" as="xs:string">
<xsl:param name="pStr" as="xs:string"/>
<xsl:value-of select="replace($pStr, '(\[|\]|\(|\))','\\$1')"/>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>([a-z]*)</t>
the wanted, correct result is produced:
\(\[a-z\]*\)

Related

why am I getting Required cardinality of value of variable $depts is exactly one; supplied value has cardinality more than one

Trying to figure out some homework here and the online teacher has never responded to any questions I ask.
I keep getting an error when I try to process the xml document.
"XTTE0570: Required cardinality of value of variable $depts is exactly one; supplied value has cardinality more than one"
The case problem instructions:
First, create a template named getemployees.
Within the getEmployees template, create a variable named depts containing a sequence of the following text strings representing the department codes for Lucy’s sample data: ‘a00’, ‘c01’, ‘d11’, ‘d21’, ‘e11’, and ‘e21’.
After the line to create the depts variable, create the departments element.
Within the departments element, insert a for-each loop that loops through each entry in the
depts sequence.
For each entry in the depts sequence do the following:
a. Create a variable named currentDept equal to the current item in the depts sequence.
b. Create an element named department with an attribute named deptiD whose value is equal to the value of the currentDept variable.
c. Use the doc() function to reference the “deptcurrent.xml” file, where current is the value of the currentDept variable. (Hint: Use the concat() function to combine the text strings for “dept”, the currentDept variable, and the text string “.xml”.)
d. Use the copy-of element to copy the contents of the employees element and its descendants to the department element.
Save your changes to the file and then use your XSLT 2.0 processor to generate the result
document horizons.xml by applying the getEmployees template within the alldepartments.xsl
style sheet.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template name="getEmployees">
<xsl:variable name="depts" select="('a00', 'c01', 'd11', 'd21', 'e11', 'e21')" as="xs:string" />
<departments>
<xsl:for-each select="$depts">
<xsl:variable name="currentDept">
<xsl:value-of select="." />
</xsl:variable>
<department deptID="{$currentDept}">
<xsl:value-of select="doc(concat('dept',$currentDept, '.xml'))" />
<xsl:copy-of select="employees" />
</department>
</xsl:for-each>
</departments>
</xsl:template>
</xsl:stylesheet>
Should generate something similar to:
<?xml version="1.0" encoding="UTF-8"?>
<departments>
<department dept="a00">
<employees>
<employee empID="10">
<firstName>Marylin</firstName>
<middleInt>A</middleInt>
<lastName>Johnson</lastName>
<department>A00</department>
<phone>3978</phone>
<email>Johnson.60#example.com/horizons</email>
<dateHired>2000-01-01</dateHired>
<title>President</title>
<edLevel>18</edLevel>
<gender>F</gender>
<birthDate>1968-08-24</birthDate>
<salary>121300</salary>
<bonus>2300</bonus>
<commission>9700</commission>
</employee>
<employee empID="40">
<firstName>Heather</firstName>
<middleInt>D</middleInt>
<lastName>Gordon</lastName>
<department>A00</department>
<phone>3915</phone>
<email>Gordon.59#example.com/horizons</email>
<dateHired>2009-03-01</dateHired>
<title>Manager</title>
<edLevel>18</edLevel>
<gender>F</gender>
<birthDate>1986-06-03</birthDate>
<salary>85400</salary>
<bonus>1700</bonus>
<commission>6500</commission>
</employee>
</employees>
</department>
</departments>
If you use the as attribute on xsl:variable to declare the type of your variable then the value you select needs to fit that declaration, so given that you have a sequence of strings you need to use <xsl:variable name="depts" select="('a00', 'c01', 'd11', 'd21', 'e11', 'e21')" as="xs:string*" />.
Additionally the
<xsl:copy-of select="employees" />
inside the for-each over a string sequence doesn't make sense (and explain the error you get after correcting the variable type), there you simply want
<xsl:copy-of select="doc(concat('dept', ., '.xml'))/employees" />

How to transform HTML encoded XML?

I have an input XML like
<values xsi:type="xsd:string"><Test objectgroupNr="001"/><bezeichnung>A&amp;B </bezeichnung></values>
which has HTML encoded characters that I want to transform to "plain" XML encoding:
<values xsi:type="xsd:string">
<Test objectgroupNr="001"/>
<bezeichnung>A&B</bezeichnung>
</values>
I could change some characters with
<xsl:character-map name="fischer">
<xsl:output-character character="<" string="<"/>
<xsl:output-character character=">" string=">"/>
</xsl:character-map>
<xsl:output method="xml" use-character-maps="fischer"/>
But it does not seem to be a good idea to type in all possible special characters like Ä, Ü, ß, é and so on...
Can this be done in an easy way with XSLT? The transformation takes place in the environment of Sonic ESB using Saxon 8.9.
According to http://www.saxonica.com/documentation8.9/extensions/functions/parse.html the extension function is supported so you should be able to use e.g.
<xsl:template match="values">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="saxon:parse(concat('<root>', ., '</root>'))/*/node()"/>
</xsl:copy>
</xsl:template>
where you put xmlns:saxon="http://saxon.sf.net/" as a namespace declaration into the stylesheet.

How can I parse a YouTube Url using XSLT?

I would like to parse a youtube url using XSLT and get only the Video ID from that URL. What's the best way to do this using XSLT?
So, if the url is: http://www.youtube.com/watch?v=qadqO3TOvbQ&feature=channel&list=UL
I only want qadqO3TOvbQ and put it into an embed code:
<iframe width="560" height="315" src="http://www.youtube.com/embed/qadqO3TOvbQ" frameborder="0" allowfullscreen=""></iframe>
I. This XPath 2.0 expression:
substring-after(tokenize($pUrl, '[?|&]')[starts-with(., 'v=')], 'v=')
produces the wanted, correct result.
Alternatively, one can use the slightly shorter:
tokenize(tokenize($pUrl, '[?|&]')[starts-with(., 'v=')], '=')[2]
Here is a complete XSLT 2.0 transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pUrl" select=
"'http://www.youtube.com/watch?v=qadqO3TOvbQ&feature=channel&list=UL'"/>
<xsl:template match="/">
<xsl:sequence select=
"tokenize(tokenize($pUrl, '[?|&]')[starts-with(., 'v=')], '=')[2]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the wanted, correct result is produced:
qadqO3TOvbQ
II. This XPath 1.0 expression:
concat
(substring-before(substring-after(concat($pUrl,'&'),'?v='),'&'),
substring-before(substring-after(concat($pUrl,'&'),'&v='),'&')
)
produces the wanted result.
Do note:
Both solutions extract the wanted string even if the query string parameter named v isn't the first one or even in the case when it is the last one.
XSLT/XPath is not best suited to string handling (1.0 especially) but you can achieve what you need by mixing up the substring-after() and substring-before() functions:
<xsl:value-of select="substring-before(substring-after($yt_url, '?v='), '&feature')" />
(assumes the YT URL is stored in an XSLT var, $yt_url, and that it has its & escaped to &).
Demo at this this XML Playground

How can I get xslt to indent xml (from Ant)?

From what I understand having looked around for an answer to this the following should work:
<xslt basedir="..." destdir="..." style="xslt-stylesheet.xsd" extension=".xml"/>
Where xslt-stylesheet.xsd contains the following:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
Unfortunately while most formatting is applied (spaces are stripped, newlines entered, etc.), indentation is not and every element is along the left side in the file. Is this an issue with the xslt processor Ant uses, or am I doing something wrong? (Using Ant 1.8.2).
It might help to set some processor-specific output options, though you should note that these may vary depending on the XSLT processor that you're using.
For example, if you're using Xalan, it defines an indent-amount property, which seems to default to 0.
To override this property at runtime, you can declare xalan namespace in your stylesheet and override using the processor-specific attribute indent-amount in your output element as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan">
<xsl:output method="xml"
encoding="UTF-8"
indent="yes"
xalan:indent-amount="2"/>
This example is from the Xalan usage patterns documentation at http://xml.apache.org/xalan-j/usagepatterns.html
If you do happen to be using Xalan, the documentation also says you can change all of the output preferences globally by setting changing the file org/apache/serializer/output_xml.properties in the serializer jar.
In the interest of completeness, the complete set of Xalan-specific xml output properties defined in that file (Xalan 2.7.1) are:
{http://xml.apache.org/xalan}indent-amount=0
{http://xml.apache.org/xalan}content-handler=org.apache.xml.serializer.ToXMLStream
{http://xml.apache.org/xalan}entities=org/apache/xml/serializer/XMLEntities
If you're not using Xalan, you might have some luck looking for some processor-specific output properties in the documentation for your XSLT processor
Different XSLT processors implement indent="yes" in different way. Some indent properly, while others only put the element starting on a new line. It seems that your XSLT processor is among the latter group.
Why is this so?
The reason is that the W3C XSLT Specification allows significant leeway in what indentation could be produced:
"If the indent attribute has the value yes, then the xml output
method may output whitespace in addition to the whitespace in the
result tree (possibly based on whitespace stripped from either the
source document or the stylesheet) in order to indent the result
nicely; if the indent attribute has the value no, it should not
output any additional whitespace. The default value is no. The xml
output method should use an algorithm to output additional whitespace
that ensures that the result if whitespace were to be stripped from
the output using the process described in [3.4 Whitespace Stripping]
with the set of whitespace-preserving elements consisting of just
xsl:text would be the same when additional whitespace is output as
when additional whitespace is not output.
NOTE:It is usually not safe to use indent="yes" with document types that include element types with mixed content."
Possible solutions:
Start using another XSLT processor. For example, Saxon indents quite well.
Remove the <xsl:strip-space elements="*"/> directive. If there are whitespace-only text nodes in the source XML, they would be copied to the output and this may result in a better-looking indented output.
I don't know if ant is OK. But concerning your XSLT :
When you use the copy-of on an element, your XSLT processor does not indent. If you change your XSLT like this, your XSLT processor will may be manage to indent :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This XSLT will go through the whole XML tree and indents each element it creates.
EDIT after comment :
You can see the following question to change your XSLT processor, maybe it will solve your problem : How to execute XSLT 2.0 with ant?
You can try adding the {http://xml.apache.org/xslt}indent-amount output property in ant, something like this:
<target name="applyXsl">
<xslt in="${inputFile}" out="${outputFile}" extension=".html" style="${xslFile}" force="true">
<outputproperty name="indent" value="yes"/>
<outputproperty name="{http://xml.apache.org/xslt}indent-amount" value="4"/>
</xslt>
</target>

What is the ROUND function behavior in Orbeon Forms?

In Orbeon Forms (dev-post-3.7.1.200911140400) we have code in an XPL that do some calculation, and part of this calculation is we ROUND the result to 2 decimal places. Below is an example of the code we use to do the calculation:
<xsl:when test="$total_c_w != 0">
<gpa><xsl:value-of select="(round(($total_p_c_w div $total_c_w) * 100) div 100)"/></gpa>
</xsl:when>
According to the standard XPATH documentation on the ROUND function; Rounds a numeric value to the nearest whole number, rounding x.5 towards positive infinity.
But we encounter a case where the ROUND function is rounding 237.5 to 237, instead of 238. This is just an example, there are other cases where a similar problem involving x.5 is happening.
For example mention, the values in the calculation are:
$total_p_c_w = 7.6, $total_c_w = 3.2
=====================================================
Alex,
Thanks for the guidance. I did some more debugging and found something weird please refer to the following XSL code that I tested with on the latest Orbeon Forms 3.9.0.201105152046 CE.
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:template match="/">
<main>
<xsl:variable name="tppcw" select="/root/studentpcw/total_p_c_w" as="xs:double"/>
<xsl:variable name="tccw" select="/root/studentpcw/total_c_w" as="xs:double"/>
<xsl:variable name="tpcw" select="sum(/root/studentpcw/total_p_c_w)"/>
<xsl:variable name="tcw" select="sum(/root/studentpcw/total_c_w)"/>
<xsl:variable name="total_p_c_w" select="7.6"/>
<xsl:variable name="total_c_w" select="3.2"/>
<var1><xsl:value-of select="sum(/root/studentpcw/total_p_c_w)"/></var1>
<var2><xsl:value-of select="sum(/root/studentpcw/total_c_w)"/></var2>
<var3><xsl:value-of select="$total_p_c_w"/></var3>
<var4><xsl:value-of select="$total_c_w"/></var4>
<result1>
<xsl:value-of select="round(($total_p_c_w div $total_c_w) * 100)"/>
</result1>
<result2>
<xsl:value-of select="round(($tppcw div $tccw) * 100)"/>
</result2>
</main>
</xsl:template>
</xsl:transform>
Apply the above code at this sample document:
<root>
<studentpcw>
<total_p_c_w>7.6</total_p_c_w>
<total_c_w>3.2</total_c_w>
</studentpcw>
</root>
The result is quite unexpected;
<main xmlns:xs="http://www.w3.org/2001/XMLSchema">
<var1>7.6</var1>
<var2>3.2</var2>
<var3>7.6</var3>
<var4>3.2</var4>
<result1>238</result1>
<result2>237</result2>
</main>
The problem is that if I assign a literal number to the variable and use that variable in the rounding function, the result is as I expected. If I select the value from a node and assign to the variable and use that variable in the rounding function, the result is wrong or unexpected.
I get a result of 238 running the following stylesheet in a nightly build through the XSLT sandbox (which, if you have Orbeon Forms installed locally, you can access through http://localhost:8080/orbeon/sandbox-transformations/xslt/).
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:template match="/">
<result>
<xsl:variable name="total_p_c_w" select="7.6"/>
<xsl:variable name="total_c_w" select="3.2"/>
<xsl:value-of select="round(($total_p_c_w div $total_c_w) * 100)"/>
</result>
</xsl:template>
</xsl:transform>
I believe this is the result you expected, but that you might be getting something different with the version you are using. Could you try the above example in the XSLT sandbox of the version you are using? If you're getting 237 instead of 238, then this is a sign that this issue has been fixed, and I would then recommend you to upgrade to a newer version of Orbeon Forms (currently 3.9).
(Note that this is most likely not something in Orbeon Forms per se, but in the XSLT implementation Orbeon Forms uses, which is the excellent Saxon.)

Resources