Re: [newbie]use of xsl:if {RE: XSL to handle display mutiple pages}

Subject: Re: [newbie]use of xsl:if {RE: XSL to handle display mutiple pages}
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Fri, 3 Nov 2000 14:38:01 -0400 (EST)
Xiaocun,

>  I started working on XSL to handle display mutiple pages in HTML.  The
>idea I tried was simple, count number of records until max records per page
>reaches.  At that time, I close of the current page/table, add a page break
>and start the next page/table.  But such logic seems does not seems to be
>allowed in xsl blocks such as xsl:if.  What can be done to get around this
>problem?

You are thinking in a procedural way about creating text within an output
file.  In XSLT, you are *not* creating text within an output file, you are
creating a node tree, so you *don't* create start tags and end tags, you
*do* create elements, and you *don't* put the name of an attribute, then a
equals sign, then a quote, then the attribute value, then another quote,
you *do* create attributes.

You need to have that shift in understanding about how XSLT works before
you're able to use it properly: it will continue to be a frustrating
language for you to use until you make that shift.

In XSLT terms, what you want to do is create a number of tables, each of
which contains a certain number of items.  So, given that the items that
you want in a single table are stored in the variable $items, you want
something like:

  <div style="page-break-before: always" />
  <table width="100%" border="1" cellspacing="0">
    <xsl:apply-templates select="tableheader" />
    <tbody>
      <xsl:apply-templates select="$items" />
    </tbody>
  </table>

There is no keeping track of how many items there are: you only select
those items that should be included in the particular table.

In fact this problem is quite a complex one for someone who is still
thinking procedurally, so I'll try to take you through it quite slowly step
by step.

The main problem is how to identify what items need to go in a particular
table.  You know the number of items that you want per page
($maxItemsPage), so you know that every $maxItemsPage + 1st item is the top
of each page (if $maxItemsPage is 5, then the 1st, 6th, 11th and so on
items start each table).  This means that you can work out which items are
those that go at the top of the page using 'mod'.  If the number of the
item, mod the number of items of the page, equals 1, then you have an item
at the start of the page.  So, for these items, the test expression:

  (position() mod $maxItemsPage) = 1

will be true.  This means you can select all those items using the select
expression:

  //item[(position() mod $maxItemsPage) = 1]

Once you have one of these items, then you know the rest of the items that
are on the page, because you know there will be $maxItemsPage - 1 of them
(the one you have, plus $maxItemsPage - 1 more gives you $maxItemsPage in
total).  In other words, if you built a list of all the items that are
after the item you currently have, and then just took those whose position
within that list was less than $maxItemsPage, then you'd get the rest of
the items for the page.  Building the list involves getting all the
following items:

  following::item

and then testing whether their position is less than $maxItemsPage involves
the test:

  position() &lt; $maxItemsPage

so the XPath is:

  following::item[position() &lt; $maxItemsPage]

So, if we have a template that matches one of those items that will appear
at the top of the page, then the items that make up the page are (a) the
current item and (b) the next $maxItemsPage - 1 items.  These are unioned
together with the '|' operator.  The '.' means 'the current node'.

  . | following::item[position() &lt; $maxItemsPage]

So, the template will look something like:

<!-- matches the top items in a page -->
<xsl:template match="item" mode="top">
  <!-- works out what items will go on the page -->
  <xsl:variable name="items"
                select=". | following::item[position() &lt; $maxItemsPage]" />
  <!-- put in the page break -->
  <div style="page-break-before: always" />
  <!-- add the table -->
  <table width="100%" border="1" cellspacing="0">
    <xsl:apply-templates select="tableheader" />
    <tbody>
      <xsl:apply-templates select="$items" />
    </tbody>
  </table>
</xsl:template>

[Note that the above is largely taken from your code: I don't know whether
the tableheader elements are really children of 'item' elements - if not
then no table header will be given.]

The only thing that it remains to do is make sure that templates are
applied to only those items that appear at the top of the page, and using
the 'top' mode that we've used for the template.  Recall that the top items
could be selected with the expression:

  //item[(position() mod $maxItemsPage) = 1]

So, given this, you can apply templates to those with:

  <xsl:apply-templates select="//item[(position() mod $maxItemsPage) = 1]"
                       mode="top" />

I hope that this helps, and shows how the declarative approach works in a
case like this.  If you need any more help on the details, please give
examples of the source XML that you're using and the result that you want.

Cheers,

Jeni

Jeni Tennison
http://www.jenitennison.com/




 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread