News


Some time ago I showed how to get an intellisense and how to configure the FO.NET to acquire national characters inside the generated pdf files.

Due to the limitations that I mensioned in my previous post, I started playing with the Apache FOP. In this post I want to show, how to acquire the same result as I showed in the two posts related to FO.NET.

 

Intellisense

 

To get the intellisense from the XSL-FO templates set the xsi:schemaLocation the same way I showed it in this post.

The only diffrence to FO.NET is that, during generating the document by the code I showed last time we will get an exception:

 

org.apache.fop.fo.ValidationException: Invalid property encountered on "fo:root": xsi:schemaLocation (See position 6:11)

 

Fortunatelly there is a very easy way to resolve this without removing the entire attribute along with the intellisense.

Add to the FopFactory the ignoreNamespace by:

 

FopFactory fopFactory = FopFactory.newInstance();

fopFactory.ignoreNamespace(http://www.w3.org/2001/XMLSchema-instance);

 

Notice that, the url specified in this method this is a namespace for the xmlns:xsi namespace, not xsi.schemaLocation.

 

Fonts / national characters

 

This point is a little dfferent to acquire, but not more complicated that it was with FO.NET.

To set the fonts in Apache FOP 1.0, we need a configuration file. A sample one can be get from the directory where we unpacked the fop binaries, from conf subdirectory. There is a file called fop.xconf. We must copy this file to our solution.

In the simplest way, in the <fonts> tag we can add  <auto-detect/>. Thanks to this, FOP will index all fonts available on the installed operating system. There probably should be no problem, if we have a http handler or a WCF Service on the server that serves the generated pdf documents. In this situation we can use all available fonts on this server.

 

To use this config file, we must set a path to it:

 

FopFactory fopFactory = FopFactory.newInstance();

fopFactory.setUserConfig(new File("fop.xconf"));



In one of my previous posts I was talking about FO.NET which I was using to generate a pdf documents from XSL-FO.

FO.NET is one of the .NET ports of Apache FOP. Unfortunatelly it is no longer maintained. I known it when I decidec to use it, because there is a lack of available (free) choices for .NET to render a pdf form XSL-FO. I hoped in this implementation I will find all I need to create a pdf file with my really simple requirements.

FO.NET is a port from some old version of Apache FOP and I found really quickly that there is a lack of some features that I needed, like dotted borders, double borders or support for margins. So I started to looking for some alternatives. I didn’t try the NFOP, another port of Apache FOP, because I found something I think much more better, the IKVM.NET project.

 

IKVM.NET it is not a pdf renderer. So what it is? From the project site:

 

IKVM.NET is an implementation of Java for Mono and the Microsoft .NET Framework. It includes the following components:

      • a Java Virtual Machine implemented in .NET

a .NET implementation of the Java class libraries

tools that enable Java and .NET interoperability

 

In the simplest form IKVM.NET allows to use a Java code library in the C# code and vice versa.

 

I tried to use an Apache FOP, the best I think open source pdf –> XSL-FO renderer written in Java from my project written in C# using an IKVM.NET and it work like a charm.

In the rest of the post I want to show, how to prepare a .NET *.dll class library from Apache FOP *.jar’s with IKVM.NET and generate a simple Hello world pdf document.

 

To start playing with IKVM.NET and Apache FOP we need to download their packages:

IKVM.NET

Apache FOP

and then unpack them.

 

From the FOP directory copy all the *.jar’s files from lib and build catalogs to some location, e.g. d:\fop.

Second step is to build the *.dll library from these files. On the console execute the following comand:

 

ikvmc –target:library –out:d:\fop\fop.dll –recurse:d:\fop

 

The ikvmc is located in the bin subdirectory where you unpacked the IKVM.NET. You must execute this command from this catalog, add this path to the global variable PATH or specify the full path to the bin subdirectory.

 

In no error occurred during this process, the fop.dll library should be created.

Right now we can create a simple project to test if we can create a pdf file.

 

So let’s create a simple console project application and add reference to the fop.dll and the IKVM dll’s: IKVM.OpenJDK.Core and IKVM.OpenJDK.XML.API.

 

Full code to generate a pdf file from XSL-FO template:

 

static void Main(string[] args)

        {

            //initialize the Apache FOP

            FopFactory fopFactory = FopFactory.newInstance();

 

            //in this stream we will get the generated pdf file

            OutputStream o = new DotNetOutputMemoryStream();

            try

            {

                Fop fop = fopFactory.newFop("application/pdf", o);

                TransformerFactory factory = TransformerFactory.newInstance();

                Transformer transformer = factory.newTransformer();

 

                //read the template from disc

                Source src = new StreamSource(new File("HelloWorld.fo"));

                Result res = new SAXResult(fop.getDefaultHandler());

                transformer.transform(src, res);

            }

            finally

            {

                o.close();

            }

            using (System.IO.FileStream fs = System.IO.File.Create("HelloWorld.pdf"))

            {

                //write from the .NET MemoryStream stream to disc the generated pdf file

                var data = ((DotNetOutputMemoryStream)o).Stream.GetBuffer();

                fs.Write(data, 0, data.Length);

            }

            Process.Start("HelloWorld.pdf");

            System.Console.ReadLine();

        }

 

Apache FOP be default using a Java’s Xalan to work with XML files. I didn’t find a way to replace this piece of code with equivalent from .NET standard library.

If any error or warning will occure during generating the pdf file, on the console will ge shown, that’s why I inserted the last line in the sample above.

The DotNetOutputMemoryStream this is my wrapper for the Java OutputStream. I have created it to have the possibility to exchange data between the .NET <-> Java objects. It’s implementation:

 

class DotNetOutputMemoryStream : OutputStream

    {

        private System.IO.MemoryStream ms = new System.IO.MemoryStream();

        public System.IO.MemoryStream Stream

        {

            get

            {

                return ms;

            }

        }

        public override void write(int i)

        {

            ms.WriteByte((byte)i);

        }

        public override void write(byte[] b, int off, int len)

        {

            ms.Write(b, off, len);

        }

        public override void write(byte[] b)

        {

            ms.Write(b, 0, b.Length);

        }

        public override void close()

        {

            ms.Close();

        }

        public override void flush()

        {

            ms.Flush();

        }

    }


The last thing we need, this is the HelloWorld.fo template.

 

<?xml version="1.0" encoding="utf-8"?>

<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <fo:layout-master-set>

    <fo:simple-page-master master-name="simple"

                  page-height="29.7cm"

                  page-width="21cm"

                  margin-top="1.8cm"

                  margin-bottom="0.8cm"

                  margin-left="1.6cm"

                  margin-right="1.2cm">

      <fo:region-body margin-top="3cm"/>

      <fo:region-before extent="3cm"/>

      <fo:region-after extent="1.5cm"/>

    </fo:simple-page-master>

  </fo:layout-master-set>

  <fo:page-sequence master-reference="simple">

    <fo:flow flow-name="xsl-region-body">

      <fo:block font-size="18pt" color="black" text-align="center">

        Hello, World!

      </fo:block>

    </fo:flow>

  </fo:page-sequence>

</fo:root>

 

I’m not going to explain how how this template is created, because this will be covered in the near future posts.

 

Generated pdf file should look that:

helloworld



I think in may I was creating a little filters module based on Full Text-Search. I have configured my dev machine, the same for two testing servers – in our company for internal testing before we deployed it to client, and then on the testing client server. Until last week this build  was still on the testing server and finally we got feedback that we can deploy it on the production one.

I only say that, I lost half a day because I had not correctly remembered what I was doing to configure the FTS on the previous servers and I had no notes for that. I foolishly believed in my memory. Lesson learned.

 

For future reference a bunch of steps to configure the FTS for searching in *.pdf and *.docx files (and by the way in other Office files like *.xlsx).

 

1. From the page (link) download and install the *.pdf IFilter for FTS.

2. To the PATH global system variable add path to the catalog, where you installed the plugin. Default for this version is: C:\Program Files\Adobe\Adobe PDF iFilter 9 for 64-bit platforms\bin

3. From the page (link) download a FilterPackx64.exe and install it.

4. Now from SSMS execute the following procedures:

-sp_fulltext_service 'load_os_resources',1

-sp_fulltext_service 'verify_signature', 0

5. Restart the server

6. Now we must check if the plugins are visible:

-select document_type, path from sys.fulltext_document_types where document_type = '.pdf'

-select document_type, path from sys.fulltext_document_types where document_type = '.docx'

7. If we see a result, then we can assume that everything is ok*.

8. Right now we can create a catalog for FTS and indexes on appropriate columns.

 

 

*I lost a lot of hours to find out, why the plugin for the *.pdf files wasn’t indexed any file in the database, but in the sys.fulltext_document_types table there was available a line for this plugin. After the deeper investigation I found that the *.pdf files actually were indexed. At least the EOF sign was added to the indexes and nothing more for each file. In the end the problem was that, I forgot to add the /bin in the path to the plugin in PATH variable..



My client have departments in Europe Central and East, so there is highly possibility that in the generated pdfs there will be at least in the people names and/or surnames some specific characters for the country language.

 

With the XSL-FO we can use some out-of-the box fonts, e.g. the default is Times. We can change it for specific block of text or the entire document to other like Helvetica or Arial. All will be good to the moment that we use only an english alphabet. If we want to add e.g. some characters from polish or bulgarian language, in the *.fo file:

 

      <fo:block >

                <fo:inline font-weight="bold">english: </fo:inline>

                <fo:inline font-weight="bold">yellow</fo:inline>

      </fo:block>

      <fo:block>

                <fo:inline font-weight="bold">polish: </fo:inline>

                <fo:inline font-weight="bold">zólty</fo:inline>

      </fo:block>

      <fo:block>

                <fo:inline font-weight="bold">russian: </fo:inline>

                <fo:inline font-weight="bold">??????</fo:inline>

      </fo:block>

      <fo:block>

                <fo:inline font-weight="bold">bulgarian: </fo:inline>

                <fo:inline font-weight="bold">????</fo:inline>

      </fo:block>

      <fo:block>

                <fo:inline font-weight="bold">english: </fo:inline>

                <fo:inline font-weight="bold">yellow</fo:inline>

      </fo:block>

      <fo:block>

                <fo:inline font-weight="bold">polish: </fo:inline>

                <fo:inline font-weight="bold"  font-family="Arial">zólty</fo:inline>

      </fo:block>

      <fo:block>

                <fo:inline font-weight="bold">russian: </fo:inline>

                <fo:inline font-weight="bold" font-family="Arial">??????</fo:inline>

      </fo:block>

      <fo:block>

                <fo:inline font-weight="bold">bulgarian: </fo:inline>

                <fo:inline font-weight="bold" font-family="Arial">????</fo:inline>

      </fo:block>

 

The result can be diffrent from the expected depending on the selected font, e.g:

 

yellow

 

 

 

 

 

 

 



As you can see Timer nor Arial work in this case.

 

The problem here is not related to XSL-FO, but rather to the renderer we are using.

I have lost a lot of time to find a solution for the using by me XSL-FO –> PDF rendered to acquire these characters in my generated files. Fortunatelly all what have to be done it is to embed the font (or part of it) in the file(s) during rendering.

 

The renderer that I’m using it is an open source FO.NET.

 

For this one, the code to generate a pdf file looks that:

 

var fonet =  Fonet.FonetDriver.Make();

fonet.Render("source.fo", "result.pdf");

 

To emded the font in the pdf, we need to set the appropriate option to the driver:

 

fonet.Options = new Fonet.Render.Pdf.PdfRendererOptions()

{

      FontType = Fonet.Render.Pdf.FontType.Embed

};


Right now, the pdf we get should look like this:

 

work

 

 

 

 

 

 



As you can see, the result for the Arial font looks exactly how it should, because this font has a characters included not only for the english language like the default Times, which we shouls avoid if we not generating a english-only documents.

 

This is worth to notice that in this situation the generated pdf file is quite large, it has more than 400 kb in size. This is of course because of embedding the entire font in it to make the document portable to systems, where the used font is not present.

Instead on embedding the entire font, we can only embed the subset of used characters by changing the options to:

 

fonet.Options = new Fonet.Render.Pdf.PdfRendererOptions()

{

      FontType = Fonet.Render.Pdf.FontType.Subset

};

 

Right now, this specific pdf is only 12 kb in size.



I'm playing lately with an XSL-FO for generating a pdf documents.
XSL-FO has a long list of available tags and attributes, which for a new guy who want to create a simple document is a nightmare to find a proper one.
Fortunatelly we can set an schema for XSL-FO, so will result in acquire a full intellisense in VS.


For a simple *.fo file, we can set the path to the schema directly in file:

<?xml version="1.0" encoding="utf-8"?>

<fo:root

      xmlns:fo="http://www.w3.org/1999/XSL/Format"

      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xsi:schemaLocation="

http://www.w3.org/1999/XSL/Format

http://www.xmlblueprint.com/documents/fop.xsd">

...

 

We can of course use the build in VS XML Schemas selector. To use it, we must copy the schema file to the Schemas catalog (defaut path for VS2012 is C:\Program Files (x86)\Microsoft Visual Studio 11.0\Xml\Schemas).

Then we can go to Properties of the opened xml/xslt file and set the new added schema to file:

 

schema

 

 

 

 

 

 

 


From now, we should have an enable intellisense as shown below:

xsl-intellisense






.

 



Sometimes we want to make a changes to the database or create a backup, but the users must be disconnected for this time. The easiest way is to set the database to the offline state:
ALTER DATABASE dbname SET OffLine WITH ROLLBACK IMMEDIATE
and back
ALTER DATABASE dbname SET OnLine WITH ROLLBACK IMMEDIATE

The alternative is to set the Restrict Access to the Restricted User in the database Properties/Options.


In my previous post I have shown how we can use a C# function in the XSLT. Now I want to go one step further and use in the transformation the ASP.NET controls along with their events.

 

Let's start from the sample xml data:

<files>
   <file Id="F58C2962-AC0D-4C55-80A8-79A724669F53" Name="file 1" Path="D:\Temp\" Extension="iso"/>
   <file Id="FDC1358E-D9C8-4A70-ABE0-E0EF5E742E08" Name="file 2" Path="D:\Temp\" Extension="jpg"/>
</files>

I want to create a table from it and for each row I want to have a link which opens the Windows Explorer with this file selected. I have an ASP.NET Development Server so it will work. XSL for this xml looks as follow:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
                xmlns:StringExtensions="urn:StringExtensions" xmlns:asp="remove"
>
  <xsl:output method="html" indent="yes"/>

  <xsl:template match="/">
    <table>
      <tr>
        <th>File name</th>
        <th>Path</th>
        <th>Extension</th>
      </tr>
      <xsl:for-each select ="files/file">
        <tr>
          <td>
            <xsl:value-of select ="@Name"/>
          </td>
          <td>
            <xsl:value-of select ="@Path"/>
          </td>
          <td>
            <xsl:value-of select ="@Extension"/>
          </td>
          <td>
            <asp:LinkButton runat="server" ID="{StringExtensions:Replace(@Id,'-','')}">Open</asp:LinkButton>
          </td>
        </tr>
      </xsl:for-each>
    </table>
  </xsl:template>
</xsl:stylesheet>

I have used here a custom function to replace some characters. How to create that function is explained in my previous post. Link is in the first line of this post.

We can't just put an ASP.NET control in the stylesheet, because we will get an exception: System.Xml.XmlException: „asp” is not a declared prefix. To workaround it we must put in the stylesheet declaration new attribute: xmlns:asp="remove".

 

When we transform the xml data with this styleshet, next step is to create an ASP.NET control from html we got:

        private Control CreateControl(string html)
        {
            html.Replace("xmlns:asp=\"remove\"", "");
            return Page.ParseControl(html);
        }

Now we can add this control to the placeholder control on our page.

The last what we have to do is to bind the click event to ours LinkButton's controls:

        private void BindEvents()
        {
            var r = PlaceHolder1.FlattenChildren().OfType<LinkButton>().ToList();
            r.ForEach(x =>
            {
                x.Click += LinkButton1_Click;
            });
        }

 

The FlattenChildren is an extension method which I found somewhere. It returns all the child control:

        public static IEnumerable<Control> FlattenChildren(this Control control)
        {
            var children = control.Controls.Cast<Control>();
            return children.SelectMany(c => FlattenChildren(c)).Concat(children);
        }

Sample event can look like this:

        protected void LinkButton1_Click(object sender, EventArgs e)
        {
            var elem = ((LinkButton)sender).ID;
            Guid fileId = Guid.Parse(elem);

            File selectedFile = DBSearchService.GetFileWithPathAndExtension(fileId);
            Process.Start(new ProcessStartInfo("explorer", String.Format("/select, {0}\\{1}.{2}",
                selectedFile.Path.Path,
                selectedFile.Name, selectedFile.Extension.Name)));
        }


Using Xslt along with XPath we can transform any xml document in the way we want and use it e.g. to create the html page. At this time, we have Xslt 2.0 and XPath 2, but unfortunatelly .Net Framework doesn't support them. We can only use Xslt 1.

We have two ways to work with Xslt 2. One is to use a 3rd party library, e.g. XQSharp or Saxon. The second option is to manually implement the missed in Xslt 1 functions. How to do it I want to show in this post.

 

First is the sample code. The goal is to create a table from this xml data:

<?xml version="1.0" encoding="utf-8"  ?>
<books>
  <book isAvailable="true" averageUsersRating="4.0">
    <title>First title</title>
    <author>First book author</author>
  </book>
  <book isAvailable="true" averageUsersRating="1.2">
    <title>Second title</title>
    <author>Second book author</author>
  </book>
  <book isAvailable="false" averageUsersRating="5.0">
    <title>Third title</title>
    <author>Third book author</author>
  </book>
</books>

The books which are currently not available must be placed in red table row. And the books titles which have rating greater that 4 must be written in upper case.

Xsl to transform this data to html table looks as follow

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
  <xsl:output method="html" indent="yes"/>

  <xsl:template match="/">
    <html>
      <head></head>
      <body>
        <table>
          <tr>
            <th>Title</th>
            <th>Author</th>
            <th>Average rating</th>
          </tr>

          <xsl:for-each select="books/book">
            <xsl:choose>
              <xsl:when test="@isAvailable='true'">
                <tr class="bookIsAvailable">
                  <xsl:call-template name="bookRowTemplate"/>
                </tr>
              </xsl:when>
              <xsl:otherwise>
                <tr class="bookIsNotAvailable">
                  <xsl:call-template name="bookRowTemplate"/>
                </tr>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>

        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template name="bookRowTemplate" >
    <td>
      <xsl:choose>
        <xsl:when test="@averageUsersRating &gt; 4">
          <!-- return title in upper case -->
        </xsl:when>
        <xsl:otherwise>
           <xsl:value-of select ="./title"/>
        </xsl:otherwise>
      </xsl:choose>        
    </td>
    <td>
      <xsl:value-of select ="./author"/>
    </td>
    <td>
      <xsl:value-of select ="@averageUsersRating"/>
    </td>
  </xsl:template>
</xsl:stylesheet>

This stylesheet is I think self-explanatory. Colouring the appropriate table rows is easy epending on the value of isAvailable attribute.

The second requirement is to write the appropriate books titles in upper case. Xslt/XPath doesn't have (or I don't know) function for that, so we must write it manually.

In C# function this is easy to write:

    public class XslStringExtensions
    {
        public string ConvertToUpperCase(string source)
        {
            return source.ToUpper(System.Globalization.CultureInfo.InvariantCulture);
        }
    }

But is it possible to use this C# method in the stylesheet? The answer is yes.

First we must add to the stylesheet new attribute: xmlns:StringExtensions="urn:StringExtensions". Now we can write in the xsl the code for executing this method:

        <xsl:when test="@averageUsersRating &gt; 4">
          <xsl:value-of select ="StringExtensions:ConvertToUpperCase(./title)"/>
        </xsl:when>

At the end, we must configure the XslCompiledTransform to use the custom C# function. To do that, we must create list with arguments:

           XsltArgumentList args = new XsltArgumentList();
           args.AddExtensionObject("urn:StringExtensions", new XslStringExtensions());'

And that list we must pass to the XslCompiledTransform.Transform object, e.g.

            XslCompiledTransform transform = new XslCompiledTransform();
            transform.Load(xslTemplate);
            XmlDocument doc=loadedXmlDoc;
            MemoryStream returnedHtml = new MemoryStream();
            transform.Transform(doc.CreateNavigator(), args, returnedHtml);


NHibernate has many ways to create and executes queries. With one of them* - the Named Queries we can execute our manually created stored procedures. This example is dedicated to SQL Server. This is important to notice, because this code is database specified. That means, for each database the query can look diffrent. E.g. in SQL Server we call the stored procedure by using the syntax: exec <procedure>. In Oracle it is: call <procedure>, so we must remember it.

 

Let's assume we have a simple stored procedure in our database which takes two arguments:

create procedure FindFilesWithNameLike(@pattern nvarchar(max), @extension nvarchar(10)) as

select f.Id,f.Name,f.[Path],f.Size,f.Extension from [File] f
join Extension e on f.Extension=e.Id 
where f.Name 
like '%'+@pattern +'%'
and e.Name=@extension

 

To use it with NHibernate, we must create a mapping. This is done in the *.hbm.xml file (I didn't find if this can be done with Fluent NHibernate and/or mapping by code).

 

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <sql-query name="FindFilesWithNameLike"  callable="true" >
    <query-param name="pattern" type="System.String"/>
    <query-param name="extension" type="System.String"/>

    <return-scalar column ="Id" type ="System.Guid"/>
    <return-scalar column ="Name" type ="System.String"/>
    <return-scalar column ="Size" type ="System.Int64"/>
    <return-scalar column ="Extension" type ="System.String"/>
    <return-scalar column ="Path" type ="System.String"/>

    exec FindFilesWithNameLike @pattern=:pattern, @extension=:extension
  </sql-query>
</hibernate-mapping>

As you can see - nothing special. We are using the query-param to define the procedure parameters and return-scalars to define, what columns (with types) are returned. At the end there is our database-specified code to execute the stored procedure with parameters.

 

When we have a mapping, we can now execute this query:

        public IEnumerable<File> Execute(string pattern, string extension)
        {
            return session.GetNamedQuery("FindFilesWithNameLike")
                  .SetParameter("pattern", pattern)
                  .SetParameter("extension", extension)
                  .SetResultTransformer(new ResultToFileTransformer())
                  .List<File>();
        }

To do this, we are using the api for named queries. When we execute this query we will have a list of list of objects. Thats why I added the SetResultTransformer. When the database table and custom entity is mapped one to one, we can use the Transformers.AliasToBean<T>, otherwise we can create custom transformer. In this example it is the ResultToFileTransformer. That class must implement the IResultTransformer interface.

*probably with using the native sql support in the NHibernate this it possible either, but I didn't checked



I just wanted to return from sql server data as xml. This is very easy to do, because sql server has a build-in suppot for that.

select Id, Name from Extensions FOR XML RAW('Extension'), ROOT('Extensions')

But using this query, the returned column has name like XML_F52E2B61-18A1-11d1-B105-00805F49916B

It's been some time when I manually created a sql query, so I lost few minutes to think, how to alias this column using the AS keyword.

Correct query looks as follows

select (select Id,Name  from Extensions  FOR XML RAW('Extension') , ROOT('Extensions')) as alias

 

I see I just need to remember some basics about sql/tsql.. Too much NHibernate I see.