Replace new line char with <br /> XSL - sharepoint

I have a Sharepoint list which I want to convert to a JSON via an XSL dataview.
I have an XSL recursive replace function which I use to replace all special characters (escape backslash, double quotes to " etc) which gives me nice clean JSON which parses correctly in the users browser.
The final thing that I need to escape / replace is the new line char. The new line causes errors in parsing the JSON in some browsers.
Here is some xsl which tests if the contents of title has a new line char, if it does we output a paragraph:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:for-each select="catalog/cd">
<xsl:if test='contains(title,"
")'>
<p>Found <xsl:value-of select="title" /></p>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Here is some sample xml:
<catalog>
<cd>
<title>Empire
Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
</catalog>
"Empire Burlesque" should be the only item to pass the test, but all three titles pass the if statement and are outputted.
EDIT
Modifying the solution below, I assume this should work if I wanted to do the search and replace on a individual node basis? I won't be able to test it in Sharepoint until tomorrow.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:for-each select="catalog/cd">
<xsl:variable name="title_clean">
<xsl:call-template name="repNL">
<xsl:with-param name="pText" select="title"/>
</xsl:call-template>
</xsl:variable>
<p><xsl:value-of select='$title_clean' /></p>
</xsl:for-each>
</xsl:template>
<xsl:template name="repNL">
<xsl:param name="pText" select="."/>
<xsl:copy-of select="substring-before(concat($pText,'
'),'
')"/>
<xsl:if test="contains($pText, '
')">
<br />
<xsl:call-template name="repNL">
<xsl:with-param name="pText" select=
"substring-after($pText, '
')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

"Empire Burlesque" should be the only
item to pass the test, but all three
titles pass the if statement and are
outputted.
Cannot reproduce the alleged problem -- tested with 9 different XSLT processors, including all from Microsoft.
Anyway:
This transformation replaces any NL character in a text node with a br element:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" name="repNL">
<xsl:param name="pText" select="."/>
<xsl:copy-of select=
"substring-before(concat($pText,'
'),'
')"/>
<xsl:if test="contains($pText, '
')">
<br />
<xsl:call-template name="repNL">
<xsl:with-param name="pText" select=
"substring-after($pText, '
')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (the provided one with an added cd to make it more interesting):
<catalog>
<cd>
<title>Line1
Line2
Line3
</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Empire
Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
</catalog>
the wanted, correct result is produced:
<catalog>
<cd>
<title>Line1<br/> Line2<br/> Line3<br/>
</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Empire<br/> Burlesque</title>
<artist>Bob Dylan</artist>
<country>USA</country>
<company>Columbia</company>
<price>10.90</price>
<year>1985</year>
</cd>
<cd>
<title>Hide your heart</title>
<artist>Bonnie Tyler</artist>
<country>UK</country>
<company>CBS Records</company>
<price>9.90</price>
<year>1988</year>
</cd>
<cd>
<title>Greatest Hits</title>
<artist>Dolly Parton</artist>
<country>USA</country>
<company>RCA</company>
<price>9.90</price>
<year>1982</year>
</cd>
</catalog>
Explanation:
The identity rule/template copies every node "as-is".
The overriding template that matches any text node (also named as "repNL") prforms the following processing:
Using a sentinel (a NL character appended to the string), the substring before the first NL character (or the complete string, if th no NL character is contained) is copied to the output.
If a NL character is really contained, then a br element is generated and the template calls itself recursively for the remaining string after this NL character.

If you are using XslCompiledTransform(C#), I believe you will encounter the issue if you use below API to transform the XML:
XslCompiledTransform.Transform(XmlReader input, XmlWriter results)
However, below API works well:
XslCompiledTransform.Transform(string, string);
This is wired, but I don't figure out why...

Related

Quetion about the xsl:template returning a xsl:map

I know that "Cannot add a map to an XML tree", especially take the xsl:map as result document.
But if I assign a xsl:template to a variable, it can take a xsl:map as result of the xsl:template. For example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="xs"
version="3.0">
<xsl:template match="/">
<xsl:variable name="m1" as="map(*)">
<xsl:call-template name="tmap1"/>
</xsl:variable>
<xsl:variable name="m2" as="map(*)">
<xsl:call-template name="tmap2"/>
</xsl:variable>
</xsl:template>
<xsl:template name="tmap1">
<xsl:sequence select="map{1:'abc', 2:'ccd'}"/>
</xsl:template>
<xsl:template name="tmap2" as="map(*)">
<xsl:map>
<xsl:map-entry key="'Mo'" select="'Monday'"/>
<xsl:map-entry key="'Tu'" select="'Tuesday'"/>
<xsl:map-entry key="'We'" select="'Wednesday'"/>
<xsl:map-entry key="'Th'" select="'Thursday'"/>
<xsl:map-entry key="'Fr'" select="'Friday'"/>
<xsl:map-entry key="'Sa'" select="'Saturday'"/>
<xsl:map-entry key="'Su'" select="'Sunday'"/>
<xsl:map-entry key="'z2'" select="'day'"/>
</xsl:map>
</xsl:template>
These will run well. But I has an error with my work code, and I make a minimum but complete demo to reproduce it.
the code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:rel="http://schemas.openxmlformats.org/package/2006/relationships"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:map = "http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="xs w rel r map v"
version="3.0">
<xsl:template match="/">
<xsl:apply-templates select="w:document/w:body/w:p"/>
</xsl:template>
<xsl:template match="w:p">
<xsl:element name="p">
<xsl:variable name="p_css" as="map(*)*">
<xsl:if test="w:pPr">
<xsl:variable name="t">
<xsl:apply-templates select="w:pPr" mode="style_item"/>
</xsl:variable>
<xsl:if test="not($t instance of map(*))">
<xsl:message>error</xsl:message>
</xsl:if>
</xsl:if>
</xsl:variable>
<xsl:if test="not(empty($p_css))">
<xsl:where-populated>
<xsl:attribute name="style" select="$p_css?2"/>
</xsl:where-populated>
</xsl:if>
<xsl:apply-templates select="w:t"/>
</xsl:element>
</xsl:template>
<xsl:template match="w:pPr" mode="style_item" as="map(*)*">
<xsl:sequence select="map{1:'hi', 2:'hello', 3:'world'}"/>
</xsl:template>
</xsl:stylesheet>
the source document:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape"
xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing"
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14 wp14">
<w:body>
<w:p w:rsidR="00F708CA" w:rsidRDefault="00F708CA" w:rsidP="006E4E72">
<w:pPr>
<w:outlineLvl w:val="0"/>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>你好,大家好。</w:t>
</w:r>
<w:bookmarkStart w:id="0" w:name="_GoBack"/>
<w:bookmarkEnd w:id="0"/>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="Normal"/>
<w:rPr>
<w:b/>
<w:b/>
<w:bCs/>
<w:color w:val="C9211E"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:b/>
<w:bCs/>
<w:color w:val="C9211E"/>
</w:rPr>
<w:t xml:space="preserve">12345good day. </w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:pStyle w:val="Normal"/>
<w:rPr>
<w:i/>
<w:i/>
<w:iCs/>
<w:color w:val="77BC65"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
<w:highlight w:val="yellow"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:i/>
<w:iCs/>
<w:color w:val="77BC65"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
<w:highlight w:val="yellow"/>
</w:rPr>
<w:t>64789hello world</w:t>
</w:r>
</w:p>
<w:sectPr>
<w:type w:val="nextPage"/>
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:left="1134" w:right="1134" w:header="0" w:top="1134" w:footer="0"
w:bottom="1134" w:gutter="0"/>
<w:pgNumType w:fmt="decimal"/>
<w:formProt w:val="false"/>
<w:textDirection w:val="lrTb"/>
</w:sectPr>
</w:body>
</w:document>
The error displays "Cannot add a map to an XML tree" when runtime,
my xslt proccessor is saxon-he 9.8.0.12
If you want a result (principal or secondary) to show/serialize a map then use the output method adaptive (or json if the map represents JSON) for that result:
<xsl:output method="adaptive" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="m1" as="map(*)">
<xsl:call-template name="tmap1"/>
</xsl:variable>
<xsl:variable name="m2" as="map(*)">
<xsl:call-template name="tmap2"/>
</xsl:variable>
<xsl:sequence select="$m1, $m2"/>
</xsl:template>
https://xsltfiddle.liberty-development.net/gWEamLu
<xsl:output method="json" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="m1" as="map(*)">
<xsl:call-template name="tmap1"/>
</xsl:variable>
<xsl:variable name="m2" as="map(*)">
<xsl:call-template name="tmap2"/>
</xsl:variable>
<xsl:sequence select="[$m1, $m2]"/>
</xsl:template>
https://xsltfiddle.liberty-development.net/gWEamLu/1
My problem is that the '<xsl:variable name="t">' variable don't have a explicit type declaration.

make an element of a record unique, compared to another xml file

Given two xml files, i need to compare if the combination of TITLEID, and ARTIST, is unique (meaning that in the second file there is not the same combination of TITLEID,ARTIST, existing in the first xml file)
<CATALOG>
<RECORD ID="109">
<TITLEID>54</TITLEID>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR> </CD>
</RECORD>
<RECORD ID="187">
<TITLEID>88</TITLEID>
<ARTIST>Bonnie Tyler</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>CBS Records</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1988</YEAR> </CD>
</RECORD>
</CATALOG>
second xml:
<CATALOG>
<RECORD ID="109">
<TITLEID>54</TITLEID>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR> </CD>
</RECORD>
<RECORD ID="187">
<TITLEID>text ',.</TITLEID>
<ARTIST>Bonnie Tyler</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>CBS Records</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1988</YEAR> </CD>
</RECORD>
</CATALOG>
So i need to do the following, only for those records that have the following condition true:
TITLEIDARTIST (from xml1) TITLEIDARTIST (from xml2)
i need to make the TITLEIDARTIST combination unique, in the first xml file, adding for instance a digit to the TITLEID (making sure though that this digit addition will not make a new number that will create a new combination that will match the second xml).
SO the desired output would be like (i only need to modify the first xml, and leave untouched the second):
<CATALOG>
<RECORD ID="109">
<TITLEID>540</TITLEID>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR> </CD>
</RECORD>
<RECORD ID="187">
<TITLEID>88</TITLEID>
<ARTIST>Bonnie Tyler</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>CBS Records</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1988</YEAR> </CD>
</RECORD>
</CATALOG>
Please note that in the second XML file, in the TITLEID, one may find text, not only digits.
With XSLT 3 you can use a composite key to identify the elements for which there are duplicates in the secondary document and then generate a new id based on generate-id():
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:param name="doc2">
<CATALOG>
<RECORD ID="109">
<TITLEID>54</TITLEID>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR>
</RECORD>
<RECORD ID="187">
<TITLEID>text ',.</TITLEID>
<ARTIST>Bonnie Tyler</ARTIST>
<COUNTRY>UK</COUNTRY>
<COMPANY>CBS Records</COMPANY>
<PRICE>9.90</PRICE>
<YEAR>1988</YEAR>
</RECORD>
</CATALOG>
</xsl:param>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:key name="ref" match="RECORD" composite="yes" use="TITLEID, ARTIST"/>
<xsl:template match="RECORD[key('ref', (TITLEID, ARTIST), $doc2)]/TITLEID">
<xsl:copy>{generate-id()}</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bnnZVZ/0
For compactness and self-containedness that example include the secondary document inline but of course you could use the parameter to pass in the document from the outside or change the code to use e.g. <xsl:param name="doc2" select="doc($doc2-uri)"/> with a second parameter to pass in the URI/location of the second document e.g. <xsl:param name="doc2-uri" as="xs:string">second-doc.xml</xsl:param>.

Avoiding circular key definitions in XSLT

I am writing some code which turns valid XML instances into presentations of code in a DITA codeblock.
I have some wrapper elmenents in my input that allow me to define some emphasis on the output. This is easy for node() items as the wrapper directly wraps the code to be emphasised. For attributes and so on, though, I need to specify some #select on my emphasis element.
Here is a brief code excerpt of how I'm trying to do this (for attributes; I've removed similar templates for other types of content):
<xsl:key name="emph" match="eg:emph[#select]">
<xsl:variable name="selected">
<xsl:evaluate xpath="#select" context-item="."/>
</xsl:variable>
<xsl:for-each select="$selected">
<xsl:sequence select="generate-id()"/>
</xsl:for-each>
</xsl:key>
<xsl:template match="#*[key('emph', generate-id(.))]" mode="eg">
<xsl:variable name="style" select="if (not(key('emph', generate-id(.))/#style)) then 'italic' else key('emph', generate-id())/#style"/>
<xsl:text> </xsl:text>
<ph outputclass="{$style}">
<xsl:next-match>
<xsl:with-param name="includeSpace" select="false()"/>
</xsl:next-match>
</ph>
</xsl:template>
<xsl:template match="#*" mode="eg">
<xsl:param name="includeSpace" as="xs:boolean" select="true()"/>
<xsl:if test="$includeSpace">
<xsl:text> </xsl:text>
</xsl:if>
<ph outputclass="AttributeName">
<xsl:value-of select="name()"/>
</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">
<xsl:value-of select="."/>
</ph>
<ph outputclass="attributeQuotes">"</ph>
</xsl:template>
With an input such as:
<eg:emph select="abbrev-journal-title/#abbrev-type">
<abbrev-journal-title abbrev-type="custom">JPhysA</abbrev-journal-title>
</eg:emph>
I would like to generate something like:
<ph outputclass="XmlFurniture"><</ph><ph outputclass="ElementName">abbrev-journal-title</ph> <ph outputclass="italic"><ph outputclass="AttributeName">abbrev-type</ph><ph outputclass="equals">=</ph><ph outputclass="attributeQuotes">"</ph><ph outputclass="AttributeValue">custom</ph><ph outputclass="attributeQuotes">"</ph></ph><ph outputclass="XmlFurniture">></ph>JPhysA<ph outputclass="XmlFurniture"></</ph><ph outputclass="ElementName">abbrev-journal-title</ph><ph outputclass="XmlFurniture">></ph>
Transforming with Saxon (PE 9.8.0.12) returns 'key definition is circular' errors - but as far as I can tell, that isn't actually the case.
Can anyone suggest a workaround, or at least explain why this approach isn't working?
Well, for the use of xsl:evaluate see https://www.w3.org/TR/xslt-30/#evaluate-dynamic-context explaining
The context item, position, and size depend on the result of
evaluating the expression in the context-item attribute. If this
attribute is absent, or if the result is an empty sequence, then the
context item, position, and size for evaluation of the target
expression are all absent.
As you don't set that attribute context-item on your xsl:evaluate at all your attempt with xpath="#select" doesn't make sense, I guess you want to use context-item="." to select the matched element.
As for selecting attribute nodes and storing them in a variable, I think you need to use
<xsl:variable name="selected" as="attribute()*">
<xsl:evaluate xpath="#select" context-item="."/>
</xsl:variable>
instead of
<xsl:variable name="selected">
<xsl:evaluate xpath="#select" context-item="."/>
</xsl:variable>
Then I would think that
<xsl:for-each select="$selected">
<xsl:sequence select="generate-id()"/>
</xsl:for-each>
can be shortened/simplified to
<xsl:sequence select="$selected!generate-id()"/>
in the context of XSLT 3.
I have now tried to build a minimal but complete example and to test that with Saxon, using Saxon 9.8.0.12 EE and Saxon 9.9.0.1 EE, I get no errors and it seems the approach work as far as the templates you have created are used.
Test XSLT is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:eg="http://example.com/eg"
exclude-result-prefixes="#all"
default-mode="eg"
version="3.0">
<xsl:mode name="eg" on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:key name="emph" match="eg:emph[#select]">
<xsl:variable name="selected" as="attribute()*">
<xsl:evaluate xpath="#select" context-item="."/>
</xsl:variable>
<xsl:sequence select="$selected ! generate-id()"/>
</xsl:key>
<xsl:template match="#*[key('emph', generate-id(.))]" mode="eg">
<xsl:variable name="style" select="if (not(key('emph', generate-id(.))/#style)) then 'italic' else key('emph', generate-id())/#style"/>
<xsl:text> </xsl:text>
<ph outputclass="{$style}">
<xsl:next-match>
<xsl:with-param name="includeSpace" select="false()"/>
</xsl:next-match>
</ph>
</xsl:template>
<xsl:template match="#*" mode="eg">
<xsl:param name="includeSpace" as="xs:boolean" select="true()"/>
<xsl:if test="$includeSpace">
<xsl:text> </xsl:text>
</xsl:if>
<ph outputclass="AttributeName">
<xsl:value-of select="name()"/>
</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">
<xsl:value-of select="."/>
</ph>
<ph outputclass="attributeQuotes">"</ph>
</xsl:template>
</xsl:stylesheet>
Sample input is
<root>
<eg:emph select="abbrev-journal-title/#abbrev-type" xmlns:eg="http://example.com/eg">
<abbrev-journal-title abbrev-type="custom">JPhysA</abbrev-journal-title>
</eg:emph>
<eg:emph select="abbrev-journal-title/#abbrev-type" xmlns:eg="http://example.com/eg" style="bold">
<abbrev-journal-title abbrev-type="custom">JPhysA</abbrev-journal-title>
</eg:emph>
</root>
Result shows the attributes are matched and transformed:
<root>
<eg:emph xmlns:eg="http://example.com/eg">
<ph outputclass="AttributeName">select</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">abbrev-journal-title/#abbrev-type</ph>
<ph outputclass="attributeQuotes">"</ph>
<abbrev-journal-title>
<ph outputclass="italic">
<ph outputclass="AttributeName">abbrev-type</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">custom</ph>
<ph outputclass="attributeQuotes">"</ph>
</ph>JPhysA</abbrev-journal-title>
</eg:emph>
<eg:emph xmlns:eg="http://example.com/eg">
<ph outputclass="AttributeName">select</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">abbrev-journal-title/#abbrev-type</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeName">style</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">bold</ph>
<ph outputclass="attributeQuotes">"</ph>
<abbrev-journal-title>
<ph outputclass="bold">
<ph outputclass="AttributeName">abbrev-type</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">custom</ph>
<ph outputclass="attributeQuotes">"</ph>
</ph>JPhysA</abbrev-journal-title>
</eg:emph>
</root>

String replace and concatenation

I have this structure
<ROWS>
<ROW>
<TEXT> This is a #good# #day# </TEXT>
<good>great</good>
<day>month</day>
</ROW>
<ROW>
<TEXT> This is a #good# #day# </TEXT>
<good>Fun</good>
<day>morning</day>
</ROW>
</ROWS>
How do I change that to
<statement> This is a great month, this is a Fun morning </statement>
Using only XSLT 1.0?
The original XML can change tag name. But not the structure! Any ideas?
This seems somewhat similar to creating form letters from a template. Assuming the example is not to be meant literally, you could try something like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<statements>
<xsl:for-each select="ROWS/ROW/TEXT">
<statement>
<xsl:call-template name="merge">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
</statement>
</xsl:for-each>
</statements>
</xsl:template>
<xsl:template name="merge">
<xsl:param name="string"/>
<xsl:param name="sep" select="'#'"/>
<xsl:choose>
<xsl:when test="contains($string, $sep) and contains(substring-after($string, $sep), $sep)">
<xsl:value-of select="substring-before($string, $sep)" />
<xsl:variable name="placeholder" select="substring-before(substring-after($string, $sep), $sep)" />
<xsl:value-of select="../*[name() = $placeholder]" />
<!-- recursive call -->
<xsl:call-template name="merge">
<xsl:with-param name="string" select="substring-after(substring-after($string, $sep), $sep)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Given an input of:
<ROWS>
<ROW>
<TEXT>The quick brown #animal# jumps over the #property# dog.</TEXT>
<animal>fox</animal>
<property>lazy</property>
</ROW>
<ROW>
<TEXT>A journey of a #number# miles #action# with a single #act#.</TEXT>
<number>thousand</number>
<action>begins</action>
<act>step</act>
</ROW>
</ROWS>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<statements>
<statement>The quick brown fox jumps over the lazy dog.</statement>
<statement>A journey of a thousand miles begins with a single step.</statement>
</statements>
Look at the XSLT 1.0 spec; find the section on string functions. Study the contains, substring-before, and substring-after functions. The solution to your problem should become clear; if it doesn't, you should at least be able to get far enough on your problem to pose a question that does not look as if it could be paraphrased as "Please do my homework for me."

XSLT 2.0 : Split a string into comma separated values

I am new to XSLT and have a requirement where in i have to manipulate a string as below.
Input string = "12345"
Output expected ="12345,1234,123,12"
Can anybody help me to achieve this in XSLT 2.0
Here is some XSLT/XPath 2.0 approach:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf"
version="2.0">
<xsl:function name="mf:sub-sequences" as="xs:string*">
<xsl:param name="input" as="xs:string"/>
<xsl:param name="min-length" as="xs:integer"/>
<xsl:sequence select="reverse(
for $length in $min-length to string-length($input)
return substring($input, 1, $length)
)"/>
</xsl:function>
<xsl:template name="main">
<xsl:variable name="s" select="'12345'"/>
<xsl:value-of select="mf:sub-sequences($s, 2)" separator=","/>
</xsl:template>
</xsl:stylesheet>
This should do the trick:
<xsl:template match="/">
<xsl:call-template name="minus-one">
<xsl:with-param name="input" select="'12345'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="minus-one">
<xsl:param name="input"/>
<xsl:value-of select="$input"/>
<xsl:if test="string-length($input) gt 2"><xsl:text>,</xsl:text>
<xsl:call-template name="minus-one">
<xsl:with-param name="input" select="substring($input, 1, string-length($input) - 1)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Here is a more efficient solution than the currently accepted one that doesn't use the reverse() function:
<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 method="text"/>
<xsl:function name="my:subSequences" as="xs:string*">
<xsl:param name="pString" as="xs:string"/>
<xsl:param name="pstartLength" as="xs:integer"/>
<xsl:sequence select=
"for $totalLength in string-length($pString),
$length in 1 to $totalLength -$pstartLength +1,
$revLength in $totalLength -$length +1
return
substring($pString, 1, $revLength)"/>
</xsl:function>
<xsl:template match="/">
<xsl:value-of select="my:subSequences('12345', 2)" separator=","/>
</xsl:template>
</xsl:stylesheet>
When this transformation is executed, the wanted, correct result is produced:
12345,1234,123,12
Explanation:
The XPath 2.0 W3C Spec defines that if the first argument of the to operator is greater than the second argument, then the resulting sequence is the empty sequense.
It is still possible to avoid this limitation and to construct a decreasing integer sequence, like this:
for $k in 0 to $big - $small
return
$big - $k
Using such expression is more efficient, especially for large sequences, than first constructing an increasing sequence and then reversing it with the reverse() function.
on
<string>12345</string>
the following xslt will produce the result 12345,1234,123,12
<?xml version="1.0" encoding="utf-8"?>
<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:output method="xml" indent="yes"/>
<xsl:template match="string">
<xsl:call-template name="doTheFunkeyMonkey">
<xsl:with-param name="data" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="doTheFunkeyMonkey">
<xsl:param name="data"/>
<xsl:value-of select="$data"/>
<xsl:if test="string-length($data) > 2">
<xsl:text>,</xsl:text>
<xsl:call-template name="doTheFunkeyMonkey">
<xsl:with-param name="data" select="substring($data,1,string-length($data)-1)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Resources