16 feb 2011

oodiff reloaded

Although there exists alread oodiff, it's a bit limited. What's about any format or image changes not reflected in the diff?

I had a oodiff before, but some day it stopped working. Now I renamed it to ooodiff.
Thanks to some notes from here I corrected it. This version should work well with subversion. Here's the code (PEP8/Pyflakes compatible):


#!/usr/bin/python
# Requires python-uno
# Code originally gotten from:
# http://people.warp.es/~xtor/blog/?p=166
# See also: (google for "openoffice uno compare documents")
# http://win32com.goermezer.de/content/view/193/274/
# http://nxsy.org/blog/447.html
# http://www-verimag.imag.fr/~moy/opendocument/


PORT = 2526
OOFFICE = 'ooffice'

# General imports
import filecmp
import os
import shutil
import sys
import time

# pyuno imports
import uno
from com.sun.star.connection import NoConnectException


class OOUnoConnectionError(Exception):
pass


def getRevision(filename, revision, save_as):
"""Export a revision of a document."""
os.system('svn export -r%s "%s" "%s"' % (revision, filename, save_as))
return save_as


def compare(filename, oldrev='', newrev='', force=False):
"""Compare two revisions of a document."""
if not oldrev:
oldrev = 'BASE'
newfilename = '%s%s%s%s' % (filename, '.' + oldrev,
'.' + (newrev or 'workingcopy'), '.diff.tmp')
if not newrev:
shutil.copyfile(filename, newfilename)
else:
getRevision(filename, newrev, newfilename)
oldfilename = '%s%s%s' % (filename, '.' + oldrev, '.tmp')
getRevision(filename, oldrev, oldfilename)
if not force and filecmp.cmp(newfilename, oldfilename):
print ("The files seem to be the same. "
"There are no changes. Use --force run the diff in OO.")
else:
oo = OO()
doc = oo.open(newfilename)
oo.compareCurrentDocument(doc, oldfilename)


class OO:
"""A small class to abstract OpenOffice application."""
def __init__(self, filename=''):
# start OO
print "Starting OO"
os.system("soffice -nodefault "
"'-accept=socket,host=localhost,port=%s;urp;'" % PORT)

# Get the uno component context from the PyUNO runtime
localctx = uno.getComponentContext()
# Create the UnoUrlResolver on the Python side.
resolver = localctx.ServiceManager.createInstanceWithContext(
"com.sun.star.bridge.UnoUrlResolver", localctx)

cnxstr = ("uno:socket,host=localhost,port=%s;urp;"
"StarOffice.ComponentContext")
cnxstr = cnxstr % PORT
# try to connect to OO
ctx = None
retries = 5
while retries:
retries -= 1
try:
ctx = resolver.resolve(cnxstr)
except NoConnectException:
time.sleep(1)
if not ctx:
raise OOUnoConnectionError("Can't connect to OpenOffice")

# Get the ServiceManager object
smgr = ctx.ServiceManager

# Create the Desktop instance
desktop = smgr.createInstance("com.sun.star.frame.Desktop")
# save objects
self.desktop = desktop
self.smgr = smgr
self.ctx = ctx
if filename:
self.open(filename)

def open(self, filename, **kwargs):
"""Open a file."""
print "Opening: %s" % filename
properties = []
for key, value in kwargs.items():
properties.append(self.getProperty(key, value))
properties = tuple(properties)
doc = self.desktop.loadComponentFromURL(
self.convertToURL(filename), "_blank", 0, properties)
return doc

def compareCurrentDocument(self, doc, filename):
"""Compare the current document to the specified one."""
print "Comparing to: %s" % filename

# Sometimes, after opening OO or loading a doc
# we've still not access to the dispatcher or current frame
# so we wait here a bit.
# Get the dispatcher
dispatcher = self.smgr.createInstance("com.sun.star.frame."
"DispatchHelper")

# Show tracked changes and compare documents
frame = doc.getCurrentController().getFrame()
property = self.getProperty('URL', self.convertToURL(filename))
dispatcher.executeDispatch(frame, ".uno:CompareDocuments", "", 0,
(property,))
property = self.getProperty("ShowTrackedChanges", True)
dispatcher.executeDispatch(frame, ".uno:ShowTrackedChanges", "", 0,
(property,))

def getProperty(name, value):
"""Read a OO property."""
prop = uno.createUnoStruct("com.sun.star.beans.PropertyValue")
prop.Name, prop.Value = name, value
return prop
getProperty = staticmethod(getProperty)

def convertToURL(filename):
"""Convert a local filename to URL required by OO."""
return uno.systemPathToFileUrl(os.path.abspath(filename))
convertToURL = staticmethod(convertToURL)


if __name__ == '__main__':
oldrev = newrev = ''
force = '--force' in sys.argv
filename = ''
for arg in sys.argv[1:]:
if arg.startswith('-r'):
if not ':' in arg:
arg += ':'
oldrev, newrev = arg[2:].split(':')
elif not arg.startswith('-'):
filename = arg
if filename:
compare(filename, oldrev, newrev, force)
else:
print """
Usage: oodiff [--force] [-r[:]]
: the file to comparte
--force: force comparison, although files seem the same
-r : same as subversion. If not specified, -rPREV assumed.

This command:
- may not work if OpenOffice.org is already running (because
it has to start with a listening port for UNO to work).

- will create some temporary files (*.tmp). You have delete them
manually when you are finished.
"""