Changing XML format based off CDATA using a XSL template - xslt

This is the first time I have attempted to use XSL, but from my research this looked like the best method. I have a number of files to convert. I am planning on using the notepad++ xmltools for the conversion. If there is another solution to my issue I am open to it.
I need to convert this format of a XML file:
<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple">
<steps><![CDATA[<p>1. do something</p>
<p>2. do more</p>
<p>3. even more</p>]]></steps>
<expectedresults><![CDATA[<p>1. result</p>
<p>2. more result</p>
<p>3 again</p>]]></expectedresults>
</testcase>
</testcases>
Into this end format:
<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple new">
<steps>
<step>
<step_number><![CDATA[1]]></step_number>
<actions><![CDATA[<p>step 1</p>]]></actions>
<expectedresults><![CDATA[<p>do something</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[2]]></step_number>
<actions><![CDATA[<p>step 2</p>]]></actions>
<expectedresults><![CDATA[<p>do more</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[3]]></step_number>
<actions><![CDATA[<p>step 3</p>]]></actions>
<expectedresults><![CDATA[<p>even more</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
</steps>
</testcase>
</testcases>
Not all test cases will have multiple steps, and expected results.
I found this in another thread: http://xsltfiddle.liberty-development.net/gWmuiHV great tool for this process.
My XSL so far is not working great. I am only getting the expected results block. This occurs whether I add expected results code block or not.
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:template match="steps">
<xsl:for-each select="p">
<xsl:copy>
<xsl:apply-templates select="p"/>
</xsl:copy>
</xsl:for-each>
<!-- <xsl:for-each select="expectedresults">
<xsl:copy>
<xsl:apply-templates select="p"/>
</xsl:copy>
</xsl:for-each>-- I get the same results whether this code is included or not. >
</xsl:template>
</xsl:stylesheet>
But I am only get this for output:
<?xml version="1.0" encoding="utf-16"?>
<p>1. result</p>
<p>2. more result</p>
<p>3 again</p>
These files will be imported into Testlink not used for html.

Transforming your input XML to your desired output XML requires some serious contortions:
Decoding the CDATA sections into an xsl:variable with parse-xml-fragment
Get the current index of these steps|expectedresults elements with
count(preceding-sibling::*)+1
Iterate over the p elements
Compartmentalise the string into the relevant parts
Output the elements with their values wrapped in CDATA sections (here the <p> element has to be escaped)
This gives you the following XSLT-3.0 code:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" >
<xsl:output method="xml" indent="yes" cdata-section-elements="step_number actions expectedresults execution_type" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="steps|expectedresults">
<xsl:variable name="st" select="parse-xml-fragment(.)" />
<xsl:variable name="pos" select="count(preceding-sibling::*)+1" />
<steps>
<xsl:for-each select="$st/p">
<step>
<xsl:variable name="cur" select="substring-before(translate(.,'.',' '),' ')" />
<step_number>
<xsl:value-of select="$cur" />
</step_number>
<actions><xsl:value-of select="concat('<p>','step ',$cur,'</p>')" /></actions>
<expectedresults>
<xsl:value-of select="concat('<p>',normalize-space(substring-after(.,' ')),'</p>')" />
</expectedresults>
<execution_type>
<xsl:value-of select="$pos" />
</execution_type>
</step>
</xsl:for-each>
</steps>
</xsl:template>
</xsl:stylesheet>
The output is:
<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple">
<steps>
<step>
<step_number><![CDATA[1]]></step_number>
<actions><![CDATA[<p>step 1</p>]]></actions>
<expectedresults><![CDATA[<p>do something</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[2]]></step_number>
<actions><![CDATA[<p>step 2</p>]]></actions>
<expectedresults><![CDATA[<p>do more</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[3]]></step_number>
<actions><![CDATA[<p>step 3</p>]]></actions>
<expectedresults><![CDATA[<p>even more</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
</steps>
<steps>
<step>
<step_number><![CDATA[1]]></step_number>
<actions><![CDATA[<p>step 1</p>]]></actions>
<expectedresults><![CDATA[<p>result</p>]]></expectedresults>
<execution_type><![CDATA[2]]></execution_type>
</step>
<step>
<step_number><![CDATA[2]]></step_number>
<actions><![CDATA[<p>step 2</p>]]></actions>
<expectedresults><![CDATA[<p>more result</p>]]></expectedresults>
<execution_type><![CDATA[2]]></execution_type>
</step>
<step>
<step_number><![CDATA[3]]></step_number>
<actions><![CDATA[<p>step 3</p>]]></actions>
<expectedresults><![CDATA[<p>again</p>]]></expectedresults>
<execution_type><![CDATA[2]]></execution_type>
</step>
</steps>
</testcase>
</testcases>

I think in XSLT 3 you want to parse the contents of the two elements, merge them and then serialize them back:
<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:output indent="yes" cdata-section-elements="actions expectedresults"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:accumulator name="step-count" as="xs:integer" initial-value="0">
<xsl:accumulator-rule match="p" select="$value + 1"/>
</xsl:accumulator>
<xsl:template match="testcase">
<testcase name="{#name} new">
<steps>
<xsl:merge>
<xsl:merge-source select="parse-xml-fragment(steps)/*">
<xsl:merge-key select="accumulator-before('step-count')"/>
</xsl:merge-source>
<xsl:merge-source select="parse-xml-fragment(expectedresults)/*">
<xsl:merge-key select="accumulator-before('step-count')"/>
</xsl:merge-source>
<xsl:merge-action>
<step>
<step_number>{position()}</step_number>
<actions>{serialize(current-merge-group()[1])}</actions>
<expectedresults>{serialize(current-merge-group()[2])}</expectedresults>
<execution_type>1</execution_type>
</step>
</xsl:merge-action>
</xsl:merge>
</steps>
</testcase>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jz1Q1yb
Or, to remove numbers from the steps and actions, you need an additional processing step:
<xsl:mode name="strip-numbers" on-no-match="shallow-copy"/>
<xsl:function name="mf:strip-numbers" as="node()*">
<xsl:param name="input" as="node()*"/>
<xsl:apply-templates select="$input" mode="strip-numbers"/>
</xsl:function>
<xsl:template mode="strip-numbers" match="p[matches(., '^\d+\.\s*')]">
<xsl:copy>{replace(., '^\d+\.\s*', '')}</xsl:copy>
</xsl:template>
<xsl:template match="testcase">
<testcase name="{#name} new">
<steps>
<xsl:merge>
<xsl:merge-source select="mf:strip-numbers(parse-xml-fragment(steps))/*">
<xsl:merge-key select="accumulator-before('step-count')"/>
</xsl:merge-source>
<xsl:merge-source select="mf:strip-numbers(parse-xml-fragment(expectedresults))/*">
<xsl:merge-key select="accumulator-before('step-count')"/>
</xsl:merge-source>
<xsl:merge-action>
<step>
<step_number>{position()}</step_number>
<actions>{serialize(current-merge-group()[1])}</actions>
<expectedresults>{serialize(current-merge-group()[2])}</expectedresults>
<execution_type>1</execution_type>
</step>
</xsl:merge-action>
</xsl:merge>
</steps>
</testcase>
</xsl:template>
https://xsltfiddle.liberty-development.net/jz1Q1yb/1
With the support for higher-order functions (i.e. with Saxon PE or EE or AltovaXML) it might also be possible to use the function for-each-pair https://www.w3.org/TR/xpath-functions/#func-for-each-pair instead of the rather verbose xsl:merge.
The use of the accumulator is also a bit tedious but required to have a merge source key based on the position, a more compact solution might be to use to construct a map of the position and the element on the fly:
<xsl:template match="testcase">
<testcase name="{#name} new">
<steps>
<xsl:merge>
<xsl:merge-source name="step"
select="mf:strip-numbers(parse-xml-fragment(steps))/*!map { 'pos' : position(), 'element' : .}">
<xsl:merge-key select="?pos"/>
</xsl:merge-source>
<xsl:merge-source name="action"
select="mf:strip-numbers(parse-xml-fragment(expectedresults))/*!map { 'pos' : position(), 'element' : .}">
<xsl:merge-key select="?pos"/>
</xsl:merge-source>
<xsl:merge-action>
<step>
<step_number>{position()}</step_number>
<actions>{current-merge-group('step')?element => serialize()}</actions>
<expectedresults>{current-merge-group('action')?element => serialize()}</expectedresults>
<execution_type>1</execution_type>
</step>
</xsl:merge-action>
</xsl:merge>
</steps>
</testcase>
</xsl:template>
https://xsltfiddle.liberty-development.net/jz1Q1yb/2

CDATA is not XML and cannot be processed directly by XSLT. In XSLT 3.0, there's a parse-xml-fragment function that can pre-process CDATA or otherwise escaped XML. However, you say that:
I am planning on using the notepad++ xmltools
AFAIK, this would limit you to XSLT 1.0. In such case, you need to process the input XML twice.
First, apply this transformation and save the result to a file:
XSLT 1.0 [1]
<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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="steps | expectedresults">
<xsl:copy>
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This should result in the following XML:
XML [2]
<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple">
<steps><p>1. do something</p>
<p>2. do more</p>
<p>3. even more</p></steps>
<expectedresults><p>1. result</p>
<p>2. more result</p>
<p>3 again</p></expectedresults>
</testcase>
</testcases>
Now you can apply the following stylesheet to the resulting file:
XSLT 1.0 [2]]
<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"
cdata-section-elements="step_number actions expectedresults execution_type"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="testcase">
<xsl:copy>
<xsl:attribute name="name">
<xsl:value-of select="#name" />
<xsl:text> new</xsl:text>
</xsl:attribute>
<xsl:for-each select="steps/p">
<step>
<xsl:variable name="i" select="position()"/>
<step_number>
<xsl:value-of select="$i"/>
</step_number>
<actions>
<xsl:text><p></xsl:text>
<xsl:value-of select="substring-after(., '. ')" />
<xsl:text></p></xsl:text>
</actions>
<expectedresults>
<xsl:text><p></xsl:text>
<xsl:value-of select="substring-after(../following-sibling::expectedresults/p[$i], '. ')"/>
<xsl:text></p></xsl:text>
</expectedresults>
<execution_type>1</execution_type>
</step>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
to get:
Final Result
<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple new">
<step>
<step_number><![CDATA[1]]></step_number>
<actions><![CDATA[<p>do something</p>]]></actions>
<expectedresults><![CDATA[<p>result</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[2]]></step_number>
<actions><![CDATA[<p>do more</p>]]></actions>
<expectedresults><![CDATA[<p>more result</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[3]]></step_number>
<actions><![CDATA[<p>even more</p>]]></actions>
<expectedresults><![CDATA[<p>again</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
</testcase>
</testcases>
Notes:
My result is somewhat different than the one you show. However, I believe it is what you intended;
I have changed the input by adding a period after 3 in <p>3 again</p>.
Added:
If what I read is true and your tool is actually using the libxslt XSLT processor, then you can do it all in one pass with the help of the EXSLT str:split() extension function that libxslt supports:
XSLT 1.0 + EXSLT
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"
cdata-section-elements="step_number actions expectedresults execution_type"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="testcase">
<xsl:variable name="steps" select="str:split(steps, '<p>')"/>
<xsl:variable name="expectedresults" select="str:split(expectedresults, '<p>')"/>
<xsl:copy>
<xsl:attribute name="name">
<xsl:value-of select="#name" />
<xsl:text> new</xsl:text>
</xsl:attribute>
<xsl:for-each select="$steps">
<step>
<xsl:variable name="i" select="position()"/>
<step_number>
<xsl:value-of select="$i"/>
</step_number>
<actions>
<xsl:text><p></xsl:text>
<xsl:value-of select="substring-after(., '. ')" />
</actions>
<expectedresults>
<xsl:text><p></xsl:text>
<xsl:value-of select="substring-after($expectedresults[$i], '. ')"/>
</expectedresults>
<execution_type>1</execution_type>
</step>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

XSL - FOP Replace html tag from CDATA Node [duplicate]

This is the first time I have attempted to use XSL, but from my research this looked like the best method. I have a number of files to convert. I am planning on using the notepad++ xmltools for the conversion. If there is another solution to my issue I am open to it.
I need to convert this format of a XML file:
<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple">
<steps><![CDATA[<p>1. do something</p>
<p>2. do more</p>
<p>3. even more</p>]]></steps>
<expectedresults><![CDATA[<p>1. result</p>
<p>2. more result</p>
<p>3 again</p>]]></expectedresults>
</testcase>
</testcases>
Into this end format:
<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple new">
<steps>
<step>
<step_number><![CDATA[1]]></step_number>
<actions><![CDATA[<p>step 1</p>]]></actions>
<expectedresults><![CDATA[<p>do something</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[2]]></step_number>
<actions><![CDATA[<p>step 2</p>]]></actions>
<expectedresults><![CDATA[<p>do more</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[3]]></step_number>
<actions><![CDATA[<p>step 3</p>]]></actions>
<expectedresults><![CDATA[<p>even more</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
</steps>
</testcase>
</testcases>
Not all test cases will have multiple steps, and expected results.
I found this in another thread: http://xsltfiddle.liberty-development.net/gWmuiHV great tool for this process.
My XSL so far is not working great. I am only getting the expected results block. This occurs whether I add expected results code block or not.
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:template match="steps">
<xsl:for-each select="p">
<xsl:copy>
<xsl:apply-templates select="p"/>
</xsl:copy>
</xsl:for-each>
<!-- <xsl:for-each select="expectedresults">
<xsl:copy>
<xsl:apply-templates select="p"/>
</xsl:copy>
</xsl:for-each>-- I get the same results whether this code is included or not. >
</xsl:template>
</xsl:stylesheet>
But I am only get this for output:
<?xml version="1.0" encoding="utf-16"?>
<p>1. result</p>
<p>2. more result</p>
<p>3 again</p>
These files will be imported into Testlink not used for html.
Transforming your input XML to your desired output XML requires some serious contortions:
Decoding the CDATA sections into an xsl:variable with parse-xml-fragment
Get the current index of these steps|expectedresults elements with
count(preceding-sibling::*)+1
Iterate over the p elements
Compartmentalise the string into the relevant parts
Output the elements with their values wrapped in CDATA sections (here the <p> element has to be escaped)
This gives you the following XSLT-3.0 code:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" >
<xsl:output method="xml" indent="yes" cdata-section-elements="step_number actions expectedresults execution_type" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="steps|expectedresults">
<xsl:variable name="st" select="parse-xml-fragment(.)" />
<xsl:variable name="pos" select="count(preceding-sibling::*)+1" />
<steps>
<xsl:for-each select="$st/p">
<step>
<xsl:variable name="cur" select="substring-before(translate(.,'.',' '),' ')" />
<step_number>
<xsl:value-of select="$cur" />
</step_number>
<actions><xsl:value-of select="concat('<p>','step ',$cur,'</p>')" /></actions>
<expectedresults>
<xsl:value-of select="concat('<p>',normalize-space(substring-after(.,' ')),'</p>')" />
</expectedresults>
<execution_type>
<xsl:value-of select="$pos" />
</execution_type>
</step>
</xsl:for-each>
</steps>
</xsl:template>
</xsl:stylesheet>
The output is:
<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple">
<steps>
<step>
<step_number><![CDATA[1]]></step_number>
<actions><![CDATA[<p>step 1</p>]]></actions>
<expectedresults><![CDATA[<p>do something</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[2]]></step_number>
<actions><![CDATA[<p>step 2</p>]]></actions>
<expectedresults><![CDATA[<p>do more</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[3]]></step_number>
<actions><![CDATA[<p>step 3</p>]]></actions>
<expectedresults><![CDATA[<p>even more</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
</steps>
<steps>
<step>
<step_number><![CDATA[1]]></step_number>
<actions><![CDATA[<p>step 1</p>]]></actions>
<expectedresults><![CDATA[<p>result</p>]]></expectedresults>
<execution_type><![CDATA[2]]></execution_type>
</step>
<step>
<step_number><![CDATA[2]]></step_number>
<actions><![CDATA[<p>step 2</p>]]></actions>
<expectedresults><![CDATA[<p>more result</p>]]></expectedresults>
<execution_type><![CDATA[2]]></execution_type>
</step>
<step>
<step_number><![CDATA[3]]></step_number>
<actions><![CDATA[<p>step 3</p>]]></actions>
<expectedresults><![CDATA[<p>again</p>]]></expectedresults>
<execution_type><![CDATA[2]]></execution_type>
</step>
</steps>
</testcase>
</testcases>
I think in XSLT 3 you want to parse the contents of the two elements, merge them and then serialize them back:
<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:output indent="yes" cdata-section-elements="actions expectedresults"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:accumulator name="step-count" as="xs:integer" initial-value="0">
<xsl:accumulator-rule match="p" select="$value + 1"/>
</xsl:accumulator>
<xsl:template match="testcase">
<testcase name="{#name} new">
<steps>
<xsl:merge>
<xsl:merge-source select="parse-xml-fragment(steps)/*">
<xsl:merge-key select="accumulator-before('step-count')"/>
</xsl:merge-source>
<xsl:merge-source select="parse-xml-fragment(expectedresults)/*">
<xsl:merge-key select="accumulator-before('step-count')"/>
</xsl:merge-source>
<xsl:merge-action>
<step>
<step_number>{position()}</step_number>
<actions>{serialize(current-merge-group()[1])}</actions>
<expectedresults>{serialize(current-merge-group()[2])}</expectedresults>
<execution_type>1</execution_type>
</step>
</xsl:merge-action>
</xsl:merge>
</steps>
</testcase>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jz1Q1yb
Or, to remove numbers from the steps and actions, you need an additional processing step:
<xsl:mode name="strip-numbers" on-no-match="shallow-copy"/>
<xsl:function name="mf:strip-numbers" as="node()*">
<xsl:param name="input" as="node()*"/>
<xsl:apply-templates select="$input" mode="strip-numbers"/>
</xsl:function>
<xsl:template mode="strip-numbers" match="p[matches(., '^\d+\.\s*')]">
<xsl:copy>{replace(., '^\d+\.\s*', '')}</xsl:copy>
</xsl:template>
<xsl:template match="testcase">
<testcase name="{#name} new">
<steps>
<xsl:merge>
<xsl:merge-source select="mf:strip-numbers(parse-xml-fragment(steps))/*">
<xsl:merge-key select="accumulator-before('step-count')"/>
</xsl:merge-source>
<xsl:merge-source select="mf:strip-numbers(parse-xml-fragment(expectedresults))/*">
<xsl:merge-key select="accumulator-before('step-count')"/>
</xsl:merge-source>
<xsl:merge-action>
<step>
<step_number>{position()}</step_number>
<actions>{serialize(current-merge-group()[1])}</actions>
<expectedresults>{serialize(current-merge-group()[2])}</expectedresults>
<execution_type>1</execution_type>
</step>
</xsl:merge-action>
</xsl:merge>
</steps>
</testcase>
</xsl:template>
https://xsltfiddle.liberty-development.net/jz1Q1yb/1
With the support for higher-order functions (i.e. with Saxon PE or EE or AltovaXML) it might also be possible to use the function for-each-pair https://www.w3.org/TR/xpath-functions/#func-for-each-pair instead of the rather verbose xsl:merge.
The use of the accumulator is also a bit tedious but required to have a merge source key based on the position, a more compact solution might be to use to construct a map of the position and the element on the fly:
<xsl:template match="testcase">
<testcase name="{#name} new">
<steps>
<xsl:merge>
<xsl:merge-source name="step"
select="mf:strip-numbers(parse-xml-fragment(steps))/*!map { 'pos' : position(), 'element' : .}">
<xsl:merge-key select="?pos"/>
</xsl:merge-source>
<xsl:merge-source name="action"
select="mf:strip-numbers(parse-xml-fragment(expectedresults))/*!map { 'pos' : position(), 'element' : .}">
<xsl:merge-key select="?pos"/>
</xsl:merge-source>
<xsl:merge-action>
<step>
<step_number>{position()}</step_number>
<actions>{current-merge-group('step')?element => serialize()}</actions>
<expectedresults>{current-merge-group('action')?element => serialize()}</expectedresults>
<execution_type>1</execution_type>
</step>
</xsl:merge-action>
</xsl:merge>
</steps>
</testcase>
</xsl:template>
https://xsltfiddle.liberty-development.net/jz1Q1yb/2
CDATA is not XML and cannot be processed directly by XSLT. In XSLT 3.0, there's a parse-xml-fragment function that can pre-process CDATA or otherwise escaped XML. However, you say that:
I am planning on using the notepad++ xmltools
AFAIK, this would limit you to XSLT 1.0. In such case, you need to process the input XML twice.
First, apply this transformation and save the result to a file:
XSLT 1.0 [1]
<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:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="steps | expectedresults">
<xsl:copy>
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This should result in the following XML:
XML [2]
<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple">
<steps><p>1. do something</p>
<p>2. do more</p>
<p>3. even more</p></steps>
<expectedresults><p>1. result</p>
<p>2. more result</p>
<p>3 again</p></expectedresults>
</testcase>
</testcases>
Now you can apply the following stylesheet to the resulting file:
XSLT 1.0 [2]]
<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"
cdata-section-elements="step_number actions expectedresults execution_type"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="testcase">
<xsl:copy>
<xsl:attribute name="name">
<xsl:value-of select="#name" />
<xsl:text> new</xsl:text>
</xsl:attribute>
<xsl:for-each select="steps/p">
<step>
<xsl:variable name="i" select="position()"/>
<step_number>
<xsl:value-of select="$i"/>
</step_number>
<actions>
<xsl:text><p></xsl:text>
<xsl:value-of select="substring-after(., '. ')" />
<xsl:text></p></xsl:text>
</actions>
<expectedresults>
<xsl:text><p></xsl:text>
<xsl:value-of select="substring-after(../following-sibling::expectedresults/p[$i], '. ')"/>
<xsl:text></p></xsl:text>
</expectedresults>
<execution_type>1</execution_type>
</step>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
to get:
Final Result
<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple new">
<step>
<step_number><![CDATA[1]]></step_number>
<actions><![CDATA[<p>do something</p>]]></actions>
<expectedresults><![CDATA[<p>result</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[2]]></step_number>
<actions><![CDATA[<p>do more</p>]]></actions>
<expectedresults><![CDATA[<p>more result</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
<step>
<step_number><![CDATA[3]]></step_number>
<actions><![CDATA[<p>even more</p>]]></actions>
<expectedresults><![CDATA[<p>again</p>]]></expectedresults>
<execution_type><![CDATA[1]]></execution_type>
</step>
</testcase>
</testcases>
Notes:
My result is somewhat different than the one you show. However, I believe it is what you intended;
I have changed the input by adding a period after 3 in <p>3 again</p>.
Added:
If what I read is true and your tool is actually using the libxslt XSLT processor, then you can do it all in one pass with the help of the EXSLT str:split() extension function that libxslt supports:
XSLT 1.0 + EXSLT
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"
cdata-section-elements="step_number actions expectedresults execution_type"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="testcase">
<xsl:variable name="steps" select="str:split(steps, '<p>')"/>
<xsl:variable name="expectedresults" select="str:split(expectedresults, '<p>')"/>
<xsl:copy>
<xsl:attribute name="name">
<xsl:value-of select="#name" />
<xsl:text> new</xsl:text>
</xsl:attribute>
<xsl:for-each select="$steps">
<step>
<xsl:variable name="i" select="position()"/>
<step_number>
<xsl:value-of select="$i"/>
</step_number>
<actions>
<xsl:text><p></xsl:text>
<xsl:value-of select="substring-after(., '. ')" />
</actions>
<expectedresults>
<xsl:text><p></xsl:text>
<xsl:value-of select="substring-after($expectedresults[$i], '. ')"/>
</expectedresults>
<execution_type>1</execution_type>
</step>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT for XML Filtering the repeating group

I have the input XML as
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<document>
<item>
<functionalName lang="en">Filte</functionalName>
<functionalName lang="hin">test1</functionalName>
<functionalName lang="chi">Filters2</functionalName>
<functionalName lang="hin">Filters3</functionalName>
</item>
<item>
<functionalName lang="en">Filte</functionalName>
<functionalName lang="chi">Filters</functionalName>
<functionalName lang="en">Filters1</functionalName>
</item>
And the desired output after parsing through the XSLT is mentioned below
XSLT is mentioned at the end of the query
Output XML:
<?xml version="1.0" encoding="UTF-8"?>
<CatalogItem>
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems count="3">
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-functionalName-en" />
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-functionalName-hin" />
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-functionalName-chi" />
</RelatedItems>
</Relationship>
</RelationshipData>
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems count="2">
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-functionalName-en" />
<RelatedItem1 referenceKey="ITEM_DESCRIPTION-functionalName-chi" />
</RelatedItems>
</Relationship>
</RelationshipData>
XSLT which I am using is not giving me the desired response. Please help
<xsl:output method="xml" indent="yes" />
<xsl:key name="functional" match="functionalName" use="#lang" />
<xsl:template match="document">
<CatalogItem>
<xsl:for-each select="item">
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems>
<xsl:attribute name="count">
<xsl:value-of select="count(functionalName[generate-id(.)=generate-id(key('functional',#lang)[1])])"/>
</xsl:attribute>
<xsl:apply-templates select="functionalName[generate-id(.)=generate-id(key('functional',#lang)[1])]"/>
</RelatedItems>
</Relationship>
</RelationshipData>
</xsl:for-each>
</CatalogItem>
</xsl:template>
<xsl:template match="functionalName">
<xsl:for-each value="{#lang}">
<xsl:variable name="language" select="../#lang" />
<RelatedItem1>
<xsl:attribute name="referenceKey">
<xsl:value-of select="concat('ITEM_DESCRIPTION','-','functionalName','-',../#lang)"/>
</xsl:attribute>
</RelatedItem1>
</xsl:for-each>
</xsl:template>
Change your key from
<xsl:key name="functional" match="functionalName" use="#lang" />
to
<xsl:key name="functional" match="functionalName" use="concat(generate-id(..), '|', #lang)" />
then you can use
<RelatedItems count="{count(functionalName[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])])}">
and
<xsl:apply-templates select="functionalName[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])]"/>
and finally you can simplify the template for functionaName to
<xsl:template match="functionalName">
<RelatedItem1 referenceKey="{concat('ITEM_DESCRIPTION','-','functionalName','-', #lang)}"/>
</xsl:template>
So the complete samples is
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:key name="functional" match="functionalName" use="concat(generate-id(..), '|', #lang)" />
<xsl:template match="document">
<CatalogItem>
<xsl:for-each select="item">
<RelationshipData>
<Relationship>
<RelationType>Descriptions_for_Item</RelationType>
<RelatedItems count="{count(functionalName[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])])}">
<xsl:apply-templates select="functionalName[generate-id() = generate-id(key('functional', concat(generate-id(..), '|', #lang))[1])]"/>
</RelatedItems>
</Relationship>
</RelationshipData>
</xsl:for-each>
</CatalogItem>
</xsl:template>
<xsl:template match="functionalName">
<RelatedItem1 referenceKey="{concat('ITEM_DESCRIPTION','-','functionalName','-', #lang)}"/>
</xsl:template>
</xsl:stylesheet>

Convert string value as XML tag name

Below is my requirement. Can we do this using XSLT? I want to convert value of AttributeName as tag under policy and corresponding AttributeValue as value.
Input :
<Policy>
<Attributes>
<AttributeName>is_policy_loan</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
<Attributes>
<AttributeName>is_policy_owners</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
<Attributes>
<AttributeName>is_policy_twoyears</AttributeName>
<AttributeValue>Yes</AttributeValue>
</Attributes>
</Policy>
Output :
<Policy>
<is_policy_loan>Yes</is_policy_loan>
<is_policy_owners>Yes</is_policy_owners>
<is_policy_twoyears>Yes</is_policy_twoyears>
</Policy>
The following xsl file will do the job:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- create the <AttributeName>AttributeValue</..> nodes -->
<xsl:template match="//Attributes">
<xsl:variable name="name" select="AttributeName" />
<xsl:element name="{$name}">
<xsl:value-of select="AttributeValue" />
</xsl:element>
</xsl:template>
<!-- wrap nodes in a `Policy` node -->
<xsl:template match="/">
<Policy>
<xsl:apply-templates/>
</Policy>
</xsl:template>
</xsl:stylesheet>
The way i would do,
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="yes" />
<xsl:template match="Policy">
<xsl:element name="Policy">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="Attributes">
<xsl:variable name="name" select="AttributeName" />
<xsl:element name="{$name}">
<xsl:value-of select="AttributeValue" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
output will be,
<Policy>
<is_policy_loan>Yes</is_policy_loan>
<is_policy_owners>Yes</is_policy_owners>
<is_policy_twoyears>Yes</is_policy_twoyears>
</Policy>

XSLT: Avoiding indentation in some nodes

I have some XSLT that replaces linebreaks with <Break/> tags and it works fine as long as there isn't multiple consecutive linebreaks. I think it's the indent="yes" that's causing problems.
Can it be disabled for some nodes?
Basically nodes with mixed content (text and elements) can not contain any linebreaks.
The input xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Account xmlns="http://example.com/account">
<Owner>
<ID>012345789</ID>
<Name>Peter Johnson</Name>
</Owner>
<Notes>
<NoteID>012345789</NoteID>
<Text>This is the description:
Line 1
Line 2
Line 3
Line 4, after double linebreak
Line 5</Text>
</Notes>
</Account>
The XSL:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://example.com/account" version="1.0">
<xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes"/>
<xsl:template name="replace_sab">
<!-- with string s, replace substring a by string b -->
<!-- s, a and b are parameters determined upon calling -->
<xsl:param name="s" />
<xsl:param name="a" />
<xsl:param name="b" />
<xsl:choose>
<xsl:when test="contains($s,$a)">
<xsl:value-of select="substring-before($s,$a)" />
<xsl:copy-of select="$b" />
<xsl:call-template name="replace_sab">
<xsl:with-param name="s" select="substring-after($s,$a)" />
<xsl:with-param name="a" select="$a" />
<xsl:with-param name="b" select="$b" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$s" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()[not(normalize-space())]"/>
<xsl:template match="text()[boolean(normalize-space())]">
<xsl:call-template name="replace_sab">
<xsl:with-param name="s" select="." />
<xsl:with-param name="a" select="'
'" />
<xsl:with-param name="b"><Break/></xsl:with-param>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
The output that I get:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Account xmlns="http://example.com/account">
<Owner>
<ID>012345789</ID>
<Name>Peter Johnson</Name>
</Owner>
<Notes>
<NoteID>012345789</NoteID>
<Text>This is the description:<Break/>Line 1<Break/>Line 2<Break/>Line 3<Break/>
<Break/>Line 4, after double linebreak<Break/>Line 5</Text>
</Notes>
</Account>
The output I would like:
<?xml version="1.0" encoding="ISO-8859-1"?>
<Account xmlns="http://example.com/account">
<Owner>
<ID>012345789</ID>
<Name>Peter Johnson</Name>
</Owner>
<Notes>
<NoteID>012345789</NoteID>
<Text>This is the description:<Break/>Line 1<Break/>Line 2<Break/>Line 3<Break/><Break/>Line 4, after double linebreak<Break/>Line 5</Text>
</Notes>
</Account>
I am using "TIBCO XSLT 1.0" XSLT engine in a Tibco BusinessWorks process.
There's no standard way of doing this.
If you were using Saxon you could use the saxon:suppress-indentation output parameter, which becomes a standard option in XSLT 3.0.
Perhaps you could find a way of inserting the Saxon serializer into your processing pipeline even if you stick with the Tibco XSLT engine.

XSLT stylesheet: merging elements into one

What is an XSLT 2.0 stylesheet that will transform
<paramList>
<param name="y" out="true"/>
<param name="y" in="true"/>
<param name="z" out="true"/>
<param name="x" in="true"/>
</paramList>
into
<paramList>
<param name="x" in="true" />
<param name="y" in="true" out="true"/>
<param name="z" out="true"/>
</paramList>
In the result, "in, only" parameters precede "in & out" parameters, which, in turn, precede "out, only" parameters. Also, the two "y" elements have been combined into one.
<?xml version="1.0" ?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/paramList">
<xsl:copy>
<xsl:for-each-group select="param" group-by="#name">
<xsl:sort select="current-group()/#in" order="descending"/>
<xsl:sort select="current-group()/#out"/>
<param name="{current-grouping-key()}">
<xsl:for-each select="current-group()/#*">
<xsl:copy/>
</xsl:for-each>
</param>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
A small improvement:
In both #Nick-Jones ' and #Obalix 's solutions, it is shorter to write:
<xsl:copy-of select="current-group()/#*"/>
or
<xsl:copy-of select="key('paramsByName', #name)/#*"/>
than, respectively:
<xsl:for-each select="current-group()/#*">
<xsl:copy/>
</xsl:for-each>
or
<xsl:for-each select="key('paramsByName', #name)">
<xsl:copy-of select="#*"/>
</xsl:for-each>
Just in case someone needs to do it in XSLT 1:
<?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:key name="paramsByName" match="param" use="#name"/>
<xsl:template match="/paramList">
<xsl:copy>
<xsl:for-each select="param[count(. | key('paramsByName', #name)[1]) = 1]">
<xsl:sort select="#name"/>
<xsl:copy>
<xsl:for-each select="key('paramsByName', #name)">
<xsl:copy-of select="#*"/>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This uses Munchian grouping as XSLT 1 does not have a grouping construct.
Edit:
It is obviously also possible to just copy the in and out attributes in this case the following style sheet does the job (also following Dimetre's suggestions:
<?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:key name="paramsByName" match="param" use="#name"/>
<xsl:template match="/paramList">
<xsl:copy>
<xsl:for-each select="param[count(. | key('paramsByName', #name)[1]) = 1]">
<xsl:sort select="#name"/>
<xsl:copy-of select="key('paramsByName', #name)/#*[local-name() = 'in' or local-name() = 'out']"/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Obalix's answer may not work in the case there are multiple paramList elements in the input document. I assume this may interest the poster if the document describe a software interface where there are multiple procedures with a paramList for each.
Here is a sample input:
<root>
<func name="one">
<paramList>
<param name="y" out="true"/>
<param name="y" in="true"/>
<param name="z" out="true"/>
<param name="x" in="true"/>
</paramList>
</func>
<func name="two">
<paramList>
<param name="z" in="true"/>
</paramList>
</func>
</root>
Here is my proposed stylesheet, built on Obalix's answer. The trick is to use a local key id containing the ID of the paramListelement.
<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:key name="paramsByName" match="param" use="concat(generate-id(..), '/', #name)"/>
<xsl:template match="paramList">
<xsl:copy>
<xsl:variable name="id" select="generate-id(.)"/>
<xsl:for-each select="param[count(. | key('paramsByName', concat($id, '/', #name))[1]) = 1]">
<xsl:copy>
<xsl:copy-of select="key('paramsByName', concat($id, '/', #name))/#*"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>