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