Removing duplicate XML nodes in XSLT - xslt

I would appreciated if someone could help me to create xslt to remove duplicates nodes from XML
based on duplicated element's value(PlayBack--ControlInfo-ControlName).
I want to remove all the duplicate elements(PlayBack--ControlInfo-ControlName) from the GStep/Step
Input XML
<?xml version="1.0" encoding="utf-8"?>
<Document>
<Meta>
<GpsFile>notepad_may_30_file</GpsFile>
<GpsId>36fa4fe8-9691-4a7f-8bc1-9543f6b7d29a</GpsId>
<ExePath>
<ExePath1>C:\WINDOWS\SYSTEM32\notepad.exe</ExePath1>
</ExePath>
</Meta>
<Process>
<GStep DialogName="Untitled - Notepad">
<Step DialogName="Untitled - Notepad">
<Step-ID>3</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>4</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>5</Step-ID>
<PlayBack--ControlInfo-ControlName>Edit</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>6</Step-ID>
<PlayBack--ControlInfo-ControlName>Replace...\tCtrl+H</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>12</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>13</Step-ID>
<PlayBack--ControlInfo-ControlName>Edit</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>14</Step-ID>
<PlayBack--ControlInfo-ControlName>Replace...\tCtrl+H</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>15</Step-ID>
<PlayBack--ControlInfo-ControlName>Cancel</PlayBack--ControlInfo-ControlName>
</Step>
</GStep>
<GStep DialogName="Replace">
<Step DialogName="Replace">
<Step-ID>8</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Replace">
<Step-ID>9</Step-ID>
<PlayBack--ControlInfo-ControlName>Cancel</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Replace">
<Step-ID>10</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Replace">
<Step-ID>16</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
</GStep>
</Process>
</Document>
Actually expecting a result like below.
<?xml version="1.0" encoding="utf-8"?>
<Document>
<Meta>
<GpsFile>notepad_may_30_file</GpsFile>
<GpsId>36fa4fe8-9691-4a7f-8bc1-9543f6b7d29a</GpsId>
<ExePath>
<ExePath1>C:\WINDOWS\SYSTEM32\notepad.exe</ExePath1>
</ExePath>
</Meta>
<Process>
<GStep DialogName="Untitled - Notepad">
<Step DialogName="Untitled - Notepad">
<Step-ID>3</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>5</Step-ID>
<PlayBack--ControlInfo-ControlName>Edit</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>6</Step-ID>
<PlayBack--ControlInfo-ControlName>Replace...\tCtrl+H</PlayBack--ControlInfo-
ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>15</Step-ID>
<PlayBack--ControlInfo-ControlName>Cancel</PlayBack--ControlInfo-ControlName>
</Step>
</GStep>
<GStep DialogName="Replace">
<Step DialogName="Replace">
<Step-ID>8</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Replace">
<Step-ID>9</Step-ID>
<PlayBack--ControlInfo-ControlName>Cancel</PlayBack--ControlInfo-ControlName>
</Step>
</GStep>
</Process>
</Document>
I tried with the below xslt code snippet.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="ControlNameInfo" match="Step" use="PlayBack--ControlInfo-ControlName"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="GStep/Step[not(generate-id() = generate-id(key('ControlNameInfo', PlayBack--
ControlInfo-ControlName)[1]))]"/>
</xsl:stylesheet>
Can anyone help
Thanks very much.

In order to keep only distinct Step nodes in each GStep , you must include the parent GStep in the key. Try:
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:strip-space elements="*"/>
<xsl:key name="k1" match="Step" use="concat(PlayBack--ControlInfo-ControlName, '|', generate-id(..))"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="GStep">
<xsl:copy>
<xsl:apply-templates select="Step[generate-id()=generate-id(key('k1', concat(PlayBack--ControlInfo-ControlName, '|', generate-id(..)))[1])]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

You can also use a group-by approach in XSLT 2.0.
XSLT 2.0:
<?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" 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>
<!-- Remove duplicates -->
<xsl:template match="GStep">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each-group select="Step" group-by="PlayBack--ControlInfo-ControlName">
<xsl:sequence select="."/>
</xsl:for-each-group>
</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>

XSL evaluate dynamic XPATH from another document

I have two sample documents defined below. In module_meta.xml only the effect nodes on xpath /mdata/effectivity are relevant. As seen below, they contain a path attribute and a effrg attribute. The goal is now to evaluate the xpath (which is defined in the module_meta.xml as the path attribute) on the module.xml and append the effrg to it. See desired_output.xml for the desired result. The xsl transformation is applied on module.xml. I know that I have to use the document() function to "include" module_meta.xml, but so far I am at a loss.
module.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE proc>
<procbody>
<info>
<action lid="a">
</action>
<action lid="b">
</action>
<action lid="c">
</action>
</info>
</procbody>
module_meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mdata>
<mdata>
<metadata>
<metadata-item name="n1" value="v1" />
<metadata-item name="n2" value="v2" />
<metadata-item name="n3" value="v3" />
</metadata>
<effectivity>
<effect path="//*[#lid='a']" effrg="0074 0080 0087" />
<effect path="//*[#lid='b']" effrg="0136 0146 0174" />
</effectivity>
</mdata>
desired_output.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE proc>
<procbody>
<info>
<action lid="a" effrg="0074 0080 0087">
</action>
<action lid="b" effrg="0136 0146 0174">
</action>
<action lid="c">
</action>
</info>
</procbody>
In XSLT 3 with xsl:evaluate support:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:mf="http://example.com/mf"
expand-text="yes">
<xsl:function name="mf:evaluate" as="element(action)?">
<xsl:param name="effect" as="element(effect)"/>
<xsl:evaluate xpath="$effect/#path" context-item="$main-doc"/>
</xsl:function>
<xsl:variable name="main-doc" select="/"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="info/action">
<xsl:copy>
<xsl:apply-templates select="#*, $meta//effect[current() is mf:evaluate(.)]/#effrg"/>
</xsl:copy>
</xsl:template>
<xsl:param name="meta">
<mdata>
<metadata>
<metadata-item name="n1" value="v1" />
<metadata-item name="n2" value="v2" />
<metadata-item name="n3" value="v3" />
</metadata>
<effectivity>
<effect path="//*[#lid='a']" effrg="0074 0080 0087" />
<effect path="//*[#lid='b']" effrg="0136 0146 0174" />
</effectivity>
</mdata>
</xsl:param>
</xsl:stylesheet>
For self-containedness of the example the second document is inline as a parameter but you can of course use <xsl:param name="meta" select="doc('module_meta.xml')"/> instead.
It might be much more efficient to use the xsl:evaluate only once for each effect element, for instance, by declaring a key doing that, and store the generated-id if there is one:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
xmlns:mf="http://example.com/mf"
expand-text="yes">
<xsl:key name="ref" match="mdata/effectivity/effect">
<xsl:variable name="referenced-node" as="node()?">
<xsl:evaluate xpath="#path" context-item="$main-doc" as="node()?"/>
</xsl:variable>
<xsl:sequence select="generate-id($referenced-node)"/>
</xsl:key>
<xsl:variable name="main-doc" select="/"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="info/action">
<xsl:copy>
<xsl:apply-templates select="#*, key('ref', generate-id(), $meta)/#effrg"/>
</xsl:copy>
</xsl:template>
<xsl:param name="meta">
<mdata>
<metadata>
<metadata-item name="n1" value="v1" />
<metadata-item name="n2" value="v2" />
<metadata-item name="n3" value="v3" />
</metadata>
<effectivity>
<effect path="//*[#lid='a']" effrg="0074 0080 0087" />
<effect path="//*[#lid='b']" effrg="0136 0146 0174" />
</effectivity>
</mdata>
</xsl:param>
</xsl:stylesheet>
If you want to match any element referenced in the other document and copy any attribute but the path one you could change the template having match="info/action" to
<xsl:template match="*[key('ref', generate-id(), $meta)]">
<xsl:copy>
<xsl:apply-templates select="#*, key('ref', generate-id(), $meta)/(#* except #path), node()"/>
</xsl:copy>
</xsl:template>

create separate output files from single xslt based on condition

I would appreciated if someone could help me to create xslt to create multiple outputs on the condition
Input
<?xml version="1.0" encoding="utf-8"?>
<Document>
<Meta>
<GpsFile>notepad_may_30_file</GpsFile>
<GpsId>36fa4fe8-9691-4a7f-8bc1-9543f6b7d29a</GpsId>
<ExePath>
<ExePath1>C:\WINDOWS\SYSTEM32\notepad.exe</ExePath1>
</ExePath>
</Meta>
<Process>
<GStep DialogName="Untitled - Notepad">
<Step DialogName="Untitled - Notepad">
<Step-ID>3</Step-ID>
<Step-Exe>Notepad.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>4</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>5</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1>Edit</PlayBack--ControlInfo-ControlName1>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>6</Step-ID>
<Step-Exe>Notepad.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1>Replace...\tCtrl+H</PlayBack--ControlInfo-ControlName1>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>12</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>13</Step-ID>
<Step-Exe>Notepad.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1>Edit</PlayBack--ControlInfo-ControlName1>
</Step>
</GStep>
<GStep DialogName="Replace">
<Step DialogName="Replace">
<Step-ID>8</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
<Step DialogName="Replace">
<Step-ID>9</Step-ID>
<Step-Exe>Notepad.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1>Cancel</PlayBack--ControlInfo-ControlName1>
</Step>
<Step DialogName="Replace">
<Step-ID>10</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
<Step DialogName="Replace">
<Step-ID>16</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
</GStep>
</Process>
</Document>
Actually expecting a output files like below.
OUTPUT1
<?xml version="1.0" encoding="utf-8"?>
<Document>
<Meta>
<GpsFile>notepad_may_30_file</GpsFile>
<GpsId>36fa4fe8-9691-4a7f-8bc1-9543f6b7d29a</GpsId>
<ExePath>
<ExePath1>C:\WINDOWS\SYSTEM32\notepad.exe</ExePath1>
</ExePath>
</Meta>
<Process>
<GStep DialogName="Untitled - Notepad">
<Step DialogName="Untitled - Notepad">
<Step-ID>3</Step-ID>
<Step-Exe>Notepad.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>6</Step-ID>
<Step-Exe>Notepad.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1>Replace...\tCtrl+H</PlayBack--ControlInfo-ControlName1>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>13</Step-ID>
<Step-Exe>Notepad.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1>Edit</PlayBack--ControlInfo-ControlName1>
</Step>
</GStep>
<GStep DialogName="Replace">
<Step DialogName="Replace">
<Step-ID>9</Step-ID>
<Step-Exe>Notepad.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1>Cancel</PlayBack--ControlInfo-ControlName1>
</Step>
</GStep>
</Process>
</Document>
OUTPUT2
<?xml version="1.0" encoding="utf-8"?>
<Document>
<Meta>
<GpsFile>notepad_may_30_file</GpsFile>
<GpsId>36fa4fe8-9691-4a7f-8bc1-9543f6b7d29a</GpsId>
<ExePath>
<ExePath1>C:\WINDOWS\SYSTEM32\notepad.exe</ExePath1>
</ExePath>
</Meta>
<Process>
<GStep DialogName="Untitled - Notepad">
<Step DialogName="Untitled - Notepad">
<Step-ID>4</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>5</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1>Edit</PlayBack--ControlInfo-ControlName1>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>12</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
</GStep>
<GStep DialogName="Replace">
<Step DialogName="Replace">
<Step-ID>8</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
<Step DialogName="Replace">
<Step-ID>10</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
<Step DialogName="Replace">
<Step-ID>16</Step-ID>
<Step-Exe>iexporer.exe</Step-Exe>
<PlayBack--ControlInfo-ControlName1 />
</Step>
</GStep>
</Process>
</Document>
Based on the value it creates a output files.
Example:Should create multiple output files depending upon th values
<Step-Exe>Notepad.exe </Step-Exe>
<Step-Exe>Iexplorer.exe </Step-Exe>
<Step-Exe>winword.exe </Step-Exe>
**Mentioned about example should create 3 output files.**
Thanks very much.

Delete Duplicate Elements from Xml and should not delete null values inXSLT

I would appreciated if someone could help me to create xslt to remove duplicates nodes from XML based on duplicated element's value(PlayBack--ControlInfo-ControlName) but not empty values.
I want to remove all the duplicate elements except null values(PlayBack--ControlInfo-ControlName) from the GStep/Step
Input XML
<?xml version="1.0" encoding="utf-8"?>
<Document>
<Meta>
<GpsFile>notepad_may_30_file</GpsFile>
<GpsId>36fa4fe8-9691-4a7f-8bc1-9543f6b7d29a</GpsId>
<ExePath>
<ExePath1>C:\WINDOWS\SYSTEM32\notepad.exe</ExePath1>
</ExePath>
</Meta>
<Process>
<GStep DialogName="Untitled - Notepad">
<Step DialogName="Untitled - Notepad">
<Step-ID>3</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>4</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>5</Step-ID>
<PlayBack--ControlInfo-ControlName>Edit</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>6</Step-ID>
<PlayBack--ControlInfo-ControlName>Replace...\tCtrl+H</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>12</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>13</Step-ID>
<PlayBack--ControlInfo-ControlName>Edit</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>14</Step-ID>
<PlayBack--ControlInfo-ControlName>Replace...\tCtrl+H</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>15</Step-ID>
<PlayBack--ControlInfo-ControlName>Cancel</PlayBack--ControlInfo-ControlName>
</Step>
</GStep>
<GStep DialogName="Replace">
<Step DialogName="Replace">
<Step-ID>8</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Replace">
<Step-ID>9</Step-ID>
<PlayBack--ControlInfo-ControlName>Cancel</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Replace">
<Step-ID>10</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Replace">
<Step-ID>16</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
</GStep>
</Process>
</Document>
Actually expecting a result like below.
<?xml version="1.0" encoding="utf-8"?>
<Document>
<Meta>
<GpsFile>notepad_may_30_file</GpsFile>
<GpsId>36fa4fe8-9691-4a7f-8bc1-9543f6b7d29a</GpsId>
<ExePath>
<ExePath1>C:\WINDOWS\SYSTEM32\notepad.exe</ExePath1>
</ExePath>
</Meta>
<Process>
<GStep DialogName="Untitled - Notepad">
<Step DialogName="Untitled - Notepad">
<Step-ID>3</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>4</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>5</Step-ID>
<PlayBack--ControlInfo-ControlName>Edit</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>6</Step-ID>
<PlayBack--ControlInfo-ControlName>Replace...\tCtrl+H</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>12</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Untitled - Notepad">
<Step-ID>15</Step-ID>
<PlayBack--ControlInfo-ControlName>Cancel</PlayBack--ControlInfo-ControlName>
</Step>
</GStep>
<GStep DialogName="Replace">
<Step DialogName="Replace">
<Step-ID>8</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Replace">
<Step-ID>9</Step-ID>
<PlayBack--ControlInfo-ControlName>Cancel</PlayBack--ControlInfo-ControlName>
</Step>
<Step DialogName="Replace">
<Step-ID>10</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
<Step DialogName="Replace">
<Step-ID>16</Step-ID>
<PlayBack--ControlInfo-ControlName />
</Step>
</GStep>
</Process>
</Document>
Xslt code snippet.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="ControlNameInfo" match="Step" use="PlayBack--ControlInfo-ControlName"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="GStep/Step[not(generate-id() = generate-id(key('ControlNameInfo', PlayBack--
ControlInfo-ControlName)[1]))]"/>
</xsl:stylesheet>
Should delete the duplicate values but should not delete empty PlayBack--ControlInfo-ControlName
Thanks very much.
Assuming it is XSLT 2 or 3 and you want to use the key function it seems you need to add a predicate to only check non-empty Steps and you additionally need to use the third argument of the key function to restrict search to the ancestor GStep:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:key name="dups" match="Step[normalize-space(PlayBack--ControlInfo-ControlName)]" use="PlayBack--ControlInfo-ControlName"/>
<xsl:template match="Step[normalize-space(PlayBack--ControlInfo-ControlName)][not(. is key('dups', PlayBack--ControlInfo-ControlName, ancestor::GStep)[1])]"/>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bEzkTcp

Changing XML format based off CDATA using a XSL template

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>