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