Getting started with the OpenOffice.org API part II : some basic Writer operations

OpenOffice.org is an open source
office suite available for Windows, Mac, Linux and Unix. Despite
several small issues, OpenOffice.org (OOo) is capable of opening,
modifying and saving Microsoft Office (MSO) documents. Besides that,
OOo has a very powerfull PDF exporter.For Java developers, the most
interesting part of OOo may be the SDK that comes with it. The SDK
exposes almost all OOo functionality and capabilities to Java through
an API.
In a series of several blogs I will describe a part of this
API. What part I will describe depends on my interest, my working
together with Geertjan Wielenga of NetBeans and possibly whatever requests I get.

In part I of the series, I described how to connect to OOo from Java. 

This
is part II of the series. In this blog I will write about some basic Writer operations, like adding and modifying text, setting general text properties, saving, closing and printing documents and exporting them to PDF. I will do my best to describe everything for both Windows and
Linux, since I work on Windows but have my PC at home on Debian Linux.
Also, my main developing IDE at home is NetBeans, but I’ll try to make
everything as IDE independent as possible.

....

A Writer document has several components. First of all, there is the text area. This area contains all texts organized in paragraphs. Second, there is the service manager. This manager is attached to the document and document specific content, like tables drawing shapes or text frames, can be modified for the current document. Third is the DrawPage which lies on top of the text. he DrawPage is much like a glasspane in a Swing application. By using the DrawPage you can add drawings and make text wrap around it in several ways. Finally, there are several services that allow for document wide styling and structuring of the text (yes, that is literally from chapter 7 of the the Developer’s Guide). This blog will focus on text and tables.

Opening and creating documents

In my previous blog I showed some code to create a new Writer document. There it was only used to show that the code succesfully connects to OOo. The line actually creating the Writer document is

XComponent xComponent = xComponentLoader.loadComponentFromURL("private:factory/swriter", "_blank", 0, pPropValues);

There are four arguments to the loadComponentromURL method. The first one is the URL the document is loaded from. When the URL is "private:factory/swriter", a new document will be created. To load an existing document, simply specify the URL to the document, e.g. "file:///C:/Documents and Settings/wouter/My Documents/Test Document.doc" (on Windows) or "file:///home/wouter/Test Document.doc" (on Linux). Please note the extension ".doc" indicates this is a Word document, which OOo should be able to open. If you would specify an Excel file here, a Calc document would be opened instead of a Writer document. Of course you can open OOo documents as well Smiley The URL can start with file, ftp, http or private.
When creating a document, you don’t necessarily need to create a new empty document. It is possible to base the new document on a template. OOo by default creates a new document when a template is opened. Only by setting the AsTemplate property to false the template is opened so you can edit the template (is it me, or doesn’t this make sense at all? It’s true though…). So, basically, opening a template with the AsTemplate property set to true (this is the default value) will open the document fo editing as if it were an exisiting document. When saving the document, the location will not be set, so you’ll have to specify it (see the last part of this blog).
The second argument specifies the target frame in which the document is opened. Usually you will use _blank here to create a new frame. The third argument refers to the order in which the target frame is searched for. For the target frame and search flag options, see the discussion about Frames in chapter 6 of the Developer’s Guide.
The fourth argument specifies certain properties for the document that is opened or created. For instance, to open a document ReadOnly, use these lines 

PropertyValue[] pPropValues = new PropertyValue[1];
pPropValues[0] = new PropertyValue();
pPropValues[0].Name = "ReadOnly";
pPropValues[0].Value = new Boolean(true);

This will make sure the document cannot be modified via the keyboard, but API calls WILL modify it. Another example is this:

PropertyValue[] pPropValues = new PropertyValue[2];
pPropValues[0] = new PropertyValue();
pPropValues[0].Name = "ReadOnly";
pPropValues[0].Value = new Boolean(true);
pPropValues[1] = new PropertyValue();
pPropValues[1].Name = "Hidden";
pPropValues[1].Value = new Boolean(true);

This will make sure no desktop is shown and all processing is done in the background. The combination of this property with the ReadOnly property doesn’t make much sense since hiding the desktop ensures noone can modify the document with a keyboard, but it does show that it is possible to combine properties. Please note that opening a document in hidden mode WILL NOT CLOSE IT! To close it, either close it via the API (more on this later), close all open documents that you can close by hand (by exiting OOo) or kill the OOo process. For more properties to set, see the discussion about Handling Documents in chapter 6 of the Developer’s Guide.

XText and cursors

In a Writer document, access to the text is gained through the XText component of the document. XText inherits from XSimpleText and this gives XText a few nice methods. First of all, there is insertString(XTextRange, String, boolean). This method inserts a String at the position of the XTextRange. If the boolean is true, the text in the XTextRange will be replaced, otherwise it will be inserted at the beginning of the XTextRange.
An XTextRange is a description of the position of the object usng the XTextRange in the text. An XTextRange may be zero or more characters wide. The most commonly use of XTextRange is by using a cursor. See below.
Another nice method on XText is insertControlCharacter(XTextRange, short, boolean). A ControlCharacter can be a paragraph break, a line break or some other control character. See the API documentation on ControlCharacter.
Finally, XText has the createTextCursor() and createTextCursorByRange(XTextRange) methods. Both return an XTextcursor (which can be tranformed into a cursor, please read on) but the first creates a new text range and the second copies the text range in XTextRange.

A Writer document knows of two kinds of cursors: visible and invisible. The visible cursor also is referred to as view cursor and it’s interface is XTextViewCursor. Invisible cursors come in four flavours: XTextcursor, XWordCursor, XSentenceCursor and XParagraphCursor. Since XTextViewCursor, XWordCursor, XSentenceCursor and XParagraphCursor inherit from XTextCursor, the all share a common set of operations. These operations include gotoStart(boolean), gotoEnd(boolean), collapseToStart() and collapseToEnd().
The boolean in gotoStart and gotoEnd specify whether or not the text between the current cursor position and start/end will be selected or not. The collapseToStart and collapseToEnd methods positions start or end at the current position. Basically, this resets the XTextRange defined by this cursor making it zeor characters wide. Calling gotoEnd or gotoStart will still go to the start or end of the entire document, however.
Besides that, each cursor type has its own gotoNextXXX, gotoPreviousXXX, gotoStartOfXXX and gotoEndOfXXX methods. Here, replace XXX either with Word, Sentence or Paragraph. These cursors will help you get around your document more easily. The alternative is to know exactly how many characters you have to move the cursor to get where you want to be (let’s call this amount x) and then either call XTextcursor.goLeft(x) or XTextcursor.goRight(x). Here’s an example. Use the code to connect locally to OOo from part I of the series and append this 

XTextDocument xTextDocument = (XTextDocument)UnoRuntime.queryInterface(XTextDocument.class, xComponent);
XText xText = xTextDocument.getText();
XTextCursor xTextCursor = xText.createTextCursor();
xText.insertString(xTextCursor, "My first text!&q uot;, false);
xTextCursor.gotoEnd(false);
xText.insertControlCharacter(xTextCursor, ControlCharacter.PARAGRAPH_BREAK, false);
xText.insertString(xTextCursor, "My second text!", false);
XParagraphCursor xParagraphCursor = (XParagraphCursor)UnoRuntime.queryInterface(XParagraphCursor.class, xTextCursor);
xParagraphCursor.gotoPreviousParagraph(false);
xParagraphCursor.setString("My Third Text! ");

This code creates a single short sentence, then appends a paragraph break and a second sentence. Finally the text range is set to the first paragraph through an XParagraphCursor and another sentence is inserted. Since the sentence in the first paragraph is NOT selected, the third sentence is prepended. Modify the code and add this line 

xParagraphCursor.gotoEndOfParagraph(true);

right after "xParagraphCursor.gotoPreviousParagraph(false);". This time the first sentence is overwritten.

Text properties

Text properties include whether the characters in a piece of text are italic or not, the weight (e.g. bold), the size, the color etc. Other properties, like whether the text is left justified or centered, line spacing, some margins, tab stops, etc. are paragraph properties. For all text properties, see chapter 7.3.2 of the eveloper’s Guide. Here’s some code you can put behind the code you already have

xParagraphCursor.gotoNextParagraph(false);
xParagraphCursor.gotoStartOfParagraph(false);
xParagraphCursor.gotoEndOfParagraph(true);
XPropertySet xCursorProps = (XPropertySet)UnoRuntime.queryInterface(XPropertySet.class, xParagraphCursor);
xCursorProps.setPropertyValue("CharPosture", com.sun.star.awt.FontSlant.ITALIC);
xCursorProps.setPropertyValue("CharWeight", new Float(com.sun.star.awt.FontWeight.BOLD));
xCursorProps.setPropertyValue("CharHeight", 20);
xCursorProps.setPropertyValue("CharColor", new Integer(0x007f007f));
xCursorProps.setPropertyValue("CharFontName", "Arial");
xCursorProps.setPropertyValue("ParaAdjust", ParagraphAdjust.CENTER);

Basically, this code gets the second paragraph and makes the text some purplish color and Bold Italic Arial of size 20. Also, it centers the paragraph. Thanks to Geertjan for some of this code. Please note that almost all properties may have predefined values. For some values, like the character height or the font name, these predefined values can easily be guessed or found. But some values, like colors, are a bit weird. As Geertjan points out in his blog, all colors have numerical values though I am not sure where to get those values. Colors can also be set by ARGB codes. An ARGB code exists of eight digits meaning four hexadecimal values, onde hex value for each of the four channels A(lpha), R(ed), G(reen) and B(lue). The digits for the A channel are optional. In this case, the value of 0x007f007f means the value 0 for A, 127 for R, 0 for G and 127 for B, which is the purple you’ve seen in your document. Any self respecting Cascading Style sheets programmer (or old fashioned HTML coder) would know how to get the values for the colors you want to set your characters to. For those that don’t: either use PhotoShop, Paint Shop or Gimp to get those values. Use a scientific calculator to transfor the decimal values to hex values.

Saving, printing, exporting, closing

Once you’re satisfied with your document, you probably want to preserve it for later usage. Before doing anything next, it’s probably a good idea to save the document to a hard disk. Especially when a Microsoft Office document is needed, having an OOo backup may come in handy in case the transformation doesn’t go well enough.
In order to save a hard disk, an XStorable object needs to be gotton from the document. Then, either one of store(), storeAsURL(String, properties) or storeToURL(String, properties) can be called. The store() method is the equivalent of the save operation. This method will only succeed if the document has a location, which can be checkd with the hasLocation() method. the location itself can be gotten via the getLocation() method. The storeToURL(String, properties) and storeAsURL(String, properties) methods save the document to the location specified by the String representing a URL (file:///, http:// or ftp://). The difference is, that storeAsURL will make the URL the new location of the file (i.e. equivalent to Save As) while storeToURL will save the file to the URL but will keep the old loation of the file. The properties can set, for example, the file type, e.g. "MS Word 97". This file type can be set through the FilterName property. See chapter 7.2.2 of the Developer’s Guide dealing with storing documents.
Closing a document can be done in a likewise manner. Request the XCloseable interface and call close(boolean) on it. The boolean specifies if anyone can take care of closing the document, possibly throwing a CloseVetoException, (true) of if you will take care of closing the document, therefore taking responsibility for any CloseVetoException (false). The basic difference is that if the boolean is true, anyone may close the document, while if false only you can. If the document, for whatever reason, cannot be closed you’ll have to wait and try again later.
Printing can be achieved by calling, you guessed it, the XPrintable interface. This interface supports the PropertyValue interface which allows you to set the printer name, paper size, paper orientation etc.
Exporting to PDF, finally, is the same as saving a document. The only thing you need to take care of is the correct FilterName property. This should be set to "writer_pdf_Export". Besides that, you NEED to use the storeToURL method.

 

This last example will take our new document, convert it to PDF and store it in the "C:\" directory under the name "mydoc.pdf". Finally, it will close the document. Since the Hidden property of the document is set to true, no OOo desktop will be shown. Please note this code is far from perfect (the filename and location are hard coded in the code so you NEED to modify that) but it does show how this all works.

First, using the code you already have, replace the line "PropertyValue[] pPropValues = new PropertyValue[0];" just prior to the loadComponentFromURL line with

PropertyValue[] pPropValues = new PropertyValue[2];
pPropValues[0] = new PropertyValue();
pPropValues[0].Name = "Hidden";
pPropValues[0].Value = new Boolean(true);

Then, append these lines to the end of your code

PropertyValue[] storeProps = new PropertyValue[2];
storeProps[0] = new PropertyValue();
storeProps[0].Name = "FilterName";
storeProps[0].Value = "writer_pdf_Export";
storeProps[1] = new PropertyValue();
storeProps[1].Name = "CompressMode";
storeProps[1].Value = "0";
xStorable.storeToURL("file:///c:/mydoc.pdf", storeProps);
System.out.println("Closing document");
XModel xModel = (XModel)UnoRuntime.queryInterface(XModel.class, xComponent);
XCloseable xCloseable = (XCloseable)UnoRuntime.queryInterface(XCloseable.class, xModel);
xCloseable.close(false);

 

Please make sure to replace the URL "file:///c:/mydoc.pdf" with something you like in case you don’t like the one I made up. Next, run the code and verify the PDF is created at the location you provided. On Windows, this should be enough to shutdown OOo after closing the document. On Linux, OOo continues to run and I’m not sure yet why. I’ll try to find out and get
back when I do. If you know, please let me know by adding a comment to this blog.

10 Comments

  1. Phong Nguyen February 25, 2011
  2. martin joseph February 20, 2009
  3. site admin November 28, 2008
  4. martin joseph November 26, 2008
  5. babu May 17, 2007
  6. priyanka February 7, 2007
  7. Rajeev E December 13, 2006
  8. Wouter van Reeven June 26, 2006
  9. Lucas Jellema June 26, 2006