Dynamically generate OpenOffice documents on the client side with XPCOM

A portable and cost-effective alternative to server dependence

With the Cross Platform Component Object Model (XPCOM) framework from Mozilla, you can dynamically export existing XML content into an OpenOffice document. The process also works for any other type of content supported by the transformation mechanism, such as XSLT. In this article, learn about a portable and cost-effective alternative to server-side solutions.

Stefanus Wiguna (wiguna@us.ibm.com), Advisory Software Engineer, IBM

Stefanus Wiguna Stefanus Wiguna is an advisory software engineer with the IBM Software Group at Research Triangle Park, N.C. He is a Principal Certified Lotus Professional and has over 10 years' experience in solution development and integration using Lotus, Web 2.0, and Java technology.



07 July 2009

Also available in Japanese

Introduction

OpenOffice is an increasingly popular — and free — open source alternative to expensive commercial office software suites. Creating content and exporting existing content into OpenOffice can be cumbersome if you don't have the right tools.

Typically, dynamically creating an OpenOffice document or exporting your existing content into an OpenOffice document involves:

  • A Web application running on a server to transform your content to the OpenDocument Format (ODF).
  • Compressing the resulting content into the OpenOffice document archive.
  • Storing the file somewhere or serving it to the user.

But what if you don't have access to server-side applications? Or you simply want to minimize server load by moving the process to the client?

Detailed discussion of XPCOM, Firefox Extension, OpenOffice, OpenDocument Format, and XSLT is outside the scope of this article. See Resources for more on these topics.

In this article, learn how to use the Cross Platform Component Object Model (XPCOM) framework from Mozilla to speed development of your application. Examples show how to dynamically export existing XML content into an OpenOffice document using XPCOM components within the Firefox Extension. And, you're not limited to exporting just XML; you can process other types of content as long as it's supported by the transformation mechanism (for example, Extensible Stylesheet Language Transformations (XSLT)).

Before actually dynamically exporting content into OpenOffice, the next section covers the requirements for building an OpenOffice document.


OpenOffice documents

The OpenOffice document is basically a compressed file consisting of a set of XML files to describe the different parts of the document, such as content and styles; a manifest; and a thumbnail.

Figure 1 shows the file structure you will get if you take an OpenOffice text document (.odt), rename it with a .zip extension, and extract it.

Figure 1. OpenOffice document structure
OpenOffice document structure

Table 1 briefly explains some of the files.

Table 1. OpenOffice document files
FileDescription
content.xmlContains the actual content of the document.
meta.xml Contains metadata information, such as creation date and author.
styles.xml Defines formatting styles for paragraphs, characters, etc.

To create a new OpenOffice document, the easiest and perhaps safest way is to edit an existing document. In this article, you'll only update content.xml where the actual content of the document is stored. The content.xml itself looks like Figure 2.

Figure 2. OpenOffice document's content.xml
OpenOffice document's content.xml

For our examples, let's call the section before the actual content the content head and the section after the actual content the content tail. The actual content itself is the content body. You'll use these sections every time you build a new OpenOffice document.


Preparing the development environment

You need to do a couple of things in the Firefox Extension development environment in order to export to OpenOffice. Since you'll be creating a new OpenOffice document by editing an existing one, you need to:

  1. Include all the files in Figure 1 in the Firefox Extension package. You will use these files to build the new OpenOffice document.
  2. Store these files outside the chrome folder of the Firefox Extension. This is to ensure that the files are not compressed during extension packaging so you can retrieve them easily later on.

An example of how the files are stored within the Firefox Extension is shown below.

Figure 3. OpenOffice document files in the Firefox Extension structure
OpenOffice document files in the Firefox Extension structure

All files can be stored in the same folder. You'll put each file in the appropriate place within the archive when you build the OpenOffice document. xml2odt.xsl is the custom stylesheet file you will use to transform existing content into ODF.

The next step is to set several variables and constants to be used for file operations throughout the OpenOffice document generation process, such as locating and retrieving files. Listing 1 shows an example. You'll use the nsIExtensionManager XPCOM interface to set variables and constants.

Listing 1. Setting up variables and constants
var eid = "export2OO"; // extension id
var em = Components.classes["@mozilla.org/extensions/manager;1"]
 .getService(Components.interfaces.nsIExtensionManager);

var oopath = "export/oo/"; // path to the OpenOffice document files
var ooxslt = "xml2odt.xsl";

const PR_WRONLY = 0x02;
const PR_RDWR = 0x04;
const PR_CREATE_FILE = 0x08;
const PR_APPEND = 0x10;
const PR_TRUNCATE = 0x20;
const PR_USEC_PER_MSEC = 1000;
const time = Date.now();

All of the code in this article can be put anywhere as long as it can be accessed by Firefox Extension. Typically, you would store it in a JavaScript file (.js) inside the chrome folder and include it in the Firefox Extension main XUL file, as shown below.

Listing 2. Including code in Firefox Extension
<overlay id="export2OO"
 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 <script type="application/x-javascript;version=1.7"
           src="chrome://export2OO/content/export.js" />

Now that the development environment is prepared, the next section gets down to the real business.


Programmatically building an OpenOffice document

Three are three main tasks involved in dynamically exporting your existing content into an OpenOffice document:

Transform existing content into OpenDocument format

You can easily transform existing content into ODF with Extensible Stylesheet Language Transformations (XSLT). For example, Listing 3 shows some content in XML format.

Listing 3. Content in XML
<h1>This is Heading 1</h1>
<h3>This is Heading 3</h3>

Listing 4 shows the same content presented in ODF.

Listing 4. Content in ODF
<text:h xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
 text:style="Heading1" text:outline-level="1">
 This is Heading 1
</text:h>
<text:h xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
 text:style="Heading3" text:outline-level="3">
 This is Heading 3
</text:h>

The ODF specification provides all sorts of information on defining and formatting styles (see Resources). Once your stylesheet file is ready, you can perform the transformation inside the Firefox Extension environment. Using the extension manager, locate the stylesheet file that will be used for the transformation. Listing 5 shows an example.

Listing 5. Locating XSLT file
// locate stylesheet file
var xslfile = em.getInstallLocation(eid).getItemFile(eid, oopath + ooxslt);

Next, load the stylesheet file.

Listing 6. Loading XSLT file
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
 .createInstance(Components.interfaces.nsIFileInputStream);
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
 .createInstance(Components.interfaces.nsIScriptableInputStream);

var xsldata = "";
fstream.init(xslfile, -1, 0, 0);
sstream.init(fstream); 
var tmp = sstream.read(4096);
while (tmp.length > 0) {
 xsldata += tmp;
 tmp = sstream.read(4096);
}
sstream.close();
fstream.close();

var parser = new DOMParser();
var xsldoc = parser.parseFromString(xsldata,"text/xml");

You can use the stylesheet to transform the content into ODF. In the following example, xmldoc is the XML document of the existing content.

Listing 7. Transforming content into ODF with XSLT
var xsltprocessor = new XSLTProcessor();
xsltprocessor.importStylesheet(xsldoc);
var newcontentdoc = xsltprocessor.transformToFragment(xmldoc,document);

At this point, it's time to update content.xml with the new content. To make a valid OpenOffice content.xml, you need to include the appropriate content head and content tail (see Figure 2).

Listing 8. Updating content.xml with new content
// prepare new content
var contentbody = (new XMLSerializer()).serializeToString(newcontentdoc);
var newcontent = xmlheader + contenthead + contentbody + contenttail

// update content.xml
var contentfile = em.getInstallLocation(eid).getItemFile(eid, oopath + "content.xml");
var fstream = Components.classes["@mozilla.org/network/file-output-stream;1"]
 .createInstance(Components.interfaces.nsIFileOutputStream);
fstream.init(contentfile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0666, 0);
fstream.write(newcontent, newcontent.length);
fstream.close();

You now have an updated content.xml. In the next section, you build the new OpenOffice document.

Build the OpenOffice document

To build a new OpenOffice document, you need to create a new OpenOffice archive and add content.xml and the other required files to the archive. The example uses the nsIZipWriter XPCOM interface for this purpose.

The first step is to create the archive file and store it somewhere on the user's machine. The following example stores the new file in the desktop area, as shown in Listing 9. Depending on requirements, you might need to create a temporary file or store it in other places.

Listing 9. Creating new OpenOffice document archive
var odtfile = Components.classes["@mozilla.org/file/directory_service;1"]
 .getService(Components.interfaces.nsIProperties)
 .get("Desk", Components.interfaces.nsIFile);
odtfile.append("new.odt");
odtfile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);

var zipWriter = Components.Constructor("@mozilla.org/zipwriter;1", "nsIZipWriter");
var odt = new zipWriter ();
odt.open(odtfile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE | PR_APPEND);

Table 2 shows some commonly used values you can use in place of Desk.

Table 2. Common directories for storing files
FileDescription
ProfDFirefox Profile directory
Desk Desktop directory (for example, ~/Desktop on Linux®, C:\Documents and Settings\username\Desktop on Windows®)
Home Operating system root directory (for example, /root on Linux, C:\ on Windows)
TmpD Operating system temporary directory

The second step is to add the required OpenOffice document files to the archive. Keep in mind that you need to add each file to its appropriate place in the structure shown in Figure 1.

Listing 10. Adding OpenOffice document files to the archive
// locate file
var contentfile = em.getInstallLocation(eid).getItemFile(eid, oopath + "content.xml");
// add file to archive
odt.addEntryFile("content.xml", 
 Components.interfaces.nsIZipWriter.COMPRESSION_NONE,
 contentfile, false);

// create directory
odt.addEntryDirectory("META-INF", time * PR_USEC_PER_MSEC, false);
// locate file
var manifestfile = em.getInstallLocation(eid).getItemFile(eid, oopath + "manifest.xml");
// add file to specific directory in archive
odt.addEntryFile("META-INF/manifest.xml",
 Components.interfaces.nsIZipWriter.COMPRESSION_NONE, 
 manifestfile, false);

odt.addEntryDirectory("Thumbnails", time * PR_USEC_PER_MSEC, false);
var thumbfile = em.getInstallLocation(eid).getItemFile(eid, oopath + "thumbnail.png");
odt.addEntryFile("Thumbnails/thumbnail.png", 
 Components.interfaces.nsIZipWriter.COMPRESSION_NONE, 
 thumbfile, false);

var metafile = em.getInstallLocation(eid).getItemFile(eid, oopath + "meta.xml");
odt.addEntryFile("meta.xml", 
 Components.interfaces.nsIZipWriter.COMPRESSION_NONE, 
 metafile, false);

var mimetypefile = em.getInstallLocation(eid).getItemFile(eid, oopath + "mimetype");
odt.addEntryFile("mimetype", 
 Components.interfaces.nsIZipWriter.COMPRESSION_NONE, 
 mimetypefile, false);

var settingsfile = em.getInstallLocation(eid).getItemFile(eid, oopath + "settings.xml");
odt.addEntryFile("settings.xml", 
 Components.interfaces.nsIZipWriter.COMPRESSION_NONE, 
 settingsfile, false);

var stylesfile = em.getInstallLocation(eid).getItemFile(eid, oopath + "styles.xml");
odt.addEntryFile("styles.xml", 
 Components.interfaces.nsIZipWriter.COMPRESSION_NONE, 
 stylesfile, false);

// close the archive
odt.close();

Now that the new OpenOffice document creation is complete and the document is ready to use, you can launch it.

Launch the OpenOffice document

Launching is optional, since the new document has already been saved on your desktop area. The new document can be opened at any time manually, or it can be used as input for other processes.

If file association has been set properly, the code in the following example will automatically open the newly created document in OpenOffice.

Listing 11. Launching OpenOffice document automatically
var ios = Components.classes["@mozilla.org/network/io-service;1"]
	   .getService(Components.interfaces.nsIIOService);
var odtURL = ios.newFileURI(odt);
location.href = odtURL.spec;

If file association has not been set, you are prompted by a window similar to the one shown below.

Figure 4. Opening OpenOffice document
Opening OpenOffice document

That's all there is to it.

Once you build and install your Firefox Extension, the code in this article will give you an Export to OpenOffice functions right on the client.


Conclusion

XPCOM's rich libraries speed up development of your application and let you do things on the client side that were previously only possible on the server side. You now have a portable and cost-effective alternative to server-side solutions.

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=405552
ArticleTitle=Dynamically generate OpenOffice documents on the client side with XPCOM
publish-date=07072009