XMLMill - convert xml to pdf with java. Generate PDF from xml/xsl.

XMLMill for Domino: UseCases

Version: 2.80 Date: November 4th, 2006
This tutorial is opened in a separate window in order to maximize the legibility of this tutorial.
To return to XMLMill, close this browser window

This page as PDFPrinter friendly pageThis guide (!) as PDF

Use case 1: On-the-fly creation of a PDF from a Web- or Notes-client (R6/R7 only)

Introduction

In this first use case we want to create a PDF starting from a quotation-document we are viewing in a web browser or in the Notes-client. So, we will start the PDF-generation from within a quote document. The quote consists of 4 main parts:

  • Customer information such as company and contact name and address information.
  • Maximum 5 producttables with product descriptions, quantities asked, unitprices, subtotals and grandtotals.
  • Text paragraphs including currency, payment and delivery conditions, paragraphs which should be inserted between the product tables.
  • Actors defining the signers of the quote, their jobtitle and the filedescription of their scanned signatures.

The quote document we will use to generate a PDF is shown in the picture below.

images/duquotesdocext.jpg
Select to enlarge

On the quotation document there are 5 links with slightly different functionality:

  1. View PDF: The PDF is generated and SHOWN to the user. The PDF is NOT SAVED on the filesystem.
  2. Save PDF: The PDF is generated and SAVED on the filesystem. The PDF is NOT SHOWN to the user.
  3. View and Save PDF: The PDF is generated, SAVED on the filesystem and SHOWN to the user.
  4. Save and mail PDF: The PDF is generated, SAVED on the filesystem and SENT to an email address. The PDF is NOT SHOWN to the user.
  5. View, Save and mail PDF: The PDF is generated, SAVED on the filesystem, SHOWN to the user and SENT to an email address.

This PDF-generation process involves following steps:

  1. From the quote we call a servlet (this servlet is a very generic part of the XMLMILLR6-tool and doesn't need to be altered by the Domino-programmer).
  2. The servlet creates a new empty document in the current database. This document will be the container of the XML-structure, a pointer to the associated XSLT-file and if required it will also contain the filename if the PDF has to be saved, a confirmation message to the user and the name of an agent that has to be run immediately after the PDF has been generated (if required). In the next step, these fields are filled.
  3. The servlet calls a notes-agent eg. the agent (generatequote), a script agent in the current database, mainly responsible for building the XML-structure. The programmer can modify this agent or create a totally new agent fit to his/her own needs. Apart from building the XML-structure, the agent defines the name of the XSLT-file that has to be combined with the XML-file. The programmer can modify the XSLT-file or make one of his own. The XSLT-file resides on the domino-server file system. Depending on the required action (View, Save, Mail....PDF), the agent may also define some other parameters: if the PDF has to be saved on the filesystem, the agent defines the PDF-filename (see agents (GenerateQuoteS) and (GenerateQuoteSE) for an example). If the PDF must not be shown to the user, a confirmation is sent to the user when the generation of the PDF is completed. The content of the response we show to the user can also be defined in the agent (see agents (GenerateQuoteS) and (GenerateQuoteSE) for an example). Finally, if another agent must be run after the generation of the PDF (eg. to mail the PDF), the agent must also define the name of the agent that has to be run after the PDF-generation (see agent (GenerateQuoteSE) for an example).
  4. The servlet then uses the XML-structure built by the (generatequote)/(generatequoteS)/(generatequoteSE)) agent and the specified XSLT-file to generate the PDF.
  5. Depending on the required action, the PDF-file is presented to the user and/or saved on the filesystem and/or mailed to an email-address.

This architecture is summarized in the picture below:

images/dugeneralarchitecture.jpg
Select to enlarge

In order to try out this use case you need following building blocks:

  1. A trial or licensed version of XMLMill for Domino
  2. The Domino Database "XMLMill Domino Usecases" (UseCases.nsf), which can be found in the download of XMLMill for Domino.
  3. The XMLMILLR6-servlet installed and configured on a Domino R6/R7-server.
  4. The Notes-agents (GenerateQuote), (GenerateQuoteS) and (GenerateQuoteSE) which can be found in the UseCases.nsf
  5. The XSLT-stylesheet duQuotesdemo.xsl

The result of this use case will be a PDF consisting of an introduction page and 5 producttables on the next 2 pages.

Introduction page:

images/duquotespdf1.jpg
Select to enlarge

Producttables:

images/duquotespdf2.jpg
Select to enlarge

A trial or licensed version of XMLMill for Domino

To install XMLMill for Domino, please go to the download page of the XMLMill website and choose the appropriate download distribution (.zip or .tar.gz format)

Since this is a use case that explains the use of XMLMIll in a Domino R6/R7-environment, you should follow the installation and configuration instructions that are explained in the installation_guide_r6.pdf in order to be able to try out this use case. Refer to the subdirectory domino in the download to find the installation_guide_r6.pdf.

The Domino Database XMLMill Domino Usecases

Please refer to the subdirectory domino in the download to find the database Usecases.nsf. Make sure you install this database on your Domino R6/R7-server if you want to try out this use case and that you have carried out all installation and configuration instructions that are described in the installation_guide_r6.pdf.

Note that you have to modify the formula behind the View, Save, Mail PDF-buttons on the Quote-form. You should alter the name of the server and the portnumber.

The XMLMILLR6-servlet installed and configured on a Domino R6/R7-server.

Please refer to the installation_guide_r6.pdf in the download for a full explanation about the installation of the servlet in your Domino R6/R7-environment.

As explained before, there are 5 buttons on the Quote document, with which you can choose whether you want to show, save, mail the PDF or combine the options. When you click on the View PDF-button on the Quotes-document the PDF-generation process is started by invoking the XMLMILLR6-servlet. The link contains following formula:

[001] 
[002] db:=@ReplaceSubstring(@Subset(@DbName;-1);"\\";"/");
[003] docid:=@Text(@DocumentUniqueID);
[004] agid:="(GenerateQuote)";
[005] @URLOpen("http://lotus_notes:81/servlet/xmlmillr6?dbid="+db+"&docid="+docid
       + "&agid="+agid + 
       "&actionid=1")

The general format of the URL is:

[001] http://servername/servlet/servletname?parameter=parameter-value

In our example, our servlet is installed on the server lotus_notes listening to port 81, in the directory servlet. The servlet name is XMLMILLR6. We pass 4 parameters to the servlet: the name of the database, the documentuniqueid of the current quote-document, the name of the Notes-agent that will be used to create the XML-structure for the PDF-document later on and an actionid specifying the type of action that is required (View PDF and/or Save PDF and/or execute additional agent).

The XMLMILLR6 servlet consists of the compiled JAVA-classes of the XMLMill PDF generation tool together with an add-on which allows communication with Domino. The servlet accepts the 4 parameters (databasename, documentuniqueid of the quote-document, name of the Notes-agent that will create the XML-structure and action id) and connects to the Notes-database. It first checks the servlets.properties file to determine which Notes-user and password must be used for connecting to this particular database. If it can't find a user and password for the database, the servlet connects to the Notes-database with the access rights of the server. (Please refer to the installation_guide_r6.pdf in the download for more information about the servlets.properties-file).

The servlet creates a new document in the current database based on the XML-form. This is a simple form containing 6 fields:

  • a Text field (DocRef) which will contain the documentuniqueid of the quote document.
  • a Text field (Store) which contains the filepath and filename of the PDF if it has to be saved on the filesystem.
  • a Text field (Execute) which contains the name of an additional agent that has to be run after the generation of the PDF (if this is required).
  • a Text field (Response) which will contain a message telling the user that the PDF has been created. This is only filled when the PDF isn't shown to the user after creation.
  • a Rich-Text field (XML) which will contain the XML-structure.
  • a Text-field (XSL) which will contain the name of the XSLT-file, responsible for the layout of the PDF-document.

When the servlet creates the new XML-document it only adds the documentuniqueid of the quote document, and saves the new XML-document. Passing the documentuniqueid to and from the servlet is necessary for keeping track of the quote document inside and outside Notes. The XML-document created and saved is shown in the picture below:

images/duxmlempty.jpg
Select to enlarge

The servlet then passes control to Notes again by invoking the Notes-agent (GenerateQuote) and passing the noteid of the new XML-document.

The agent (GenerateQuote) is a Lotus Script agent that defines the XML-structure for the PDF-document and defines the name of the XSLT-file. The total XML-structure is saved in the XML-field on the XML-document (this document was created by the XMLMILLR6-servlet). The name of the XSLT-file will be written in the XSL-field. The content of both fields are further used by the XMLMILLR6-servlet to carry out the final transformation to the PDF, which is then shown to the user.

Up until now we explained what happens if a user presses the button View PDF. Exactly the same mechanism is applied for the other buttons. In the code behind the other buttons there are differences in the value of the action id and the agent that is called. Let's take a look at the button View, Save and Mail PDF. You find the code below:

[001] 
[002] db:=@ReplaceSubstring(@Subset(@DbName;-1);"\\";"/");
[003] docid:=@Text(@DocumentUniqueID);
[004] agid:="(GenerateQuoteSE)";
[005] @URLOpen("http://lotus_notes:81/servlet/xmlmillr6?dbid="+db+"&docid="+docid
       + "&agid="+agid + 
       "&actionid=7")

Note that the agent here is (GenerateQuoteSE) and the actionid is 7.

The value of the action id determines what will be done with the generated PDF: view, save, execute another action or a combination of these options. In our example the "execute another action" is an agent that mails the PDF. In your environment, it may be some other action. Below we provide the different possibilities and the according action ids:

Action Id

Action

1

View PDF

2

Save PDF

3

View and Save PDF

6

Save and Mail PDF

7

View, Save and Mail PDF

Depending on the required action, the base version of the (GenerateQuote) agent needs to be extended to fill some other parameters in the XML-document. If the PDF needs to be saved, we have to define a name for the PDF. This name may be static or it may be generated dynamically eg. depending on the quotation number. In any case our agent has to provide a name for the PDF. If we only show the PDF to the user, it doesn't need to be saved to the filesystem, so our agent doesn't have to provide a name in that case. If we want to mail the PDF after it has been generated, our agent has to specify the name of the agent responsible for the mail. If we don't want to show the PDF to the user after it has been created, our agent needs to specify a message informing the user, that the process has completed. Therefore we created 3 slightly different versions of the (GenerateQuote)-agent: the (GenerateQuoteS) and the (GenerateQuoteSE)-agents. They only differ in the amount op parameters they fill in the XML-document. Below we provide the different action ids and the according agent:

Action Id and Action

Agent

1 - View PDF

(GenerateQuote)

2 - Save PDF

(GenerateQuoteS)

3 - View and Save PDF

(GenerateQuoteS)

6 - Save and Mail PDF

(GenerateQuoteSE)

7 - View, Save and Mail PDF

(GenerateQuoteSE)

In the next section, we will explain the (GenerateQuote)-agent, since it is the base version for the other 2 agents two. So we assume the View PDF-button was pressed.

The agent (GenerateQuote)

You can find the agent (GenerateQuote) in the Domino-database Usecases.nsf . Note that this agent will run as webuser. We will repeat and explain the different parts of this agent below.

As we mentioned before the agent (GenerateQuote) is a Lotus Script agent that defines the XML-structure for the PDF-document and defines the name of the XSLT-file. The total XML-structure is saved in the XML-field on the XML-document (this document was created by the XMLMILLR6-servlet in the previous step). The name of the XSLT-file will be written in the XSL-field. The content of both fields are further used by the XMLMILLR6-servlet to carry out the final transformation to the PDF.

The agent consists of following parts:

  1. Initialization of different variables and objects.
  2. Get a handle of the quotes-document.
  3. Defining the name of the XSLT-file in the XML-document created by the servlet.
  4. Start creating the XML-structure.
  5. Creating the different XML-parts of the quote (customerinfo, producttables, signerinfo.
  6. XML-ending tags and saving the XML-document.

Initialization of different variables and objects

[001] Sub Initialize
[002] 	
[003] 	'1) Initializing
[004] 	
[005] 	Dim session As New notessession
[006] 	Dim db As notesdatabase
[007] 	Dim agent As NotesAgent
[008] 	Set db=session.currentdatabase
[009] 	Set agent = session.CurrentAgent
[010] 	Dim doc As notesdocument
[011] 	Dim xmldoc As notesdocument
[012] 	Dim contact As String
[013] 	Dim ntables As Integer
[014] 	Dim i As Integer
[015] 	Dim j As Integer
[016] 	Dim nroflines As Integer
[017] 	Dim countitem As notesitem
[018] 	Dim quantityitem As notesitem
[019] 	Dim prodcodeitem As notesitem
[020] 	Dim unitpriceitem As notesitem
[021] 	Dim totalpriceitem As notesitem
[022] 	Dim grandtotalpriceitem As notesitem
[023] 	Dim descriptionitem As notesitem
[024] 	Dim textitem As notesitem
[025] 	Dim grandtotalprice As String
[026] 	Dim totalprice As String
[027] 	Dim unitprice As String
[028] 	Dim rtitem As notesrichtextitem
[029] 	

First we initialize the variables and Notes-objects we will need in this script.

Get a handle of the quotes-document

[001]  '2) get a handle of the quotes document
[002] 	
[003] 	Set xmldoc = db.GetDocumentByID(agent.parameterdocid)
[004] 	Set doc=db.GetDocumentByUNID(xmldoc.docref(0))
[005] 	

The agent first gets a handle to the quotes-document. This required following steps:

  • When the servlet called the agent (GenerateQuote), it passed the note-idof the XML-document it created as parameter. So the agent knows the note-id of the XML-document.
  • The agent gets a handle of the XML-document by means of its Note-id.
  • In this XML-document the servlet saved the documentuniqueid of the quote we are dealing with in the docref-field. So, now the agent captures this documentuniqueid and re-establishes a handle to the quote by means of its documentuniqueid.

So, know the script has access to the quote-document and to the XML-document where the XML-structure will be written to.

Defining the name of the XSLT-file in the XML-document created by the servlet

[001] '3) defining the name of the XSLT-stylesheet to find the correct XSLT-file 
      later on 
[003] 	
[004]    xmldoc.xsl="duQuotesdemo.xsl"
[005] 
[006] 	

In the xsl-field in the XML-document, we write the name of the XSLT-stylesheet, which must be used for the quotation-PDF. The servlet will capture this name later on and search for this XSLT-file on the filesystem of the Domino-server. Refer to the installation_guide_r6.pdf to learn where the XSLT-stylesheet should be placed on the Domino-server-filesystem.

Start creating the XML-structure

Part 4 of the agent generates the XML-structure based on the quotes-document. The XML-structure will be written to the Richtext-item "XML". Then the XML-header element is defined, followed by the the opening elements <quotes> <quote>.

[001] 	
[002] '4) Start creating xml
[003] Set rtitem = New NotesRichTextItem( xmldoc , "XML" )				
[004] Call rtitem.AppendText( "<?xml version=""1.0"" encoding=""windows-1252"" 
      standalone=""no""?>" )
[006] Call rtitem.AddNewLine( 1 )
[007] rtitem.AppendText("<quotes>")
[008] Call rtitem.AddNewLine( 1 )
[009] rtitem.AppendText("  <quote>")
[010] Call rtitem.AddNewLine( 1 
      )

Creating the different XML-parts of the quote (customerinfo, producttables, signerinfo)

The XML-structure based on the quotes-document consists of following main tags:

[001] <quotes>
[002] 	<quote>
[003] 		<quotedata>
[004] 			<reference>	             </reference>
[005] 			....		
[006] 		</quotedata>	
[007] 		<customerdata>		
[008] 			<company>	     	</company>		
[009] 			...
[010] 		</customerdata>
[011] 		<table>		
[012] 			<lines>			
[013] 				<line>				
[014] 					<qty> 	</qty>				
[015] 					<description>	</description>	
[016] 					....
[017] 				</line>		
[018] 			</lines>		
[019] 			<grandtotal>  	</grandtotal>		
[020] 			<comments>	</comments>	
[021] 		</table>
[022] 		<signerdata>	
[023] 			<name>			
[024] 				<signer1> 	</signer1>			
[025] 				<signer2>	</signer2>		
[026] 			</name>		
[027] 			<jobtitle>			
[028] 				<signer1> 	</signer1>			
[029] 				<signer2>	</signer2>	
[030] 			</jobtitle>	
[031] 		</signerdata>              
[032] 	  </quote>
[033] </quotes>

The <quotes> element is the root. If there would be different quote-documents, the <quotes> element would contain the structure for ALL quote-documents. In this use case we only process 1 document so here the <quotes> element only contains the structure for 1 quote-document and is somewhat redundant.

The <quote> element holds the structure for 1 quote document. Below this tag you find the different parts of the quotation-document:

  1. The <quotedata> element holds information belonging to this particular quote such as referencenumber, deliveryconditions, payment terms.
  2. The <customerdata> element shows information concerning the quotation's customer such as companyname, address, contact person.
  3. The <table> element holds the different producttables. Each table contains a certain amount of lines and each line consists of fields such as quantity, description, price and subtotal. Besides different product lines a product table also contains a grandtotal and a comment or title on top of the table.
  4. The <signerdata> element holds information concerning one or two signers of the quotation: their name, jobtitle and signature. Note that the content of the signature-field is the filename of the jpg-files with the scanned signatures, residing on the Domino-Server file system (subdirectory images\).

Quotation info

The <quotedata> element contains the quotation information: reference number, creation date, source of the quotation, productfamily, currency, startdate and enddate of the contractperiod, deliverytime, deliveryconditions, paymentconditions. The value of each of the fields is captured and enclosed between the corresponding child elements of the <quotedata> element as you can see in the script code below:

[001] ' 5a) general quote information 
[002] 
[003] 	rtitem.AppendText("    <quotedata>")
[004] 	Call rtitem.AddNewLine( 1 )
[005] 	rtitem.AppendText("      <nbr>" + doc.reference(0) + "</nbr>")
[006] 	Call rtitem.AddNewLine( 1 )
[007] 	rtitem.AppendText("      <date>" + Format(doc.createdat(0), "dd/mm/yyyy") 
       +"</date>")
[009] 	Call rtitem.AddNewLine( 1 )	
[010] 	rtitem.AppendText("      <reference>" + doc.wreference(0)+ 
       "</reference>")
[012] 	Call rtitem.AddNewLine( 1 )	
[013] 	rtitem.AppendText("      <products>" + doc.wproducts(0)+ "</products>")
[014] 	Call rtitem.AddNewLine( 1 )	
[015] 	rtitem.AppendText("      <currency>" + doc.wcurrency(0)+ "</currency>")
[016] 	Call rtitem.AddNewLine( 1 )
[017] 	rtitem.AppendText("      <validbdate>" + Format(doc.wpccontractbdate(0), 
       "dd/mm/yyyy") + "</validbdate>")
[019] 	Call rtitem.AddNewLine( 1 )	
[020] 	rtitem.AppendText("      <validedate>" + Format(doc.wpccontractedate(0), 
       "dd/mm/yyyy") + "</validedate>")
[022] 	Call rtitem.AddNewLine( 1 )
[023] 	rtitem.AppendText("      <deliverytime>" + doc.wpcdeliverytime(0) + 
       "</deliverytime>")
[025] 	Call rtitem.AddNewLine( 1 )	
[026] 	rtitem.AppendText("      <deliveryconditions>" + doc.wpcdelivery(0) + 
       "</deliveryconditions>")
[028] 	Call rtitem.AddNewLine( 1 )	
[029] 	rtitem.AppendText("      <paymentconditions>" + doc.wpcclosing(0) + 
       "</paymentconditions>")
[031] 	Call rtitem.AddNewLine( 1 )	
[032] 	rtitem.AppendText("    </quotedata>")
[033] 	Call rtitem.AddNewLine( 1 
       )

Since the <quotedata> element contains the quotation information, we open each tag holding a piece of quotation information eg. <nbr>, fill it with the value found in the quotation document and close the tag again:

[001] Print #filenum,"<nbr>" + doc.reference(0)+ " 
      </nbr>"

We repeat this for every piece of quotation information.

Customer info

The <customerdata> element contains the customer information: companyname, contact fullname, contact lastname, contact salutation, company address (street + nr, zipcode, city and country) . The value of each of the fields is captured and enclosed between the corresponding child elements of the <customerdata> element as you can see in the script code below:

[001] '5b) Customerinformation  
[002] 	
[003] 	rtitem.AppendText("    <customerdata>")
[004] 	Call rtitem.AddNewLine( 1 )
[005] 	rtitem.AppendText("      <company>" + doc.companyname(0) + "</company>")
[006] 	Call rtitem.AddNewLine( 1 )
[007] 	rtitem.AppendText("      <contact>"+Trim(doc.contactfirstname(0)+ " " 
       +doc.contactlastname(0)) 
[009] 	+"</contact>")
[010] 	Call rtitem.AddNewLine( 1 )
[011] 	rtitem.AppendText("      <contactlastname>"+ doc.contactlastname(0) 
       +"</contactlastname>")
[013] 	Call rtitem.AddNewLine( 1 )
[014] 	rtitem.AppendText("      <contactsalutation>"+ doc.contactsalutation(0) 
       +"</contactsalutation>")
[016] 	Call rtitem.AddNewLine( 1 )
[017] 	rtitem.AppendText("      <pcode>" + doc.companyzip(0) +  "</pcode>")
[018] 	Call rtitem.AddNewLine( 1 )
[019] 	rtitem.AppendText("      <city>" + doc.companycity(0)+ "</city>")
[020] 	Call rtitem.AddNewLine( 1 )
[021] 	rtitem.AppendText("      <country>" + doc.companycountry(0) + 
       "</country>")
[023] 	Call rtitem.AddNewLine( 1 )
[024] 	rtitem.AppendText("      <address>" + doc.companyaddress(0) + 
       "</address>")
[026] 	Call rtitem.AddNewLine( 1 )
[027] 	rtitem.AppendText("    
       </customerdata>")

Since the <customerdata> element contains the customer information, we open each tag holding a piece of customer information eg. <company>, fill it with the value found in the quotation document and close the tag again:

[001] Print #filenum,"<company>" + doc.companyname(0)+ " 
      </company>"

We repeat this for every piece of customer information.

Product info

The product information starting with the table element looks somewhat more complicated. This is a result from the double loop: first we have to iterate over the different producttables and secondly in 1 table we have to iterate over the different productlines. Every iteration over a producttable starts with the <table id="..."> element. The id is the number of the producttable. The different productlines in 1 producttable are enclosed in the <lines></lines> elements and the information of every single productline is contained in the <line></line> elements. So, globally you get following structure for the productinfo:

[001] 
[002] <table id="1">
[003] 	<lines>
[004] 		<line>
[005] 			<quantity>....</quantity>
[006] 			....		
[007] 		</line>
[008] 	</lines>
[009] </table>
[010] 
[011] <table id="2">
[012] ...
[013] </table>

First we will give you the entire processing concerning the structure of the different producttables. We will explain the different parts after the code:

[001] '5c) Productinformation: traversing through the different producttables  
[002] 	
[003] 	ntables=Cint(doc.qtproducttables(0))	
[004] 	
[005] 	For i=1 To ntables
[006] 		
[007] 		Call rtitem.AddNewLine( 1 )
[008] 		rtitem.AppendText("      <table id=""" + Cstr(i) +""">")
[009] 		Call rtitem.AddNewLine( 1 )
[010] 		rtitem.AppendText("    <lines>")
[011] 		Call rtitem.AddNewLine( 1 )
[012] 		
[013] 		Set quantityitem=doc.getfirstitem("PTQT_"+Cstr(i))
[014] 		Set prodcodeitem=doc.getfirstitem("PTProductDescription_"+Cstr(i))
[015] 		Set unitpriceitem=doc.getfirstitem("PTUP_"+Cstr(i))
[016] 		Set totalpriceitem=doc.getfirstitem("PTTotalPrice_"+Cstr(i))			
[017] 		Set 
        grandtotalpriceitem=doc.getfirstitem("PTGrandTotalPrice_"+Cstr(i))			
[019] 		Set descriptionitem =doc.getfirstitem("PTdescT_"+Cstr(i))	
[020] 		
[021] 		If  i <> 1 Then
[022] 			Set textitem=doc.getfirstitem("WtextT" + Cstr(i-1) + "T" + Cstr(i))
[023] 		Else
[024] 			Set textitem=doc.getfirstitem("WtextT"+Cstr(i))
[025] 		End If
[026] 		
[027] 		grandtotalprice=cstr(grandtotalpriceitem.values(0))
[028] 		
[029] 		Set countitem=doc.getfirstitem("PTQT_"+Cstr(i))	
[030] 		nroflines=Ubound(countitem.values)+1 ' values start at 0  
[031] 		
[032] 		For j=1 To nroflines
[033] 			
[034] 			totalprice=cstr(totalpriceitem.values(j-1))		
[035] 			unitprice=cstr(unitpriceitem.values(j-1))
[036] 			rtitem.AppendText("      <line id=""" + Cstr(j) +""">")
[037] 			Call rtitem.AddNewLine( 1 )
[038] 			rtitem.AppendText("        <item>" +Trim(prodcodeitem.values(j-1)) + 
         "</item>")
[040] 			Call rtitem.AddNewLine( 1 )
[041] 			rtitem.AppendText("        <description> <![CDATA[" + 
         Trim(descriptionitem.values(j-1)) + "]]>
[043] 			</description>")
[044] 			Call rtitem.AddNewLine( 1 )
[045] 			rtitem.AppendText("        <priceexcl>" + unitprice + "</priceexcl>")
[046] 			Call rtitem.AddNewLine( 1 )
[047] 			rtitem.AppendText("        <qty>" +  Trim(quantityitem.values(j-1))  + 
         "</qty>")
[049] 			Call rtitem.AddNewLine( 1 )
[050] 			rtitem.AppendText("        <subtotal>" + totalprice + "</subtotal>")
[051] 			Call rtitem.AddNewLine( 1 )
[052] 			rtitem.AppendText("      </line>")
[053] 			Call rtitem.AddNewLine( 1 )
[054] 		Next
[055] 		
[056] 		rtitem.AppendText("    </lines>")
[057] 		Call rtitem.AddNewLine( 1 )
[058] 		rtitem.AppendText("    <grandtotal>" + grandtotalprice + 
        "</grandtotal>")
[060] 		Call rtitem.AddNewLine( 1 )
[061] 		rtitem.AppendText("    <comments>" + textitem.values(0) + 
        "</comments>")
[063] 		Call rtitem.AddNewLine( 1 )
[064] 		rtitem.AppendText("      </table>")
[065] 		Call rtitem.AddNewLine( 1 )
[066] 		
[067] 	Next	

First we have to know how many producttables there are in the quotation document. We capture the number in the doc.qtproducttables-field. Then we iterate trough these producttables and create a <table> element for each of them. To position ourself on a particular producttable, the counter i is used. I starts at 1 and mounts up to the amount op producttables to be processed (variable ntables).

[001] ntables=Cint(doc.qtproducttables(0))	
[002] 	
[003] 	For i=1 To ntables
[004] 		
[005] 		Call rtitem.AddNewLine( 1 )
[006] 		rtitem.AppendText("      <table id=""" + Cstr(i) +""">")
[007] 		Call rtitem.AddNewLine( 1 )
[008] 		

Every table consists of:

  • 1 or more productlines.
  • A grandtotal.
  • A comment between this table and the previous table.

A producttable contains 1 or more productlines. 1 productline has different characteristics: <item>, <description>,<priceexcl>,<qty> and <subtotal>. To start processing the productlines, we open the <lines> element. This tag contains ALL productlines in a particular producttable. Next we capture the different fields belonging to a productline (productcode, description, quantity, unitprice, subtotal,...), the grandtotal and the commentfield. This is shown in the partially reproduced code below:

[001] rtitem.AppendText("    <lines>")
[002] 		Call rtitem.AddNewLine( 1 )
[003] 		
[004] 		Set quantityitem=doc.getfirstitem("PTQT_"+Cstr(i))
[005] 		Set prodcodeitem=doc.getfirstitem("PTProductDescription_"+Cstr(i))
[006] 		Set unitpriceitem=doc.getfirstitem("PTUP_"+Cstr(i))
[007] 		Set totalpriceitem=doc.getfirstitem("PTTotalPrice_"+Cstr(i))			
[008] 		Set 
        grandtotalpriceitem=doc.getfirstitem("PTGrandTotalPrice_"+Cstr(i))			
[010] 		Set descriptionitem =doc.getfirstitem("PTdescT_"+Cstr(i))	
[011] 		
[012] 		If  i <> 1 Then
[013] 			Set textitem=doc.getfirstitem("WtextT" + Cstr(i-1) + "T" + Cstr(i))
[014] 		Else
[015] 			Set textitem=doc.getfirstitem("WtextT"+Cstr(i))
[016] 		End If
[017] 		
[018] 		grandtotalprice=cstr(grandtotalpriceitem.values(0))		

Once we have a handle to the fields belonging to a particular producttable, we have to loop through them. The producttable fields are multi-value fields and there are as many values in one field as there are productlines in a producttable. So, we first have to define the amount of values in the multi-value field and then we have to loop through these fields. To position ourself on a particular productline, the counter j is used. J starts at 1 and mounts up to the amount op productlines to be processed (variable nroflines). Every productline is enclosed by the <line> ... </line> elements.

[001] Set countitem=doc.getfirstitem("PTQT_"+Cstr(i))	
[002] 		nroflines=Ubound(countitem.values)+1 ' values start at 0  
[003] 		
[004] 		For j=1 To nroflines
[005] 			
[006] 			totalprice=cstr(totalpriceitem.values(j-1))		
[007] 			unitprice=cstr(unitpriceitem.values(j-1))
[008] 			rtitem.AppendText("      <line id=""" + Cstr(j) +""">")
[009] 			Call rtitem.AddNewLine( 1 )
[010] 			rtitem.AppendText("        <item>" +Trim(prodcodeitem.values(j-1)) + 
         "</item>")
[012] 			Call rtitem.AddNewLine( 1 )
[013] 			rtitem.AppendText("        <description> <![CDATA[" + 
         Trim(descriptionitem.values(j-1)) + "]]>
[015] 			</description>")
[016] 			Call rtitem.AddNewLine( 1 )
[017] 			rtitem.AppendText("        <priceexcl>" + unitprice + "</priceexcl>")
[018] 			Call rtitem.AddNewLine( 1 )
[019] 			rtitem.AppendText("        <qty>" +  Trim(quantityitem.values(j-1))  + 
         "</qty>")
[021] 			Call rtitem.AddNewLine( 1 )
[022] 			rtitem.AppendText("        <subtotal>" + totalprice + "</subtotal>")
[023] 			Call rtitem.AddNewLine( 1 )
[024] 			rtitem.AppendText("      </line>")
[025] 			Call rtitem.AddNewLine( 1 )
[026] 		Next

For every tag that contains the productline information, we open the tag holding a piece of productline information eg. "<item>", fill it with the value found in the quotation document and close the tag again:

[001] Print #filenum,"<item>" + Trim(prodcodeitem.values(j-1))+ " 
      </item>"

We repeat this for every piece of productline information. Note the expression:

[001] <![CDATA[" + Trim(descriptionitem.values(j-1)) + 
      "]]>

The CDATA-expression ensures that the section can include markup characters like & and < without having to literally escape them using &amp and &lt.

When all productlines are traversed, the <lines> element is closed again. Next the grandtotal and the comments are appended to the producttable and the current table is closed (</table> element). The counter then moves to the following producttable and continues the iteration until all producttables are processed.

[001] 	rtitem.AppendText("    </lines>")
[002] 		Call rtitem.AddNewLine( 1 )
[003] 		rtitem.AppendText("    <grandtotal>" + grandtotalprice + 
        "</grandtotal>")
[005] 		Call rtitem.AddNewLine( 1 )
[006] 		rtitem.AppendText("    <comments>" + textitem.values(0) + 
        "</comments>")
[008] 		Call rtitem.AddNewLine( 1 )
[009] 		rtitem.AppendText("      </table>")
[010] 		Call rtitem.AddNewLine( 1 )
[011] 		
[012] 	Next	

Signer info

The <signerdata> element contains the signer information: the name, title and signature of the 2 signers of the quotation. The value of each of the fields is captured and enclosed between the corresponding child elements of the <signerdata> element as you can see in the script code below:

[001] '5D) Signerinformation  
[002] 	
[003] 	Call rtitem.AddNewLine( 1 )
[004] 	rtitem.AppendText("    <signerdata>")
[005] 	Call rtitem.AddNewLine( 1 )
[006] 	rtitem.AppendText("      <name>")
[007] 	Call rtitem.AddNewLine( 1 )
[008] 	rtitem.AppendText("      	<signer1>" + doc.qtsigner1(0) + "</signer1>" )
[009] 	Call rtitem.AddNewLine( 1 )
[010] 	rtitem.AppendText("      	<signer2>" + doc.qtsigner2(0) + "</signer2>" )
[011] 	Call rtitem.AddNewLine( 1 )
[012] 	rtitem.AppendText("      </name>")
[013] 	Call rtitem.AddNewLine( 1 )
[014] 	rtitem.AppendText("      <jobtitle>")
[015] 	Call rtitem.AddNewLine( 1 )
[016] 	rtitem.AppendText("      	<signer1>" + doc.qtjobtitle1(0) + "</signer1>" 
       )
[018] 	Call rtitem.AddNewLine( 1 )
[019] 	rtitem.AppendText("      	<signer2>" + doc.qtjobtitle2(0) + "</signer2>" 
       )
[021] 	Call rtitem.AddNewLine( 1 )
[022] 	rtitem.AppendText("      </jobtitle>")
[023] 	Call rtitem.AddNewLine( 1 )
[024] 	rtitem.AppendText("      <signature>")
[025] 	Call rtitem.AddNewLine( 1 )
[026] 	rtitem.AppendText("      	<signer1>" + doc.qtsignature1(0) + "</signer1>" 
       )
[028] 	Call rtitem.AddNewLine( 1 )
[029] 	rtitem.AppendText("      	<signer2>" + doc.qtsignature2(0) + "</signer2>" 
       )
[031] 	Call rtitem.AddNewLine( 1 )
[032] 	rtitem.AppendText("      </signature>")
[033] 	Call rtitem.AddNewLine( 1 )
[034] 	rtitem.AppendText("    </signerdata>")
[035] 	Call rtitem.AddNewLine( 1 
       )	

Since the <signerdata> element contains the signer information, we open each tag holding a piece of signer information eg. <name>. Then we capture the values of the two signers using the <signer1> and <signer2> elements and close the signer elements and their parent:

[001] rtitem.AppendText("      <name>")
[002] 	Call rtitem.AddNewLine( 1 )
[003] 	rtitem.AppendText("      	<signer1>" + doc.qtsigner1(0) + "</signer1>" )
[004] 	Call rtitem.AddNewLine( 1 )
[005] 	rtitem.AppendText("      	<signer2>" + doc.qtsigner2(0) + "</signer2>" )
[006] 	Call rtitem.AddNewLine( 1 )
[007] 	rtitem.AppendText("      </name>")
[008] 	Call rtitem.AddNewLine( 1 )

We repeat this for every piece of signer information.

XML-ending tags and saving the XML-document

After the <signerdata> element is closed, we have captured all the required information in the quotation and we can now close the remaining opened <quotes><quote> elements and save the XML-document.

[001] 	rtitem.AppendText("  </quote>")
[002] 	Call rtitem.AddNewLine( 1 )
[003] 	rtitem.AppendText( "</quotes>")
[004] 	Call rtitem.AddNewLine( 1 )	
[005] 	Call 
       xmldoc.save(True,False)

NOTE: In a production environment you should write a daily-scheduled clean-up agent deleting all documents with the form "XML" to get rid of the temporary documents with the XML-structure of the generated PDF's.

NOTE: As we mentioned before, the other 2 agents differ in the fields that are filled in the XML-document. As an example we will explain this part of the (GenerateQuoteSE)-agent. This agent is used when the PDF needs to be saved and mailed afterwards. You find the code partially reproduced below:

[001] 	'2) defining the type to find the correct xslt-file later on 
[002] 	
[003] 	xmldoc.xsl="duquotesdemo.xsl"
[004] 	filename=replacesubstring(doc.reference(0),"/","_")
[005] 	xmldoc.store= "\\Lab_server\xmlmill\domino\" & filename & ".pdf"
[006] 	sendto=""
[007] 	Set sendtoitem=doc.getfirstitem("SendTo")
[008] 	Forall z In sendtoitem.values
[009] 		If sendto="" Then
[010] 			sendto=z
[011] 		Else
[012] 			sendto=sendto + ", " + z
[013] 		End If
[014] 		
[015] 	End Forall
[016] 	
[017] 	response=|<HTML><head><title>XMLMill for Domino</title><script 
       language="JavaScript">|
[019] 	response=response + |<!-- var w=350;  //set Width for window|
[020] 	response=response +  |var h=260;   //set Height for window|
[021] 	response=response +  
[022] 	|var Ycor=(screen.height-h)/2;var 
       Xcor=(screen.width-w)/2;window.moveTo(Xcor, 
       Ycor);window.resizeTo(w,h+10);|
[025] 	response=response +  |--> </script> </head>|
[026] 	response=response +  
[027] 	|<body bgcolor="#FFFF99"><p><b><u>The .pdf document has been saved 
       :</u></b></p>|
[029] 	response=response +  |<p>Path: | + xmldoc.store(0) + |</p>|
[030] 	response=response +  |<p><b><u>Messages have been sent to:</u></b></p>|
[031] 	response=response +  |<p>| + sendto + |</p>|
[032] 	
[033] 	response=response +  |</body></html>|
[034] 	xmldoc.response=response
[035] 	
[036] 	xmldoc.execute= "(SendMail)"
[037] 	
[038] 	Call 
       xmldoc.save(true,false)

As the PDF must be saved, we specify the filepath and filename in the store-field. Note that the filepath is set to \\Lab_server\xmlmill\domino\ and that the filename is dynamically generated as the quotation number with "/" replaced by "_". Since the PDF has to be mailed to 1 or more email addresses, we capture the email addresses in order to be able to use them in the confirmation message we want to show to the user. In the variable "response" we build the HTML-page we want to show to the user when the PDF has been saved and mailed. The filepath, filename and addressees are confirmed to the user. The confirmation message is stored in the response-field. Finally, in the execute-field we define the name of the agent (SendMail) that will send the mail with the PDF attached.

We will now come back to our base version (GenerateQuote): when the agent is finished, the servlet that called the agent continues its course. It wil combine the XML written in the richtext-field in the XML-document on one hand and the XSLT-file duquotesdemo.xsl on the other hand and use XMLMill to transform both to a PDF. In the next section we will explain the structure of the XSLT-file duQuotesdemo.xsl.

The XSLT-stylesheet duquotesdemo.xsl

Overview

In order to transform the XML-datastructure in this use case, a stylesheet has been created which can be found in the domino/ directory in the download.

Please open the duquotesdemo.xsl in your xml-editor when you go through this chapter.

First the global structure of the duquotesdemo.xsl is described. The next sections go into deeper detail afterwards.

The <?xml?> prolog

The prolog is the first line of a xml or xsl document defining the encoding of the xml or xsl document:

[001] <?xml version="1.0" 
      encoding="UTF-8"?>

This is important as you need an editor that supports the specified encoding in order to correctly see the content of the document and write any changes back in the correct encoding (the one defined here). If the encoding is UTF-8 and you edit the xml document with a editor that only supports ASCII you will see 'strange' characters.

  • It is strongly recommended to use a xml-editor which supports different encodings.

The <xsl:stylesheet> element

The <xsl:stylesheet> element defines a reference to the ml: namespace that contains all valid XMLMill elements and their attributes.

[001] <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
[002]       xmlns:ml="http://www.xmlmill.com/XSL/Format" 
[003]       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
[004]       xsi:schemaLocation="http://www.xmlmill.com/XSL/Format xmlmill.xsd" 
[005]       version="1.0" 
[006] >
[007]   <xsl:output method="xml" indent="yes" encoding="UTF-16"/>     
[008]   <xsl:output/>
[009]   <xsl:decimal-format decimal-separator="," grouping-separator="."/> 
[010]   ...
[011]           

1

Define the ml: namespace by referring to the correct namespace name. In this case the namespace name is a URI mapping to: http://www.xmlmill.com/XSL/Format.

2

Define an instance of a XMLSchema.

3

Define where the instance of the XMLSchema is located. The XMLSchema is referenced using the namespace name (http://www.xmlmill.com/XSL/Format). The second parameter (xmlmill.xsd) defines the actual location and name of .xsd schema.

4

When the xml document is transformed with the xsl stylesheet, an internal .mill document is generated, used to generate the .pdf document. The <output> element defines the characteristics of this intermediate .mill file.

5

Optionally the decimal symbol is defined as ',' and the thousands-grouping-separator is defined as '.' in the <xsl:decimal-format> element.

  • The docs/xsd/ directory contains a default xsl stylesheet and the xmlmill.xsd and xslt.xsd file. Please keep these two files in the same directory as the xslt.xsd file is referenced in the xmlmill.xsd file.

The <ml:documents> element

[001] ...
[002]   <xsl:template match="/">
[003]     <ml:documents  whitespace-collapse="on" space-collapse="on">
[004]       ...   
[005]           

In the first part of the stylesheet, we position ourselves on the root element of the XML-file. This is done by an XSL-reserved expression <xsl:template match="/">.

Next, the <documents> element is processed. The <documents> element is the root tag of the ml: namespace. It can contain multiple <ml:document> elements each contaning elements to define a separate pdf document.

The <ml:document> element

[001] <ml:document file="output\quotes.pdf" 
      >

The <document> element contains an file attribute defining the name and path (optional) of the .pdf document to generate (<document file="output\quotes.pdf">).

The <ml:layout-master-set> element

[001] ...
[002]   <ml:layout-master-set>  
[003]     <ml:simple-page-master master-name="content-pages" 
          reference-orientation="0" page-width="595" page-height="842" 
          margin-top="10pt" margin-bottom="10pt" margin-left="0pt" 
          margin-right="0pt">
[007]       <ml:region-body column-count="1" .../>
[008]       <ml:region-before overflow="visible" extent="2cm" 
            precedence="false"/>
[010]       <ml:region-after overflow="visible" extent="2cm" 
            precedence="false"/>
[012]       <ml:region-start overflow="visible" extent="2.20485cm"/>
[013]       <ml:region-end overflow="visible" extent="2.20485cm"/>
[014]     </ml:simple-page-master>
[015]   </ml:layout-master-set>
[016] ...

The <ml:layout-master-set> element contains ml:simple-page-master element(s) which define the page-layout of the page where this <ml:layout-master-set> is applied to (referenced by the <ml:page-sequence>).

A page can contain up to five 'regions', of which 3 are mostly used:

<ml:region-before>

The upper region on the page (mainly used to define the 'header' of a page).

<ml:region-body>

This is the center region on the page to define the content of a page.

<ml:region-after>

The bottom region on the page (mainly used to define the 'footer' of a page).

  • For more information regarding these elements please visit the dtdguide.pdf document contained in the docs/ directory in the download.(
  • Also visit the examples in the samples/mill and samples/xmlxsl directory to consult how these elements can be used.

The <ml:page-sequence> element

The <page-sequence> element contains the content of the pages to generate. It contains two important elements:

<ml:static-content>

This element defines the content that should be repeated on each page (like a header of footer). THe flow-name refers to a region defined in the simple-page-master that is referenced by the master-reference attribute in the parent <ml:page-sequence>.

<ml:flow>

This element defines the 'flowing' content of a pages, adding new pages to the document if needed.

The bulk of the elements will be children of the <ml:flow> element describing the content of a page. The example will generate two pages, an introduction page and a second page describing the products details.

The introduction page contains:

  1. A table with the name and address info of our company and the customer.
  2. Some introduction paragraphs.
  3. A bulleted list with conditions and agreements.
  4. A closing paragraph.
  5. The signer information.
  6. A red horizontal line and a page-break.

The second page contains the products tables.

  • For more information regarding these elements please visit the dtdguide.pdf document contained in the docs/ directory in the download.(
  • Also visit the examples in the samples/mill and samples/xmlxsl directory to consult how these elements can be used.

Conclusion

In the previous sections we have explained the Notes-agent and the XSLT-stylesheet which are combined by XMLMill and result in the quotation PDF shown to the user on the web or in the Notes-client:

Copyright © 2001 - 2012. All rights reserved. XMLMill and XMLMill logo are trademarks of Pecunia Data Systems, bvba.
Powered by Apache CocoonPowered by XMLMill