OpenOffice.org is great, Python is great. Guess what ? PyUNO
is great



Scripting OpenOffice.org with Python is an efficient way to produce high quality
additional functionalities to your favourite office suite.



But programs has to be tested to ensure reliability and so Python addons
and pyUNO scripts.

Python comes with various test tools and one of them is doctest.



Here is an example that illustrate a doctest use on a pyUNO script. We have
3 main steps

  • start OpenOffice.org in listen mode
  • write the doctest
  • run the doctest

Starting OpenOffice.org in listen mode


As we plan to use pyUNO for external scripting, we have to start OOo in
listen mode as explained in the Developer's guide

/opt/openoffice.org2.0/program/soffice -accept="socket,host=localhost,port=11111;urp;StarOffice.ServiceManager"

Write the doctest


A doctest is a plain text file mixing comments and Python code from the
interpreter. The idea is to write a story, describing what happens while
testing. Then we end with both a documentation and a testing program. The
idea is to mimic what happens in the Python editor as if we typed directly
the command. The expression is evaluated and compared to the given
value

>>>a+1
2

If the answer is not 2 while performing the test, it will fail and the error will be reported with the expected value against the current value



Here is a rather long (but not complex) example illustrating some basic
OpenOffice.org Python scripting command. A more complete test (dealing also with calc and file export) is provided at the end. These basic commands can be usefull to start dealing with the OpenOffice.org API through pyUNO

We are going to test some basics aspect of Openoffice.org
OOo can be scripted using Python though pyUNO bridge

First, we need to import some classical python modules:

>>> import sys, os, tempfile, time

Then, the pyUNO modules that are delivered within OOo:

>>> import uno, unohelper

We will also need some specific services and nammed constants
from OpenOffice.org API. They can be imported as any regular python module:

>>> from com.sun.star.connection import NoConnectException
>>> from com.sun.star.beans import PropertyValue
>>> from com.sun.star.text.ControlCharacter import PARAGRAPH_BREAK

This last value is a named constant we will need when dealing with Writer.
Let's verify its value:

>>> PARAGRAPH_BREAK
0

Now, we load an helper module that provides some usefull shortcuts when
dealing with pyUNO Bridge:

>>> from oootools import OOoTools

We now are ready to start our tests.
The first action to do is to connect to a listening OOo instance we
launched.

Let's define the listening host we have to reach and the port ...

>>> HOST = 'localhost'
>>> PORT = 11111

We now call out helper connecting class:

>>> ooo = OOoTools(HOST, PORT)
>>> ctx = ooo.ctx
>>> desktop = ooo.desktop

So, we are now connected to the listen OpenOffice.org instance

We open a new blank writer document:

>>> doc = desktop.loadComponentFromURL("private:factory/swriter",'_blank',0,())
>>> doc.Text.String
u''

To populate the document, a cursor is needed:

>>> cursor = doc.Text.createTextCursor()
>>> cursor.ParaStyleName
u'Standard'

The default paragraph style is 'Standard', but we plan to write our outline.
So we change the ParaStyleName under the cursor:

>>> cursor.ParaStyleName = "Heading 1"
>>> cursor.ParaStyleName
u'Heading 1'

It is now time to insert a first text:

>>> doc.Text.insertString(cursor, "Title Level 1", False)
>>> doc.Text.String
u'Title Level 1'

We insert a paragraph break and some other sentences with various styles:

>>> doc.Text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False)
>>> cursor.ParaStyleName = "Heading 2"
>>> doc.Text.insertString(cursor, "Title Level 2", False)
>>> doc.Text.insertControlCharacter(cursor, PARAGRAPH_BREAK, False)
>>> doc.Text.insertString(cursor, "A normal sentence !!!", False)
>>> doc.Text.String
u'Title Level 1nTitle Level 2nA normal sentence !!!'

We did not affect any paragraph style for the last sentence, relying on the
'following style' property of the "Heading 2" style.
So the paragraph style of this new paragraph should be different:

>>> cursor.ParaStyleName == "Heading 2"
False
>>> cursor.ParaStyleName
u'Text body'

To verify our outline, we generate a content index at the start of the
document:

>>> cursor = doc.Text.createTextCursorByRange(doc.Text.Start)
>>> anIndex =doc.createInstance("com.sun.star.text.ContentIndex")
>>> anIndex.supportsService("com.sun.star.text.ContentIndex")
True

Setup some properties:

>>> anIndex.CreateFromOutline = True
>>> anIndex.CreateFromLevelParagraphStyles = True
>>> anIndex.CreateFromChapter = False
>>> anIndex.IsProtected=False

and insert this first index at the cursor:

>>> doc.DocumentIndexes.Count
0
>>> doc.Text.insertTextContent(cursor, anIndex, False)
>>> anIndex.update()
>>> doc.DocumentIndexes.Count
1

We change the title of this index:

>>> anIndex.HeaderSection.Anchor.String = "The testing index"
>>> anIndex.HeaderSection.Anchor.String
u'The testing index'

and verfiy its content:

>>> anIndex.Anchor.String
u'The testing indexnTitle Level 1t1nTitle Level 2t1'
>>> doc.Text.String
u'The testing indexnTitle Level 1t1nTitle Level 2t1nTitle Level 1nTitle Level 2nA normal sentence !!!'

So we have an outline with its index. Lets save this to PDF.

We first define the export filter property:

>>> args = (ooo.makePropertyValue('FilterName','writer_pdf_Export'),)
>>> args
((com.sun.star.beans.PropertyValue){ Name = (string)"FilterName", Handle = (long)0x0, Value = (any){ (string)"writer_pdf_Export" }, State = (com.sun.star.beans.PropertyState)DIRECT_VALUE },)

We define a temp file and verify it is not already in use:

>>> pdf_filename = os.path.join(tempfile.gettempdir() ,str(time.time()) + 'testooo-writer.pdf' )

>>> if os.path.isfile(pdf_filename):
... os.remove(pdf_filename)
...

But OOo only deals with URL notations. We use the helper function
and the store the file to this URL:

>>> url = unohelper.systemPathToFileUrl(pdf_filename )
>>> doc.storeToURL(url,args)

We check briefly the file has been created and that it is not empty:

>>> os.path.isfile(pdf_filename)
True
>>> os.path.getsize(pdf_filename) != 0
True

and finally delete the PDF file:

>>> if os.path.isfile(pdf_filename):
... os.remove(pdf_filename)
...

Close our Writer file and all remaining documents:

>>> doc.close(False)
>>> ooo.closeAll()

That's the end of our test !!!!!


This code is written in a my_test.txt file. There is to be noticed that
taking all the line starting with >>> leads to a workable
code.

Run the doctest


OpenOffice.org is shipped with Python 2.3.4. Doctest is available on this
version but lacks the testfile method only available in python 2.4. There are
some workaround like

  • replacing
    python
    with a newer (be carefull, this will not work if you use OpenOffice.org builds from the project with debian - need
    to rebuild python due to UCS options - Using debian OpenOffice.org build works, of course)
  • quick and dirty : copy a doctest.py file from your python 2.4 to
    /opt/openoffice.org2.0/program/python-core-2.3.4/lib





Once doctest available, you can launch OpenOffice.org Python and test your
file

$ /opt/openoffice.org2.0/program/python
Python 2.3.4 (#1, Feb 1 2006, 21:07:49)
[GCC 3.4.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import doctest
>>> doctest.testfile('my_test.txt')
(0, 156)
>>>

The doctest returns a couple of values : the first is the number of failed
tests (0 here), the second the total number of tests (156 here)

More details is given on errors. Options can be added to the doctest.testfile() method.

To conclude


Python is an efficient language that can be used for OpenOffice.org.
pyUNO programmers can use doctest to evaluate their programs and perform regression tests. This will lead to high quality and robust Openoffice.org Extensions



(Post originally written by Laurent Godard on the old Nuxeo blogs.)