2
votes

I need to display certain data in a tabular form, and would prefer to use multi-pass xslt using node-set() so that I can avoid deploying additional tools (like xsltproc). Right now, I'm able to perform the required task in two steps i.e.

Step 1: convert XML-1 to XMl-2 using identity template (xsl:copy, using xsl:element to add dynamic elements 'dev' and 'qa`):

<projectteam>
  <member>
    <name>John</name>
    <role>dev</role>
    <hrs>100</hrs>
  </member>
  <member>
    <name>Peter</name>
    <role>qa</role>
    <hrs>80</hrs>
  </member>
</projectteam>

To

<projectteam>
  <member>
    <name>John</name>
    <dev>100</dev>
  </member>
  <member>
    <name>Peter</name>
    <qa>80</qa>
  </member>
<projectteam>

And then, use another XSLT-FO style sheet to transform XML #2 into a PDF document with the required layout:

name | dev | qa |
-----------------
John | 100 |    |
Peter|     | 80 |
-----------------
Total| 100 | 80 |

I've tried using node-set() (incorrectly I suppose) to combine both the steps, but it wouldn't work as the result I get is as follows:

name | dev | qa |
-----------------
Total|  0  |  0 |

Stylesheet-1: converts XML-1 to XML-2, imports another stylesheet 'projDisplay.xsl', uses node-set() to invoke the imported stylesheet, but the data doesn't get displayed?

<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:fo="http://www.w3.org/1999/XSL/Format"
  xmlns:exslt="http://exslt.org/common"
>

  <!-- import stylesheet to display XML-2 in tabular form -->
  <xsl:import href="projDisplay.xsl"/>

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

  <xsl:template match="@*|node()">
    <xsl:variable name="newXmlData">
      <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
    </xsl:variable>

    <!-- ==> This is where my problem is - it goes to the template defined in  -->
    <!-- projDisplay.xsl (xslt-fo, pretty big one with page layout etc. hence  -->
    <!-- not included here, but it works on its own though) as I can see the   -->
    <!-- table header, and an empty totals row, but non of the rows are displayed -->

    <xsl:apply-templates 
      select="exslt:node-set($newXmlData)/projectteam" mode="display"
    />
  </xsl:template>

  <!-- replace element 'role' with a new element - role name (i.e. dev or qa) -->
  <!-- and set its value as 'hrs                                              -->
  <xsl:template match="role">
    <xsl:element name="{.}"> <xsl:value-of select="../hrs"/> </xsl:element>
  </xsl:template>

  <!-- eliminate element 'hrs' -->
  <xsl:template match="hrs"/>
</xsl:stylesheet>

The commented section in the stylesheet doesn't look right to me. Any suggestions about how to correct it?

Thanks!

2
What happens if you use just select='exslt:node-set($newXmlData)' (without /projectteam) in apply-templates? - Pavel Minaev
Tried it, and it doesn't work. - Krishna
Can you try my changed answer? I have no XSLT-FO to test with, and I don't have projDisplay.xsl either, so it's a bit trial and error for me. - Tomalak
No, doesn't work either; I get this error: xsl:apply-imports not allowed in a xsl:for-each - Krishna
It didn't work. But, I went back to your original solution, and with some minor tweaks and changes to 'mode' flags, it worked YEY!!! Thanks a lot for your help!! I really appreciate it! I'll post the modified xsl below as a reference for someone looking for a similar solution. - Krishna

2 Answers

3
votes

Here's the solution that works. It's based on Tomalak's original solution with some minor modifications (like mode flags etc.) Again, thanks to Tomalak!!

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:exslt="http://exslt.org/common"
  version="1.0">

 <xsl:import href="projDisplay.xsl"/>

 <xsl:template match="/">
  <!-- store intermediate form as RTF -->
  <xsl:variable name="newXmlData">
   <xsl:apply-templates mode="filter"/>
  </xsl:variable>

  <!-- Now apply templates (with xslt-fo) defined in projDisplay.xsl with     -->
  <!-- the root template as <xsl:template match="projectteam" mode="display"> -->
  <!-- to the above RTF (i.e. after the original XML has be convertedr)       -->
  <xsl:apply-templates select="exslt:node-set($newXmlData)/projectteam" mode="display"/>

 </xsl:template>

 <!-- use identity templates to copy and modify original XML -->
 <xsl:template match="@*|node()" mode="filter">
  <xsl:copy>
   <xsl:apply-templates select="@*|node()" mode="filter"/>
  </xsl:copy>
  </xsl:template>

 <!-- replace element 'role' with a new element - role name (i.e. dev or qa) -->
 <!-- and set its value as 'hrs                                              -->
 <xsl:template match="member/role" mode="filter">
  <xsl:element name="{.}"> <xsl:value-of select="../hrs"/> </xsl:element>
 </xsl:template>

 <!-- eliminate element 'hrs' -->
 <xsl:template match="hrs" mode="filter"/>

</xsl:stylesheet>
2
votes

What about:

<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:fo="http://www.w3.org/1999/XSL/Format"
  xmlns:exslt="http://exslt.org/common"
  exclude-result-prefixes="exslt"
>

  <xsl:import href="projDisplay.xsl"/>

  <xsl:template match="/">
    <!-- store intermediary format as a RTF -->
    <xsl:variable name="newXmlData">
      <xsl:apply-templates />
    </xsl:variable>
    <!-- now we can the apply imported rules -->
    <xsl:apply-templates 
      select="exslt:node-set($newXmlData)/projectteam"
      mode="import"
    />
  </xsl:template>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="member/role">
    <xsl:element name="{.}">
      <xsl:value-of select="../hrs" />
    </xsl:element>
  </xsl:template>

  <xsl:template match="member/*" />

  <xsl:template match="projectteam" mode="import">
    <fo:root>
      <xsl:apply-imports />
    </fo:root>
  </xsl:template>

</xsl:stylesheet>