I am quite new to XSLT,
I have a source XML message, which in its simplified version looks something like this:
<?xml version='1.0' encoding='iso-8859-1'?>
<Message>
<Invalid>
<InvalidBody>
<SynchError>
<ErrorText>The value of %1 should not be %2.</ErrorText>
<ErrorParameter>
<!-- Error Parameter is %1 identifier -->
<ErrorParameterType>value</ErrorParameterType>
<ErrorParameterValue>someField</ErrorParameterValue>
</ErrorParameter>
<ErrorParameter>
<!-- Error Parameter is %2 identifier -->
<ErrorParameterType>value</ErrorParameterType>
<ErrorParameterValue>someValue</ErrorParameterValue>
</ErrorParameter>
</SynchError>
</InvalidBody>
</Invalid>
</Message>
Now, I would like to use XSLT 1.0 to extract the ErrorText string and substitute the parameters %1 and %2 with the corresponding ErrorParameter/ErrorParameterValue values. The number of parameters %1, %2, %3... cannot be known in advance, so the solution should be flexible enough to accommodate a variable number of parameters.
Is there any elegant way to do this?
So, after quite a lot of googling around and a healthy dose of headache, I came up with the following solution, which seems to work like a charm:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:variable name="err-text" select="/Message/Invalid/InvalidBody/SynchError/ErrorText" />
<xsl:variable name="param-count" select="count(/Message/Invalid/InvalidBody/SynchError/ErrorParameter)" />
<xsl:call-template name="replace-params">
<xsl:with-param name="position" select="$param-count"/>
<xsl:with-param name="source-text" select="$err-text" />
</xsl:call-template>
</xsl:template>
<xsl:template name="replace-params">
<xsl:param name="position"/>
<xsl:param name="source-text"/>
<xsl:choose>
<xsl:when test="$position = 0">
<xsl:value-of select="$source-text"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="replace-params">
<xsl:with-param name="position" select="$position - 1"/>
<xsl:with-param name="source-text">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="$source-text" />
<xsl:with-param name="replace" select="concat('%', $position)" />
<xsl:with-param name="by" select="/Message/Invalid/InvalidBody/SynchError/ErrorParameter[$position]/ErrorParameterValue" />
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="string-replace-all">
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text"
select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I use the "string-replace-all" template as a substitute for the XSLT 2.0 replace() function, since I cannot exclude multiple occurrences of a single parameter.
The "replace-params" template is applied recursively on the original text, iterating backwards on the index of the set of ErrorParameters.
The way I have tackled similar problems is to create a named template that recurses through the string (text element of) <ErrorText> with each cycle picking out the first n% item, then dereferences the <ErrorParameter> to access the contents of that and store in a result, then snip off the n% item just process and calls itself to grab the next one. When there are no more %n items left, return the result.
Here's an example of that, this template basically counts comma-separated items in a parameter passed in on the first cycle called List, and returns a $Count when it's finished.
<xsl:template name="CountList">
<xsl:param name="List"/>
<xsl:param name="Count" select="0"/>
<xsl:choose>
<xsl:when test="contains($List,',') = false()">
<xsl:value-of select="$Count"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="CountList">
<xsl:with-param name="List">
<xsl:value-of select="substring-after($List,',')"/>
</xsl:with-param>
<xsl:with-param name="Count">
<xsl:value-of select="$Count + 1"/>
</xsl:with-param>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
You can try something like this. You can use a replace instead of 'substring-before' and 'after' if this function is supported.
<xsl:template match="SynchError">
<xsl:apply-templates select="ErrorParameter[1]">
<xsl:with-param name="text"><xsl:value-of select="ErrorText"/></xsl:with-param>
<xsl:with-param name="position">1</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="ErrorParameter">
<xsl:param name="text"/>
<xsl:param name="position"/>
<xsl:apply-templates select="following::ErrorParameter">
<xsl:with-param name="position"><xsl:value-of select="number($position)+1"/></xsl:with-param>
<xsl:with-param name="text"><xsl:value-of select="concat(substring-before($text,concat('%',$position)),ErrorParameterValue,substring-after($text,concat('%',$position)))"/></xsl:with-param>
</xsl:apply-templates>
<xsl:if test="not(following::ErrorParameter)">
<xsl:value-of select="concat(substring-before($text,concat('%',$position)),ErrorParameterValue,substring-after($text,concat('%',$position)))"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Here's another way you could look at it:
XSLT 1.0
<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="/">
<output>
<xsl:call-template name="merge">
<xsl:with-param name="string" select="Message/Invalid/InvalidBody/SynchError/ErrorText"/>
<xsl:with-param name="parameters" select="Message/Invalid/InvalidBody/SynchError/ErrorParameter"/>
</xsl:call-template>
</output>
</xsl:template>
<xsl:template name="merge">
<xsl:param name="string"/>
<xsl:param name="parameters"/>
<xsl:param name="flag" select="'%'"/>
<xsl:choose>
<xsl:when test="contains($string, $flag)">
<xsl:variable name="subsequent-char"
select="substring(translate(substring-after($string, $flag), '0123456789', ''), 1, 1)"/>
<xsl:variable name="i"
select="substring-before(substring-after($string, $flag), $subsequent-char)" />
<xsl:value-of select="substring-before($string, $flag)"/>
<xsl:value-of select="$parameters[number($i)]/ErrorParameterValue"/>
<!-- recursive call -->
<xsl:call-template name="merge">
<xsl:with-param name="string" select="substring-after($string, concat($flag, $i))"/>
<xsl:with-param name="parameters" select="$parameters"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Related
(This question is a less simplified version of my problem. The more simplified version which was already answered can be found here. I'm posting this more complicated question due to a comment by michael.hor257k who suggested that there may be an alternative approach that could solve it - possibly using select in a loop, or possibly a completely different approach.)
I'd like to process an XML file, over whose format I have no control, to generate C++ code. I need to process functions defined in XML in several different ways to produce different parts of the code. As part of this I need to select a subset of function parameters that match a complicated criteria and pass this selection to several named templates; the named templates need to be able to access the original document.
This example creates a complex selection of C++ function parameters that do not have constant values (ie the same min and max), where the min and max may be decimal or hexadecimal, using the "GenerateNonFixedParameters" template. The parameters refer to enumerations which are located elsewhere in the document, and these definitions are referenced by the named template call "ListParameterValues".
There are two problems.
The creation of the variable "nonFixedParameters" does not use select. I cannot work out how to use select for such a complicated case (XSL 1.0), but maybe there is a way.
A copy of the nodes does not suffice, as the "ListParameterValues" template as it currently stands needs to operate on an original set of nodes from the document.
Example XSL with the locations of these two problems marked:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:template match="/">
<xsl:for-each select="//function">
<!-- 1. This does not use 'select' therefore it does not work. This is XSL 1.0 so as="node()*" cannot be used. -->
<xsl:variable name="nonFixedParameters">
<xsl:call-template name="GenerateNonFixedParameters"/>
</xsl:variable>
<xsl:call-template name="ListParameterValues">
<xsl:with-param name="parameters" select="$nonFixedParameters"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="ListParameterValues">
<xsl:param name="parameters"/>
<xsl:for-each select="$parameters">
<xsl:value-of select="#name"/>
<xsl:text>[</xsl:text>
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<!-- 2. This must be executed in the context of a document node, therefore this does not work. -->
<xsl:for-each select="//enum[#name=current()/#enum]/value">
<xsl:if test="#val >= $min and #val <= $max">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="GenerateNonFixedParameters">
<xsl:for-each select="parameter">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:if test="$min != $max">
<!-- Here a copy is clearly the wrong approach! -->
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:transform>
Simple XML to feed the above:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<dictionary>
<enum name="EnumName">
<value name="firstValue" val="1" />
<value name="secondValue" val="2" />
<value name="thirdValue" val="3" />
<value name="forthValue" val="4" />
<value name="fifthValue" val="5" />
</enum>
</dictionary>
<function name="FunctionOne">
<parameter name="p1" type="enum" enum="EnumName" min="2" max="0x4"/>
<parameter name="p2" type="enum" enum="EnumName" min="0x03" max="3"/>
</function>
</body>
Wanted output. Note that p1 has all names within [min..max] listed, but p2 has none listed because min and max have the same value.
p1[secondValue thirdValue forthValue ] p2[]
I think your stylesheet can be made to work with XSLT 1.0 if you use an extension function like exsl:node-set to convert your result tree fragment into a node-set and if you store the root node of the primary input tree into a global variable or parameter as then you will be able to compare nodes in your primary input document to nodes of the newly constructed, temporary tree.
Based on these suggestions the code would look like
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:exsl="http://exslt.org/common">
<xsl:output method="text" encoding="iso-8859-1" omit-xml-declaration="yes" />
<xsl:variable name="main-root" select="/"/>
<xsl:template match="/">
<xsl:for-each select="//function">
<!-- 1. Using exsl:node-set or similar you can convert that result tree fragment into a node set to process it further -->
<xsl:variable name="nonFixedParameters">
<xsl:call-template name="GenerateNonFixedParameters"/>
</xsl:variable>
<xsl:call-template name="ListParameterValues">
<xsl:with-param name="parameters" select="$nonFixedParameters"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="ListParameterValues">
<xsl:param name="parameters"/>
<!-- <xsl:for-each xmlns:ms="urn:schemas-microsoft-com:xslt" select="ms:node-set($parameters)/parameter"> for MSXML or XslTransform -->
<xsl:for-each select="exsl:node-set($parameters)/parameter">
<xsl:value-of select="#name"/>
<xsl:text>[</xsl:text>
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<!-- 2. This must be executed in the context of a document node, therefore using the global variable works. -->
<xsl:for-each select="$main-root//enum[#name=current()/#enum]/value">
<xsl:if test="#val >= $min and #val <= $max">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="GenerateNonFixedParameters">
<xsl:for-each select="parameter">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:if test="$min != $max">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:transform>
The example is online at http://xsltransform.net/94hvTzi/1.
Let me show a different approach that actually selects and processes the original nodes, in their original context - as was discussed in the previous thread. Consider:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text" encoding="utf-8"/>
<xsl:template match="/">
<xsl:for-each select="body/function">
<xsl:call-template name="select-parameters">
<xsl:with-param name="input-set" select="parameter"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="select-parameters">
<xsl:param name="input-set"/>
<xsl:param name="output-set" select="dummy-node"/>
<xsl:variable name="current-node" select="$input-set[1]" />
<xsl:choose>
<xsl:when test="$current-node">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="$current-node/#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="$current-node/#max" />
</xsl:call-template>
</xsl:variable>
<!-- recursive call -->
<xsl:call-template name="select-parameters">
<xsl:with-param name="input-set" select="$input-set[position() > 1]"/>
<xsl:with-param name="output-set" select="$output-set | $current-node[$min != $max]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- call a template to process the currently selected node-set -->
<xsl:call-template name="process-parameters">
<xsl:with-param name="input-set" select="$output-set"/>
</xsl:call-template>
<!-- call more templates here, if required -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:key name="enum-by-name" match="enum" use="#name" />
<xsl:template name="process-parameters">
<xsl:param name="input-set"/>
<xsl:for-each select="$input-set">
<xsl:variable name="min">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#min" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="max">
<xsl:call-template name="ToNum">
<xsl:with-param name="hexOrNum" select="#max" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat(#name, '[')"/>
<xsl:for-each select="key('enum-by-name', #enum)/value[#val >= $min and #val <= $max]">
<xsl:value-of select="#name"/>
<xsl:text> </xsl:text>
</xsl:for-each>
<xsl:text>] </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template name="HexToNum">
<xsl:param name="hex" />
<xsl:param name="num" select="0"/>
<xsl:param name="msb" select="translate(substring($hex, 1, 1), 'abcdef', 'ABCDEF')"/>
<xsl:param name="value" select="string-length(substring-before('0123456789ABCDEF', $msb))"/>
<xsl:param name="result" select="16 * $num + $value"/>
<xsl:if test="string-length($hex) > 1">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring($hex, 2)"/>
<xsl:with-param name="num" select="$result"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($hex) <= 1">
<xsl:value-of select="$result"/>
</xsl:if>
</xsl:template>
<xsl:template name="ToNum">
<xsl:param name="hexOrNum" />
<xsl:if test="starts-with($hexOrNum, '0x')">
<xsl:call-template name="HexToNum">
<xsl:with-param name="hex" select="substring-after($hexOrNum, '0x')"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(starts-with($hexOrNum, '0x'))">
<xsl:value-of select="$hexOrNum"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The problem with this approach is that it works exactly as advertised; the nodes selected at the end of the selecting processes are the original, unmodified parameters. As a result, they still carry the mixture of decimal and hexadecimal values, and you must convert these again when processing the selected set.
So it might well be more worthwhile to pre-process the parameters by normalizing the values to a common base, then use the result (converted to a node-set) for the rest of the processing. I wouldn't spend so much effort at selecting those that meet the criteria - because once the values are consistent, the selection becomes trivial. If you like, I will post a demo showing that.
I can't find the exact answer for this question so I hope someone will help me here.
I have a string and I want get the substring after the last '.'. I'm using xslt 1.0.
How is this done? This is my code.
<xsl:choose>
<xsl:otherwise>
<xsl:attribute name="class">method txt-align-left case-names</xsl:attribute>
<xsl:value-of select="./#name"/> // this prints a string eg: 'something1.something2.something3'
</xsl:otherwise>
</xsl:choose>
When i paste the suggested code I get an error message. "Parsing an XSLT stylesheet failed."
I can't think of a way to do this with a single expression in XSLT 1.0, but you can do it with a recursive template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<n>
<xsl:call-template name="GetLastSegment">
<xsl:with-param name="value" select="'something1.something2.something3'" />
<xsl:with-param name="separator" select="'.'" />
</xsl:call-template>
</n>
</xsl:template>
<xsl:template name="GetLastSegment">
<xsl:param name="value" />
<xsl:param name="separator" select="'.'" />
<xsl:choose>
<xsl:when test="contains($value, $separator)">
<xsl:call-template name="GetLastSegment">
<xsl:with-param name="value" select="substring-after($value, $separator)" />
<xsl:with-param name="separator" select="$separator" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Result:
<n>something3</n>
I did the same behaviour with a xsl:function - usage is then a little bit simpler:
<xsl:function name="ns:substring-after-last" as="xs:string" xmlns:ns="yourNamespace">
<xsl:param name="value" as="xs:string?"/>
<xsl:param name="separator" as="xs:string"/>
<xsl:choose>
<xsl:when test="contains($value, $separator)">
<xsl:value-of select="ns:substring-after-last(substring-after($value, $separator), $separator)" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value" />
</xsl:otherwise>
</xsl:choose>
</xsl:function>
And you can call it directly in a value-of:
<xsl:value-of select="ns:substring-after-last(.,'=')" xmlns:ns="yourNamespace"/>
Here is a solution using EXSLT str:tokenize:
<xsl:if test="substring($string, string-length($string)) != '.'"><xsl:value-of select="str:tokenize($string, '.')[last()]" /></xsl:if>
(the if is here because if your string ends with the separator, tokenize won't return an empty string)
I resolved it
<xsl:call-template name="GetLastSegment">
<xsl:with-param name="value" select="./#name" />
</xsl:call-template>
Did not need the
<xsl:with-param name="separator" value="'.'" />
in the template call
My question is How to un-escape xml that has already been escaped.
I tried the code provided by Tomalak in response to How to unescape XML characters with help of XSLT?, but I can't get that to do what I want.
I have SoapMsg Xml. The body contains a few elements one of which is a String. This string
contains Escaped XML. This is often done in RPC SoapMsg because they don't allow complex
types. To Get around this they embed Escaped-Xml inside a String Element, see sXmlParameters in the input below.
Example Input:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:pan="http://wsdl.somebody.com/Stuff/">
<soap:Header />
<soap:Body>
<pan:SomeCommand>
<first>eefbb52a0fee443cbda838caffbc2654</first>
<second>f26eb2f5dabc457ca045e64585f7b185</second>
<sXmlParameters><PARAMETERS><TIMEOUTDATETIME>2011-03-15
2:09:48.997</TIMEOUTDATETIME></PARAMETERS></sXmlParameters>
</pan:SomeCommand>
</soap:Body>
</soap:Envelope>
I also see this data escaped with <![CDATA[>]]>, I need to un-escape it also.
Converted Output:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:pan="http://wsdl.somebody.com/Stuff/">
<soap:Header />
<soap:Body>
<pan:SomeCommand>
<first>eefbb52a0fee443cbda838caffbc2654</first>
<second>f26eb2f5dabc457ca045e64585f7b185</second>
<sXmlParameters>
<PARAMETERS>
<TIMEOUTDATETIME>2011-03-15 2:09:48.997</TIMEOUTDATETIME>
</PARAMETERS>
</sXmlParameters>
</pan:SomeCommand>
</soap:Body>
</soap:Envelope>
This will already take care of half of your problem – not the CDATA part:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//sXmlParameters">
<xsl:copy>
<xsl:call-template name="unescape">
<xsl:with-param name="escaped" select="string(.)"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="unescape">
<xsl:param name="escaped"/>
<xsl:choose>
<xsl:when test="contains($escaped,'<')">
<xsl:variable name="beforeelem" select="substring-before($escaped,'<')"/>
<xsl:variable name="elemname1" select="substring-before(substring-after($escaped,'<'),' ')"/>
<xsl:variable name="elemname2" select="substring-before(substring-after($escaped,'<'),'>')"/>
<xsl:variable name="elemname3" select="substring-before(substring-after($escaped,'<'),'/>')"/>
<xsl:variable name="hasattributes" select="string-length($elemname1) > 0 and ((string-length($elemname2)=0 or string-length($elemname1) < string-length($elemname2)) and (string-length($elemname3)=0 or string-length($elemname1) < string-length($elemname3)))"/>
<xsl:variable name="elemclosed" select="string-length($elemname3) > 0 and (string-length($elemname2)=0 or string-length($elemname3) < string-length($elemname2))"/>
<xsl:variable name="elemname">
<xsl:choose>
<xsl:when test="$hasattributes">
<xsl:value-of select="$elemname1"/>
</xsl:when>
<xsl:when test="not($elemclosed)">
<xsl:value-of select="$elemname2"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$elemname3"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="elemclosetag" select="concat('</',$elemname,'>')"/>
<xsl:variable name="innercontent">
<xsl:if test="not($elemclosed)">
<xsl:call-template name="skipper-before">
<xsl:with-param name="source" select="substring-after(substring-after($escaped,'<'),'>')"/>
<xsl:with-param name="delimiter" select="$elemclosetag"/>
</xsl:call-template>
</xsl:if>
</xsl:variable>
<xsl:variable name="afterelem">
<xsl:choose>
<xsl:when test="not($elemclosed)">
<xsl:call-template name="skipper-after">
<xsl:with-param name="source" select="substring-after(substring-after($escaped,'<'),'>')"/>
<xsl:with-param name="delimiter" select="$elemclosetag"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-after(substring-after($escaped,'<'),'/>')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$elemname}">
<xsl:if test="$hasattributes">
<xsl:call-template name="unescapeattributes">
<xsl:with-param name="escapedattributes">
<xsl:choose>
<xsl:when test="not($elemclosed)">
<xsl:value-of select="normalize-space(substring-after($elemname2,' '))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space(substring-after($elemname3,' '))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:call-template name="unescape">
<xsl:with-param name="escaped" select="$innercontent"/>
</xsl:call-template>
</xsl:element>
<xsl:call-template name="unescape">
<xsl:with-param name="escaped" select="$afterelem"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="unescapetext">
<xsl:with-param name="escapedtext" select="$escaped"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="unescapeattributes">
<xsl:param name="escapedattributes"/>
<xsl:variable name="attrname" select="substring-before($escapedattributes,'=')"/>
<xsl:variable name="attrquote" select="substring($escapedattributes,string-length($attrname)+2,1)"/>
<xsl:variable name="attrvalue" select="substring-before(substring-after($escapedattributes,$attrquote),$attrquote)"/>
<xsl:variable name="afterattr" select="substring-after(substring-after($escapedattributes,$attrquote),$attrquote)"/>
<xsl:attribute name="{$attrname}">
<xsl:call-template name="unescapetext">
<xsl:with-param name="escapedtext" select="$attrvalue"/>
</xsl:call-template>
</xsl:attribute>
<xsl:if test="contains($afterattr,'=')">
<xsl:call-template name="unescapeattributes">
<xsl:with-param name="escapedattributes" select="normalize-space($afterattr)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="unescapetext">
<xsl:param name="escapedtext"/>
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="$escapedtext"/>
<xsl:with-param name="replace">></xsl:with-param>
<xsl:with-param name="by">></xsl:with-param>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="replace"><</xsl:with-param>
<xsl:with-param name="by"><</xsl:with-param>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="replace">&</xsl:with-param>
<xsl:with-param name="by">&</xsl:with-param>
</xsl:call-template>
</xsl:template>
<!-- replaces substrings in strings -->
<xsl:template name="string-replace-all">
<xsl:param name="text"/>
<xsl:param name="replace"/>
<xsl:param name="by"/>
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)"/>
<xsl:value-of select="$by"/>
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="substring-after($text,$replace)"/>
<xsl:with-param name="replace" select="$replace"/>
<xsl:with-param name="by" select="$by"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- returns the substring after the last delimiter -->
<xsl:template name="skipper-after">
<xsl:param name="source"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($source,$delimiter)">
<xsl:call-template name="skipper-after">
<xsl:with-param name="source" select="substring-after($source,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$source"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- returns the substring before the last delimiter -->
<xsl:template name="skipper-before">
<xsl:param name="source"/>
<xsl:param name="delimiter"/>
<xsl:param name="result"/>
<xsl:choose>
<xsl:when test="contains($source,$delimiter)">
<xsl:call-template name="skipper-before">
<xsl:with-param name="source" select="substring-after($source,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
<xsl:with-param name="result">
<xsl:if test="result!=''">
<xsl:value-of select="concat($result,$delimiter)"/>
</xsl:if>
<xsl:value-of select="substring-before($source,$delimiter)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$result"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I found that I can use saxon to do this in a much simpler way using the following:
<xsl:template match="SomeCommand">
<sXmlParameters>
<xsl:apply-templates select="saxon:parse(.)" />
</sXmlParameters>
</xsl:template>
there is also saxon:seriralize() that can be used to escape the xml
thanks to all for you input
Wrote a SAX parser for xml-escaped strings in pure xsl 1.0+EXSLT
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:pxml="https://github.com/ilyakharlamov/pure-xsl/parseStringAsXML"
version="1.0">
<xsl:import href="https://raw.githubusercontent.com/ilyakharlamov/pure-xsl/master/parseStringAsXML.xsl"/>
<xsl:template match="/">
<xsl:call-template name="pxml:parseStringAsXML">
<xsl:with-param name="string"><PARAMETERS><TIMEOUTDATETIME>2011-03-152:09:48.997</TIMEOUTDATETIME></PARAMETERS></xsl:with-param>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Output:
<PARAMETERS>
<TIMEOUTDATETIME>2011-03-152:09:48.997</TIMEOUTDATETIME>
</PARAMETERS>
i am converting an html form to xml sequence, i am using a recursive function to achieve this
so the input to param "list" will be of the form
name=value&name=value&name=value
The template below does this fine and returns an xml sequence as follows
<name>value</name><name>value</name><name>value</name>
Ok so the problem
some of the name value pairs are special and i would like to add a attribute to them so the output would be
<name>value</name><name attr="special">value</name><name>value</name>
so to do this i have an external xml file with a list of special names as follows
<settings><google><option from="color"/><option from="size"/></google></settings>
So if we assume i have a xsl variable $SETTINGS connected to this external document above
<xsl:for-each select="$SETTINGS/google/option"></xsl:for-each>
Should be node with 2 children 1 called color and the other size
wot i want to do is if 1 of these children names = $name add the attribute
something like <xsl:if test="$name = $SETTINGS/google/option/ckild name">
<xsl:template name="tokenize">
<xsl:param name="list"/>
<xsl:variable name="seperator" select="'&'"/>
<xsl:variable name="first" select="substring-before(concat($list, $seperator), $seperator)"/>
<xsl:variable name="butfirst" select="substring-after($list, $seperator)"/>
<xsl:variable name="name" select="normalize-space(substring-before($first, '='))"/>
<xsl:variable name="value" select="normalize-space(substring-after($first, '='))"/>
<xsl:if test="string-length($name)>0 and string-length($value)>0">
<xsl:element name="{$name}">
<xsl:for-each select="$SETTINGS/google/option">
-----> <xsl:if test="$name = $SETTINGS/google/option/ckild name">
<xsl:attribute name="option"/>
</xsl:if>
</xsl:for-each>
<xsl:value-of select="$value"/>
</xsl:element>
</xsl:if>
<xsl:if test="$butfirst">
<xsl:call-template name="tokenize">
<xsl:with-param name="list" select="$butfirst"/>
<xsl:with-param name="seperator" select="$seperator"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Many Thanks
Tim Dodgson
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="SETTINGS" select="/settings"/>
<xsl:template match="/">
<xsl:call-template name="tokenize">
<xsl:with-param name="pString"
select="'color=blue&name=value&size=big'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="pString"/>
<xsl:param name="pSeperator" select="'&'"/>
<xsl:choose>
<xsl:when test="not($pString)"/>
<xsl:when test="contains($pString,$pSeperator)">
<xsl:call-template name="tokenize">
<xsl:with-param name="pString"
select="substring-before($pString, $pSeperator)"/>
<xsl:with-param name="pSeperator" select="$pSeperator"/>
</xsl:call-template>
<xsl:call-template name="tokenize">
<xsl:with-param name="pString"
select="substring-after($pString, $pSeperator)"/>
<xsl:with-param name="pSeperator" select="$pSeperator"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vName"
select="normalize-space(substring-before($pString,'='))"/>
<xsl:variable name="vValue"
select="normalize-space(substring-after($pString,'='))"/>
<xsl:if test="$vName and $vValue">
<xsl:element name="{$vName}">
<xsl:if test="$vName = $SETTINGS/google/option/#from">
<xsl:attribute name="attr">special</xsl:attribute>
</xsl:if>
<xsl:value-of select="$vValue"/>
</xsl:element>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With this input:
<settings>
<google>
<option from="color"/>
<option from="size"/>
</google>
</settings>
Output:
<color attr="special">blue</color>
<name>value</name>
<size attr="special">big</size>
Note: Node set comparison
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.