tags:

views:

427

answers:

2

Hello,

I've a need to display certain XML content in tabular form (XSL-FO for pdf reports), and not all the columns to displayed are present in source XML. So, I was wondering if there is a way to transform source XML by embedding additional columns based on certain element values, and then process the resulting XML to display content?

As an example, for source data:

<projectteam>
  <member>
    <name>John Doe</name>
    <role>dev</role>
 <hrs>100</hrs>
  </member>
  <member>
    <name>Paul Coder</name>
    <role>dev</role>
 <hrs>40</hrs>
  </member>
  <member>
    <name>Henry Tester</name>
    <role>qa</role>
 <hrs>80</hrs>
  </member>
  <member>
    <name>Peter Tester</name>
    <role>qa</role>
 <hrs>40</hrs>
  </member>
</projectteam>

I'd like the data to be displayed as :

Name         Role  Dev   QA
---------------------------
John Doe     dev   100  
Paul Coder   dev    40
Henry Tester qa          80
Peter Tester qa          40
---------------------------
Role Totals:       140  120
---------------------------

I would like to know if I can use something like:

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

So that I can embed elements <dev>100</dev> and so on at run time during first pass, and then use the resulting XML to display data for new columsn 'dev' and 'qa', that way, calculating totals for each role type will be much simpler (for eg. "sum(preceding-sibling::member/dev)" for dev column), and the data for each cell in 'dev' and 'qa' colums could simply be the value-of these tags respectively.

It got the desired results the hard way using following stylesheet (page formatting details omitted to keep it brief), but am not convinced that this is the apt solution.

    ...
              <fo:table-body>
      <!-- fills table rows -->
                <xsl:apply-templates select="member"/>

      <!-- dislpay totals for each role -->
      <fo:table-row height="12pt" border-bottom="1pt solid black">
      <fo:table-cell number-columns-spanned="2">
        <fo:block>Role Totals:</fo:block>
      </fo:table-cell>

      <fo:table-cell text-align="right">
        <xsl:call-template name="RoleTotals">
          <xsl:with-param name="node" select="//member[1]"/>
         <xsl:with-param name="roleName" select="'dev'"/>
        </xsl:call-template>
      </fo:table-cell>
      <fo:table-cell text-align="right">
        <xsl:call-template name="RoleTotals">
          <xsl:with-param name="node" select="//member[1]"/>
         <xsl:with-param name="roleName" select="'qa'"/>
        </xsl:call-template>
      </fo:table-cell>
      </fo:table-row>
              </fo:table-body>
    ...
    </fo:root>
  </xsl:template>

  <xsl:template match="member">
  <fo:table-row border-bottom="1pt solid black">
      <fo:table-cell> <fo:block> <xsl:value-of select="name"/></fo:block></fo:table-cell>
      <fo:table-cell> <fo:block> <xsl:value-of select="role"/></fo:block></fo:table-cell>
      <fo:table-cell text-align="right">
        <fo:block> 
      <xsl:if test="role = 'dev'"><xsl:value-of select="hrs"/></xsl:if>
  </fo:block>
      </fo:table-cell>
      <fo:table-cell text-align="right">
        <fo:block> 
      <xsl:if test="role = 'qa'"><xsl:value-of select="hrs"/></xsl:if>
  </fo:block>
      </fo:table-cell>
    </fo:table-row>
  </xsl:template>

  <xsl:template name="RoleTotals">
    <xsl:param name="node"/>
    <xsl:param name="roleName"/>
    <xsl:param name="RT" select="0"/>
    <xsl:variable name="newRT">
    <xsl:choose>
      <xsl:when test="$node/role = $roleName">
         <xsl:value-of select="$RT + $node/hrs"/>
      </xsl:when>
      <xsl:otherwise><xsl:value-of select="$RT"/></xsl:otherwise>
    </xsl:choose>
    </xsl:variable>
   <xsl:choose>
     <xsl:when test="$node/following-sibling::member">
      <xsl:call-template name="RoleTotals">
        <xsl:with-param name="node" select="$node/following-sibling::member[1]"/>
        <xsl:with-param name="roleName" select="$roleName"/>
        <xsl:with-param name="RT" select="$newRT"/>
      </xsl:call-template>
     </xsl:when>
     <xsl:otherwise>
      <fo:block><xsl:value-of select="$newRT"/></fo:block>
     </xsl:otherwise>
   </xsl:choose> 
  </xsl:template>
A: 

Yes, you can.

To answer your question. I haven't read the huge stylesheet, to be true.

Michael Krelin - hacker
Can you suggest how? I've tried various combinations but nothing worked.The real problem I have is somewhat complex i.e I've a Mailing elementthat can have multiple Containers, and each of these Containers canhave multiple Bundles. Each bundle is of a specific LevelType, andhas an element that describes TotalPieces. So, to display TotalPieces per buldle, in a column ment for its LevelType getslittle tricky, and to show the totals per levelType at the bottomgets even messier. This is where I sorely miss the ability to addnew elements (i.e. LevelType w/value set to TotalPieces).Thanks
Krishna
Exactly like you put it: `<xsl:element name="{role}" />` (in your example in context of `member` of course). I thought you didn't try it. What exactly doesn't work for you and doesn't work saying what?
Michael Krelin - hacker
Well, I added a new template as:<pre><xsl:template match="member"> <xsl:element name="{role}"> <xsl:value-of select="hrs"/> <xsl:apply-templates select="member"/> </xsl:element></xsl:template></pre>and tried to use <dev> element in the existing template for member,and it did't work (although I'm still not clear about templates)I tried adding these lines in existing template for member:<pre> <xsl:element name="{role}"> <xsl:value-of select="hrs"/> </xsl:element></pre>and all I get is a warning from FOP regarding unknown formatting object.
Krishna
second try: (prev. post got jumbled up)1. <xsl:template match="member"> <xsl:element name="{role}"> <xsl:value-of select="hrs"/> <xsl:apply-templates select="member"/> </xsl:element> </xsl:template> 2. (from within existing template for member): <xsl:element name="{role}"> <xsl:value-of select="hrs"/> </xsl:element>Both didn't work
Krishna
But fop is not supposed recognize `<dev/>` elements. And I'd rather solve problems one step at a time. First you need to produce input for fop and see if it is what you want it to be. Basing your ideas on fixing xml you produce solely on fop's messages sounds like a wrong thing to me.
Michael Krelin - hacker
Thanks! It works! I used xsltproc to produce translated xml, and thenused fop to produce pdf report. I recently started looking at xslt/fop, and missed an important step in the learning process i.e.'xsltproc', and thought fop(xml, xsl) = pdf was the only way to go.But this solution implies that I'd need to deploy additional tools(xsltproc etc.) on the client machines, and execute an additional step.. It would be nice if multi-pass transformation is possible...Thanks for your input!!
Krishna
A: 

What would happen if more job roles, other than Dev and QA got added? Would your stylesheet be able to cope? Maybe you can make use of Muenchian Grouping to get all possible roles in the stylesheet, and then generate columns for each possible role dynamically?

<xsl:key name="roles" match="role" use="."/>

<xsl:template match="/projectteam">
 <table border="1">
  <tr>
   <td>Name</td>
   <td>Role</td>
   <xsl:for-each select="member[generate-id(role) = generate-id(key('roles', role)[1])]">
    <td>
     <xsl:value-of select="role"/>
    </td>
   </xsl:for-each>
  </tr>
  <xsl:apply-templates select="member"/>
 </table>
</xsl:template>

<xsl:template match="member">
 <xsl:variable name="currentrole" select="role"/>
 <xsl:variable name="currenthrs" select="hrs"/>
 <tr>
  <td>
   <xsl:value-of select="name"/>
  </td>
  <td>
   <xsl:value-of select="role"/>
  </td>
  <xsl:for-each select="/projectteam/member[generate-id(role) = generate-id(key('roles', role)[1])]">
   <td>
    <xsl:choose>
     <xsl:when test="$currentrole = role">
      <xsl:value-of select="$currenthrs"/>
     </xsl:when>
    </xsl:choose>
   </td>
  </xsl:for-each>
 </tr>
</xsl:template>

I've outputted as HTML, not XSL-FO, but maybe this gives you the general idea.

Tim C
Thanks!! I've tried this and it works gereat!That's really a nice, generic solution! Now I know some usecases for 'generate0id()' and key element! I have fixed set of 'role'like columns (albeit 11 of them), so this approach would cut down thecode quite a bit, but then again introducing 11 new elements dynamicallywould make it a lot easier to total them at the end. I'll betrying out this approach and multi-pass stylesheet (using node-set()) to see which one makes more sense.Thanks
Krishna