Archive for the ‘Uncategorized’ Category.

Apache FOP XSL-FO implementation and footers

“flows” in XSL-FO can have static areas surrounding them, which are
naturaly used to make headers and footers on pages. Let’s consider
sales invoice like this:

sample invoice

Here, of course, you can position header and footer absolutely using
<fo:block-container left=”xx” top=”xx” position=”absolute”>.
However, when your invoice grows and you run out of space on the page,
and you want to see something like this:

invoice how it should page correctly

In XSL-FO standard this is achived using special static sections of the page:

<fo:page-sequence-master master-name="all-pages">
  <fo:repeatable-page-master-alternatives>
    <fo:conditional-page-master-reference 
            page-position="any" master-reference="any-page-id"/>
    <fo:conditional-page-master-reference 
            page-position="last" master-reference="last-page-id"/>
  </fo:repeatable-page-master-alternatives>
</fo:page-sequence-master>

Here you use master-reference parameter values your page master definition , for example:

<fo:simple-page-master master-name="last-page-id">

Inside that master you can define different footer. A good example is given here http://www.xmlpdf.com/ibex-examples.html

Problem is, tracking which page is “last” is very tricky in
free-flow documents. Take HTML for example, the common question about
how to make something 100% high is hard to answer. The content can flow
differently, and, more importantly, the element that is trying to make
use of that position can affect its own position. For example, now in
XSL-FO, a footer on the last page can be so high that the engine will
have to create another page after the page that was considered “last”
just to fit it. In which case, page before last will suddenly fit all
content on the last page too, because it doesn’t have footer anymore.
So engine has to make a decision on how to fix that in one or another
position! What I am getting to is that Apache FOP doesn’t do that yet,
and in general many complex things having to do with flow. It’s working
just fine with everything else it seems. This is why current version is
in “maintanence” now and developers are working on redesigning for next
version (which doesn’t have name yet, but either 0.30 or 1.0 ūüėČ

So what do you do in the mid time? Well, you have 3 workarounds
(none of which is perfect). First one that is normally suggested is to
use fo:footnote element to emulate the footer on the last page. Don’t
forget that you have to have footnote reference text, otherwise it
won’t generate anything:

<fo:block id="end">
<fo:footnote><fo:inline color="white">.</fo:inline>
<fo:footnote-body>...

The problem with footnote is that you can’t use absolutely
positioned blocks in it, and it seems to only like tables for fine
formatting. So the example that i gave above on the picture might be
hard to implement.

The second workaround is to use some code inside your program
(before FOP call) to tell which items will be on last page, and put
them into a different page master. That may be hard for some
applications, again, like in example I gave, each line in the invoice
can have wrapping text and as a result starts taking more space than
originally estimated.

The last solution is not perfect either – don’t make special
footer.This is what I ended up doing (and it was fine in my case, since
every invoice page has same footer in my clients case). So in the end
it looks like this:

result of implementing paging on invoice in FOP

You may think of switching to commercial XSL-FO generator, like
RenderX or Antenna House, which seem to support flows better, and
overall are high quality commercial products.

http://www.renderx.net/Content/tools/xep.html

http://www.antennahouse.com/product.htm

For my own software I am thinking of supporting many different
renderers, based on clients ability to buy commercial renderer or to
live with Apache FOP limitations (which will be fixed some day).

string.Trim(char[])

Apparently you should be more careful with Trim. How precisely it works i don’t yet understand, but the following operation:

variable.Trim(‚ÄĚ’ ‚Äú.ToCharArray())

does not always remove space or/and single quote from the ends of
the string. I think it is related to the fact that in that case it
doesn’t use Char.IsWhiteSpace, like if you use Trim without parameters.

Complex Sum in XSLT

When generating reports from XML data using XSLT I have to perform complex mathematical operations. Or, rather, operations that generic XPath can’t do. To achive that you can use variables in XSLT and fill node lists with them. So we have:

<xsl:variable name="totalCommission">
 <xsl:for-eachselect="orderz/accounts/payment/orderzs[@o_status != -9 and @o_prodtype != 'J']">
  <accum><xsl:value-of select="(@o_extprice  - @o_discount -itemmaster/itempricelist/@storecost*@qty) * ../../../../@salescommission div 100"/></accum>
 </xsl:for-each>
</xsl:variable>

as a result we create a variable that generates its element using some algorithm. After that, we can run a sum operation against it:

$<xsl:value-ofselect="format-number(sum(msxsl:node-set($totalCommission)/accum),'#,##0.00')" />

Very important in this operation is support of node set generator msxsl:node-set(), which is supported in MSXML that i use in this case.
To use it the root XML node will look like this:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"version="1.0" xmlns:msxsl="urn:schemas-microsoft-com:xslt">

XML Cache

Developers frequently want to get data structures out of their code¬†and into XML. But that bears a performance hit, since every time you¬†read the file you have to parse it. In case of client applications you¬†can avoid it by placing XML objects in the memory. However, in case of¬†web-apps, memory gets destroyed after the request. Besides, you can’t¬†monitor changes in the files themselves. ASP.NET introduced a very useful object called Cache, which is designed to help¬†with precisely that problem. It stores objects on the application level¬†(similar to Application object) but also allows you to monitor changes¬†in the data. The following code is implementation of XmlDocument object¬†storage and file change monitoring:

public static XmlDocument GetDOM(string path, Page page)
{
  if (page.Cache["xmlcache:dom:"+path] != null)
    return (XmlDocument)page.Cache["xmlcache:dom:"+path];
  else
  {
    XmlDocument x = new XmlDocument();
    x.Load(page.MapPath(path));
    page.Cache.Insert("xmlcache:dom:"+path,
      x,new CacheDependency(page.MapPath(path)));
    return x;
  }
}

The methods to store XslTransform and XPathDocument would look¬†exactly the same. However, you should carefully monitor the contents of¬†XslTransform document – CacheDependency object doesn’t monitor internal¬†links of XSLT language, like xsl:import. By the way, I couldn’t find a¬†way to automatically find all those tags in XSLT document and generate¬†CacheDependancy for each of them.

Oh, and one other important thing. ASP.NET 2.0 extended on¬†CacheDependency and introduced an object that monitors changes in SQL¬†database! It’s ideal for many applications where you fill drop-downs¬†with data from database, for example.

Simple Serializer/Deserializer

public static string SerializeObject(object obj)
{
 MemoryStream temp = new MemoryStream();
 XmlSerializer serializer = new XmlSerializer(obj.GetType());
 serializer.Serialize(temp,obj);

 temp.Seek(0,SeekOrigin.Begin);
 byte[] byteArray = new byte[temp.Length];
 int count = 0;
 while (count < temp.Length)
 {
  byteArray[count++] = Convert.ToByte(temp.ReadByte());
 }
 ASCIIEncoding asciiEncoding = new ASCIIEncoding();
 char[] charArray = new char[asciiEncoding.GetCharCount(byteArray, 0, count)];
 asciiEncoding.GetDecoder().GetChars(byteArray, 0, count, charArray, 0);
 return new string(charArray);
}
public static object DeSerializeObject(string s,object obj)
{
 ASCIIEncoding asciiEncoding = new ASCIIEncoding();
 byte[] inp = asciiEncoding.GetBytes(s);
 MemoryStream temp = new MemoryStream();
 XmlSerializer serializer = new XmlSerializer(obj.GetType());

 temp.Write(inp,0, inp.Length);
 temp.Seek(0,SeekOrigin.Begin);
 return serializer.Deserialize(temp);
}

Update: apparently XmlSerializer is pretty limited in which types it¬†can reflect, it’s designed to fit current WDSL schema, which is limited¬†itself. Well, not really, but in any case right now XmlSerializer can’t¬†serialize multidimentional arrays, for example. In the code above you¬†can replace XmlSerializer with SoapFormatter, while keeping everything¬†else exactly the same. That will generate different (SOAP) envelope and¬†you will be able to serialize any type of data.