1# ************************************************************* 2# 3# Licensed to the Apache Software Foundation (ASF) under one 4# or more contributor license agreements. See the NOTICE file 5# distributed with this work for additional information 6# regarding copyright ownership. The ASF licenses this file 7# to you under the Apache License, Version 2.0 (the 8# "License"); you may not use this file except in compliance 9# with the License. You may obtain a copy of the License at 10# 11# http://www.apache.org/licenses/LICENSE-2.0 12# 13# Unless required by applicable law or agreed to in writing, 14# software distributed under the License is distributed on an 15# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16# KIND, either express or implied. See the License for the 17# specific language governing permissions and limitations 18# under the License. 19# 20# ************************************************************* 21 22# XScript implementation for python 23import uno 24import unohelper 25import sys 26import os 27import imp 28import time 29import ast 30 31try: 32 unicode 33except NameError: 34 unicode = str 35 36class LogLevel: 37 NONE = 0 # production level 38 ERROR = 1 # for script developers 39 DEBUG = 2 # for script framework developers 40 41PYSCRIPT_LOG_ENV = "PYSCRIPT_LOG_LEVEL" 42PYSCRIPT_LOG_STDOUT_ENV = "PYSCRIPT_LOG_STDOUT" 43 44# Configuration ---------------------------------------------------- 45LogLevel.use = LogLevel.NONE 46if os.environ.get(PYSCRIPT_LOG_ENV) == "ERROR": 47 LogLevel.use = LogLevel.ERROR 48elif os.environ.get(PYSCRIPT_LOG_ENV) == "DEBUG": 49 LogLevel.use = LogLevel.DEBUG 50 51# True, writes to stdout (difficult on windows) 52# False, writes to user/Scripts/python/log.txt 53LOG_STDOUT = os.environ.get(PYSCRIPT_LOG_STDOUT_ENV, "1") != "0" 54 55ENABLE_EDIT_DIALOG=False # offers a minimal editor for editing. 56#------------------------------------------------------------------- 57 58def encfile(uni): 59 if sys.version_info[0] > 2: 60 return uni 61 else: 62 return uni.encode( sys.getfilesystemencoding()) 63 64def lastException2String(): 65 (excType,excInstance,excTraceback) = sys.exc_info() 66 ret = str(excType) + ": "+str(excInstance) + "\n" + \ 67 uno._uno_extract_printable_stacktrace( excTraceback ) 68 return ret 69 70def logLevel2String( level ): 71 ret = " NONE" 72 if level == LogLevel.ERROR: 73 ret = "ERROR" 74 elif level >= LogLevel.DEBUG: 75 ret = "DEBUG" 76 return ret 77 78def getLogTarget(): 79 ret = sys.stdout 80 if not LOG_STDOUT: 81 try: 82 pathSubst = uno.getComponentContext().ServiceManager.createInstance( 83 "com.sun.star.util.PathSubstitution" ) 84 userInstallation = pathSubst.getSubstituteVariableValue( "user" ) 85 if len( userInstallation ) > 0: 86 systemPath = uno.fileUrlToSystemPath( userInstallation + "/Scripts/python/log.txt" ) 87 ret = open( systemPath , "a" ) 88 except Exception as e: 89 print("Exception during creation of pythonscript logfile: "+ lastException2String() + "\n, delagating log to stdout\n") 90 return ret 91 92class Logger(LogLevel): 93 def __init__(self , target ): 94 self.target = target 95 96 def isDebugLevel( self ): 97 return self.use >= self.DEBUG 98 99 def debug( self, msg ): 100 if self.isDebugLevel(): 101 self.log( self.DEBUG, msg ) 102 103 def isErrorLevel( self ): 104 return self.use >= self.ERROR 105 106 def error( self, msg ): 107 if self.isErrorLevel(): 108 self.log( self.ERROR, msg ) 109 110 def log( self, level, msg ): 111 if self.use >= level: 112 try: 113 self.target.write( 114 time.asctime() + 115 " [" + 116 logLevel2String( level ) + 117 "] " + 118 encfile(msg) + 119 "\n" ) 120 self.target.flush() 121 except Exception as e: 122 print("Error during writing to stdout: " +lastException2String() + "\n") 123 124log = Logger( getLogTarget() ) 125 126log.debug( "pythonscript loading" ) 127 128#from com.sun.star.lang import typeOfXServiceInfo, typeOfXTypeProvider 129from com.sun.star.uno import RuntimeException 130from com.sun.star.lang import XServiceInfo 131from com.sun.star.io import IOException 132from com.sun.star.ucb import CommandAbortedException, XCommandEnvironment, XProgressHandler, Command 133from com.sun.star.task import XInteractionHandler 134from com.sun.star.beans import XPropertySet, Property 135from com.sun.star.container import XNameContainer 136from com.sun.star.xml.sax import XDocumentHandler, InputSource 137from com.sun.star.uno import Exception as UnoException 138from com.sun.star.script import XInvocation 139from com.sun.star.awt import XActionListener 140 141from com.sun.star.script.provider import XScriptProvider, XScript, XScriptContext, ScriptFrameworkErrorException 142from com.sun.star.script.browse import XBrowseNode 143from com.sun.star.script.browse.BrowseNodeTypes import SCRIPT, CONTAINER, ROOT 144from com.sun.star.util import XModifyListener 145 146LANGUAGENAME = "Python" 147GLOBAL_SCRIPTCONTEXT_NAME = "XSCRIPTCONTEXT" 148CALLABLE_CONTAINER_NAME = "g_exportedScripts" 149 150# pythonloader looks for a static g_ImplementationHelper variable 151g_ImplementationHelper = unohelper.ImplementationHelper() 152g_implName = "org.openoffice.pyuno.LanguageScriptProviderFor"+LANGUAGENAME 153 154 155 156BLOCK_SIZE = 65536 157def readTextFromStream( inputStream ): 158 # read the file 159 code = uno.ByteSequence( b"" ) 160 while True: 161 read,out = inputStream.readBytes( None , BLOCK_SIZE ) 162 code = code + out 163 if read < BLOCK_SIZE: 164 break 165 if sys.version_info[0] > 2: 166 return str( code.value, 'utf-8' ) 167 else: 168 return code.value 169 170def toIniName( str ): 171 # TODO: what is the official way to get to know whether i am on the windows platform ? 172 if( hasattr(sys , "dllhandle") ): 173 return str + ".ini" 174 return str + "rc" 175 176 177""" definition: storageURI is the system dependent, absolute file url, where the script is stored on disk 178 scriptURI is the system independent uri 179""" 180class MyUriHelper: 181 182 def __init__( self, ctx, location ): 183 self.s_UriMap = \ 184 { "share" : "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" + toIniName( "bootstrap") + "::BaseInstallation}/share/Scripts/python" , \ 185 "share:uno_packages" : "vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE/uno_packages", \ 186 "user" : "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" + toIniName( "bootstrap") + "::UserInstallation}/user/Scripts/python" , \ 187 "user:uno_packages" : "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages" } 188 self.m_uriRefFac = ctx.ServiceManager.createInstanceWithContext("com.sun.star.uri.UriReferenceFactory",ctx) 189 if location.startswith( "vnd.sun.star.tdoc" ): 190 self.m_baseUri = location + "/Scripts/python" 191 self.m_scriptUriLocation = "document" 192 else: 193 self.m_baseUri = expandUri( self.s_UriMap[location] ) 194 self.m_scriptUriLocation = location 195 log.debug( "initialized urihelper with baseUri="+self.m_baseUri + ",m_scriptUriLocation="+self.m_scriptUriLocation ) 196 197 def getRootStorageURI( self ): 198 return self.m_baseUri 199 200 def getStorageURI( self, scriptURI ): 201 return self.scriptURI2StorageUri(scriptURI) 202 203 def getScriptURI( self, storageURI ): 204 return self.storageURI2ScriptUri(storageURI) 205 206 def storageURI2ScriptUri( self, storageURI ): 207 if not storageURI.startswith( self.m_baseUri ): 208 message = "pythonscript: storage uri '" + storageURI + "' not in base uri '" + self.m_baseUri + "'" 209 log.debug( message ) 210 raise RuntimeException( message ) 211 212 ret = "vnd.sun.star.script:" + \ 213 storageURI[len(self.m_baseUri)+1:].replace("/","|") + \ 214 "?language=" + LANGUAGENAME + "&location=" + self.m_scriptUriLocation 215 log.debug( "converting storageURI="+storageURI + " to scriptURI=" + ret ) 216 return ret 217 218 def scriptURI2StorageUri( self, scriptURI ): 219 try: 220 myUri = self.m_uriRefFac.parse(scriptURI) 221 ret = self.m_baseUri + "/" + myUri.getName().replace( "|", "/" ) 222 log.debug( "converting scriptURI="+scriptURI + " to storageURI=" + ret ) 223 return ret 224 except UnoException as e: 225 log.error( "error during converting scriptURI="+scriptURI + ": " + e.Message) 226 raise RuntimeException( "pythonscript:scriptURI2StorageUri: " +e.getMessage(), None ) 227 except Exception as e: 228 log.error( "error during converting scriptURI="+scriptURI + ": " + str(e)) 229 raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + str(e), None ) 230 231 232class ModuleEntry: 233 def __init__( self, lastRead, module ): 234 self.lastRead = lastRead 235 self.module = module 236 237def hasChanged( oldDate, newDate ): 238 return newDate.Year > oldDate.Year or \ 239 newDate.Month > oldDate.Month or \ 240 newDate.Day > oldDate.Day or \ 241 newDate.Hours > oldDate.Hours or \ 242 newDate.Minutes > oldDate.Minutes or \ 243 newDate.Seconds > oldDate.Seconds or \ 244 newDate.HundredthSeconds > oldDate.HundredthSeconds 245 246def ensureSourceState( code ): 247 if not code.endswith( "\n" ): 248 code = code + "\n" 249 code = code.replace( "\r", "" ) 250 return code 251 252 253def checkForPythonPathBesideScript( url ): 254 if url.startswith( "file:" ): 255 path = unohelper.fileUrlToSystemPath( url+"/pythonpath.zip" ); 256 log.log( LogLevel.DEBUG, "checking for existence of " + path ) 257 if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path: 258 log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" ) 259 sys.path.append( path ) 260 261 path = unohelper.fileUrlToSystemPath( url+"/pythonpath" ); 262 log.log( LogLevel.DEBUG, "checking for existence of " + path ) 263 if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path: 264 log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" ) 265 sys.path.append( path ) 266 267 268class ScriptContext(unohelper.Base): 269 def __init__( self, ctx, doc, inv ): 270 self.ctx = ctx 271 self.doc = doc 272 self.inv = inv 273 274 # XScriptContext 275 def getDocument(self): 276 if self.doc: 277 return self.doc 278 return self.getDesktop().getCurrentComponent() 279 280 def getDesktop(self): 281 return self.ctx.ServiceManager.createInstanceWithContext( 282 "com.sun.star.frame.Desktop", self.ctx ) 283 284 def getComponentContext(self): 285 return self.ctx 286 287 def getInvocationContext(self): 288 return self.inv 289 290#---------------------------------- 291# Global Module Administration 292# does not fit together with script 293# engine lifetime management 294#---------------------------------- 295#g_scriptContext = ScriptContext( uno.getComponentContext(), None ) 296#g_modules = {} 297#def getModuleByUrl( url, sfa ): 298# entry = g_modules.get(url) 299# load = True 300# lastRead = sfa.getDateTimeModified( url ) 301# if entry: 302# if hasChanged( entry.lastRead, lastRead ): 303# log.debug("file " + url + " has changed, reloading") 304# else: 305# load = False 306# 307# if load: 308# log.debug( "opening >" + url + "<" ) 309# 310# code = readTextFromStream( sfa.openFileRead( url ) ) 311 312 # execute the module 313# entry = ModuleEntry( lastRead, imp.new_module("ooo_script_framework") ) 314# entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = g_scriptContext 315# entry.module.__file__ = url 316# exec code in entry.module.__dict__ 317# g_modules[ url ] = entry 318# log.debug( "mapped " + url + " to " + str( entry.module ) ) 319# return entry.module 320 321class ProviderContext: 322 def __init__( self, storageType, sfa, uriHelper, scriptContext ): 323 self.storageType = storageType 324 self.sfa = sfa 325 self.uriHelper = uriHelper 326 self.scriptContext = scriptContext 327 self.modules = {} 328 self.rootUrl = None 329 self.mapPackageName2Path = None 330 331 def getTransientPartFromUrl( self, url ): 332 rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1) 333 return rest[0:rest.find("/")] 334 335 def getPackageNameFromUrl( self, url ): 336 rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1) 337 start = rest.find("/") +1 338 return rest[start:rest.find("/",start)] 339 340 341 def removePackageByUrl( self, url ): 342 items = list(self.mapPackageName2Path.items()) 343 for i in items: 344 if url in i[1].pathes: 345 self.mapPackageName2Path.pop(i[0]) 346 break 347 348 def addPackageByUrl( self, url ): 349 packageName = self.getPackageNameFromUrl( url ) 350 transientPart = self.getTransientPartFromUrl( url ) 351 log.debug( "addPackageByUrl : " + packageName + ", " + transientPart + "("+url+")" + ", rootUrl="+self.rootUrl ) 352 if packageName in self.mapPackageName2Path: 353 package = self.mapPackageName2Path[ packageName ] 354 package.pathes = package.pathes + (url, ) 355 else: 356 package = Package( (url,), transientPart) 357 self.mapPackageName2Path[ packageName ] = package 358 359 def isUrlInPackage( self, url ): 360 values = list(self.mapPackageName2Path.values()) 361 for i in values: 362# print "checking " + url + " in " + str(i.pathes) 363 if url in i.pathes: 364 return True 365# print "false" 366 return False 367 368 def setPackageAttributes( self, mapPackageName2Path, rootUrl ): 369 self.mapPackageName2Path = mapPackageName2Path 370 self.rootUrl = rootUrl 371 372 def getPersistentUrlFromStorageUrl( self, url ): 373 # package name is the second directory 374 ret = url 375 if self.rootUrl: 376 pos = len( self.rootUrl) +1 377 ret = url[0:pos]+url[url.find("/",pos)+1:len(url)] 378 log.debug( "getPersistentUrlFromStorageUrl " + url + " -> "+ ret) 379 return ret 380 381 def getStorageUrlFromPersistentUrl( self, url): 382 ret = url 383 if self.rootUrl: 384 pos = len(self.rootUrl)+1 385 packageName = url[pos:url.find("/",pos+1)] 386 package = self.mapPackageName2Path[ packageName ] 387 ret = url[0:pos]+ package.transientPathElement + "/" + url[pos:len(url)] 388 log.debug( "getStorageUrlFromPersistentUrl " + url + " -> "+ ret) 389 return ret 390 391 def getFuncsByUrl( self, url ): 392 src = readTextFromStream( self.sfa.openFileRead( url ) ) 393 checkForPythonPathBesideScript( url[0:url.rfind('/')] ) 394 src = ensureSourceState( src ) 395 396 allFuncs = [] 397 g_exportedScripts = [] 398 399 a = ast.parse(src, url) 400 401 if isinstance(a, ast.Module): 402 for node in a.body: 403 if isinstance(node, ast.FunctionDef): 404 allFuncs.append(node.name) 405 elif isinstance(node, ast.Assign): 406 is_exported = False 407 for subnode in node.targets: 408 if isinstance(subnode, ast.Name) and \ 409 subnode.id == "g_exportedScripts": 410 is_exported = True 411 break 412 if is_exported: 413 value_node = node.value 414 if isinstance(value_node, ast.List) or \ 415 isinstance(value_node, ast.Tuple): 416 for elt in value_node.elts: 417 if isinstance(elt, ast.Str): 418 g_exportedScripts.append(elt.s) 419 elif isinstance(elt, ast.Name): 420 g_exportedScripts.append(elt.id) 421 elif isinstance(value_node, ast.Str): 422 g_exportedScripts.append(value_node.s) 423 elif isinstance(value_node, ast.Name): 424 g_exportedScripts.append(value_node.id) 425 return g_exportedScripts 426 return allFuncs 427 428 def getModuleByUrl( self, url ): 429 entry = self.modules.get(url) 430 load = True 431 lastRead = self.sfa.getDateTimeModified( url ) 432 if entry: 433 if hasChanged( entry.lastRead, lastRead ): 434 log.debug( "file " + url + " has changed, reloading" ) 435 else: 436 load = False 437 438 if load: 439 log.debug( "opening >" + url + "<" ) 440 441 src = readTextFromStream( self.sfa.openFileRead( url ) ) 442 checkForPythonPathBesideScript( url[0:url.rfind('/')] ) 443 src = ensureSourceState( src ) 444 445 # execute the module 446 entry = ModuleEntry( lastRead, imp.new_module("ooo_script_framework") ) 447 entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.scriptContext 448 449 code = None 450 if url.startswith( "file:" ): 451 code = compile( src, encfile(uno.fileUrlToSystemPath( url ) ), "exec" ) 452 else: 453 code = compile( src, url, "exec" ) 454 exec(code, entry.module.__dict__) 455 entry.module.__file__ = url 456 self.modules[ url ] = entry 457 log.debug( "mapped " + url + " to " + str( entry.module ) ) 458 return entry.module 459 460#-------------------------------------------------- 461def isScript( candidate ): 462 ret = False 463 if isinstance( candidate, type(isScript) ): 464 ret = True 465 return ret 466 467#------------------------------------------------------- 468class ScriptBrowseNode( unohelper.Base, XBrowseNode , XPropertySet, XInvocation, XActionListener ): 469 def __init__( self, provCtx, uri, fileName, funcName ): 470 self.fileName = fileName 471 self.funcName = funcName 472 self.provCtx = provCtx 473 self.uri = uri 474 475 def getName( self ): 476 return self.funcName 477 478 def getChildNodes(self): 479 return () 480 481 def hasChildNodes(self): 482 return False 483 484 def getType( self): 485 return SCRIPT 486 487 def getPropertyValue( self, name ): 488 ret = None 489 try: 490 if name == "URI": 491 ret = self.provCtx.uriHelper.getScriptURI( 492 self.provCtx.getPersistentUrlFromStorageUrl( self.uri + "$" + self.funcName ) ) 493 elif name == "Editable" and ENABLE_EDIT_DIALOG: 494 ret = not self.provCtx.sfa.isReadOnly( self.uri ) 495 496 log.debug( "ScriptBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) ) 497 except Exception as e: 498 log.error( "ScriptBrowseNode.getPropertyValue error " + lastException2String()) 499 raise 500 501 return ret 502 def setPropertyValue( self, name, value ): 503 log.debug( "ScriptBrowseNode.setPropertyValue called " + name + "=" +str(value ) ) 504 def getPropertySetInfo( self ): 505 log.debug( "ScriptBrowseNode.getPropertySetInfo called " ) 506 return None 507 508 def getIntrospection( self ): 509 return None 510 511 def invoke( self, name, params, outparamindex, outparams ): 512 if name == "Editable": 513 servicename = "com.sun.star.awt.DialogProvider" 514 ctx = self.provCtx.scriptContext.getComponentContext() 515 dlgprov = ctx.ServiceManager.createInstanceWithContext( 516 servicename, ctx ) 517 518 self.editor = dlgprov.createDialog( 519 "vnd.sun.star.script:" + 520 "ScriptBindingLibrary.MacroEditor?location=application") 521 522 code = readTextFromStream(self.provCtx.sfa.openFileRead(self.uri)) 523 code = ensureSourceState( code ) 524 self.editor.getControl("EditorTextField").setText(code) 525 526 self.editor.getControl("RunButton").setActionCommand("Run") 527 self.editor.getControl("RunButton").addActionListener(self) 528 self.editor.getControl("SaveButton").setActionCommand("Save") 529 self.editor.getControl("SaveButton").addActionListener(self) 530 531 self.editor.execute() 532 533 return None 534 535 def actionPerformed( self, event ): 536 try: 537 if event.ActionCommand == "Run": 538 code = self.editor.getControl("EditorTextField").getText() 539 code = ensureSourceState( code ) 540 mod = imp.new_module("ooo_script_framework") 541 mod.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.provCtx.scriptContext 542 exec(code, mod.__dict__) 543 values = mod.__dict__.get( CALLABLE_CONTAINER_NAME , None ) 544 if not values: 545 values = list(mod.__dict__.values()) 546 547 for i in values: 548 if isScript( i ): 549 i() 550 break 551 552 elif event.ActionCommand == "Save": 553 toWrite = uno.ByteSequence( 554 str( 555 self.editor.getControl("EditorTextField").getText().encode( 556 sys.getdefaultencoding())) ) 557 copyUrl = self.uri + ".orig" 558 self.provCtx.sfa.move( self.uri, copyUrl ) 559 out = self.provCtx.sfa.openFileWrite( self.uri ) 560 out.writeBytes( toWrite ) 561 out.close() 562 self.provCtx.sfa.kill( copyUrl ) 563# log.debug("Save is not implemented yet") 564# text = self.editor.getControl("EditorTextField").getText() 565# log.debug("Would save: " + text) 566 except Exception as e: 567 # TODO: add an error box here ! 568 log.error( lastException2String() ) 569 570 571 def setValue( self, name, value ): 572 return None 573 574 def getValue( self, name ): 575 return None 576 577 def hasMethod( self, name ): 578 return False 579 580 def hasProperty( self, name ): 581 return False 582 583 584#------------------------------------------------------- 585class FileBrowseNode( unohelper.Base, XBrowseNode ): 586 def __init__( self, provCtx, uri , name ): 587 self.provCtx = provCtx 588 self.uri = uri 589 self.name = name 590 self.funcnames = None 591 592 def getName( self ): 593 return self.name 594 595 def getChildNodes(self): 596 ret = () 597 try: 598 self.funcnames = self.provCtx.getFuncsByUrl( self.uri ) 599 600 scriptNodeList = [] 601 for i in self.funcnames: 602 scriptNodeList.append( 603 ScriptBrowseNode( 604 self.provCtx, self.uri, self.name, i )) 605 ret = tuple( scriptNodeList ) 606 log.debug( "returning " +str(len(ret)) + " ScriptChildNodes on " + self.uri ) 607 except Exception as e: 608 text = lastException2String() 609 log.error( "Error while evaluating " + self.uri + ":" + text ) 610 raise 611 return ret 612 613 def hasChildNodes(self): 614 try: 615 return len(self.getChildNodes()) > 0 616 except Exception as e: 617 return False 618 619 def getType( self): 620 return CONTAINER 621 622 623 624class DirBrowseNode( unohelper.Base, XBrowseNode ): 625 def __init__( self, provCtx, name, rootUrl ): 626 self.provCtx = provCtx 627 self.name = name 628 self.rootUrl = rootUrl 629 630 def getName( self ): 631 return self.name 632 633 def getChildNodes( self ): 634 try: 635 log.debug( "DirBrowseNode.getChildNodes called for " + self.rootUrl ) 636 contents = self.provCtx.sfa.getFolderContents( self.rootUrl, True ) 637 browseNodeList = [] 638 for i in contents: 639 if i.endswith( ".py" ): 640 log.debug( "adding filenode " + i ) 641 browseNodeList.append( 642 FileBrowseNode( self.provCtx, i, i[i.rfind("/")+1:len(i)-3] ) ) 643 elif self.provCtx.sfa.isFolder( i ) and not i.endswith("/pythonpath"): 644 log.debug( "adding DirBrowseNode " + i ) 645 browseNodeList.append( DirBrowseNode( self.provCtx, i[i.rfind("/")+1:len(i)],i)) 646 return tuple( browseNodeList ) 647 except Exception as e: 648 text = lastException2String() 649 log.error( "DirBrowseNode error: " + str(e) + " while evaluating " + self.rootUrl) 650 log.error( text) 651 return () 652 653 def hasChildNodes( self ): 654 return True 655 656 def getType( self ): 657 return CONTAINER 658 659 def getScript( self, uri ): 660 log.debug( "DirBrowseNode getScript " + uri + " invoked" ) 661 raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 ) 662 663 664class ManifestHandler( XDocumentHandler, unohelper.Base ): 665 def __init__( self, rootUrl ): 666 self.rootUrl = rootUrl 667 668 def startDocument( self ): 669 self.urlList = [] 670 671 def endDocument( self ): 672 pass 673 674 def startElement( self , name, attlist): 675 if name == "manifest:file-entry": 676 if attlist.getValueByName( "manifest:media-type" ) == "application/vnd.sun.star.framework-script": 677 self.urlList.append( 678 self.rootUrl + "/" + attlist.getValueByName( "manifest:full-path" ) ) 679 680 def endElement( self, name ): 681 pass 682 683 def characters ( self, chars ): 684 pass 685 686 def ignoreableWhitespace( self, chars ): 687 pass 688 689 def setDocumentLocator( self, locator ): 690 pass 691 692def isPyFileInPath( sfa, path ): 693 ret = False 694 contents = sfa.getFolderContents( path, True ) 695 for i in contents: 696 if sfa.isFolder(i): 697 ret = isPyFileInPath(sfa,i) 698 else: 699 if i.endswith(".py"): 700 ret = True 701 if ret: 702 break 703 return ret 704 705# extracts META-INF directory from 706def getPathesFromPackage( rootUrl, sfa ): 707 ret = () 708 try: 709 fileUrl = rootUrl + "/META-INF/manifest.xml" 710 inputStream = sfa.openFileRead( fileUrl ) 711 parser = uno.getComponentContext().ServiceManager.createInstance( "com.sun.star.xml.sax.Parser" ) 712 handler = ManifestHandler( rootUrl ) 713 parser.setDocumentHandler( handler ) 714 parser.parseStream( InputSource( inputStream , "", fileUrl, fileUrl ) ) 715 for i in tuple(handler.urlList): 716 if not isPyFileInPath( sfa, i ): 717 handler.urlList.remove(i) 718 ret = tuple( handler.urlList ) 719 except UnoException as e: 720 text = lastException2String() 721 log.debug( "getPathesFromPackage " + fileUrl + " Exception: " +text ) 722 pass 723 return ret 724 725 726class Package: 727 def __init__( self, pathes, transientPathElement ): 728 self.pathes = pathes 729 self.transientPathElement = transientPathElement 730 731class DummyInteractionHandler( unohelper.Base, XInteractionHandler ): 732 def __init__( self ): 733 pass 734 def handle( self, event): 735 log.debug( "pythonscript: DummyInteractionHandler.handle " + str( event ) ) 736 737class DummyProgressHandler( unohelper.Base, XProgressHandler ): 738 def __init__( self ): 739 pass 740 741 def push( self,status ): 742 log.debug( "pythonscript: DummyProgressHandler.push " + str( status ) ) 743 def update( self,status ): 744 log.debug( "pythonscript: DummyProgressHandler.update " + str( status ) ) 745 def pop( self ): 746 log.debug( "pythonscript: DummyProgressHandler.push " + str( event ) ) 747 748class CommandEnvironment(unohelper.Base, XCommandEnvironment): 749 def __init__( self ): 750 self.progressHandler = DummyProgressHandler() 751 self.interactionHandler = DummyInteractionHandler() 752 def getInteractionHandler( self ): 753 return self.interactionHandler 754 def getProgressHandler( self ): 755 return self.progressHandler 756 757#maybe useful for debugging purposes 758#class ModifyListener( unohelper.Base, XModifyListener ): 759# def __init__( self ): 760# pass 761# def modified( self, event ): 762# log.debug( "pythonscript: ModifyListener.modified " + str( event ) ) 763# def disposing( self, event ): 764# log.debug( "pythonscript: ModifyListener.disposing " + str( event ) ) 765 766def getModelFromDocUrl(ctx, url): 767 """Get document model from document url.""" 768 doc = None 769 args = ("Local", "Office") 770 ucb = ctx.getServiceManager().createInstanceWithArgumentsAndContext( 771 "com.sun.star.ucb.UniversalContentBroker", args, ctx) 772 identifier = ucb.createContentIdentifier(url) 773 content = ucb.queryContent(identifier) 774 p = Property() 775 p.Name = "DocumentModel" 776 p.Handle = -1 777 778 c = Command() 779 c.Handle = -1 780 c.Name = "getPropertyValues" 781 c.Argument = uno.Any("[]com.sun.star.beans.Property", (p,)) 782 783 env = CommandEnvironment() 784 try: 785 ret = content.execute(c, 0, env) 786 doc = ret.getObject(1, None) 787 except Exception as e: 788 log.isErrorLevel() and log.error("getModelFromDocUrl: %s" % url) 789 return doc 790 791def mapStorageType2PackageContext( storageType ): 792 ret = storageType 793 if( storageType == "share:uno_packages" ): 794 ret = "shared" 795 if( storageType == "user:uno_packages" ): 796 ret = "user" 797 return ret 798 799def getPackageName2PathMap( sfa, storageType ): 800 ret = {} 801 packageManagerFactory = uno.getComponentContext().getValueByName( 802 "/singletons/com.sun.star.deployment.thePackageManagerFactory" ) 803 packageManager = packageManagerFactory.getPackageManager( 804 mapStorageType2PackageContext(storageType)) 805# packageManager.addModifyListener( ModifyListener() ) 806 log.debug( "pythonscript: getPackageName2PathMap start getDeployedPackages" ) 807 packages = packageManager.getDeployedPackages( 808 packageManager.createAbortChannel(), CommandEnvironment( ) ) 809 log.debug( "pythonscript: getPackageName2PathMap end getDeployedPackages (" + str(len(packages))+")" ) 810 811 for i in packages: 812 log.debug( "inspecting package " + i.Name + "("+i.Identifier.Value+")" ) 813 transientPathElement = penultimateElement( i.URL ) 814 j = expandUri( i.URL ) 815 pathes = getPathesFromPackage( j, sfa ) 816 if len( pathes ) > 0: 817 # map package name to url, we need this later 818 log.isErrorLevel() and log.error( "adding Package " + transientPathElement + " " + str( pathes ) ) 819 ret[ lastElement( j ) ] = Package( pathes, transientPathElement ) 820 return ret 821 822def penultimateElement( aStr ): 823 lastSlash = aStr.rindex("/") 824 penultimateSlash = aStr.rindex("/",0,lastSlash-1) 825 return aStr[ penultimateSlash+1:lastSlash ] 826 827def lastElement( aStr): 828 return aStr[ aStr.rfind( "/" )+1:len(aStr)] 829 830class PackageBrowseNode( unohelper.Base, XBrowseNode ): 831 def __init__( self, provCtx, name, rootUrl ): 832 self.provCtx = provCtx 833 self.name = name 834 self.rootUrl = rootUrl 835 836 def getName( self ): 837 return self.name 838 839 def getChildNodes( self ): 840 items = list(self.provCtx.mapPackageName2Path.items()) 841 browseNodeList = [] 842 for i in items: 843 if len( i[1].pathes ) == 1: 844 browseNodeList.append( 845 DirBrowseNode( self.provCtx, i[0], i[1].pathes[0] )) 846 else: 847 for j in i[1].pathes: 848 browseNodeList.append( 849 DirBrowseNode( self.provCtx, i[0]+"."+lastElement(j), j ) ) 850 return tuple( browseNodeList ) 851 852 def hasChildNodes( self ): 853 return len( self.mapPackageName2Path ) > 0 854 855 def getType( self ): 856 return CONTAINER 857 858 def getScript( self, uri ): 859 log.debug( "DirBrowseNode getScript " + uri + " invoked" ) 860 raise IllegalArgumentException( "PackageBrowseNode couldn't instantiate script " + uri , self , 0 ) 861 862 863 864 865class PythonScript( unohelper.Base, XScript ): 866 def __init__( self, func, mod ): 867 self.func = func 868 self.mod = mod 869 def invoke(self, args, out, outindex ): 870 log.debug( "PythonScript.invoke " + str( args ) ) 871 try: 872 ret = self.func( *args ) 873 except UnoException as e: 874 # UNO Exception continue to fly ... 875 text = lastException2String() 876 complete = "Error during invoking function " + \ 877 str(self.func.__name__) + " in module " + \ 878 self.mod.__file__ + " (" + text + ")" 879 log.debug( complete ) 880 # some people may beat me up for modifying the exception text, 881 # but otherwise office just shows 882 # the type name and message text with no more information, 883 # this is really bad for most users. 884 e.Message = e.Message + " (" + complete + ")" 885 raise 886 except Exception as e: 887 # General python exception are converted to uno RuntimeException 888 text = lastException2String() 889 complete = "Error during invoking function " + \ 890 str(self.func.__name__) + " in module " + \ 891 self.mod.__file__ + " (" + text + ")" 892 log.debug( complete ) 893 raise RuntimeException( complete , self ) 894 log.debug( "PythonScript.invoke ret = " + str( ret ) ) 895 return ret, (), () 896 897def expandUri( uri ): 898 if uri.startswith( "vnd.sun.star.expand:" ): 899 uri = uri.replace( "vnd.sun.star.expand:", "",1) 900 uri = uno.getComponentContext().getByName( 901 "/singletons/com.sun.star.util.theMacroExpander" ).expandMacros( uri ) 902 if uri.startswith( "file:" ): 903 uri = uno.absolutize("",uri) # necessary to get rid of .. in uri 904 return uri 905 906#-------------------------------------------------------------- 907class PythonScriptProvider( unohelper.Base, XBrowseNode, XScriptProvider, XNameContainer): 908 def __init__( self, ctx, *args ): 909 if log.isDebugLevel(): 910 mystr = "" 911 for i in args: 912 if len(mystr) > 0: 913 mystr = mystr +"," 914 mystr = mystr + str(i) 915 log.debug( "Entering PythonScriptProvider.ctor" + mystr ) 916 917 doc = None 918 inv = None 919 storageType = "" 920 921 if isinstance(args[0],unicode ): 922 storageType = args[0] 923 if storageType.startswith( "vnd.sun.star.tdoc" ): 924 doc = getModelFromDocUrl(ctx, storageType) 925 else: 926 inv = args[0] 927 try: 928 doc = inv.ScriptContainer 929 content = ctx.getServiceManager().createInstanceWithContext( 930 "com.sun.star.frame.TransientDocumentsDocumentContentFactory", 931 ctx).createDocumentContent(doc) 932 storageType = content.getIdentifier().getContentIdentifier() 933 except Exception as e: 934 text = lastException2String() 935 log.error( text ) 936 937 isPackage = storageType.endswith( ":uno_packages" ) 938 939 try: 940# urlHelper = ctx.ServiceManager.createInstanceWithArgumentsAndContext( 941# "com.sun.star.script.provider.ScriptURIHelper", (LANGUAGENAME, storageType), ctx) 942 urlHelper = MyUriHelper( ctx, storageType ) 943 log.debug( "got urlHelper " + str( urlHelper ) ) 944 945 rootUrl = expandUri( urlHelper.getRootStorageURI() ) 946 log.debug( storageType + " transformed to " + rootUrl ) 947 948 ucbService = "com.sun.star.ucb.SimpleFileAccess" 949 sfa = ctx.ServiceManager.createInstanceWithContext( ucbService, ctx ) 950 if not sfa: 951 log.debug("PythonScriptProvider couldn't instantiate " +ucbService) 952 raise RuntimeException( 953 "PythonScriptProvider couldn't instantiate " +ucbService, self) 954 self.provCtx = ProviderContext( 955 storageType, sfa, urlHelper, ScriptContext( uno.getComponentContext(), doc, inv ) ) 956 if isPackage: 957 mapPackageName2Path = getPackageName2PathMap( sfa, storageType ) 958 self.provCtx.setPackageAttributes( mapPackageName2Path , rootUrl ) 959 self.dirBrowseNode = PackageBrowseNode( self.provCtx, LANGUAGENAME, rootUrl ) 960 else: 961 self.dirBrowseNode = DirBrowseNode( self.provCtx, LANGUAGENAME, rootUrl ) 962 963 except Exception as e: 964 text = lastException2String() 965 log.debug( "PythonScriptProvider could not be instantiated because of : " + text ) 966 raise e 967 968 def getName( self ): 969 return self.dirBrowseNode.getName() 970 971 def getChildNodes( self ): 972 return self.dirBrowseNode.getChildNodes() 973 974 def hasChildNodes( self ): 975 return self.dirBrowseNode.hasChildNodes() 976 977 def getType( self ): 978 return self.dirBrowseNode.getType() 979 980 def getScript( self, uri ): 981 log.debug( "DirBrowseNode getScript " + uri + " invoked" ) 982 983 raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 ) 984 985 def getScript( self, scriptUri ): 986 try: 987 log.debug( "getScript " + scriptUri + " invoked") 988 989 storageUri = self.provCtx.getStorageUrlFromPersistentUrl( 990 self.provCtx.uriHelper.getStorageURI(scriptUri) ); 991 log.debug( "getScript: storageUri = " + storageUri) 992 fileUri = storageUri[0:storageUri.find( "$" )] 993 funcName = storageUri[storageUri.find( "$" )+1:len(storageUri)] 994 995 mod = self.provCtx.getModuleByUrl( fileUri ) 996 log.debug( " got mod " + str(mod) ) 997 998 func = mod.__dict__[ funcName ] 999 1000 log.debug( "got func " + str( func ) ) 1001 return PythonScript( func, mod ) 1002 except Exception as e: 1003 text = lastException2String() 1004 log.error( text ) 1005 raise ScriptFrameworkErrorException( text, self, scriptUri, LANGUAGENAME, 0 ) 1006 1007 1008 # XServiceInfo 1009 def getSupportedServices( self ): 1010 return g_ImplementationHelper.getSupportedServices(g_implName) 1011 1012 def supportsService( self, ServiceName ): 1013 return g_ImplementationHelper.supportsService( g_implName, ServiceName ) 1014 1015 def getImplementationName(self): 1016 return g_implName 1017 1018 def getByName( self, name ): 1019 log.debug( "getByName called" + str( name )) 1020 return None 1021 1022 1023 def getElementNames( self ): 1024 log.debug( "getElementNames called") 1025 return () 1026 1027 def hasByName( self, name ): 1028 try: 1029 log.debug( "hasByName called " + str( name )) 1030 uri = expandUri(name) 1031 ret = self.provCtx.isUrlInPackage( uri ) 1032 log.debug( "hasByName " + uri + " " +str( ret ) ) 1033 return ret 1034 except Exception as e: 1035 text = lastException2String() 1036 log.debug( "Error in hasByName:" + text ) 1037 return False 1038 1039 def removeByName( self, name ): 1040 log.debug( "removeByName called" + str( name )) 1041 uri = expandUri( name ) 1042 if self.provCtx.isUrlInPackage( uri ): 1043 self.provCtx.removePackageByUrl( uri ) 1044 else: 1045 log.debug( "removeByName unknown uri " + str( name ) + ", ignoring" ) 1046 raise NoSuchElementException( uri + "is not in package" , self ) 1047 log.debug( "removeByName called" + str( uri ) + " successful" ) 1048 1049 def insertByName( self, name, value ): 1050 log.debug( "insertByName called " + str( name ) + " " + str( value )) 1051 uri = expandUri( name ) 1052 if isPyFileInPath( self.provCtx.sfa, uri ): 1053 self.provCtx.addPackageByUrl( uri ) 1054 else: 1055 # package is no python package ... 1056 log.debug( "insertByName: no python files in " + str( uri ) + ", ignoring" ) 1057 raise IllegalArgumentException( uri + " does not contain .py files", self, 1 ) 1058 log.debug( "insertByName called " + str( uri ) + " successful" ) 1059 1060 def replaceByName( self, name, value ): 1061 log.debug( "replaceByName called " + str( name ) + " " + str( value )) 1062 removeByName( name ) 1063 insertByName( name ) 1064 log.debug( "replaceByName called" + str( uri ) + " successful" ) 1065 1066 def getElementType( self ): 1067 log.debug( "getElementType called" ) 1068 return uno.getTypeByName( "void" ) 1069 1070 def hasElements( self ): 1071 log.debug( "hasElements got called") 1072 return False 1073 1074g_ImplementationHelper.addImplementation( \ 1075 PythonScriptProvider,g_implName, \ 1076 ("com.sun.star.script.provider.LanguageScriptProvider", 1077 "com.sun.star.script.provider.ScriptProviderFor"+ LANGUAGENAME,),) 1078 1079 1080log.debug( "pythonscript finished intializing" ) 1081