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
Related
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>
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.
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>
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>
I have a source xml that contains the addresses in spot and need to transform into an xml that holds all addresses into a single element and references each one.
I am using Saxon 9.1 processor and stylesheet version 1.0.
Thank you for helping.
Source Code:
<?xml version="1.0" encoding="utf-8"?>
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AddressDetails StartDate="1992-04-03" Type="Previous">
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<Address>
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressDetails>
</ContactDetails>
Target Code:
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="Prev_1" />
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="Curr_2" />
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="Mail_3" />
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="Prev_1" />
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
<Address>
<ID UniqueID="Curr_2" />
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
<Address>
<ID UniqueID="Mail_3" />
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressSegment>
</Application>
Have played with key and generate-id as I was trying to Generate the ID's first and copy them in the address. Here is my last trial of the xslt (best result I got was to have the UniqueID's empty so I have no idea how far off this solution is :) )
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="ID_key" match="*[#ReferedID]" use="#ReferedID"/>
<xsl:template match="/">
<Application>
<ContactDetails>
<xsl:for-each select="Application/Person/ContactDetails/AddressDetails">
<AddressDetails>
<xsl:attribute name="StartDate">
<xsl:value-of select="#StartDate"/>
</xsl:attribute>
<xsl:attribute name="Type">
<xsl:value-of select="#Type" />
</xsl:attribute>
<AddressRef>
<xsl:attribute name="ReferedID">
<xsl:value-of select="generate-id()"/>
</xsl:attribute>
</AddressRef>
</AddressDetails>
</xsl:for-each>
</ContactDetails>
<AddressSegment>
<xsl:for-each select="Application/Person/ContactDetails/AddressDetails">
<Address>
<ID>
<xsl:attribute name="UniqueID">
<xsl:value-of select="Address/ID[generate-id()=generate-id(key('ID_key',#UniqueID))]" />
</xsl:attribute>
</ID>
<City>
<xsl:attribute name="City">
<xsl:value-of select="Address/City/#City"/>
</xsl:attribute>
</City>
<Postcode>
<sl:attribute name="Postcode">
<xsl:value-of select="Address/Postcode/#Postcode"/>
</xsl:attribute>
</Postcode>
</Address>
</xsl:for-each>
</AddressSegment>
</Application>
</xsl:template>
</xsl:stylesheet>
To give you an example of how you could use generate-id and modes, the sample stylesheet
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="ContactDetails">
<xsl:copy>
<xsl:copy-of select="#*"/>
<ContactDetails>
<xsl:apply-templates select="AddressDetails/Address" mode="det"/>
</ContactDetails>
<AddressSegment>
<xsl:apply-templates select="AddressDetails/Address"/>
</AddressSegment>
</xsl:copy>
</xsl:template>
<xsl:template match="Address" mode="det">
<AddressDetails StartDate="{../#StartDate}" Type="{../#Type}">
<AddressRef ReferedID="{generate-id()}"/>
</AddressDetails>
</xsl:template>
<xsl:template match="Address">
<xsl:copy>
<xsl:copy-of select="#*"/>
<ID ID="{generate-id()}"/>
<xsl:copy-of select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
transforms the input
<?xml version="1.0" encoding="utf-8"?>
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AddressDetails StartDate="1992-04-03" Type="Previous">
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<Address>
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressDetails>
</ContactDetails>
with Saxon 6.5.5 into the output
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="d0e3"/>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="d0e7"/>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="d0e11"/>
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="d0e3"/>
<City City="Wien"/>
<Postcode Postcode="LSP-123"/>
</Address>
<Address>
<ID ID="d0e7"/>
<City City="Toronto"/>
<Postcode Postcode="LKT-947"/>
</Address>
<Address>
<ID ID="d0e11"/>
<City City="Sydney"/>
<Postcode Postcode="OKU-846"/>
</Address>
</AddressSegment>
</ContactDetails>
The following stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*" />
<xsl:output indent="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="ContactDetails">
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<xsl:apply-templates select="AddressDetails" />
</ContactDetails>
<AddressSegment>
<xsl:apply-templates select="AddressDetails/Address"
mode="ref" />
</AddressSegment>
</Application>
</xsl:template>
<xsl:template match="AddressDetails/Address">
<AddressRef ReferedID="{generate-id()}" />
</xsl:template>
<xsl:template match="AddressDetails/Address" mode="ref">
<xsl:copy>
<xsl:apply-templates select="#*" />
<ID ID="{generate-id(../*)}" />
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
On this input:
<?xml version="1.0" encoding="utf-8"?>
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AddressDetails StartDate="1992-04-03" Type="Previous">
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<Address>
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressDetails>
</ContactDetails>
Produces the desired result:
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="d1e3" />
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="d1e7" />
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="d1e11" />
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="d1e3" />
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
<Address>
<ID ID="d1e7" />
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
<Address>
<ID ID="d1e11" />
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressSegment>
</Application>
Note the use of the Identity Transform, the most fundamental transformation.
Create Unique IDs and reference them in the same document
If you need to create unique ID for an element, use thegenerate-id() function. Do note that this function generates a unique identifier for a given element in the input document. Therefore if you call the function on the same element, you'll always obtain the same ID. This is really what you want.
For simplicity, in the following example transform, I've applied the templates to AddressDetails two times, each time with a different mode.
Do note the correct generation and reference of the id in the output by applying the generate-id() function on the AddressDetails node.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="ContactDetails">
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<xsl:apply-templates select="AddressDetails" mode="contact"/>
</ContactDetails>
<AddressSegment>
<xsl:apply-templates select="AddressDetails" mode="segment"/>
</AddressSegment>
</Application>
</xsl:template>
<xsl:template match="AddressDetails" mode="contact">
<xsl:copy>
<xsl:copy-of select="#*"/>
<AddressRef ReferedID="{generate-id(.)}"/>
</xsl:copy>
</xsl:template>
<xsl:template match="AddressDetails" mode="segment">
<Address>
<ID ID="{generate-id(.)}"/>
<xsl:copy-of select="Address/*"/>
</Address>
</xsl:template>
</xsl:stylesheet>
When applied to the input provided in the question, returns:
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="d1e3"/>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="d1e13"/>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="d1e23"/>
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="d1e3"/>
<City City="Wien"/>
<Postcode Postcode="LSP-123"/>
</Address>
<Address>
<ID ID="d1e13"/>
<City City="Toronto"/>
<Postcode Postcode="LKT-947"/>
</Address>
<Address>
<ID ID="d1e23"/>
<City City="Sydney"/>
<Postcode Postcode="OKU-846"/>
</Address>
</AddressSegment>
</Application>