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:
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
<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 (ie 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>
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>
.
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>
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.