Compare xml node values in xsl and highlight differences - xslt

I have an xml. I am transforming it using xsl stylesheet and showing in html page using java. My requirement is , I need to compare two node values and if there is a difference, I need to highlight the changed character value. How can this be done ?
XML :
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Message>
<DiffDetailMessage>
<TestCaseID>000001</TestCaseID>
<res1>VI7002 1D</res1>
<res2>VI7002 DD </res2>
</DiffDetailMessage>
<DiffDetailMessage>
<TestCaseID>000002</TestCaseID>
<res1>BS7002 1D</res1>
<res2>BS7002 SS </res2>
</DiffDetailMessage>
</Message>
XSL :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>Report</h2>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="Message/DiffDetailMessage">
<table border="2">
<xsl:apply-templates select="TestCaseID"/>
<xsl:apply-templates select="res1"/>
<xsl:apply-templates select="res2"/>
</table>
</xsl:template>
<xsl:template match="TestCaseID">
<tr><td><b>Test CaseID </b></td>
<td><xsl:value-of select="."/></td></tr>
</xsl:template>
<xsl:template match="res1">
<tr><td><b>Res1</b></td>
<td><xsl:value-of select="."/> </td> </tr>
</xsl:template>
<xsl:template match="res2">
<tr><td><b>Res2</b></td>
<td><xsl:value-of select="."/></td> </tr>
</xsl:template>
</xsl:stylesheet>
How can I compare node values res1 with res2 ? In my case, value of "VI70002 ID" should be compared against "VI7002 DD" and since there is a change, I should highlight "D" character in html page using xsl. Can someone help on this regard.

If you are using only XSLT-1.0, you can use a recursive named template to iterate over the string char-by-char: the following template takes two strings as input and puts a bold emphasis on each different char. If the second string is longer than the first one, these trailing chars are highlighted, too.
<xsl:template name="cmp">
<xsl:param name="str1" />
<xsl:param name="str2" />
<xsl:choose>
<xsl:when test="substring($str1,1,1) = substring($str2,1,1)">
<xsl:value-of select="substring($str2,1,1)"/>
</xsl:when>
<xsl:when test="substring($str1,1,1) != substring($str2,1,1)">
<b><xsl:value-of select="substring($str2,1,1)"/></b>
</xsl:when>
<xsl:when test="$str1 = '' and substring($str2,1,1)">
<b><xsl:value-of select="substring($str2,1,1)"/></b>
</xsl:when>
</xsl:choose>
<xsl:if test="string-length($str1) > 0 or string-length($str2) > 0">
<xsl:call-template name="cmp">
<xsl:with-param name="str1" select="substring($str1,2)" />
<xsl:with-param name="str2" select="substring($str2,2)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Call this template from one of the other templates to get a (partially) highlighted "string", e.g.
<xsl:template match="res2">
<tr><td><b>Res2</b></td>
<td>
<xsl:call-template name="cmp">
<xsl:with-param name="str1" select="../res1" />
<xsl:with-param name="str2" select="." />
</xsl:call-template>
</td> </tr>
</xsl:template>

Related

Why do I end up with 8 extra spaces when applying the transform from within MatLab rather than letting Firefox handle it?

I have the following structure:
<suite>
<faults>
<fault componentName="comp1">
<introduceWhen>Time1</introduceWhen>
<signals>
<signal name="sig11" value="1"/>
<signal name="sig22" value="1"/>
</signals>
</fault>
<fault componentName="comp2">
<introduceWhen>Time2</introduceWhen>
<signals>
<signal name="sig44" value="0"/>
</signals>
</fault>
</faults>
</suite>
And using the follwing template I extract some data and put it in a table cell that has white-space: pre set, in order to allow me to put each fault on a new line.
<?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" indent="yes"/>
<xsl:output doctype-system="http://www.w3.org/TR/html4/strict.dtd"/>
<xsl:output doctype-public="-//W3C//DTD HTML 4.01//EN"/>
<xsl:template match="/suite">
<html>
<head>
<title>Space Demo</title>
<style>
td.faults {text-align: left; white-space: pre;}
</style>
</head>
<body>
<table>
<tr>
<td class="faults">
<xsl:apply-templates select="faults"/>
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="faults">
<xsl:for-each select="fault">
<xsl:value-of select="concat(#componentName, ' ', introduceWhen, ' ')"/>
<xsl:for-each select="signals/signal">
<xsl:value-of select="concat(#value, ' ')"/>
</xsl:for-each>
<xsl:if test="position()!=last()">
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It works exactly as I want it to if I only display the XML in Firefox, with the XSLT as a stylesheet. But if I apply the transform in MatLab, the resulting HTML gets generated with an extra 8 spaces after the $1
. MatLab uses Saxon 6.5.5 for XSLT processing.
I tried using the following template to trim the string, but it made no difference:
<xsl:template name="trim">
<xsl:param name="str"/>
<xsl:choose>
<xsl:when test="string-length($str) > 0 and substring($str, 1, 1) = ' '">
<xsl:call-template name="trim">
<xsl:with-param name="str">
<xsl:value-of select="substring($str, 2)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:when test="string-length($str) > 0 and substring($str, string-length($str)) = ' '">
<xsl:call-template name="trim">
<xsl:with-param name="str">
<xsl:value-of select="substring($str, 1, string-length($str)-1)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
What can I do to get rid of the extra 8 spaces? There is a working example of this behavior here: http://xsltransform.net/jz1PuP4
The indent is apparently an attempt of the Saxon processor to make the HTML code more pretty. Neither Xalan nor libxslt produce a similar indent.
Since HTML browsers ignore superfluous white space characters, the addition is harmless and I see no reason why you should be concerned.
Changing the output method to xml will remove the indent, because unlike with HTML, white space can be critical.

Split attribute characters and output them using XSLT 1.0

I am in need to transform the below coding using XSLT 1.0 based on the separators attributes given. The text should be separated based on the separators given:
Input:
<chapter xmlns="http://www.w3.org/1998/Math/MathML">
<math display="inline"><mfenced separators=", : . ;"><mn>1</mn><mtext>b</mtext><mo>%</mo><mi>d</mi><mi>e</mi></mfenced></math>
<math display="inline"><mfenced separators=", ;"><mi>a</mi><mi>b</mi><mi>c</mi><mi>d</mi><mi>e</mi></mfenced></math>
<math display="inline"><mfenced separators=", : . ; ; : . ;"><mi>a</mi><mi>b</mi><mi>c</mi><mi>d</mi><mi>e</mi></mfenced></math>
</chapter>
output required:
1,b:%.d;e
a,b;c;d;e
a,b:c.d;e
Also please note that if there are too many separator characters, the extra ones are ignored. If separator characters are given, but there are too few, the last one is repeated as necessary
I could not able to get the output only if the separator characters are lesser than the child elements.
XSLT 1.0 tried:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://www.w3.org/1998/Math/MathML">
<xsl:template match="m:mfenced">
<xsl:variable name="text" select="#separators"/>
<xsl:for-each select="child::*">
<xsl:apply-templates/>
<xsl:choose>
<xsl:when test="contains($text,' ')">
<xsl:variable name="attr" select="string-length(translate($text, ' ', ''))"/>
<xsl:variable name="ch" select="count(parent::*/child::*)-1"/>
<xsl:if test="$ch=$attr"><xsl:value-of select="substring($text,count(preceding-sibling::*)+position(),1)"/></xsl:if>
<xsl:if test="$ch gt $attr">
<xsl:if test="not(substring($text,count(preceding-sibling::*)+position(),1)='')"><xsl:value-of select="substring($text,count(preceding-sibling::*)+position(),1)"/></xsl:if>
<xsl:if test="(substring($text,count(preceding-sibling::*)+position(),1)='')"><xsl:value-of select="substring($text,count(preceding-sibling::*)+1,1)"/></xsl:if>
</xsl:if>
<xsl:if test="$ch lt $attr and count(following-sibling::*)>0"><xsl:value-of select="substring($text,count(preceding-sibling::*)+position(),1)"/></xsl:if>
</xsl:when>
<xsl:otherwise><xsl:if test="count(following-sibling::*)>0"><xsl:value-of select="$text"/></xsl:if></xsl:otherwise></xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The following solution is based on obtaining the position of each <m:mi> within the <m:fenced> elements to obtain the next operator to be outputted.
Note. I am assuming that the string length used to represent each operator is 1.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:m="http://www.w3.org/1998/Math/MathML">
<xsl:output method="text" />
<!-- Ignore all text nodes (just for demo) -->
<xsl:template match="text()" />
<xsl:template match="m:mfenced">
<!-- Print children values and operators -->
<xsl:apply-templates select="*" mode="list-op">
<xsl:with-param name="separator-str" select="#separators" />
<xsl:with-param name="separator-len" select="string-length(#separators)" />
</xsl:apply-templates>
<!-- Print new line -->
<xsl:text>
</xsl:text>
</xsl:template>
<!-- Last m:mi elements for each m:mfenced are just printed -->
<xsl:template match="*[last()]" mode="list-op">
<xsl:value-of select="."/>
</xsl:template>
<!-- In this template we use the position() function to calculate the next
operator that is going to be outputted -->
<xsl:template match="*" mode="list-op">
<xsl:param name="separator-str" />
<!-- This parameter is not required, but allows us to cache
the length of the separators string instead of calculating it
for each m:mi element -->
<xsl:param name="separator-len" />
<!-- Print current value -->
<xsl:value-of select="." />
<!-- Calculate the separator position within the string -->
<xsl:variable name="string-position" select="2*position() - 1" />
<!-- Check if the position oveflows the position in the array, and
if it does, print the last separator in the string. -->
<xsl:choose>
<xsl:when test="$separator-len >= $string-position">
<xsl:value-of select="substring($separator-str, $string-position, 1)" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($separator-str, $separator-len)" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

how to set a variable name dynamic in xsl

following is my code where i want to give the hidden variable and assign the value for it to access later.
<xsl:for-each select="//Root/Record">
<xsl:if test="(#CIMtrek_accountlist_customer_number != '') ">
<option style="padding:5px;">
<xsl:attribute name="class">>
<xsl:choose>
<xsl:when test="(position() mod 2) = 0">
AlternateRowOne
</xsl:when>
<xsl:otherwise>
AlternateRowTwo
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
// here i want to set hidden varialble and assign the value for it
<xsl:attribute name="value">
<xsl:value-of
select="#CIMtrek_accountlist_customer_number" /></xsl:attribute>
<xsl:value-select="#CIMtrek_accountlist_customer_number" />
</option>
</xsl:if>
</xsl:for-each>
the variable name will be some thing similar to like this
<input type="hidden"
name="hdnDialogListCtrlPlaceholder_CIMtrek_DailyshipCo_Destination_"+i
id="hdnDialogListCtrlPlaceholder_CIMtrek_DailyshipCo_Destination_"+i
/>
where i =0
and i++;
name_1
name_2
name_n
is it possible to use : <FieldRef Name="<FieldInternalName>" Explicit="TRUE"/>
how to do this in xsl
I typically achieve recursion in the following manner.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:call-template name="recur">
<xsl:with-param name="max_recursions" select="5"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="recur">
<xsl:param name="n">0</xsl:param>
<xsl:param name="max_recursions"/>
REPEATING UNIT HERE
<xsl:if test="$max_recursions != $n">
<xsl:call-template name="recur">
<xsl:with-param name="n" select="$n + 1"/>
<xsl:with-param name="max_recursions" select="$max_recursions"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
It isn't often advised though and you can usually achieve much faster, more succinct and more readable code through a well constructed XPath.
You're trying to write procedural code in XSLT, and that won't work. If you explain what transformation you want to perform (what's the input, what's the output, and how are they related?) then we can show you how to do it "the XSLT way", that is, declaratively.

XSLT 1.0 How to match a string to another, list it, and under a different heading, list the unmatched string (specific situation)

I have been training myself in XSLT for about 1.5 months. I have made a simplified shorter version of what I am having trouble figuring out, and would highly appreciate any help at all as I am stuck on the issue. Thanks!
Basic Situation:
There's a string in a root attribute with an ancestor of element definition
xpath:
/v3:QualityMeasureDocument/v3:component/v3:dataCriteriaSection/v3:definition/v3:valueSet/v3:id/#root
...that when matched with the id from a valueSet attribute with an ancestor of element entry, xpath:
/v3:QualityMeasureDocument/v3:component/v3:dataCriteriaSection/v3:entry/v3:observationCriteria/v3:value/#valueSet
or
/v3:QualityMeasureDocument/v3:component/v3:dataCriteriaSection/v3:entry/v3:observationCriteria/v3:code/#valueSet
the output (needs to and currently does) display the string, along with its required attribute(s).
However, when there is no match for the string in these locations, the string must also be listed, with a 'Not Specified' header.
THE ERROR:
The 'Not Specified' header and its string are being listed even when all of the existing strings match. In this scenario, there should only be matched strings listed.
The Problem Translator (XSL file) :
<?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"
xmlns:v3="urn:hl7-org:v3"
xmlns:rvs="urn:ihe:iti:svs:2008">
<xsl:output method="html" standalone="yes" omit-xml-declaration="no" indent="yes" xalan:indent-amount="2"/>
<xsl:template
match="/v3:QualityMeasureDocument">
<html>
<head>
<title>Test 'I'</title>
</head>
<body>
<ul>
<xsl:apply-templates select="//v3:dataCriteriaSection" />
</ul>
</body>
</html>
</xsl:template>
<xsl:template
match="v3:dataCriteriaSection">
<xsl:for-each select="//v3:entry">
<xsl:if test="*/v3:value/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:value/#valueSet"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="*/v3:code/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:code/#valueSet"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="*/v3:participant/v3:roleParticipant/v3:code/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:participant/v3:roleParticipant/v3:code/#valueSet"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template
name="definitionValueSet">
<xsl:param name="cur_valueSetID"/>
<xsl:for-each select="//v3:valueSet">
<xsl:choose>
<xsl:when test="$cur_valueSetID != v3:id/#root or
not( v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)] ) or
not( v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID )">
<xsl:if test="not($cur_valueSetID = '')">
<li>
<xsl:text>Not Specified</xsl:text>
<ul>
<li>
<xsl:text>ValueSet: </xsl:text>
<xsl:value-of select="$cur_valueSetID"></xsl:value-of>
</li>
</ul>
</li>
</xsl:if>
</xsl:when>
<xsl:when test="v3:id/#root = $cur_valueSetID">
<xsl:if test="v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)]">
<xsl:if test="v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID">
<li>
<xsl:text>Id: </xsl:text>
<xsl:value-of select="v3:id/#root"/>
<xsl:for-each select="v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/rvs:ConceptList/rvs:Concept">
<ul>
<li>
<xsl:if test="not(#code = '')">
<xsl:if test="#code">
<xsl:text>code = </xsl:text>
<xsl:value-of select="#code"></xsl:value-of>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:if>
</li>
</ul>
</xsl:for-each>
</li>
</xsl:if>
</xsl:if>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
XML input file 'I' xml :
<?xml version="1.0" encoding="UTF-8"?>
<QualityMeasureDocument xmlns="urn:hl7-org:v3">
<component>
<dataCriteriaSection>
<definition>
<valueSet>
<!-- Value Set for Race -->
<id root='1.1.1.1.1.1.1' />
<text>
<reference
value='https://localhost/RetrieveValueSet?id=1.1.1.1.1.1.1' />
<RetrieveValueSetResponse xmlns="urn:ihe:iti:svs:2008">
<ValueSet id="1.1.1.1.1.1.1">
<ConceptList>
<Concept code="4" />
<Concept code="5" />
<Concept code="6" />
</ConceptList>
</ValueSet>
</RetrieveValueSetResponse>
</text>
</valueSet>
</definition>
<definition>
<valueSet>
<id root='1.1.1.1.1.1.2' />
<text>
<reference
value='https://localhost/RetrieveValueSet?id=1.1.1.1.1.1.2' />
<RetrieveValueSetResponse xmlns="urn:ihe:iti:svs:2008">
<ValueSet id="1.1.1.1.1.1.2">
<ConceptList>
<Concept code="007.2" />
<Concept code="007.3" />
</ConceptList>
</ValueSet>
</RetrieveValueSetResponse>
</text>
</valueSet>
</definition>
<entry>
<observationCriteria>
<code code="424144002" codeSystem="123123213"
displayName="FEWFW" />
<value>
<low />
<high />
</value>
</observationCriteria>
</entry>
<entry>
<observationCriteria>
<code code="DFHKJ" codeSystem="ASKJDHK" displayName="ASDNJK" />
<value>
<width />
</value>
</observationCriteria>
</entry>
<entry>
<observationCriteria>
<code code="FDSFD" codeSystem="JHBHJB" displayName="HJGJH" />
<value valueSet="1.1.1.1.1.1.1" />
</observationCriteria>
</entry>
<entry>
<encounterCriteria>
<code valueSet="1.1.1.1.1.1.2" />
</encounterCriteria>
</entry>
</dataCriteriaSection>
</component>
</QualityMeasureDocument>
Expected Output Code 'I' HTML :
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<html xmlns:xalan="http://xml.apache.org/xalan" xmlns:rvs="urn:ihe:iti:svs:2008" xmlns:v3="urn:hl7-org:v3">
<head>
<title>Test 'I'</title>
</head>
<body>
<ul>
<li>Id: 1.1.1.1.1.1.1
<ul>
<li>code = 4 </li>
</ul>
<ul>
<li>code = 5 </li>
</ul>
<ul>
<li>code = 6 </li>
</ul>
</li>
<li>Id: 1.1.1.1.1.1.2
<ul>
<li>code = 007.2 </li>
</ul>
<ul>
<li>code = 007.3 </li>
</ul>
</li>
</ul>
</body>
</html>
It would be simple to create the correct output through a hack, or just erasing code, but this needs to work in all situations. Such as, having no matches at all and displaying only the 'Not Specified' header and its string each time it occurs, or a mixture of both situations. The code currently works in a situation where there are no matches, and displays the 'Not Specified' header and its string each time it occurs.
It seems like if this could be done, "if it's not a match AND hasn't already been listed" it would solve the problem.
Hope that helps. If you would like more information or more files let me know. Any tips at all would be great! Thanks.
I think the main problem you are having is within your named template "definitionValueSet", where you loop over valueSet elements with an xsl:for-each
<xsl:for-each select="//v3:valueSet">
Within this you test whether the relevant attributes match your current parameter, and if you find a match, you output your list. But all valueSet elements will be searched, so even if one matches, you are still going to be processing the ones that don't and so your xsl:if for a non-match is called too. This results in the "Not Specified" being output.
What you really need to be doing is matching only the valueSet element if it exists, and if none exist that match, output your "Not Specified".
One way to do this is to first create a variable that holds the unique id of the valueSet element that matches
<xsl:variable name="valueSet">
<xsl:value-of select="generate-id(//v3:valueSet[v3:id/#root = $cur_valueSetID and v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)] and v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID])"/>
</xsl:variable>
Then, you can test whether this has been set, and if so, apply the template for that element. If not set, you can have your not-specified code
<xsl:choose>
<xsl:when test="$valueSet">
<xsl:apply-templates select="//v3:valueSet[generate-id() = $valueSet]"/>
</xsl:when>
<xsl:otherwise>
<!-- Not Specified -->
</xsl:otherwise>
</xsl:choose>
Here is the full XSLT, which should hopefully give the output you require
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xalan" xmlns:v3="urn:hl7-org:v3" xmlns:rvs="urn:ihe:iti:svs:2008">
<xsl:output method="html" standalone="yes" omit-xml-declaration="no" indent="yes" xalan:indent-amount="2"/>
<xsl:template match="/v3:QualityMeasureDocument">
<html>
<head>
<title>Test 'I'</title>
</head>
<body>
<ul>
<xsl:apply-templates select="//v3:dataCriteriaSection"/>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="v3:dataCriteriaSection">
<xsl:for-each select=".//v3:entry">
<xsl:if test="*/v3:value/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:value/#valueSet"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="*/v3:code/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:code/#valueSet"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="*/v3:participant/v3:roleParticipant/v3:code/#valueSet">
<xsl:call-template name="definitionValueSet">
<xsl:with-param name="cur_valueSetID" select="*/v3:participant/v3:roleParticipant/v3:code/#valueSet"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="definitionValueSet">
<xsl:param name="cur_valueSetID"/>
<xsl:variable name="valueSet">
<xsl:value-of select="generate-id(//v3:valueSet[v3:id/#root = $cur_valueSetID and v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)] and v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID])"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$valueSet != ''">
<xsl:apply-templates select="//v3:valueSet[generate-id() = $valueSet]"/>
</xsl:when>
<xsl:otherwise>
<li>
<xsl:text>Not Specified</xsl:text>
<ul>
<li>
<xsl:text>ValueSet: </xsl:text>
<xsl:value-of select="$cur_valueSetID"/>
</li>
</ul>
</li>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="v3:valueSet">
<li>
<xsl:text>Id: </xsl:text>
<xsl:value-of select="v3:id/#root"/>
<xsl:for-each select="v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/rvs:ConceptList/rvs:Concept">
<ul>
<li>
<xsl:if test="not(#code = '')">
<xsl:if test="#code">
<xsl:text>code = </xsl:text>
<xsl:value-of select="#code"/>
<xsl:text/>
</xsl:if>
</xsl:if>
</li>
</ul>
</xsl:for-each>
</li>
</xsl:template>
</xsl:stylesheet>
Do note, as mentioned in a comment, be careful with the use of expression //v3:entry. This is an absolute expression, and will match any element at all in the document (i.e it searches from the root element downwards). If you wanted only to look under the current element, use the relative expression .//v3:entry
EDIT: For a slight variant on this, as a potentially more efficient way to look up the valueSet elements you could define a key at the top of the XSLT document
<xsl:key name="valueSet" match="v3:valueSet" use="generate-id()" />
Then, instead of doing this to look up the correct valueSet element
<xsl:apply-templates select="//v3:valueSet[generate-id() = $valueSet]"/>
You could use the key instead
<xsl:apply-templates select="key('valueSet', $valueSet)"/>
The following is a simple-ish alternate solution (does not use generate-id() or key() ) provided for comprehension. It is likely less efficient than and should not replace Tim C's excellent answer. I am simply providing this so people can learn, and to show that I put full effort into solving this issue, instead of just getting what I needed and moving on.
The solution is a fix to the original 'Problem Translator'. The only section that needs to be edited (although heavily) from that XSL file is the "definitionValueSet" named template.
First we need to create a variable $valueSetData which stores all of the values of v3:id/#root in a one time pass, with a ',' in between them, in order to be individually referenced later. It's like a new temporary database.
The for-each contains a predicate which limits the matches as per requirements. These are included within the declaration because they are local nodes there. Also, this keeps from processing extra conditional checks later.
<xsl:variable name="valueSetData">
<xsl:for-each select="//v3:valueSet[
v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)] and
v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID]">
<xsl:value-of select="v3:id/#root"/>
<xsl:text>,</xsl:text>
</xsl:for-each>
</xsl:variable>
Next we have the choose statement, edited for specifics. The statement determines if there is a match to the $cur_valueSetID within the $valueSetData 'database'.
The for-each predicate limits duplicate matches (with the wrong values in addition due to context).
<xsl:choose>
<xsl:when test="contains($valueSetData, $cur_valueSetID)">
<xsl:for-each select="//v3:valueSet[$cur_valueSetID = v3:id/#root]">
<!-- Display found matches which meet all requirements -->
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<!-- Display Not Specified -->
</xsl:otherwise>
</xsl:choose>
The full "definitionValueSet" template:
<xsl:template
name="definitionValueSet">
<xsl:param name="cur_valueSetID"/>
<xsl:variable name="valueSetData">
<xsl:for-each select="//v3:valueSet[
v3:text/v3:reference[starts-with(#value, 'https://') and contains(#value, $cur_valueSetID)] and
v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/#id = $cur_valueSetID]">
<xsl:value-of select="v3:id/#root"/>
<xsl:text>,</xsl:text>
</xsl:for-each>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($valueSetData, $cur_valueSetID)">
<xsl:for-each select="//v3:valueSet[$cur_valueSetID = v3:id/#root]">
<li>
<xsl:text>Id: </xsl:text>
<xsl:value-of select="v3:id/#root"/>
<xsl:for-each select="v3:text/rvs:RetrieveValueSetResponse/rvs:ValueSet/rvs:ConceptList/rvs:Concept">
<ul>
<li>
<xsl:if test="not(#code = '')">
<xsl:if test="#code">
<xsl:text>code = </xsl:text>
<xsl:value-of select="#code"></xsl:value-of>
<xsl:text/>
</xsl:if>
</xsl:if>
</li>
</ul>
</xsl:for-each>
</li>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<li>
<xsl:text>Not Specified</xsl:text>
<ul>
<li>
<xsl:text>ValueSet: </xsl:text>
<xsl:value-of select="$cur_valueSetID"></xsl:value-of>
</li>
</ul>
</li>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Get N characters introduction text with XSLT 1.0 from XHTML

How I can get first n characters with XSLT 1.0 from XHTML? I'm trying to create introduction text for news.
Everything is UTF-8
HTML entity aware ( &), one entity = one character
HTML tag aware (adds missing end tags)
Input HTML is always valid
If input text is over n chars add '...' to end output
Input tags are restricted to: a, img, p, div, span, b, strong
Example input HTML:
<img src="image.jpg" alt="">text link here
Example output with 9 characters:
<img src="image.jpg" alt="">text link...
Example input HTML:
<p>link here text</p>
Example output with 4 characters:
<p>link...</p>
Here is a starting point, although it currently doesn't contain any code to handle the requirement "Input tags are restricted to: a, img, p, div, span, b, strong"
It works by looping through the child nodes of a node, and totalling the length of the preceding siblings up to that point. Note that the code to get the length of the preceding siblings requires the use of the node-set function, which is an extension function to XSLT 1.0. In my example I am using Microsoft Extension function.
Where a node is not a text node, the total length of characters up to that point will be the sum of the lengths of the preceding siblings, put the sum of the preceding siblings of the parent node (which is passed as a parameter to the template).
Here is the XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:param name="MAXCHARS">9</xsl:param>
<xsl:template match="/body">
<xsl:apply-templates select="child::node()"/>
</xsl:template>
<xsl:template match="node()">
<xsl:param name="LengthToParent">0</xsl:param>
<!-- Get length of previous siblings -->
<xsl:variable name="previousSizes">
<xsl:for-each select="preceding-sibling::node()">
<length>
<xsl:value-of select="string-length(.)"/>
</length>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="LengthToNode" select="sum(msxsl:node-set($previousSizes)/length)"/>
<!-- Total amount of characters processed so far -->
<xsl:variable name="LengthSoFar" select="$LengthToNode + number($LengthToParent)"/>
<!-- Check limit is not exceeded -->
<xsl:if test="$LengthSoFar < number($MAXCHARS)">
<xsl:choose>
<xsl:when test="self::text()">
<!-- Output text nonde with ... if required -->
<xsl:value-of select="substring(., 1, number($MAXCHARS) - $LengthSoFar)"/>
<xsl:if test="string-length(.) > number($MAXCHARS) - $LengthSoFar">...</xsl:if>
</xsl:when>
<xsl:otherwise>
<!-- Output copy of node and recursively call template on its children -->
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="child::node()">
<xsl:with-param name="LengthToParent" select="$LengthSoFar"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied to this input
<body>
<img src="image.jpg" alt="" />text link here
</body>
The output is:
<body>
<img src="image.jpg" alt="" />text link...
</body>
When applied to this input (and changing the parameter to 4 in the XSLT)
<p>link here text</p>
The output is:
<p>link...</p>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="pMaxLength" select="4"/>
<xsl:template match="node()">
<xsl:param name="pPrecedingLength" select="0"/>
<xsl:variable name="vContent">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="node()[1]">
<xsl:with-param name="pPrecedingLength"
select="$pPrecedingLength"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:variable>
<xsl:variable name="vLength"
select="$pPrecedingLength + string-length($vContent)"/>
<xsl:if test="$pMaxLength + 3 >= $vLength and
(string-length($vContent) or not(node()))">
<xsl:copy-of select="$vContent"/>
<xsl:apply-templates select="following-sibling::node()[1]">
<xsl:with-param name="pPrecedingLength" select="$vLength"/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="text()" priority="1">
<xsl:param name="pPrecedingLength" select="0"/>
<xsl:variable name="vOutput"
select="substring(.,1,$pMaxLength - $pPrecedingLength)"/>
<xsl:variable name="vSumLength"
select="$pPrecedingLength + string-length($vOutput)"/>
<xsl:value-of select="concat($vOutput,
substring('...',
1 div ($pMaxLength
= $vSumLength)))"/>
<xsl:apply-templates select="following-sibling::node()[1]">
<xsl:with-param name="pPrecedingLength"
select="$vSumLength"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
With this input and 9 as pMaxLength:
<html><img src="image.jpg" alt=""/>text link here</html>
Output:
<html><img src="image.jpg" alt="">text link...</html>
And this input with 4 as pMaxLength:
<html><p>link here text</p></html>
Output:
<html><p>link...</p></html>
As indicated by many: this gets very messy very fast. So I just added another field to DB which has the introduction text.