Question

Below is my xml file.

<xml>
<top>
 <main>
    <firstname>John</firstname>
    <lastname>John</lastname>
    <table></table>
    <chapter>
       <firstname>Alex</firstname>
       <lastname>Robert</lastname>
       <p>Sample text chap</p>
       <figure name="f1.svg"></figure>
       <chapter>
          <firstname>Rebec</firstname>
          <lastname></lastname>
          <p>Sample text</p>
          <figure name="f2.svg"></figure>
       </chapter>
    </chapter>
 </main>
</top>
</xml>

Desired output:

<bold>John
table
<bold>Robert
Sample text chap
f1.svg
<bold> Rebec
Sample text
f2.svg

Explaination: I have written an xslt to do this. I need to fetch the xml nodes dynamically. I cannot write: xsl:apply-templates select='main/lastname'. Because my xml format could change anytime. I have tried a logic to first fetch all the xml nodes using '$root/*'. Then if 'table' element is encountered, i use xsl:apply-templates select='current()[name() = 'TABLE']' and perform table creation operations. This works fine. I get the desired output but my figure elements only displays f1.svg at every place in the output. f2.svg is not shown. And how do I match only 'lastname' and make it bold? I want to make the code as generic/modular as possible so that it loops through all the elements of the xml tree and does some formatting on the specific nodes. Below is a recursive xslt. With this my data is getting repeated. I am writing recursive template because xslt is not sequential.

XSLT:

<xsl:call-template name="FetchNodes">
<xsl:with-param name="endIndex" select="$NumberOfNodes" />
<xsl:with-param name="startIndex" select="1" />
<xsl:with-param name="context" select="$root/*" />
</xsl:call-template>


<xsl:template name="FetchNodes">
    <xsl:param name="endIndex" />
    <xsl:param name="startIndex" />
    <xsl:param name="context" />
    <xsl:if test="$startIndex &lt;= $endIndex">
           <xsl:if test="$context[$startIndex][name() = 'table']"">
                  <xsl:apply-templates select="$context[$startIndex][name() = 'table']"" mode="table" />
      </xsl:if>
            <xsl:call-template name="FetchNodes">
        <xsl:with-param name="endIndex" select="$endIndex" />
        <xsl:with-param name="startIndex" select="$startIndex + 1"/>
        <xsl:with-param name="context" select="$context" />
    </xsl:call-template>
</xsl:if>
</xsl:template>

    <xsl:template match="node()" mode="table">   
       <xsl:value-of select="node()" />
    </xsl:template>

With the above xslt, something is incorrect in the xpath of apply templates. Output is not proper.
I want XSL FO output.

Can anybody suggest something?

Was it helpful?

Solution

The problem it displaying "f1.svg" instead of "f2.svg" is because of this line

<xsl:variable name="ImageName">
    <xsl:value-of select="$root/*/chapter/figure/@name" />
</xsl:variable>

You are already positioned on a figure at this point, so you only need to use a relative xpath expression here. The one you are currently using is an absolute path and so will always return the first @name attribute regardless of your context. It should look this this

<xsl:variable name="ImageName">
    <xsl:value-of select="@name" />
</xsl:variable>

Or better still, like this

<xsl:variable name="ImageName" select="@name" />

Having said, the code is in a template that is trying to match an element a FIGURE element, which does not exist in the XML you have shown us. You can actually simplify the template match to this, for example

 <xsl:template match="figure" mode="figure">

As for making things bold, you can just add the font-weight attribute to any block you want to make bold. Something like this:

<xsl:choose>
   <xsl:when test="self::lastname">
      <fo:inline font-weight="bold"><xsl:value-of select="text()" /></fo:inline>
   </xsl:when>
   <xsl:otherwise>
      <xsl:value-of select="text()" />
   </xsl:otherwise>
</xsl:choose>

EDIT: Having said all that, you may not be taking the correct approach to the problem. It may be better to use template matching, taking advantage of XSLT's built-in template to navigate over the document. Essentially, just write a template for each element you want to match, and generate the output, and then carry on matching its children.

For example, to turn a chapter into an fo:block do this

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

To output the firstname in bold, do this

<xsl:template match="firstname">
   <fo:inline font-weight="bold">
      <xsl:value-of select="text()"/>
   </fo:inline>
</xsl:template>

To turn a figure into an image, do this (Note the use of Attribute Value Templates here, the curly braces indicate an expression to be evaluated, not output literally)

<xsl:template match="figure">
   <fo:block>
      <fo:external-graphic src="../resources/{@name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
   </fo:block>
</xsl:template>

Try this XSLT as a starting point, and build on it

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

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

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

   <xsl:template match="firstname">
      <fo:inline font-weight="bold">
         <xsl:value-of select="text()"/>
      </fo:inline>
   </xsl:template>

   <xsl:template match="lastname"/>

   <xsl:template match="figure">
      <fo:block>
         <fo:external-graphic src="../resources/{@name}" content-height="60%" scaling="uniform" padding-left="2cm"/>
      </fo:block>
   </xsl:template>
</xsl:stylesheet>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top