2

The XSLT code written below, which checks for sublists and converts to XSL-FO, is not working properly:

<xsl:template match="ol|OL">
   <fo:list-block provisional-distance-between-starts="1cm"
      provisional-label-separation="0.5cm">
      <xsl:attribute name="space-after">
        <xsl:choose>
          <xsl:when test="ancestor::ul or ancestor::ol">
            <xsl:text>0pt</xsl:text>
          </xsl:when>
          <xsl:otherwise>
            <xsl:text>12pt</xsl:text>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:attribute>
      <xsl:attribute name="start-indent">
        <xsl:variable name="ancestors">
          <xsl:choose>
            <xsl:when test="count(ancestor::ol) or count(ancestor::ul)">
              <xsl:value-of select="1 + 
                                    (count(ancestor::ol) + 
                                     count(ancestor::ul)) * 
                                    1.25"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:text>1</xsl:text>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:variable>
        <xsl:value-of select="concat($ancestors, 'cm')"/>
      </xsl:attribute>
      <xsl:apply-templates select="*"/>
    </fo:list-block>
  </xsl:template>

We use FOP to create a PDF, but I am getting this exception

9010004 : FOP_RENDER_ERROR; nested exception: org.apache.fop.fo.ValidationException: Error(Unknown location): fo:list-block is not a valid child element of fo:list-block.

What's wrong with the XSLT?

lfurini
  • 3,729
  • 4
  • 30
  • 48
Codebeginner
  • 193
  • 4
  • 14
  • What is "not working properly"?? – potame Dec 21 '15 at 13:10
  • 1
    Cannot be sure without looking at the whole xslt, but you probably have an `ol` directly nested inside another `ol` in your input; that produces an `fo:list-block` inside another one, which is a violation of [`fo:list-block` content type](http://www.w3.org/TR/xsl/#fo_list-block). If that's the case, you have to modify your stylesheet to better handle nested lists, for example creating an `fo:list-item` whose `fo:list-item-label` is just an empty `fo:block`, and whose `fo:list-item-body` contains the inner `fo:list-block`. – lfurini Dec 21 '15 at 15:41
  • The error tells you exactly whats the problem is `fo:list-block is not a valid child element of fo:list-block`. The best way to troubleshoot FOP errors is to look at the resulting FO file generated. But you have not provided this. – PhillyNJ Dec 21 '15 at 22:32

2 Answers2

2

We cannot be sure without looking at the whole XSLT, but you probably have an ol directly nested inside another ol in your input. In such a situation, your XSLT would producec an fo:list-block inside another one, which is a violation of fo:list-block content type, and that's what FOP is complaining about.

If that's the case, you have to modify your stylesheet to better handle nested lists, for example creating an fo:list-item whose fo:list-item-label is just an empty fo:block, and whose fo:list-item-body contains the inner fo:list-block:

<xsl:template match="ol|OL">
    <xsl:choose>
        <xsl:when test="parent::ol or parent::OL">
            <!-- create a list item with empty label -->
            <fo:list-item>
                <fo:list-item-label end-indent="label-end()">
                    <fo:block/>
                </fo:list-item-label>
                <fo:list-item-body start-indent="body-start()">
                    <xsl:apply-templates select="." mode="nestingOk"/>
                </fo:list-item-body>
            </fo:list-item>
        </xsl:when>
        <xsl:otherwise>
            <!-- nothing special to do -->
            <xsl:apply-templates select="." mode="nestingOk"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="ol|OL" mode="nestingOk">
    <!-- this is just like in your old template -->
    <fo:list-block provisional-distance-between-starts="1cm"
      provisional-label-separation="0.5cm">
        <xsl:attribute name="space-after">
            <xsl:choose>
                <xsl:when test="ancestor::ul or ancestor::ol">
                    <xsl:text>0pt</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:text>12pt</xsl:text>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:attribute>
        <xsl:attribute name="start-indent">
            <xsl:variable name="ancestors">
                <xsl:choose>
                    <xsl:when test="count(ancestor::ol) or count(ancestor::ul)">
                        <xsl:value-of select="1 + 
                                    (count(ancestor::ol) + 
                                     count(ancestor::ul)) * 
                                    1.25"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:text>1</xsl:text>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:variable>
            <xsl:value-of select="concat($ancestors, 'cm')"/>
        </xsl:attribute>
      <xsl:apply-templates select="*"/>
    </fo:list-block>
</xsl:template>
  • a new template matching ol|OL has been added; it just checks whether the current list is directly nested inside another one, in which case an fo:list-item is created to wrap the inner fo:list-block (according to your preferences, you could use two simpler templates instead, with a node test in the matching expression)
  • your old template is given a different mode (I deliberately left its content unchanged), so that it is called by the new one when we are sure that the fo:list-block will be in a valid position

However the root cause of the problem is an invalid input, as neither HTML nor xhtml allow nested lists made like this:

    <!-- this is NOT valid! -->
    <ol>
        <li>alpha</li>
        <li>bravo</li>
        <li>charlie</li>
        <ol>
            <li>lorem</li>
            <li>ipsum</li>
        </ol>
    </ol>

They should instead be like this:

    <!-- this is valid -->
    <ol>
        <li>alpha</li>
        <li>bravo</li>
        <li>charlie
            <ol>
                <li>lorem</li>
                <li>ipsum</li>
            </ol>
        </li>
    </ol>

Complicating your stylesheet to handle correctly an invalid input probably means opening a can of worms; for example, in your original stylesheet the template matches ol|OL, but then in other expressions you just consider ancestor::ul or ancestor::ol without taking into account OL or UL ancestors ...

In conclusion, I would strongly suggest following Tony Graham's advice and normalize your input before applying the XSLT, which could also be simplified a bit.

Community
  • 1
  • 1
lfurini
  • 3,729
  • 4
  • 30
  • 48
  • Thank you @lfurini. Worked perfectly fine.I just changed this line to , SInce I needed ordered/unordered list under ordered list. – Codebeginner Dec 29 '15 at 07:07
2

You could run your XHTML (which I'm presuming it is) through a 'Tidy' program (e.g., JTidy at http://jtidy.sourceforge.net/) to regularise the markup and possibly avoid the problem.

You could use focheck (https://github.com/AntennaHouse/focheck) to validate the FO output from your XSLT. focheck also comes bundled in Oxygen 17.1, and if you have Oxygen, you can use its XSLT debugger to find which parts of your source and XSLT produced which output (though you can't validate the XSLT output directly in the output pane of the debugger AFAIK). Once you know what's causing the problem, you can work out what needs to change to fix it.

Tony Graham
  • 7,306
  • 13
  • 20
  • This is probably a more correct and cleaner approach, as [placing `ol` directly inside `ol` is not correct HTML / xhtml](http://stackoverflow.com/questions/5899337/proper-way-to-make-html-nested-list); a "normalization step" before XSLT is applied would also fix tag capitalization, so that the template could just match `ol` instead of `ol|OL`. JTidy can fix both list nesting and tag capitalization with the default settings. – lfurini Dec 23 '15 at 12:44