Changing XML format based off CDATA using a XSL template

Related searches

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"?>


    &lt;p&gt;1. result&lt;/p&gt;
&lt;p&gt;2. more result&lt;/p&gt;
&lt;p&gt;3 again&lt;/p&gt;

These files will be imported into Testlink not used for html.

Transforming your input XML to your desired output XML requires some serious contortions:

  1. Decoding the CDATA sections into an xsl:variable with parse-xml-fragment
  2. Get the current index of these steps|expectedresults elements with

    count(preceding-sibling::*)+1
    
  3. Iterate over the p elements

  4. Compartmentalise the string into the relevant parts
  5. 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('&lt;p&gt;','step ',$cur,'&lt;/p&gt;')" /></actions>                    
                    <expectedresults>
                        <xsl:value-of select="concat('&lt;p&gt;',normalize-space(substring-after(.,' ')),'&lt;/p&gt;')" />
                    </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>

How to output CDATA with XSL | XSLT, Example. The following XSL transform shows how to preserve formatting in an XML document output. The comments node is set to output as a CDATA section. The <xsl:template> Element. The <xsl:template> element is used to build templates. The match attribute is used to associate a template with an XML element. The match attribute can also be used to define a template for the entire XML document. The value of the match attribute is an XPath expression (i.e. match="/" defines the whole document).

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

XSL Transformations (XSLT) Version 3.0, XSLT 3.0 is designed to be used in conjunction with XPath 3.0, which is Changes since XSLT 2.0 are listed in J Changes since XSLT 2.0. However, XSLT is used for a wide range of transformation tasks, not exclusively for formatting This example uses rule-based processing to convert a simple XML� Instead of having room being a child element of section, I'd like it to be an attribute of class, along with credits. If possible, I'd like to sort the class elements based on enrollment size (smallest to biggest). I'd also like to avoid using <xsl:for-each> as I've heard it is bad to use. Original XML file:

XSLT <xsl:output>, The <xsl:output> element defines the format of the output document. Sets the W3C version number for the output format (only used with method="html" or method="xml") list of elements whose text contents should be written as CDATA sections The character encoding is set to "UTF-8" and the output will be indented for� Good question, +1. See my answer for a complete, short and easy solution that is based on the most fundamental XSLT design pattern -- the use and overriding of the identity rule/template. Extensive explanation is also provided. – Dimitre Novatchev Mar 18 '11 at 13:22

"Unwrap" CDATA with XSLT - Adobe Support Community, I have some CDATA sections in an XML file that contain markup that I want to be parsed eventually, and I want to be able to extract it with the markup intact. Can I � Employee XML Datasource Data Template. This data template combines data that exists in a table called "dept" with data from an xml file called "employee.xml". It follows the same format as the Employee data template but the employee data comes from an xml file instead of from the emp table.

XSL Transformations, The transformed XML document may use the vocabulary of the original document , The transformation and formatting halves of XSL can function independently of each other. This chapter is based on the November 16, 1999, XSLT 1.0 Recommendation. Similarly, CDATA sections merely become part of text nodes . XSLT Introduction XSL Languages XSLT Transform XSLT <template> XSLT <value-of> XSLT <for-each> XSLT <sort> XSLT <if> XSLT <choose> XSLT Apply XSLT on the Client XSLT on the Server XSLT Edit XML XSLT Examples

Run the above report and get the output and save it as a XML file; Use the XML data in building the Template using Microsoft Word; This seemed to be a little cumbersome for me. So I was searching for alternatives. Then I stumbled upon the following solution. And I hope this helps all of you. This is part one of a series of blogs.

Comments
  • Merge and accumulators examples should be always rewarded.
  • Sorry, I have no idea what you're talking about.
  • Yes, my previous comment was not clear, sorry. The end output was not in the correct format.
  • Yes, my previous comment was not clear, sorry. The end output was not in the correct format. testcase >steps>step>step_number should be the final format. Your answer is missing the <steps> from the final output. I also tried your XSLT 1.0 + EXSLT version but was getting an error from the EXSLT str:split() usage on line 15. Do I need to install an extension to support this?
  • I am still not sure I understand the problem. If you want to add a steps wrapper, then just add it as a child of testcase. This is a trivial task and I don't see why you would not be able to figure this out for yourself. Re EXSLT, there is nothing to install: either your processor supports it or it doesn't.