Search code examples
xmlxsltpdfapache-fop

Fixing the location of a word in a line in PDF with FOP


I am trying to implement FOP to output a PDF using XML and XSLT files.

My problem is the following I need to fix the position of words in a line (but not through using tables) for example:

I have the following xml:

 <address>
    <Line1 length="32" noLine="5" col="60" />
    <Line2 length="32" noLine="6" col="60">Mr. John Kane</Line2 >
    <Line3 length="32" noLine="7" col="60">15 Street Springfield</Line3 >
    <Line4 length="32" noLine="8" col="60" />
    <Line5 length="32" noLine="9" col="60" />
    <Line6 length="6" noLine="10" col="60">75009</Line6 >
    <Line7 length="25" noLine="10" col="67">Freesberg</Line7 >
    <Line8 length="25" noLine="11" col="67">Idaho</Line8 >
  </address>
  1. Where the length is the word/sentence length
  2. noLine is the line number
  3. col is the beginning position of the word/sentence in the line

I did the lines but I can't seem to get to insert the word/sentence in the right position (col) in the line.

This is a part from my xslt:

<fo:block font-size="10" font-family="monospace">
<xsl:for-each select="*">
<xsl:variable name="currentNode" select ="name(.)"/>

<xsl:choose>
<xsl:when test="$currentNode = 'address'">
                                                <xsl:for-each select="*">
                                                    <xsl:variable name="length" select ="@length"/>
                                                    <xsl:variable name="noLine" select ="@noLine"/>
                                                    <xsl:variable name="col" select ="@col"/>
                                                    <xsl:variable name="precNoLig" select = "preceding-sibling::*[1]/@noLine"/>
                                                    <xsl:choose>
                                                        <xsl:when test="$precNoLig = $noLine">
                                                            <fo:block font-size="10" font-family="monospace" text-indent="60">
                                                                &#160;<xsl:value-of select="." />
                                                            </fo:block>
                                                        </xsl:when>
                                                        <xsl:otherwise>
                                                            <!--<fo:block font-size="10" font-family="monospace" >-->
                                                                &#x2028;<xsl:value-of select="." />
                                                            <!--</fo:block>-->
                                                        </xsl:otherwise>
                                                    </xsl:choose>
                                                </xsl:for-each>
    </xsl:when>
    </xsl:choose>

    </xsl:for-each>
    </fo:block>

This is the expected output as PDF :

                                         Mr. John Kane
                                         15 Street Springfield


                                         75009 Freesberg
                                               Idaho

Where it has the following positions in the PDF (col):

<-----------------60-------------------->
<-----------------60-------------------->Mr. John Kane
<-----------------60-------------------->15 Street Springfield
<-----------------60-------------------->
<-----------------60-------------------->
<-----------------60-------------------->75009 Freesberg
<-----------------67-------------------------->Idaho

Any help would be appreciated.


Solution

  • It's a little convoluted, but this should work:

    <?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:strip-space elements="*" />
      <xsl:output method="text"/>
    
      <xsl:variable name="sp" select="'                                                                                          '" />
    
      <xsl:template match="address/*">
        <xsl:variable name="value">
          <xsl:value-of select="." />
          <xsl:if test="following-sibling::*[1]/@noLine = @noLine">
            <xsl:value-of select="$sp" />
          </xsl:if>
        </xsl:variable>
        <xsl:choose>
          <xsl:when test="preceding-sibling::*[1]/@noLine = @noLine">
            <xsl:variable name="col" select="preceding-sibling::*[1]/@col + preceding-sibling::*[1]/@length" />
            <xsl:value-of select="concat(substring($sp,1,@col - $col),substring($value,1,@length))" />
          </xsl:when>
          <xsl:otherwise>
            <xsl:if test="preceding-sibling::*"><xsl:text>&#10;</xsl:text></xsl:if>
            <xsl:value-of select="concat(substring($sp,1,@col),substring($value,1,@length))" />
          </xsl:otherwise>
        </xsl:choose>
      </xsl:template>
    </xsl:stylesheet>
    

    For this transform to work as-is, it's critical that your Line.. elements are in noLine/col order, as it bases how to pad it out on the previous element. If they're out of order it'll get it badly wrong.

    The sp variable must contain at least as many spaces as you'll ever need to pad it out to, i.e. the max value of any length or col attribute. This solution doesn't include spaces at the end of a line- it's actually easier to pad out each line with trailing spaces so that it fills the length given, but I assumed it would probably be preferable not to.

    I've only tried it with the sample given, if it doesn't work for any other input you've got, let me know and I'll see if I can adapt it.

    EDIT: I just noticed that you needed it in PDF format, sorry. I just looked at the output, and didn't notice the format. Hopefully you can adapt this to the format you need (It's actually quite similar to what you'd already tried), but if not, I'd suggest you manually create a PDF that represents your output and add the XML for that PDF to your question.