Page 1 of 1

How to insert found element into preceding element?

Posted: Thu Oct 27, 2016 6:22 pm
by davidcoe
I have a fairly flat XML input file. Some elements have to be nested based on criterion if found.

My input file:

Code: Select all


<document>
<proc-title>Process Title 1</proc-title>
<note>Process NOTE</note>
<trim.para>Process note text.</trim.para>
<stepl1>Step 1</stepl1>
<stepl1>Step 2</stepl1>
<stepl2>Step 2a</stepl2>
<stepl2>Step 2b</stepl2>
<stepl3>Step 2b-1</stepl3>
<stepl3>Step 2b-2</stepl3>
<stepl1>Step 3</stepl1>
<note>Step 3 NOTE</note>
<trim.para>Step 3 note text.</trim.para>
<stepl1>Step 4</stepl1>
<stepl1>Step 5</stepl1>
</document>
My expected output file:

Code: Select all


<proc>
<title>Process Title 1</title>
<note><trim.para>Process note text.</trim.para></note>
<stepl1><para>Step 1</para></stepl1>
<stepl1><para>Step 2</para>
<stepl2><para>Step 2a</para></stepl2>
<stepl2><para>Step 2b</para>
<stepl3><para>Step 2b-1</para></stepl3>
<stepl3><para>Step 2b-2</para></stepl3>
</stepl2>
<stepl1><specpara><para>Step 3</para>
<note><trim.para>Step 3 note text.</trim.para></note></specpara>
</stepl1>
<stepl1><para>Step 4</para></stepl1>
<stepl1><para>Step 5</para></stepl1>
</proc>
My XSLT thus far:

Code: Select all


<xsl:template name="proc">    
<proc>
<title>
<xsl:value-of select="."/>
</title>
<xsl:if test="following-sibling::stepl1">
<xsl:for-each select="following-sibling::stepl1">
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:for-each>
<xsl:if test="following-sibling::stepl2">
<xsl:for-each select="following-sibling::stepl2">
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:for-each>
</xsl:if>
...
</xsl:if>
<xsl:for-each select="following-sibling::trim.para">
<note>
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</note>
</xsl:for-each>
</proc>
</xsl:template>
I cannot seem to find the correct method to nest elements to match my expected output. The nested IF statement in above example did not work. I thought of using some check for preceding-sibling.

I appreciate any assistance in solving this transform.

Thanks,
David Coe

Re: How to insert found element into preceding element?

Posted: Fri Oct 28, 2016 9:19 am
by Radu
Hi David,

As the question is XSLT specific you could try to join an XSLT specific mailing list and ask there:

http://www.mulberrytech.com/xsl/xsl-list/#Archives

In XSLT you can add xsl:message's to output debug information:

http://www.w3schools.com/xml/ref_xsl_el_message.asp

Coming back to your XSL template <xsl:template name="proc">. So the name of the template is proc. Do you explicitly call it from another template? Is the XSLT you gave a sample only a part of a larger XSLT?

Inside the template you are using something like:

Code: Select all

<title>
<xsl:value-of select="."/>
</title>
Each XSLT template has a context XML node on which it matched. The XPath expression "." will match that node. So if your XSLT template has as context the original <proc-title> element, the following-sibling::stepl1 will select all the stepl1 elements which come after it.
You can open the XML in the Oxygen editor area, place the caret in the <proc-title> element and run an XPath using the XPath toolbar, Oxygen will run the XPath in the context of the tag in which the caret is placed, so this might also be useful for debugging your situation.

You can also use the Oxygen XSLT Debugger perspective to debug your XSLT.

Regards,
Radu

Re: How to insert found element into preceding element?

Posted: Fri Oct 28, 2016 3:23 pm
by davidcoe
First of all, thank you for trying to answer the question with incomplete information. I apologize for that.

Secondly, I have been searching for a decent example to my problem and I have not yet found one. I thought to try posting my problem here first because I use Oxygen XML Editor exclusively to edit XML, XSLT, et al. I will look at the forum list you suggested. I will also try using the the XSLT/XPath degugging suggestions.

Thirdly, the full input XML, XSL, and output from this code is listed below. My previously posted expected results is still valid.

Input XML:

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="op_guide.xsl"?>
<document>
<proc-title>Process Title 1</proc-title>
<note>Process NOTE</note>
<trim.para>Process note text.</trim.para>
<stepl1>Step 1</stepl1>
<stepl1>Step 2</stepl1>
<stepl2>Step 2a</stepl2>
<stepl2>Step 2b</stepl2>
<stepl3>Step 2b-1</stepl3>
<stepl3>Step 2b-2</stepl3>
<stepl1>Step 3</stepl1>
<note>Step 3 NOTE</note>
<trim.para>Step 3 note text.</trim.para>
<stepl1>Step 4</stepl1>
<stepl1>Step 5</stepl1>
</document>
My XSL:

Code: Select all

<?xml version="1.0"?>

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>

<xsl:template match="document">
<xsl:for-each select="proc-title">
<xsl:call-template name="proc"/>
</xsl:for-each>
</xsl:template>

<xsl:template match="proc-title">
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="stepl1">
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="stepl2">
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="stepl3">
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="stepl4">
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="stepl5">
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="note">
<xsl:apply-templates/>
</xsl:template>

<xsl:template match="trim.para">
<xsl:apply-templates/>
</xsl:template>

<xsl:template name="proc">
<proc>
<title>
<xsl:value-of select="."/>
</title>
<xsl:for-each select="following-sibling::stepl1">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
</xsl:copy>
</xsl:for-each>
<xsl:for-each select="following-sibling::stepl2">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
</xsl:copy>
</xsl:for-each>
<xsl:for-each select="following-sibling::stepl3">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
</xsl:copy>
</xsl:for-each>
<xsl:for-each select="following-sibling::stepl4">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
</xsl:copy>
</xsl:for-each>
<xsl:for-each select="following-sibling::stepl5">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
</xsl:copy>
</xsl:for-each>
<xsl:for-each select="following-sibling::trim.para">
<note>
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</note>
</xsl:for-each>
</proc>
</xsl:template>

<xsl:template match="text()">
<xsl:value-of select="normalize-space(.)"/>
</xsl:template>

</xsl:stylesheet>
My current results:

Code: Select all

<?xml version="1.0" encoding="UTF-8"?>
<proc>
<title>Process Title 1</title>
<stepl1>
<para>Step 1</para>
</stepl1>
<stepl1>
<para>Step 2</para>
</stepl1>
<stepl1>
<para>Step 3</para>
</stepl1>
<stepl1>
<para>Step 4</para>
</stepl1>
<stepl1>
<para>Step 5</para>
</stepl1>
<stepl2>
<para>Step 2a</para>
</stepl2>
<stepl2>
<para>Step 2b</para>
</stepl2>
<stepl3>
<para>Step 2b-1</para>
</stepl3>
<stepl3>
<para>Step 2b-2</para>
</stepl3>
<note>
<trim.para>Process note text.</trim.para>
</note>
<note>
<trim.para>Step 3 note text.</trim.para>
</note>
</proc>

Re: How to insert found element into preceding element?

Posted: Fri Oct 28, 2016 4:25 pm
by Radu
Hi,

I think I understand better what you want.
You will have to nest the step levels directly in the XSLT. I'm giving you an example of how to nest steps level2 inside steps level1:

Code: Select all

            <xsl:for-each select="following-sibling::stepl1">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
<xsl:variable name="currentStep" select="."/>
<xsl:for-each select="following-sibling::stepl2[preceding-sibling::stepl1[1] = $currentStep]">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
I used the XPath following-sibling::stepl2[preceding-sibling::stepl1[1] = $currentStep] to select all step2 which come as siblings to this step1, but only the consecutive ones.

Regards,
Radu

Re: How to insert found element into preceding element?

Posted: Fri Oct 28, 2016 6:08 pm
by davidcoe
That solution worked well for consecutive steps and I was able to successfully implement for all steps, as shown below. It may not look elegant but the solution does work.

The Notes elements are trickier to manage because they are not consecutive. I am still working that part of the solution as I improve my understanding of XPath and axis. I've read a lot about them but I need to apply to really understand.

Elements <note><trim.para> related to <proc-title> does not need to be surrounded with <specpara>. But any step that has a note have to wrap <specpara> around the <trim.para> and the step's <para> elements. I am thinking to use <xsl:if> to test if the step has a note then add <specpara> and <trim.para> to the step. Any thoughts about it are appreciated.

Updated source XML - I added two substeps under step 4 to help me learn and understand the given solution thus far:

Code: Select all


<document>
<proc-title>Process Title 1</proc-title>
<note>Process NOTE</note>
<trim.para>Process note text.</trim.para>
<stepl1>Step 1</stepl1>
<stepl1>Step 2</stepl1>
<stepl2>Step 2a</stepl2>
<stepl2>Step 2b</stepl2>
<stepl3>Step 2b-1</stepl3>
<stepl3>Step 2b-2</stepl3>
<stepl1>Step 3</stepl1>
<note>Step 3 NOTE</note>
<trim.para>Step 3 note text.</trim.para>
<stepl1>Step 4</stepl1>
<stepl2>Step 4a</stepl2>
<stepl2>Step 4b</stepl2>
<stepl1>Step 5</stepl1>
</document>
My updated expected result - one correction to <specpara> that was previously posted:

Code: Select all


<proc>
<title>Process Title 1</title>
<note><trim.para>Process note text.</trim.para></note>
<stepl1><para>Step 1</para></stepl1>
<stepl1><para>Step 2</para>
<stepl2><para>Step 2a</para></stepl2>
<stepl2><para>Step 2b</para>
<stepl3><para>Step 2b-1</para></stepl3>
<stepl3><para>Step 2b-2</para></stepl3>
</stepl2>
<stepl1>
<specpara>
<note><trim.para>Step 3 note text.</trim.para></note>
<para>Step 3</para>
</specpara>
</stepl1>
<stepl1><para>Step 4</para></stepl1>
<stepl1><para>Step 5</para></stepl1>
</proc>
My XSLT - only the PROC template is shown, all previously posted templates remain the same:

Code: Select all


  <xsl:template name="proc">    
<proc>
<title>
<xsl:value-of select="."/>
</title>
<xsl:if test="following">
</xsl:if>
<xsl:for-each select="following-sibling::stepl1">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
<xsl:variable name="currentStep" select="."/>
<xsl:for-each select="following-sibling::stepl2[preceding-sibling::stepl1[1] = $currentStep]">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
<xsl:variable name="currentStep" select="."/>
<xsl:for-each select="following-sibling::stepl3[preceding-sibling::stepl2[1] = $currentStep]">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
<xsl:variable name="currentStep" select="."/>
<xsl:for-each select="following-sibling::stepl4[preceding-sibling::stepl3[1] = $currentStep]">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
<xsl:variable name="currentStep" select="."/>
<xsl:for-each select="following-sibling::stepl5[preceding-sibling::stepl4[1] = $currentStep]">
<xsl:copy>
<para>
<xsl:value-of select="."/>
</para>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>

<xsl:for-each select="following-sibling::trim.para">
<note>
<xsl:copy>
<xsl:value-of select="."/>
</xsl:copy>
</note>
</xsl:for-each>
</proc>
</xsl:template>
My results:

Code: Select all


<?xml version="1.0" encoding="UTF-8"?>
<proc>
<title>Process Title 1</title>
<stepl1>
<para>Step 1</para>
</stepl1>
<stepl1>
<para>Step 2</para>
<stepl2>
<para>Step 2a</para>
</stepl2>
<stepl2>
<para>Step 2b</para>
<stepl3>
<para>Step 2b-1</para>
</stepl3>
<stepl3>
<para>Step 2b-2</para>
</stepl3>
</stepl2>
</stepl1>
<stepl1>
<para>Step 3</para>
</stepl1>
<stepl1>
<para>Step 4</para>
<stepl2>
<para>Step 4a</para>
</stepl2>
<stepl2>
<para>Step 4b</para>
</stepl2>
</stepl1>
<stepl1>
<para>Step 5</para>
</stepl1>
<note>
<trim.para>Process note text.</trim.para>
</note>
<note>
<trim.para>Step 3 note text.</trim.para>
</note>
</proc>