1#!/usr/bin/env python 2# -*- coding: utf-8 -*- 3 4""" 5This python script connects with OpenOffice.org using PyUNO and provides 6us the functionality to control OpenOffice.org. 7 8 # Execute the python-script with the ODT-file as argument 9 python `kde4-config --install data`/calligrawords/scripts/extensions/oouno.py /path/mydoc.odt 10 # Define the hostaddress and port the OpenOffice.org server is running on 11 python `kde4-config --install data`/calligrawords/scripts/extensions/oouno.py --host=192.168.0.1 --port=2002 /path/mydoc.odt 12 13We are using the PyUNO module to access OpenOffice.org. For this an optional hidden 14OpenOffice.org instance need to be started. Then the script connects as client to 15this OpenOffice.org server instance and controls it. 16If the script is run and there connecting to the server failed, then it will (optional) 17startup such a OpenOffice.org server instance and shuts it down again once the work is 18done. A faster way is, to startup and shutdown the server instance by yourself and then 19the script does not need to do it for you each time. 20 21 # Start OpenOffice.org with visible mainwindow 22 soffice -nologo "-accept=socket,host=localhost,port=2002;urp;" 23 # Start OpenOffice.org in background 24 soffice -nologo -norestore -invisible -headless "-accept=socket,host=localhost,port=2002;urp;" 25 26(C)2007 Sebastian Sauer <mail@dipe.org> 27 28http://kross.dipe.org 29http://www.calligra.org/words 30http://udk.openoffice.org/python/python-bridge.html 31 32Dual-licensed under LGPL v2+higher and the BSD license. 33""" 34 35import sys, os, getopt, time, traceback, popen2, subprocess, signal #, threading 36 37try: 38 import uno 39 from com.sun.star.connection import NoConnectException as UnoNoConnectException 40 from com.sun.star.task import ErrorCodeIOException as UnoErrorCodeIOException 41 #from com.sun.star import connection as UnoConnection 42 from unohelper import Base as UnoBase 43 #from unohelper import systemPathToFileUrl, absolutize 44 from com.sun.star.beans import PropertyValue as UnoPropertyValue 45 #from com.sun.star.uno import Exception as UnoException 46 #from com.sun.star.io import IOException as UnoIOException 47 from com.sun.star.io import XOutputStream as UnoXOutputStream 48except ImportError, e: 49 print >> sys.stderr, "Failed to import the OpenOffice.org PyUNO python module. This script requires the PyUNO python module to communicate with the OpenOffice.org server." 50 raise e 51 52class UnoConfig: 53 """ The configuration for to access the OpenOffice.org functionality. """ 54 55 def __init__(self): 56 # The host the OpenOffice.org Server runs on. 57 self.host= "localhost" 58 # The port the OpenOffice.org Server runs on. 59 self.port = 2002 60 # Number of seconds we try to connect before aborting, set to 0 to try to 61 # connect only once and -1 to disable timeout and try to connect forever. 62 self.timeout = 45 63 # Startup OpenOffice.org instance if not running already. 64 self.startupServer = True 65 # Hide the client window. 66 self.hideClient = True 67 # Close new documents once not needed any longer. 68 self.autoCloseDocument = True 69 # The used logger we write debug-output to. 70 self._logger = sys.stdout 71 72 # The file to load. 73 self.loadfile = "" 74 # The file to save. 75 self.savefile = "" 76 77class UnoDocument: 78 """ Class that represents an OpenOffice.org UNO document within an UnoClient. """ 79 80 class OutputStream( UnoBase, UnoXOutputStream ): 81 """ The OutputStream class offers the default implementation of an output-stream 82 the content of the document could be written to. """ 83 84 def __init__(self): 85 self.filterName = "Text (encoded)" 86 #self.filterName = "HTML (StarWriter)" 87 #self.filterName = "writer_pdf_Export" 88 self.closed = 0 89 def closeOutput(self): 90 self.closed = 1 91 def writeBytes(self, seq): 92 sys.stdout.write(seq.value) 93 def flush(self): 94 pass 95 96 def __init__(self, unoConfig, desktop): 97 self.unoConfig = unoConfig 98 self.desktop = desktop 99 self.doc = None 100 101 def __del__(self): 102 if self.unoConfig.autoCloseDocument: 103 self.close() 104 105 def load(self, fileUrl): 106 if not fileUrl.startswith("file://"): 107 raise "Invalid file url \"%s\"" % fileUrl 108 109 fileName = fileUrl[7:] 110 if not os.path.isfile(fileName): 111 raise "There exist no such file \"%s\"" % fileName 112 113 self.close() 114 115 fileBaseName = os.path.basename(fileName) 116 self.unoConfig._logger.write("Loading document %s ...\n" % fileBaseName) 117 118 inProps = [] 119 if self.unoConfig.hideClient: 120 inProps.append( UnoPropertyValue("Hidden" , 0 , True, 0) ) 121 122 self.doc = self.desktop.loadComponentFromURL(fileUrl , "_blank", 0, tuple(inProps)) 123 if not self.doc: 124 raise "Failed to load document %s" % fileName 125 126 self.unoConfig._logger.write("Done loading document %s\n" % fileBaseName) 127 128 def save(self, fileUrl): 129 if not self.doc: 130 raise "Failed to save cause there is no document" 131 if fileUrl.startswith("file://"): 132 fileUrl = fileUrl[7:] 133 if not fileUrl: 134 raise "Failed to save cause invalid file \"%s\" defined." % fileUrl 135 136 try: 137 import unohelper 138 outUrl = unohelper.systemPathToFileUrl(fileUrl) 139 outProps = [] 140 141 fileExt = os.path.splitext(fileUrl)[1].lower() 142 if fileExt == '.txt' or fileExt == '.text': 143 outProps.append( UnoPropertyValue('FilterName', 0, 'Text (encoded)', 0) ) 144 elif fileExt == '.htm' or fileExt == '.html': 145 outProps.append( UnoPropertyValue('FilterName', 0, 'HTML (StarWriter)', 0) ) 146 elif fileExt == '.pdf': 147 outProps.append( UnoPropertyValue('FilterName', 0, 'writer_pdf_Export', 0) ) 148 #else: opendocument... 149 150 print "Save to: %s" % outUrl 151 self.doc.storeToURL(outUrl, tuple(outProps)) 152 except: 153 traceback.print_exc() 154 155 def close(self): 156 if self.doc: 157 self.doc.close(True) 158 self.doc = None 159 160 def read(self, outputstream = OutputStream()): 161 outProps = [] 162 outProps.append( UnoPropertyValue("FilterName" , 0, outputstream.filterName, 0) ) 163 outProps.append( UnoPropertyValue("Overwrite" , 0, True , 0) ) 164 outProps.append( UnoPropertyValue("OutputStream", 0, outputstream, 0) ) 165 166 try: 167 self.doc.storeToURL("private:stream", tuple(outProps)) 168 except UnoErrorCodeIOException, e: 169 self.unoConfig._logger.write("ErrorCodeIOException: %s" % e.ErrCode) 170 171class UnoServer: 172 """ Class that provides functionality to deal with the OpenOffice.org server instance. """ 173 174 def __init__(self, unoConfig): 175 self.unoConfig = unoConfig 176 177 self.unoConfig._logger.write("Starting OpenOffice.org at %s:%s ...\n" % (self.unoConfig.host,self.unoConfig.port)) 178 try: 179 self.process = popen2.Popen3([ #subprocess.Popen([ 180 'soffice', 181 '-nologo', #don't show startup screen. 182 '-minimized', #keep startup bitmap minimized. 183 '-norestore', #suppress restart/restore after fatal errors. 184 '-invisible', #no startup screen, no default document and no UI. 185 '-headless', 186 "-accept=socket,host=%s,port=%s;urp;" % (self.unoConfig.host,self.unoConfig.port) 187 ], ) 188 except IOError: 189 traceback.print_exc() 190 raise 191 192 def __del__(self): 193 if hasattr(self,'process'): 194 os.kill(self.process, signal.SIGKILL) 195 #os.kill(self.process.pid, signal.SIGINT) 196 #killedpid, stat = os.waitpid(self.process, os.WNOHANG) 197 #if killedpid == 0: 198 #print >> sys.stderr, "Failed to kill OpenOffice.org Server process" 199 200class UnoClient: 201 """ Class that provides the client-functionality to deal with an OpenOffice.org 202 server instance. """ 203 204 def __init__(self, unoConfig): 205 self.unoConfig = unoConfig 206 self.unoServer = None 207 self.document = None 208 209 # get the uno component context from the PyUNO runtime 210 localContext = uno.getComponentContext() 211 # create the UnoUrlResolver 212 resolver = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext) 213 214 # connect to the running office 215 elapsed = 0 216 while True: 217 self.unoConfig._logger.write("Trying to connect with OpenOffice.org on %s:%s ...\n" % (self.unoConfig.host,self.unoConfig.port)) 218 try: 219 # the UNO url we like to resolve 220 url = "uno:socket,host=%s,port=%s;urp;StarOffice.ComponentContext" % (self.unoConfig.host,self.unoConfig.port) 221 # fetch the ComponentContext 222 componentContext = resolver.resolve(url) 223 # fetch the service manager 224 self.servicemanager = componentContext.ServiceManager 225 # create the desktop 226 self.desktop = self.servicemanager.createInstanceWithContext("com.sun.star.frame.Desktop", componentContext) 227 # create the UnoDocument instance 228 self.unoDocument = UnoDocument(self.unoConfig, self.desktop) 229 # job is done 230 break 231 except UnoNoConnectException: 232 self.unoConfig._logger.write("Failed to connect with OpenOffice.org on %s:%s ...\n" % (self.unoConfig.host,self.unoConfig.port)) 233 if self.unoConfig.startupServer: 234 if not self.unoServer: 235 self.unoServer = UnoServer(self.unoConfig) 236 if self.unoConfig.timeout >= 0: 237 if elapsed >= self.unoConfig.timeout: 238 raise "Failed to connect to OpenOffice.org on %s:%s" % (self.unoConfig.host,self.unoConfig.port) 239 elapsed += 1 240 time.sleep(1) 241 242 self.unoConfig._logger.write("Connected with OpenOffice.org on %s:%s\n" % (self.unoConfig.host,self.unoConfig.port)) 243 244 def __del__(self): 245 if self.unoServer: 246 self.desktop.terminate() 247 time.sleep(1) 248 self.unoServer = None 249 250class UnoController: 251 """ Class that offers high level access to control all aspects of OpenOffice.org 252 we may need. """ 253 254 def __init__(self, unoConfig = UnoConfig()): 255 self.unoConfig = unoConfig 256 self.unoClient = None 257 258 def connect(self): 259 self.unoClient = UnoClient(self.unoConfig) 260 261 def disconnect(self): 262 self.unoClient = None 263 264 def loadDocument(self, fileUrl): 265 if not self.unoClient: 266 raise "The client is not connected" 267 self.unoClient.unoDocument.load(fileUrl) 268 269 def saveDocument(self, fileUrl): 270 if not self.unoClient: 271 raise "The client is not connected" 272 self.unoClient.unoDocument.save(fileUrl) 273 274 def writeDocument(self, outputstream = UnoDocument.OutputStream()): 275 self.unoClient.unoDocument.read(outputstream) 276 277#class WordsOutputStream( UnoDocument.OutputStream ): 278 #def __init__(self, unoConfig): 279 ##self.filterName = "Text (encoded)" 280 #self.filterName = "HTML (StarWriter)" 281 ##self.filterName = "writer_pdf_Export" 282 283 #import Words 284 #self.doc = Words.mainFrameSet().document() 285 #self.html = "" 286 287 #def closeOutput(self): 288 ##self.doc.setHtml(self.html) 289 ##self.html = "" 290 #pass 291 #def writeBytes(self, seq): 292 #self.html += seq.value 293 #def flush(self): 294 #if self.html != "": 295 ##print self.html 296 #self.doc.setHtml(self.html) 297 #self.html = "" 298 299def start(unoconfig, opts, args): 300 print "ARGS: ", "".join(args) 301 print "OPTS: ", "\n".join( [ "%s=%s" % (s,getattr(unoconfig,s)) for s in dir(unoconfig) if not s.startswith('_') ] ) 302 303 #class ProgressThread(threading.Thread): 304 #def __init__(self, unoconfig): 305 #self.done = False 306 #self.unoconfig = unoconfig 307 #threading.Thread.__init__(self) 308 ##self.progress = self.forms.showProgressDialog("Import...", "Initialize...") 309 ##self.progress.labelText = "Loading %s" % file 310 ##self.progress.value = 0 311 ##self.progress.update() 312 #def finish(self): 313 #if not self.done: 314 ##self.progress.value = 100 315 #self.done = True 316 #def run(self): 317 #while not self.done: 318 ##if self.value == self.progress.value: 319 ## self.value = self.value + 1 320 ##self.progress.value = self.value 321 #time.sleep(1) 322 ##self.progress.reset() 323 #progressThread = ProgressThread(unoconfig) 324 #progressThread.start() 325 #progressThread.finish() 326 #progressThread.join() # wait till the thread finished 327 328 controller = UnoController(unoconfig) 329 controller.connect() 330 try: 331 if unoconfig.loadfile: 332 controller.loadDocument( "file://%s" % unoconfig.loadfile ) 333 334 if unoconfig.savefile: 335 controller.saveDocument( "file://%s" % unoconfig.savefile ) 336 337 #TODO disabled for now 338 #outputstream = UnoDocument.OutputStream() 339 #controller.writeDocument(outputstream) 340 #outputstream.flush() 341 342 finally: 343 controller.disconnect() 344 345def main(argv): 346 unoconfig = UnoConfig() 347 348 def usage(): 349 print "Syntax:\n %s [options]" % os.path.split(argv[0])[1] 350 print "\n ".join([ "Options:", "--help prints usage informations", ]) 351 for s in dir(unoconfig): 352 if not s.startswith('_'): 353 v = getattr(unoconfig,s) 354 print " --%s (%s, %s)" % (s,type(v).__name__,v) 355 try: 356 opts, args = getopt.getopt(argv[1:], "h", ["help"] + [ "%s=" % s for s in dir(unoconfig) if not s.startswith('_') ]) 357 except getopt.GetoptError, e: 358 usage() 359 print "\nArgument Error: ",e,"\n" 360 sys.exit(2) 361 362 for opt, arg in opts: 363 if opt in ("-h", "--help"): 364 usage() 365 sys.exit() 366 elif opt.startswith('--'): 367 n = opt[2:] 368 if not n.startswith('_'): 369 try: 370 t = type( getattr(unoconfig,n) ) 371 if t == bool: 372 setattr(unoconfig, n, arg and arg != 'None' and arg != 'False' and arg != '0') 373 else: 374 setattr(unoconfig, n, t(arg)) 375 except ValueError, e: 376 print "Argument Error: ",e,"\n" 377 usage() 378 sys.exit(2) 379 380 start(unoconfig, opts, args) 381 382if __name__ == "__main__": 383 main(sys.argv) 384