Click here to Skip to main content
15,881,380 members
Articles / Programming Languages / XSLT

Creating PDF Documents from XML

Rate me:
Please Sign up or sign in to vote.
5.00/5 (25 votes)
16 Aug 2013CPOL3 min read 237K   5.7K   57   43
Creating PDF documents from XML

Introduction

This article is about using XML/XSL-FO and the open source library FO.NET to generate PDF documents by mixing input parameters, images and static localization strings. I think that this kind of approach is ideal to automate the process of generating invoices or simple dynamic documents/reports related to web service/server pages input parameters.

Background

The following image describes the idea behind this solution:

Image 1

The user submits its request to the web service. The request is serialized to XML and merged with a static localization XML. The result is passed through an XSLT sheet and the output is an XSL-FO document. XSL-FO (http://www.w3schools.com/xslfo/) is a language for formatting XML data for output to screen, paper or other media. It can be parsed by FO.NET (http://fonet.codeplex.com/) in order to produce PDF documents.

Architecture Overview

The following UML diagram represents the relationships between the most important classes in the solution.

Image 2

A short description of each class and of their responsibilities:

  • IPdfPrintableDocument: represents a basic interface for a PDF printable document
  • Document: represents a really simple printable document
  • XmlTransformationManager: gets the printable document instance and merges it with the related XML culture file. The output is a new XML file that is passed through the related XSLT file. The final output is an XSL-FO document that can be parsed by FO.NET.
  • PdfPrinterDriver: handles the PDF generation process with the help of the FonetDriver class.
  • ResourceManager: it is responsible to access the localization and transformation files. It makes use of the CacheUtility class to store retrieved files in cache in order to speed up the execution of subsequent requests
  • CacheUtility: a simple wrapper for System.Web.Caching.Cache class

Settings

XML
<pdfPrinterConfiguration>
    <settings
    xsltFolderPath="~/App_Data/Resources/XSLT/"
    localizationXmlFolderPath="~/App_Data/Resources/XML/"
    defaultCulture="en-US"
    defaultDateFormat="MM/dd/yyyy"
    pdfOutputFolder="~/GeneratedDocuments/"
      />
</pdfPrinterConfiguration>  

In the web.config, there is a little configuration section:

  • xsltFolderPath: tells the application where to find the XSLT files
  • localizationXmlFolderPath: tells the application where to find the localization files
  • defaultCulture: default application culture
  • defaultDateFormat: DateTime format
  • pdfOutputFolder: tells the application where to save PDF files.

Creating a Simple WCF Service

I decided to test the PDF printer core with a simple WCF service. First of all, let's create an IPrintableDocument class:

XML
[Serializable, DataContract(Namespace = "http://Schemas/PdfPrinter/Common")]
public class Document : IPrintableDocument
{
    [DataMember(IsRequired = true)]
    public string Description;
 
    public string ToXml()
    {
        return ObjectXmlSerializer.SerializeObjectToXmlFormattedString(this);
    }
}  

Then, let's write out simple web service with request and response:

XML
[ServiceBehavior(Namespace = "http://Schemas/PdfPrinter/WebServices")]
public class PdfPrinterService : IPdfPrinter
{
    public PdfPrinterResponse Print(PdfPrinterRequest request)
    {
        return PdfPrinterFacade.PrintPdf(request);
    }
} 
XML
[MessageContract]
public class PdfPrinterResponse
{
    [MessageHeader]
    public string Info;
 
    [MessageBodyMember]
    public string Message;        
} 
XML
[MessageContract]
public class PdfPrinterRequest
{
    private Document _document;
 
    [MessageBodyMember]
    public Document Document
    {
        get { return _document; }
        set { _document = value; }
    }        
}   

Finally, let's implement our XSLT and localization XML files and name them as Document class.

I put Document.xslt under "~/App_Data/Resources/XSLT/" folder. This is a simple XSLT that shows some powerful features of XSL-FO. Then, I wrote a simple localization XML file Document.xml and I put it under "~/App_Data/Resources/XML/" folder.

Running the Code

Let's invoke our web service with Document.Description = "Hello".

All input data is merged within an XML document that looks like this:

XML
<PdfPrinter>
  <Document>
    <Description>Hello</Description>
  </Document>
  <culture language="en-US">
 
    <label id="Footer" text="This documents has been auto-generated with Pdf Printer Service"/>
    <label id="Page" text="Page " />
    <label id="Of" text=" of " />
 
    <label id="Message" text="You sent the following message:" />
    <label id="EmptyMessage" text="- Empty message -" />
 
    <label id="Date_Header_1" text="Date" />
    <label id="LongWord_Header_2" text="Very Long Word" />
    <label id="Decimal_Header_3" text="Decimal" />
    <label id="Integer_Header_4" text="Integer" />
    <label id="Total" text="Total" />
 
    <label id="Date_Field_1" text="11/02/1984" />
    <label id="LongWord_Field_2" text="ThisIsAVeryVeryLongBreakedWord" />
    <label id="Decimal_Field_3" text="1234.5678" />
    <label id="Integer_Field_4" text="3" />
 
  </culture>
</PdfPrinter>

Then the XSLT file produces the XSL-FO document by querying previous XML content. It makes use of a helper extension class (PdfPrinter.Core.Common.XsltExtensionService) which simplifies some common operations.

XML
. . .
<xsl:variable name="logo" 
    select="utilityExtension:MapPath('~/App_Data/Resources/IMAGES/logo.jpg')"/>
<fo:block>
   <fo:block>
   <fo:external-graphic src="url('{$logo}')" 
        content-width="auto" content-height="auto"/>
   </fo:block>
   <fo:block padding-top="2pt">
   <fo:block text-align="left" font-size="16pt">
   <fo:inline font-weight="bold">
   <xsl:value-of select="/PdfPrinter/culture/label[@id='Message']/@text"/>
   </fo:inline>
   </fo:block>
   <fo:block text-align="left" padding-top="2pt" font-size="16pt">
   <xsl:choose>
   <xsl:when test="/PdfPrinter/Document/Description != ''">
   <xsl:value-of select="/PdfPrinter/Document/Description"/>
   </xsl:when>
   <xsl:otherwise>
   <xsl:value-of select="/PdfPrinter/culture/label[@id='EmptyMessage']/@text"/>
   </xsl:otherwise>
   </xsl:choose>
   </fo:block>
   </fo:block>
</fo:block>
. . . 

The final result is parsed by FO.NET and the output looks really great!

Image 3

Conclusions

The benefit of generating PDF documents with this approach is that the developer doesn't have to know any particular API and can modify at runtime the document layout just by changing some XSLT queries or adding new XSL-FO statements.

I hope that my work will be helpful and enjoyable to use!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Italy Italy
Take a look at my professional profile and become a part of my network on LinkedIn. http://lnkd.in/YcYs3R

Comments and Discussions

 
AnswerRe: Source code doesn't generate PDF Pin
Marco Merola1-Nov-13 5:41
Marco Merola1-Nov-13 5:41 
GeneralMy vote of 5 Pin
Burak Ozdiken1-Aug-13 19:16
Burak Ozdiken1-Aug-13 19:16 
GeneralMy vote of 3 Pin
MacSpudster1-Aug-13 11:38
professionalMacSpudster1-Aug-13 11:38 
GeneralRe: My vote of 3 Pin
Marco Merola1-Aug-13 12:16
Marco Merola1-Aug-13 12:16 
GeneralRe: My vote of 3 Pin
MacSpudster5-Aug-13 6:11
professionalMacSpudster5-Aug-13 6:11 
GeneralMessage Closed Pin
5-Aug-13 10:23
Marco Merola5-Aug-13 10:23 
GeneralRe: My vote of 3 Pin
MacSpudster6-Aug-13 11:34
professionalMacSpudster6-Aug-13 11:34 
GeneralRe: My vote of 3 Pin
MacSpudster6-Aug-13 12:10
professionalMacSpudster6-Aug-13 12:10 
In regards to taking a PDF file and filling in the AcroFields, there is reasonable example code when Google'd.

The thing, is my code is exceedingly nested deep into both "public" code (that is not unlike what is on the net) and "proprietary" code. I'd have to extrapolate a 'working' example of just public code from all of the code to provide an example; which, after reviewing the code and knowing it'd take some doing, I won't be providing a complete example.

Below is the function I use to set an AcroField. Note that I pass a reference for the AcroField, which is obtained from a PDFStamper. I use a class called PDFDocItem which has both a PDFReader and PDFStamper, and other attributes, such as SourceFilePath, SourceFileName, and DestinationFullPath, as shown below

C#
//create the PdfReader & PdfStamper
myPDFDocItem.Reader = new iTextSharp.text.pdf.PdfReader(myPDFDocItem.SourceFilePath + myPDFDocItem.SourceFileName);
myPDFDocItem.Stamper = new iTextSharp.text.pdf.PdfStamper(myPDFDocItem.Reader, new System.IO.FileStream(myPDFDocItem.DestinationFullPath, System.IO.FileMode.Create));

//Get the AcroFields
iTextSharp.text.pdf.AcroFields tFields = myPDFDocItem.Stamper.AcroFields;

//call SetAcroField; an example:
SetAcroField(ref tFields, "FirstName", "Barry")

/*
SetAcroField needs to be called for each AcroField needing to be set.
Now, remember, the AcroField name has to be the same in both the PDF's acrofield, of which you'll need to use a PDF Editor like Acrobat Professional, and in your code.
I use a for loop to go through a table of pre-defined fields & corresponding data.
*/


protected void SetAcroField(ref iTextSharp.text.pdf.AcroFields tFields, string pKeyAcroField, string pValueForAcroField)
{
	try //try #a
	{
		pValue = pValue.Replace(kNewLineMarker, Environment.NewLine);
		if (tFields.Fields.ContainsKey(pKeyAcroField)) { tFields.SetField(pKeyAcroField, pValueForAcroField); }
	}// try #a
	catch (Exception ex)
	{
		string m = ex.Message;
		//insert errorHandling here
	}// try #a

}//void SetAcroField(ref iTextSharp.text.pdf.AcroFields tFields, string pKeyAcroField, string pValueForAcroField)


Certainly, there is allot more going on here, such as first getting the values (of a key) in a table populated before calling SetAcroField.
GeneralRe: My vote of 3 Pin
Marco Merola7-Aug-13 4:05
Marco Merola7-Aug-13 4:05 
GeneralRe: My vote of 3 Pin
MacSpudster7-Aug-13 8:55
professionalMacSpudster7-Aug-13 8:55 
GeneralRe: My vote of 3 Pin
Marco Merola7-Aug-13 10:18
Marco Merola7-Aug-13 10:18 
QuestionRe: My vote of 3 Pin
stixoffire4-Nov-14 10:59
stixoffire4-Nov-14 10:59 
QuestionSource code? Pin
Prabu ram30-Jul-13 20:18
Prabu ram30-Jul-13 20:18 
AnswerRe: Source code? Pin
Marco Merola1-Aug-13 12:29
Marco Merola1-Aug-13 12:29 
GeneralRe: Source code? Pin
Prabu ram1-Aug-13 19:54
Prabu ram1-Aug-13 19:54 
GeneralMy vote of 5 Pin
Burak Ozdiken30-Jul-13 20:05
Burak Ozdiken30-Jul-13 20:05 
GeneralRe: My vote of 5 Pin
Marco Merola31-Jul-13 23:10
Marco Merola31-Jul-13 23:10 
GeneralRe: My vote of 5 Pin
Burak Ozdiken1-Aug-13 19:14
Burak Ozdiken1-Aug-13 19:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.