I'm trying to parse element contents and split them on a delimiter, while keeping all elements in the parent. I don't need -- don't want -- to find the delimiter inside the child elements.
<data>
<parse-field>Some text <an-element /> more text; cheap win? ;
<another-element>with delimiter;!</another-element>; final text</parse-field>
</data>
Should become
<data>
<parsed-field>
<field>Some text <an-element /> more text</field>
<field>cheap win?</field>
<field><another-element>with limiter;!</another-element></field>
<field>final text</field>
</parsed-field>
</data>
I've got a hacked-together solution that examines all "parse-field/text()" and replaces the delimiter with <token />, then a second pass to pick out the pieces around the<token>s, but it's... hacked. And unpleasant. I'm wondering if there's a better way.
I'm using XSLT-2.0, open to XSLT-1.0 solutions. SAXON processor.
This is not (yet?) a complete answer, just an outline of a possible approach. If you would make your first pass something like:
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="parse-field/text()">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="';'"/>
<xsl:choose>
<xsl:when test="contains($text, $delimiter)">
<field>
<xsl:value-of select="substring-before($text, $delimiter)"/>
</field>
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="position()=last()">
<field><xsl:value-of select="$text"/></field>
</xsl:when>
<xsl:when test="$text">
<text><xsl:value-of select="$text"/></text>
</xsl:when>
</xsl:choose>
</xsl:template>
you would obtain:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<parse-field>
<text>Some text </text>
<an-element/>
<field> more text</field>
<field> cheap win? </field>
<another-element>with delimiter;!</another-element>
<field/>
<field> final text</field>
</parse-field>
</data>
This is now a grouping problem, where elements of <parse-field> need to be grouped, with each group ending with <field>.
Best approach I've had so far, in simple form:
<xsl:variable name="delimiter" select="';'" />
<xsl:template match="foo">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:call-template name="tokenize" />
</xsl:copy>
</xsl:template>
<xsl:template name="tokenize">
<xsl:variable name="rough">
<xsl:apply-templates mode="tokenize" />
</xsl:variable>
<xsl:copy>
<xsl:group-by select="$rough/*" group-ending-with="delimiter">
<field><xsl:apply-templates select="current-group()[not(self::delimiter)]" /></field>
</xsl:group>
</xsl:copy>
</xsl:template>
<xsl:template match="*" mode="tokenize">
<xsl:copy>
<xsl:apply-templates select="#*|*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="text()" mode="tokenize">
<xsl:analyze-string select="." regex="([^{$delimiter}]*){$delimiter}">
<xsl:matching-substring>
<xsl:value-of select="regex-group(1)" /><delimiter/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="." />
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
Related
I'm working on a xsl template and one specific string is giving me a hard time.
<prixmenu>29.00-90.00</prixmenu>
Desired output :
<prixmenu>29€-90€</prixmenu>
The whole code I have is :
XML (input) :
<?xml version="1.0" encoding="UTF-8"?>
<data>
<etablissement not="15.0" etoile="2" >
<nom>L'Auberge Asterix</nom>
<index>AUBERGE ASTERIX (L')</index>
<id>0123456789</id>
<regionindex>Paris</regionindex>
<equipe>
<chef>Ratatouille</chef>
</equipe>
<pictoPratique cave_remarquable="0" coeur="0" ></pictoPratique>
<coordonnees>
<adresse>Random adress</adresse>
<tel>01 23 45 67 89/tel>
<fermetures>mam,maa,</fermetures>
<pratique terrasse="0"
voiturier="0"
parcPrive="1"
handicap="0"
airConditione="0"
piscine="0"
tennis="0"
chien="1"
relaischat="0"
delivery="0"
clickAndCollect="0"
itineraireGourmand="0"
menu_kids="1"
hebergement="0"
></pratique>
</coordonnees>
<texte>Lorem ipsum</texte>
<prixrestau>
<prixcarte>0</prixcarte>
<prixmenu>29.00-90.00</prixmenu>
</prixrestau>
</etablissement>
</data>
XSL stylesheet :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="data | node()[not(self::coordonnees)]">
<xsl:copy>
<xsl:apply-templates select="nom" />
<xsl:choose>
<xsl:when test="#etoile = 1">
<etoile>1 étoile</etoile>
</xsl:when>
<xsl:when test="#etoile > 1">
<etoile><xsl:value-of select="#etoile" /> étoiles</etoile>
</xsl:when>
</xsl:choose>
<xsl:apply-templates select="index" />
<xsl:apply-templates select="id" />
<xsl:apply-templates
select="#*[not(.='0')][name( ) != 'etoile'] | node()[not(self::index)][not(self::id)][not(self::prixrestau)][not(self::nom)][not(self::etoile)]" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*[name( ) != 'etoile']">
<xsl:element name="{name()}">
<xsl:value-of select="number(.)" />
</xsl:element>
</xsl:template>
<xsl:template match="prixrestau/prixmenu" name="split">
<xsl:param name="pText" select="."/>
<xsl:if test="$pText">
<xsl:value-of select="number(substring-before(concat($pText, '-'), '-'))"/>
<xsl:call-template name="split">
<xsl:with-param name="pText" select="substring-after($pText, '-')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="coordonnees">
<xsl:copy>
<xsl:apply-templates select="adresse" />
<xsl:apply-templates select="tel" />
<xsl:element name="prixrestau">
<xsl:value-of select="translate(../prixrestau/prixmenu,'.00.','€')"/>
<xsl:apply-templates select="../prixrestau/prixcarte[not(.='0')]" />
<xsl:element name="prixmenu">
<xsl:apply-templates select="../prixrestau/prixmenu" />
</xsl:element>
</xsl:element>
<xsl:apply-templates select="fermetures" />
<xsl:apply-templates
select="#*[not(.='0')] | node()[not(self::adresse)][not(self::tel)][not(self::fermetures)]" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Desired output :
<?xml version="1.0"?>
<data>
<etablissement>
<nom>L'Auberge Asterix</nom>
<etoile>2 étoiles</etoile>
<index>AUBERGE ASTERIX (L')</index>
<id>0123456789</id>
<not>13</not>
<regionindex>Paris</regionindex>
<equipe>
<chef>Ratatouille</chef>
</equipe>
<pictoPratique/>
<coordonnees>
<adresse>Random adress</adresse>
<tel>01 23 45 67 89</tel>
<prixrestau><prixmenu>29€-90€</prixmenu></prixrestau>
<fermetures>mam,maa,</fermetures>
<pratique>
<parcPrive>1</parcPrive>
<chien>1</chien>
<menu_kids>1</menu_kids>
</pratique>
</coordonnees>
<texte>Lorem ipsum</texte>
</etablissement>
</data>
Any suggestions on how to proceed ? Should I import FXSL or switch to XSLT 2.0 as it probably be less of a pain to get to the desired result ?
I've tried splitting it with the following code (I belive it's called a 'recursive template" :
<xsl:template match="prixrestau/prixmenu" name="split">
<xsl:param name="pText" select="."/>
<xsl:if test="$pText">
<xsl:value-of select="number(substring-before(concat($pText, '-'), '-'))"/>
<xsl:call-template name="split">
<xsl:with-param name="pText" select="substring-after($pText, '-')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
This outputs
<prixmenu>2990</prixmenu>
And i've also tried
<xsl:value-of select="translate(../prixrestau/prixmenu,'.00.','€')"/>
and the output is :
29€-9€
Because '.00.' and '€'don't have the same length (MDN reference)...
How about:
<xsl:template match="prixmenu">
<xsl:copy>
<xsl:value-of select="format-number(substring-before(., '-'), '0€')"/>
<xsl:text>-</xsl:text>
<xsl:value-of select="format-number(substring-after(., '-'), '0€')"/>
</xsl:copy>
</xsl:template>
If you like, you could change the first xsl:value-of instruction to:
<xsl:value-of select="format-number(substring-before(., '-'), '0€-')"/>
and get rid of the xsl:text part - but I think it's better to keep it to make the code more readable.
I need to split two tags with comma separated string into a list of parent-child tags as shown below.
For example, the input will be :-
<UserID>162,163</UserID>
<UserName>Stacy,Stephen</UserName>
Expected output :-
Expected Output
Please help to achieve this result using xslt code
I tried the following format which I got from another query, but its generating a nested pattern instead of the list :-
<xsl:template name="tokenize">
<xsl:param name="textID" select="."/>
<xsl:param name="textName" select="."/>
<xsl:param name="separator" select="','"/>
<User>
<xsl:choose>
<xsl:when test="not(contains($textID, $separator))">
<ID>
<xsl:value-of select="normalize-space($textID)"/>
</ID>
</xsl:when>
<xsl:otherwise>
<ID>
<xsl:value-of select="normalize-space(substring-before($textID, $separator))"/>
</ID>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="substring-after($textID, $separator)"/>
<xsl:with-param name="textName" select="substring-after($textName, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="not(contains($textName, $separator))">
<Name>
<xsl:value-of select="normalize-space($textName)"/>
</Name>
</xsl:when>
<xsl:otherwise>
<Name>
<xsl:value-of select="normalize-space(substring-before($textName, $separator))"/>
</Name>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="substring-after($textID, $separator)"/>
<xsl:with-param name="textName" select="substring-after($textName, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</User>
</xsl:template>
There are few assumptions before simplifying the tokenize template shared in the XSLT code.
The count of comma separated values in <UserID> and <UserName> is always equal.
There is a 1-1 correspondence on the indexes of the values i.e. 162 <-> Stacy and 163 <-> Stephen.
XSLT version is 1.0
A parent node <UserList> has been added as a root node for the shared input XML.
XSLT
<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="UserList">
<xsl:copy>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="normalize-space(UserID)" />
<xsl:with-param name="textName" select="normalize-space(UserName)" />
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="textID" />
<xsl:param name="textName" />
<xsl:param name="separator" select="','" />
<xsl:choose>
<xsl:when test="not(contains($textID, $separator) and contains($textName, $separator)) ">
<User>
<ID><xsl:value-of select="$textID" /></ID>
<Name><xsl:value-of select="$textName" /></Name>
</User>
</xsl:when>
<xsl:otherwise>
<User>
<ID><xsl:value-of select="substring-before($textID, $separator)" /></ID>
<Name><xsl:value-of select="substring-before($textName, $separator)" /></Name>
</User>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="normalize-space(substring-after($textID, $separator))" />
<xsl:with-param name="textName" select="normalize-space(substring-after($textName, $separator))" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output
<UserList>
<User>
<ID>162</ID>
<Name>Stacy</Name>
</User>
<User>
<ID>163</ID>
<Name>Stephen</Name>
</User>
</UserList>
I have a XML file following this scheme:
<translationData>
<product>
<attributeValue>
<value>1/4"</value>
<value1>1/4"</value1>
<currentValue>aaaa;bbbb</currentValue>
</attributeValue>
</product>
</translationData>
because of the semicolon in "currentValue" i need to escape the semicolon AND the double quotes in "value".
I am able to escape the semicolon by placing all text in qoutes as following:
XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" />
<xsl:param name="delim" select="';'" />
<xsl:param name="quote" select="'"'" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
<xsl:apply-templates select="translationData/product/attributeValue" />
</xsl:template>
<xsl:template match="attributeValue">
<xsl:apply-templates />
<xsl:if test="following::*">
<xsl:value-of select="$break" />
</xsl:if>
</xsl:template>
<xsl:template match="*">
<!-- remove normalize-space() if you want keep white-space at it is -->
<xsl:value-of select="concat($quote, translate(.,'"','\"'), $quote)" />
<xsl:if test="following-sibling::*">
<xsl:value-of select="$delim" />
</xsl:if>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
but somehow the Output is:
"1/4\";"1/4\";"aaaa;bbbb"
instead of
"1/4\"";"1/4\"";"aaaa;bbbb"
Where am I going wrong?
I am new to XML and XSLT and did not find any question handling this specific case.
XSLT code is from an answer by #Tomalak for another question. see here
The translate() function will only replace each single character with another single character.
To replace a single character " with a two-character string\" you need to use a named recursive template or - if your processor supports it - an extension function such as EXSLT str:replace().
Here's an example of using a recursive template:
...
<xsl:template match="*">
<xsl:text>"</xsl:text>
<xsl:call-template name="replace">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
<xsl:text>"</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>;</xsl:text>
</xsl:if>
</xsl:template>
...
<xsl:template name="replace">
<xsl:param name="text"/>
<xsl:param name="searchString">"</xsl:param>
<xsl:param name="replaceString">\"</xsl:param>
<xsl:choose>
<xsl:when test="contains($text,$searchString)">
<xsl:value-of select="substring-before($text,$searchString)"/>
<xsl:value-of select="$replaceString"/>
<!-- recursive call -->
<xsl:call-template name="replace">
<xsl:with-param name="text" select="substring-after($text,$searchString)"/>
<xsl:with-param name="searchString" select="$searchString"/>
<xsl:with-param name="replaceString" select="$replaceString"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
...
I would like to print path of element and attributes if any along with values using XSLT. e.g
XML :
<root>
<node attr='abc' module='try'>
<subnode>Roshan</subnode>
<subnode>Chetan</subnode>
<subnode>Jennifer</subnode>
</node>
</root>
Output :
/root##
/root/node##
/root/node/#attr##abc
/root/node/#module##try
/root/node/subnode[1]##Roshan
/root/node/subnode[2]##Chetan
/root/node/subnode[3]##Jennifer
I am trying with below snippet, but could only print path of element and it's value
<xsl:template match="*">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/',local-name())" />
<xsl:if
test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
<xsl:value-of
select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')" />
</xsl:if>
<!-- <xsl:call-template name="attrData"></xsl:call-template> -->
</xsl:for-each>
<xsl:text>##</xsl:text>
<xsl:apply-templates select="node()" />
</xsl:template>
I am new to XSLT. Please help!!!!
I made the following XSLT and added also the [position] to the output. You can remove that if you need.
This gives this output:
/root[1]
/root[1]/node[1]
/root[1]/node[1]/#attr[1]##abc
/root[1]/node[1]/#module[1]##try
/root[1]/node[1]/subnode[1]##Roshan
/root[1]/node[1]/subnode[2]##Chetan
/root[1]/node[1]/subnode[3]##Jennifer
With this XSLT. With the two output template you can choose how to print the Xpath.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="" version="2.0">
<xsl:output method="text" encoding="utf-8" />
<xsl:template match="/">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="*">
<xsl:call-template name="generateXPath">
<xsl:with-param name="previous" select="''"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="generateXPath">
<xsl:param name="previous" as="xs:string"/>
<xsl:variable name="this" select="." as="node()"/>
<xsl:if test="not(empty(.))">
<xsl:variable name="thisXPath" select="concat($previous, '/', name(.),'[', count(preceding-sibling::*[name() = name($this)])+1,']')"></xsl:variable>
<xsl:apply-templates select="." mode="output">
<xsl:with-param name="previous" select="$previous"/>
</xsl:apply-templates>
<xsl:text>
</xsl:text>
<xsl:for-each select="*|#*">
<xsl:call-template name="generateXPath">
<xsl:with-param name="previous" select="$thisXPath"/>
</xsl:call-template>
</xsl:for-each>
</xsl:if>
</xsl:template>
<xsl:template match="*" mode="output">
<xsl:param name="previous" as="xs:string"/>
<xsl:variable name="this" select="." as="node()"/>
<xsl:variable name="thisXPath">
<xsl:value-of select="concat($previous, '/', name(.),'[', count(preceding-sibling::*[name() = name($this)])+1,']')"></xsl:value-of>
<xsl:if test="not(*)">
<xsl:value-of select="concat('##',text())"></xsl:value-of>
</xsl:if>
</xsl:variable>
<xsl:value-of select="$thisXPath" />
</xsl:template>
<xsl:template match="#*" mode="output">
<xsl:param name="previous" as="xs:string"/>
<xsl:variable name="this" select="." as="node()"/>
<xsl:variable name="thisXPath" select="concat($previous, '/#', name(.),'[', count(preceding-sibling::*[name() = name($this)])+1,']','##',.)"></xsl:variable>
<xsl:value-of select="$thisXPath" />
</xsl:template>
</xsl:stylesheet>
There is XML document:
<data>how;many;i;can;tell;you</data>
Need to get XML using XSLT version 1:
<manydata>
<onedata>how</onedata>
<onedata>many</onedata>
<onedata>i</onedata>
<onedata>can</onedata>
<onedata>tell</onedata>
<onedata>you</onedata>
</manydata>
How I can do it?
UPDATE:
Output format must be XML.
This recursive solution is probably one of the shortest possible:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="data">
<manydata><xsl:apply-templates/></manydata>
</xsl:template>
<xsl:template match="text()" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<onedata>
<xsl:value-of select=
"substring-before(concat($pText,';'),';')"/>
</onedata>
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select=
"substring-after($pText,';')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document;
<data>how;many;i;can;tell;you</data>
the wanted, correct result is produced:
<manydata>
<onedata>how</onedata>
<onedata>many</onedata>
<onedata>i</onedata>
<onedata>can</onedata>
<onedata>tell</onedata>
<onedata>you</onedata>
</manydata>
<xsl:template match="data">
<manydata>
<!--
empty <manydata/> will be generated,
if <data/> without child text()
-->
<xsl:apply-templates select="text()"/>
</manydata>
</xsl:template>
<xsl:template match="data/text()">
<!-- start point for recursion -->
<xsl:call-template name="tokenize-string">
<xsl:with-param name="separator" select="';'"/>
<xsl:with-param name="string" select="text()"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize-string">
<xsl:param name="separator"/>
<xsl:param name="string"/>
<xsl:variable name="string-before-separator"
select="substring-before( $string, $separator )"/>
<onedata>
<xsl:choose>
<!-- if $separator presents in $string take first piece -->
<xsl:when test="$string-before-separator">
<xsl:value-of select="$string-before-separator"/>
</xsl:when>
<!-- take whole $string, if no $separator in the $string -->
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</onedata>
<!-- recursive call, if separator was found -->
<xsl:if test="$string-before-separator">
<xsl:call-template name="tokenize-string">
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="string"
select="substring-after( $string, $separator )"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:element name="manydata">
<xsl:call-template name="splitsemicolons">
<xsl:with-param name="text" select="text()" />
</xsl:call-template>
</xsl:element>
</xsl:template>
<xsl:template name="splitsemicolons">
<xsl:param name="text" />
<xsl:choose>
<xsl:when test="contains($text,';')">
<xsl:element name="onedata">
<xsl:value-of select="substring-before($text,';')" />
</xsl:element>
<xsl:call-template name="splitsemicolons">
<xsl:with-param name="text" select="substring-after($text,';')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="onedata">
<xsl:value-of select="$text" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This uses a named template that is called recursively, each time outputting what's before the first ;, and calling itself with everything after the first ;. If there isn't a ;, it just outputs whatever's left as-is.
You can use the XSLT extension library to get the tokenize function. Here is how the final code will look like:
<xsl:template match="/">
<manydata>
<xsl:for-each select="str:tokenize(data, ';')">
<xsl:value-of select="." />
</xsl:for-each>
</manydata>
</xsl:template>
</xsl:stylesheet>
Please note you will have to import the extension library into you XSLT using:
<xsl:import href="path/str.xsl" />
before you use the library functions.