xref: /trunk/main/scripting/source/pyprov/pythonscript.py (revision d2f6c07679d37bc64a0e3b6bd154f402cd1d2bdb)
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, XInputStream
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
176class EmptyInputStream( unohelper.Base, XInputStream ):
177      def __init__( self ):
178          pass
179
180      def closeInput(self):
181          pass
182
183      def readBytes( self, seq, n ):
184          return 0, ""
185
186      def readSomeBytes( self, seq, n ):
187          return 0, ""
188
189      def skipBytes( self, n ):
190          pass
191
192      def available( self ):
193          return 0
194
195
196""" definition: storageURI is the system dependent, absolute file url, where the script is stored on disk
197                scriptURI is the system independent uri
198"""
199class MyUriHelper:
200
201    def __init__( self, ctx, location ):
202        self.s_UriMap = \
203        { "share" : "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" +  toIniName( "bootstrap") + "::BaseInstallation}/share/Scripts/python" , \
204          "share:uno_packages" : "vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE/uno_packages", \
205          "user" : "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" + toIniName( "bootstrap") + "::UserInstallation}/user/Scripts/python" , \
206          "user:uno_packages" : "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages" }
207        self.m_uriRefFac = ctx.ServiceManager.createInstanceWithContext("com.sun.star.uri.UriReferenceFactory",ctx)
208        if location.startswith( "vnd.sun.star.tdoc" ):
209            self.m_baseUri = location + "/Scripts/python"
210            self.m_scriptUriLocation = "document"
211        else:
212            self.m_baseUri = expandUri( self.s_UriMap[location] )
213            self.m_scriptUriLocation = location
214        log.debug( "initialized urihelper with baseUri="+self.m_baseUri + ",m_scriptUriLocation="+self.m_scriptUriLocation )
215
216    def getRootStorageURI( self ):
217        return self.m_baseUri
218
219    def getStorageURI( self, scriptURI ):
220        return self.scriptURI2StorageUri(scriptURI)
221
222    def getScriptURI( self, storageURI ):
223        return self.storageURI2ScriptUri(storageURI)
224
225    def storageURI2ScriptUri( self, storageURI ):
226        if not storageURI.startswith( self.m_baseUri ):
227            message = "pythonscript: storage uri '" + storageURI + "' not in base uri '" + self.m_baseUri + "'"
228            log.debug( message )
229            raise RuntimeException( message )
230
231        ret = "vnd.sun.star.script:" + \
232              storageURI[len(self.m_baseUri)+1:].replace("/","|") + \
233              "?language=" + LANGUAGENAME + "&location=" + self.m_scriptUriLocation
234        log.debug( "converting storageURI="+storageURI + " to scriptURI=" + ret )
235        return ret
236
237    def scriptURI2StorageUri( self, scriptURI ):
238        try:
239            myUri = self.m_uriRefFac.parse(scriptURI)
240            ret = self.m_baseUri + "/" + myUri.getName().replace( "|", "/" )
241            log.debug( "converting scriptURI="+scriptURI + " to storageURI=" + ret )
242            return ret
243        except UnoException as e:
244            log.error( "error during converting scriptURI="+scriptURI + ": " + e.Message)
245            raise RuntimeException( "pythonscript:scriptURI2StorageUri: " +e.getMessage(), None )
246        except Exception as e:
247            log.error( "error during converting scriptURI="+scriptURI + ": " + str(e))
248            raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + str(e), None )
249
250
251class ModuleEntry:
252    def __init__( self, lastRead, module ):
253        self.lastRead = lastRead
254        self.module = module
255
256def hasChanged( oldDate, newDate ):
257    return newDate.Year > oldDate.Year or \
258           newDate.Month > oldDate.Month or \
259           newDate.Day > oldDate.Day or \
260           newDate.Hours > oldDate.Hours or \
261           newDate.Minutes > oldDate.Minutes or \
262           newDate.Seconds > oldDate.Seconds or \
263           newDate.HundredthSeconds > oldDate.HundredthSeconds
264
265def ensureSourceState( code ):
266    if not code.endswith( "\n" ):
267        code = code + "\n"
268    code = code.replace( "\r", "" )
269    return code
270
271
272def checkForPythonPathBesideScript( url ):
273    if url.startswith( "file:" ):
274        path = unohelper.fileUrlToSystemPath( url+"/pythonpath.zip" );
275        log.log( LogLevel.DEBUG,  "checking for existence of " + path )
276        if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path:
277            log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" )
278            sys.path.append( path )
279
280        path = unohelper.fileUrlToSystemPath( url+"/pythonpath" );
281        log.log( LogLevel.DEBUG,  "checking for existence of " + path )
282        if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path:
283            log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" )
284            sys.path.append( path )
285
286
287class ScriptContext(unohelper.Base):
288    def __init__( self, ctx, doc, inv ):
289        self.ctx = ctx
290        self.doc = doc
291        self.inv = inv
292
293   # XScriptContext
294    def getDocument(self):
295        if self.doc:
296            return self.doc
297        return self.getDesktop().getCurrentComponent()
298
299    def getDesktop(self):
300        return self.ctx.ServiceManager.createInstanceWithContext(
301            "com.sun.star.frame.Desktop", self.ctx )
302
303    def getComponentContext(self):
304        return self.ctx
305
306    def getInvocationContext(self):
307        return self.inv
308
309#----------------------------------
310# Global Module Administration
311# does not fit together with script
312# engine lifetime management
313#----------------------------------
314#g_scriptContext = ScriptContext( uno.getComponentContext(), None )
315#g_modules = {}
316#def getModuleByUrl( url, sfa ):
317#    entry =  g_modules.get(url)
318#    load = True
319#    lastRead = sfa.getDateTimeModified( url )
320#    if entry:
321#        if hasChanged( entry.lastRead, lastRead ):
322#            log.debug("file " + url + " has changed, reloading")
323#        else:
324#            load = False
325#
326#    if load:
327#        log.debug( "opening >" + url + "<" )
328#
329#        code = readTextFromStream( sfa.openFileRead( url ) )
330
331        # execute the module
332#        entry = ModuleEntry( lastRead, imp.new_module("ooo_script_framework") )
333#        entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = g_scriptContext
334#        entry.module.__file__ = url
335#        exec code in entry.module.__dict__
336#        g_modules[ url ] = entry
337#        log.debug( "mapped " + url + " to " + str( entry.module ) )
338#    return entry.module
339
340class ProviderContext:
341    def __init__( self, storageType, sfa, uriHelper, scriptContext ):
342        self.storageType = storageType
343        self.sfa = sfa
344        self.uriHelper = uriHelper
345        self.scriptContext = scriptContext
346        self.modules = {}
347        self.rootUrl = None
348        self.mapPackageName2Path = None
349
350    def getTransientPartFromUrl( self, url ):
351        rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1)
352        return rest[0:rest.find("/")]
353
354    def getPackageNameFromUrl( self, url ):
355        rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1)
356        start = rest.find("/") +1
357        return rest[start:rest.find("/",start)]
358
359
360    def removePackageByUrl( self, url ):
361        items = list(self.mapPackageName2Path.items())
362        for i in items:
363            if url in i[1].pathes:
364                self.mapPackageName2Path.pop(i[0])
365                break
366
367    def addPackageByUrl( self, url ):
368        packageName = self.getPackageNameFromUrl( url )
369        transientPart = self.getTransientPartFromUrl( url )
370        log.debug( "addPackageByUrl : " + packageName + ", " + transientPart + "("+url+")" + ", rootUrl="+self.rootUrl )
371        if packageName in self.mapPackageName2Path:
372            package = self.mapPackageName2Path[ packageName ]
373            package.pathes = package.pathes + (url, )
374        else:
375            package = Package( (url,), transientPart)
376            self.mapPackageName2Path[ packageName ] = package
377
378    def isUrlInPackage( self, url ):
379        values = list(self.mapPackageName2Path.values())
380        for i in values:
381#           print "checking " + url + " in " + str(i.pathes)
382            if url in i.pathes:
383                return True
384#        print "false"
385        return False
386
387    def setPackageAttributes( self, mapPackageName2Path, rootUrl ):
388        self.mapPackageName2Path = mapPackageName2Path
389        self.rootUrl = rootUrl
390
391    def getPersistentUrlFromStorageUrl( self, url ):
392        # package name is the second directory
393        ret = url
394        if self.rootUrl:
395            pos = len( self.rootUrl) +1
396            ret = url[0:pos]+url[url.find("/",pos)+1:len(url)]
397        log.debug( "getPersistentUrlFromStorageUrl " + url +  " -> "+ ret)
398        return ret
399
400    def getStorageUrlFromPersistentUrl( self, url):
401        ret = url
402        if self.rootUrl:
403            pos = len(self.rootUrl)+1
404            packageName = url[pos:url.find("/",pos+1)]
405            package = self.mapPackageName2Path[ packageName ]
406            ret = url[0:pos]+ package.transientPathElement + "/" + url[pos:len(url)]
407        log.debug( "getStorageUrlFromPersistentUrl " + url + " -> "+ ret)
408        return ret
409
410    def getFuncsByUrl( self, url ):
411        src = readTextFromStream( self.sfa.openFileRead( url ) )
412        checkForPythonPathBesideScript( url[0:url.rfind('/')] )
413        src = ensureSourceState( src )
414
415        allFuncs = []
416        g_exportedScripts = []
417
418        a = ast.parse(src, url)
419
420        if isinstance(a, ast.Module):
421            for node in a.body:
422                if isinstance(node, ast.FunctionDef):
423                    allFuncs.append(node.name)
424                elif isinstance(node, ast.Assign):
425                    is_exported = False
426                    for subnode in node.targets:
427                        if isinstance(subnode, ast.Name) and \
428                            subnode.id == "g_exportedScripts":
429                            is_exported = True
430                            break
431                    if is_exported:
432                        value_node = node.value
433                        if isinstance(value_node, ast.List) or \
434                            isinstance(value_node, ast.Tuple):
435                            for elt in value_node.elts:
436                                if isinstance(elt, ast.Str):
437                                    g_exportedScripts.append(elt.s)
438                                elif isinstance(elt, ast.Name):
439                                    g_exportedScripts.append(elt.id)
440                        elif isinstance(value_node, ast.Str):
441                            g_exportedScripts.append(value_node.s)
442                        elif isinstance(value_node, ast.Name):
443                            g_exportedScripts.append(value_node.id)
444                        return g_exportedScripts
445        return allFuncs
446
447    def getModuleByUrl( self, url ):
448        entry =  self.modules.get(url)
449        load = True
450        lastRead = self.sfa.getDateTimeModified( url )
451        if entry:
452            if hasChanged( entry.lastRead, lastRead ):
453                log.debug( "file " + url + " has changed, reloading" )
454            else:
455                load = False
456
457        if load:
458            log.debug( "opening >" + url + "<" )
459
460            src = readTextFromStream( self.sfa.openFileRead( url ) )
461            checkForPythonPathBesideScript( url[0:url.rfind('/')] )
462            src = ensureSourceState( src )
463
464            # execute the module
465            entry = ModuleEntry( lastRead, imp.new_module("ooo_script_framework") )
466            entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.scriptContext
467
468            code = None
469            if url.startswith( "file:" ):
470                code = compile( src, encfile(uno.fileUrlToSystemPath( url ) ), "exec" )
471            else:
472                code = compile( src, url, "exec" )
473            exec(code, entry.module.__dict__)
474            entry.module.__file__ = url
475            self.modules[ url ] = entry
476            log.debug( "mapped " + url + " to " + str( entry.module ) )
477        return  entry.module
478
479#--------------------------------------------------
480def isScript( candidate ):
481    ret = False
482    if isinstance( candidate, type(isScript) ):
483        ret = True
484    return ret
485
486#-------------------------------------------------------
487class ScriptBrowseNode( unohelper.Base, XBrowseNode , XPropertySet, XInvocation, XActionListener ):
488    def __init__( self, provCtx, parent, fileName, funcName ):
489        self.parent = parent
490        self.fileName = fileName
491        self.funcName = funcName
492        self.provCtx = provCtx
493
494    def uri( self ):
495        return self.parent.uri()
496
497    def getName( self ):
498        return self.funcName
499
500    def getChildNodes(self):
501        return ()
502
503    def hasChildNodes(self):
504        return False
505
506    def getType( self):
507        return SCRIPT
508
509    def getPropertyValue( self, name ):
510        ret = None
511        try:
512            if name == "URI":
513                ret = self.provCtx.uriHelper.getScriptURI(
514                    self.provCtx.getPersistentUrlFromStorageUrl( self.uri() + "$" + self.funcName ) )
515            elif name == "Editable" and ENABLE_EDIT_DIALOG:
516                ret = not self.provCtx.sfa.isReadOnly( self.uri() )
517
518            log.debug( "ScriptBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
519        except Exception as e:
520            log.error( "ScriptBrowseNode.getPropertyValue error " + lastException2String())
521            raise
522
523        return ret
524    def setPropertyValue( self, name, value ):
525        log.debug( "ScriptBrowseNode.setPropertyValue called " + name + "=" +str(value ) )
526    def getPropertySetInfo( self ):
527        log.debug( "ScriptBrowseNode.getPropertySetInfo called "  )
528        return None
529
530    def getIntrospection( self ):
531        return None
532
533    def invoke( self, name, params, outparamindex, outparams ):
534        if name == "Editable":
535            servicename = "com.sun.star.awt.DialogProvider"
536            ctx = self.provCtx.scriptContext.getComponentContext()
537            dlgprov = ctx.ServiceManager.createInstanceWithContext(
538                servicename, ctx )
539
540            self.editor = dlgprov.createDialog(
541                "vnd.sun.star.script:" +
542                "ScriptBindingLibrary.MacroEditor?location=application")
543
544            code = readTextFromStream(self.provCtx.sfa.openFileRead(self.uri()))
545            code = ensureSourceState( code )
546            self.editor.getControl("EditorTextField").setText(code)
547
548            self.editor.getControl("RunButton").setActionCommand("Run")
549            self.editor.getControl("RunButton").addActionListener(self)
550            self.editor.getControl("SaveButton").setActionCommand("Save")
551            self.editor.getControl("SaveButton").addActionListener(self)
552
553            self.editor.execute()
554
555        return None, (), ()
556
557    def actionPerformed( self, event ):
558        try:
559            if event.ActionCommand == "Run":
560                code = self.editor.getControl("EditorTextField").getText()
561                code = ensureSourceState( code )
562                mod = imp.new_module("ooo_script_framework")
563                mod.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.provCtx.scriptContext
564                exec(code, mod.__dict__)
565                values = mod.__dict__.get( CALLABLE_CONTAINER_NAME , None )
566                if not values:
567                    values = list(mod.__dict__.values())
568
569                for i in values:
570                    if isScript( i ):
571                        i()
572                        break
573
574            elif event.ActionCommand == "Save":
575                toWrite = uno.ByteSequence(
576                    str(
577                    self.editor.getControl("EditorTextField").getText().encode(
578                    sys.getdefaultencoding())) )
579                copyUrl = self.uri() + ".orig"
580                self.provCtx.sfa.move( self.uri(), copyUrl )
581                out = self.provCtx.sfa.openFileWrite( self.uri() )
582                out.writeBytes( toWrite )
583                out.close()
584                self.provCtx.sfa.kill( copyUrl )
585#                log.debug("Save is not implemented yet")
586#                text = self.editor.getControl("EditorTextField").getText()
587#                log.debug("Would save: " + text)
588        except Exception as e:
589            # TODO: add an error box here !
590            log.error( lastException2String() )
591
592
593    def setValue( self, name, value ):
594        return None
595
596    def getValue( self, name ):
597        return None
598
599    def hasMethod( self, name ):
600        return False
601
602    def hasProperty( self, name ):
603        return False
604
605
606#-------------------------------------------------------
607class FileBrowseNode( unohelper.Base, XBrowseNode, XPropertySet, XInvocation ):
608    def __init__( self, provCtx, parent, name ):
609        self.provCtx = provCtx
610        self.parent = parent
611        self.name = name
612        self.funcnames = None
613
614    def uri( self ):
615        return self.parent.rootUrl + "/" + self.name + ".py"
616
617    def getName( self ):
618        return self.name
619
620    def getChildNodes(self):
621        ret = ()
622        try:
623            self.funcnames = self.provCtx.getFuncsByUrl( self.uri() )
624
625            scriptNodeList = []
626            for i in self.funcnames:
627                scriptNodeList.append(
628                    ScriptBrowseNode(
629                    self.provCtx, self, self.name, i ))
630            ret = tuple( scriptNodeList )
631        except Exception as e:
632            text = lastException2String()
633            log.error( "FileBrowseNode.getChildNodes error while evaluating " + self.uri() + ":" + text )
634            raise
635        return ret
636
637    def hasChildNodes(self):
638        try:
639            return len(self.getChildNodes()) > 0
640        except Exception as e:
641            return False
642
643    def getType( self):
644        return CONTAINER
645
646    # XPropertySet
647
648    def getPropertyValue( self, name ):
649        ret = None
650        try:
651            if name == "Editable":
652                ret = not self.provCtx.sfa.isReadOnly( self.uri() )
653            elif name == "Deletable":
654                ret = not self.provCtx.sfa.isReadOnly( self.uri() )
655            elif name == "Renamable":
656                ret = not self.provCtx.sfa.isReadOnly( self.uri() )
657
658            log.debug( "FileBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
659        except Exception as e:
660            log.error( "FileBrowseNode.getPropertyValue error " + lastException2String())
661            raise
662
663        return ret
664
665    def setPropertyValue( self, name, value ):
666        log.debug( "FileBrowseNode.setPropertyValue called " + name + "=" +str(value ) )
667
668    def getPropertySetInfo( self ):
669        log.debug( "FileBrowseNode.getPropertySetInfo called "  )
670        return None
671
672    # XInvocation
673
674    def getIntrospection( self ):
675        log.debug( "FileBrowseNode.getIntrospection() called" )
676        return None
677
678    def invoke( self, name, params, outparamindex, outparams ):
679        log.debug("FileBrowseNode.invoke called for " + name + "," + str(params) + "," + str(outparamindex) + "," + str(outparams))
680        try:
681            if name == "Editable":
682                log.error("Editable not implemented")
683            elif name == "Deletable":
684                self.provCtx.sfa.kill( self.uri() )
685                return True, (), ()
686            elif name == "Renamable":
687                newUri = self.parent.rootUrl + "/" + params[0] + ".py"
688                self.provCtx.sfa.move( self.uri(), newUri )
689                self.name = params[0]
690                return self, (), ()
691        except Exception as e:
692            log.error( "FileBrowseNode.invoke error " + lastException2String() )
693            raise
694        return None, (), ()
695
696    def setValue( self, name, value ):
697        return None
698
699    def getValue( self, name ):
700        log.debug( "FileBrowseNode.getValue() called" )
701        return None
702
703    def hasMethod( self, name ):
704        return False
705
706    def hasProperty( self, name ):
707        return False
708
709
710class DirBrowseNode( unohelper.Base, XBrowseNode, XPropertySet, XInvocation ):
711    def __init__( self, provCtx, name, rootUrl, depth ):
712        self.provCtx = provCtx
713        self.name = name
714        self.rootUrl = rootUrl
715        self.depth = depth
716        log.debug( "DirBrowseNode constructor for " + name + "," + rootUrl )
717
718    def getName( self ):
719        return self.name
720
721    def getChildNodes( self ):
722        try:
723            log.debug( "DirBrowseNode.getChildNodes called for " + self.rootUrl )
724            contents = self.provCtx.sfa.getFolderContents( self.rootUrl, True )
725            browseNodeList = []
726            for i in contents:
727                if i.endswith( ".py" ):
728                    log.debug( "adding filenode " + i )
729                    browseNodeList.append(
730                        FileBrowseNode( self.provCtx, self, i[i.rfind("/")+1:len(i)-3] ) )
731                elif self.provCtx.sfa.isFolder( i ) and not i.endswith("/pythonpath"):
732                    log.debug( "adding DirBrowseNode " + i )
733                    browseNodeList.append(
734                        DirBrowseNode( self.provCtx, i[i.rfind("/")+1:len(i)], i, self.depth + 1 ) )
735            return tuple( browseNodeList )
736        except Exception as e:
737            text = lastException2String()
738            log.error( "DirBrowseNode error: " + str(e) + " while evaluating " + self.rootUrl)
739            log.error( text)
740            return ()
741
742    def hasChildNodes( self ):
743        return True
744
745    def getType( self ):
746        return CONTAINER
747
748    # XScriptProvider
749
750    def getScript( self, uri ):
751        log.debug( "DirBrowseNode getScript " + uri + " invoked" )
752        raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 )
753
754    # XPropertySet
755
756    def getPropertyValue( self, name ):
757        ret = None
758        try:
759            if name == "Creatable":
760                ret = True
761            elif name == "Deletable":
762                ret = self.depth > 0 and not self.provCtx.sfa.isReadOnly( self.rootUrl )
763            elif name == "Renamable":
764                ret = self.depth > 0 and not self.provCtx.sfa.isReadOnly( self.rootUrl )
765
766            log.debug( "DirBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
767        except Exception as e:
768            log.error( "DirBrowseNode.getPropertyValue error " + lastException2String())
769            raise
770
771        return ret
772
773    def setPropertyValue( self, name, value ):
774        log.debug( "DirBrowseNode.setPropertyValue called " + name + "=" +str(value ) )
775
776    def getPropertySetInfo( self ):
777        log.debug( "DirBrowseNode.getPropertySetInfo called "  )
778        return None
779
780    # XInvocation
781
782    def getIntrospection( self ):
783        log.debug( "DirBrowseNode.getIntrospection() called" )
784        return None
785
786    def invoke( self, name, params, outparamindex, outparams ):
787        log.debug("DirBrowseNode.invoke called for " + name + "," + str(params) + "," + str(outparamindex) + "," + str(outparams))
788        try:
789            if name == "Creatable":
790                if self.depth == 0:
791                    subFolderUrl = self.rootUrl + "/" + params[0]
792                    self.provCtx.sfa.createFolder( subFolderUrl )
793                    childNode = DirBrowseNode( self.provCtx, subFolderUrl[subFolderUrl.rfind("/")+1:len(subFolderUrl)], subFolderUrl, self.depth + 1 )
794                    return childNode, (), ()
795                else:
796                    scriptUrl = self.rootUrl + "/" + params[0] + ".py"
797                    # Creates an empty file
798                    self.provCtx.sfa.writeFile( scriptUrl, EmptyInputStream() )
799                    childNode = FileBrowseNode( self.provCtx, self, params[0] )
800                    return childNode, (), ()
801            elif name == "Deletable":
802                self.provCtx.sfa.kill( self.rootUrl )
803                return True, (), ()
804            elif name == "Renamable":
805                newUrl = self.rootUrl[0:self.rootUrl.rfind("/")+1] + params[0]
806                self.provCtx.sfa.move( self.rootUrl, newUrl )
807                self.rootUrl = newUrl
808                self.name = params[0]
809                return self, (), ()
810        except Exception as e:
811            log.error( "DirBrowseNode.invoke error: " + lastException2String())
812            raise
813        return None, (), ()
814
815    def setValue( self, name, value ):
816        return None
817
818    def getValue( self, name ):
819        log.debug( "DirBrowseNode.getValue() called" )
820        return None
821
822    def hasMethod( self, name ):
823        return False
824
825    def hasProperty( self, name ):
826        return False
827
828
829class ManifestHandler( XDocumentHandler, unohelper.Base ):
830    def __init__( self, rootUrl ):
831        self.rootUrl = rootUrl
832
833    def startDocument( self ):
834        self.urlList = []
835
836    def endDocument( self ):
837        pass
838
839    def startElement( self , name, attlist):
840        if name == "manifest:file-entry":
841            if attlist.getValueByName( "manifest:media-type" ) == "application/vnd.sun.star.framework-script":
842                self.urlList.append(
843                    self.rootUrl + "/" + attlist.getValueByName( "manifest:full-path" ) )
844
845    def endElement( self, name ):
846        pass
847
848    def characters ( self, chars ):
849        pass
850
851    def ignoreableWhitespace( self, chars ):
852        pass
853
854    def setDocumentLocator( self, locator ):
855        pass
856
857def isPyFileInPath( sfa, path ):
858    ret = False
859    contents = sfa.getFolderContents( path, True )
860    for i in contents:
861        if sfa.isFolder(i):
862            ret = isPyFileInPath(sfa,i)
863        else:
864            if i.endswith(".py"):
865                ret = True
866        if ret:
867            break
868    return ret
869
870# extracts META-INF directory from
871def getPathesFromPackage( rootUrl, sfa ):
872    ret = ()
873    try:
874        fileUrl = rootUrl + "/META-INF/manifest.xml"
875        inputStream = sfa.openFileRead( fileUrl )
876        parser = uno.getComponentContext().ServiceManager.createInstance( "com.sun.star.xml.sax.Parser" )
877        handler = ManifestHandler( rootUrl )
878        parser.setDocumentHandler( handler )
879        parser.parseStream( InputSource( inputStream , "", fileUrl, fileUrl ) )
880        for i in tuple(handler.urlList):
881            if not isPyFileInPath( sfa, i ):
882                handler.urlList.remove(i)
883        ret = tuple( handler.urlList )
884    except UnoException as e:
885        text = lastException2String()
886        log.debug( "getPathesFromPackage " + fileUrl + " Exception: " +text )
887        pass
888    return ret
889
890
891class Package:
892    def __init__( self, pathes, transientPathElement ):
893        self.pathes = pathes
894        self.transientPathElement = transientPathElement
895
896class DummyInteractionHandler( unohelper.Base, XInteractionHandler ):
897    def __init__( self ):
898        pass
899    def handle( self, event):
900        log.debug( "pythonscript: DummyInteractionHandler.handle " + str( event ) )
901
902class DummyProgressHandler( unohelper.Base, XProgressHandler ):
903    def __init__( self ):
904        pass
905
906    def push( self,status ):
907        log.debug( "pythonscript: DummyProgressHandler.push " + str( status ) )
908    def update( self,status ):
909        log.debug( "pythonscript: DummyProgressHandler.update " + str( status ) )
910    def pop( self ):
911        log.debug( "pythonscript: DummyProgressHandler.push " + str( event ) )
912
913class CommandEnvironment(unohelper.Base, XCommandEnvironment):
914    def __init__( self ):
915        self.progressHandler = DummyProgressHandler()
916        self.interactionHandler = DummyInteractionHandler()
917    def getInteractionHandler( self ):
918        return self.interactionHandler
919    def getProgressHandler( self ):
920        return self.progressHandler
921
922#maybe useful for debugging purposes
923#class ModifyListener( unohelper.Base, XModifyListener ):
924#    def __init__( self ):
925#        pass
926#    def modified( self, event ):
927#        log.debug( "pythonscript: ModifyListener.modified " + str( event ) )
928#    def disposing( self, event ):
929#        log.debug( "pythonscript: ModifyListener.disposing " + str( event ) )
930
931def getModelFromDocUrl(ctx, url):
932    """Get document model from document url."""
933    doc = None
934    args = ("Local", "Office")
935    ucb = ctx.getServiceManager().createInstanceWithArgumentsAndContext(
936        "com.sun.star.ucb.UniversalContentBroker", args, ctx)
937    identifier = ucb.createContentIdentifier(url)
938    content = ucb.queryContent(identifier)
939    p = Property()
940    p.Name = "DocumentModel"
941    p.Handle = -1
942
943    c = Command()
944    c.Handle = -1
945    c.Name = "getPropertyValues"
946    c.Argument = uno.Any("[]com.sun.star.beans.Property", (p,))
947
948    env = CommandEnvironment()
949    try:
950        ret = content.execute(c, 0, env)
951        doc = ret.getObject(1, None)
952    except Exception as e:
953        log.isErrorLevel() and log.error("getModelFromDocUrl: %s" % url)
954    return doc
955
956def mapStorageType2PackageContext( storageType ):
957    ret = storageType
958    if( storageType == "share:uno_packages" ):
959        ret = "shared"
960    if( storageType == "user:uno_packages" ):
961        ret = "user"
962    return ret
963
964def getPackageName2PathMap( sfa, storageType ):
965    ret = {}
966    packageManagerFactory = uno.getComponentContext().getValueByName(
967        "/singletons/com.sun.star.deployment.thePackageManagerFactory" )
968    packageManager = packageManagerFactory.getPackageManager(
969        mapStorageType2PackageContext(storageType))
970#    packageManager.addModifyListener( ModifyListener() )
971    log.debug( "pythonscript: getPackageName2PathMap start getDeployedPackages" )
972    packages = packageManager.getDeployedPackages(
973        packageManager.createAbortChannel(), CommandEnvironment( ) )
974    log.debug( "pythonscript: getPackageName2PathMap end getDeployedPackages (" + str(len(packages))+")" )
975
976    for i in packages:
977        log.debug( "inspecting package " + i.Name + "("+i.Identifier.Value+")" )
978        transientPathElement = penultimateElement( i.URL )
979        j = expandUri( i.URL )
980        pathes = getPathesFromPackage( j, sfa )
981        if len( pathes ) > 0:
982            # map package name to url, we need this later
983            log.isErrorLevel() and log.error( "adding Package " + transientPathElement + " " + str( pathes ) )
984            ret[ lastElement( j ) ] = Package( pathes, transientPathElement )
985    return ret
986
987def penultimateElement( aStr ):
988    lastSlash = aStr.rindex("/")
989    penultimateSlash = aStr.rindex("/",0,lastSlash-1)
990    return  aStr[ penultimateSlash+1:lastSlash ]
991
992def lastElement( aStr):
993    return aStr[ aStr.rfind( "/" )+1:len(aStr)]
994
995class PackageBrowseNode( unohelper.Base, XBrowseNode ):
996    def __init__( self, provCtx, name, rootUrl ):
997        self.provCtx = provCtx
998        self.name = name
999        self.rootUrl = rootUrl
1000
1001    def getName( self ):
1002        return self.name
1003
1004    def getChildNodes( self ):
1005        items = list(self.provCtx.mapPackageName2Path.items())
1006        browseNodeList = []
1007        for i in items:
1008            if len( i[1].pathes ) == 1:
1009                browseNodeList.append(
1010                    DirBrowseNode( self.provCtx, i[0], i[1].pathes[0], 0 ))
1011            else:
1012                for j in i[1].pathes:
1013                    browseNodeList.append(
1014                        DirBrowseNode( self.provCtx, i[0]+"."+lastElement(j), j, 0 ) )
1015        return tuple( browseNodeList )
1016
1017    def hasChildNodes( self ):
1018        return len( self.mapPackageName2Path ) > 0
1019
1020    def getType( self ):
1021        return CONTAINER
1022
1023    def getScript( self, uri ):
1024        log.debug( "DirBrowseNode getScript " + uri + " invoked" )
1025        raise IllegalArgumentException( "PackageBrowseNode couldn't instantiate script " + uri , self , 0 )
1026
1027
1028
1029
1030class PythonScript( unohelper.Base, XScript ):
1031    def __init__( self, func, mod ):
1032        self.func = func
1033        self.mod = mod
1034    def invoke(self, args, out, outindex ):
1035        log.debug( "PythonScript.invoke " + str( args ) )
1036        try:
1037            ret = self.func( *args )
1038        except UnoException as e:
1039            # UNO Exception continue to fly ...
1040            text = lastException2String()
1041            complete = "Error during invoking function " + \
1042                str(self.func.__name__) + " in module " + \
1043                self.mod.__file__ + " (" + text + ")"
1044            log.debug( complete )
1045            # some people may beat me up for modifying the exception text,
1046            # but otherwise office just shows
1047            # the type name and message text with no more information,
1048            # this is really bad for most users.
1049            e.Message = e.Message + " (" + complete + ")"
1050            raise
1051        except Exception as e:
1052            # General python exception are converted to uno RuntimeException
1053            text = lastException2String()
1054            complete = "Error during invoking function " + \
1055                str(self.func.__name__) + " in module " + \
1056                self.mod.__file__ + " (" + text + ")"
1057            log.debug( complete )
1058            raise RuntimeException( complete , self )
1059        log.debug( "PythonScript.invoke ret = " + str( ret ) )
1060        return ret, (), ()
1061
1062def expandUri(  uri ):
1063    if uri.startswith( "vnd.sun.star.expand:" ):
1064        uri = uri.replace( "vnd.sun.star.expand:", "",1)
1065        uri = uno.getComponentContext().getByName(
1066                    "/singletons/com.sun.star.util.theMacroExpander" ).expandMacros( uri )
1067    if uri.startswith( "file:" ):
1068        uri = uno.absolutize("",uri)   # necessary to get rid of .. in uri
1069    return uri
1070
1071#--------------------------------------------------------------
1072class PythonScriptProvider( unohelper.Base, XBrowseNode, XScriptProvider, XNameContainer, XPropertySet, XInvocation):
1073    def __init__( self, ctx, *args ):
1074        if log.isDebugLevel():
1075            mystr = ""
1076            for i in args:
1077                if len(mystr) > 0:
1078                    mystr = mystr +","
1079                mystr = mystr + str(i)
1080            log.debug( "Entering PythonScriptProvider.ctor with args " + mystr )
1081
1082        doc = None
1083        inv = None
1084        storageType = ""
1085
1086        if isinstance(args[0],unicode ):
1087            storageType = args[0]
1088            if storageType.startswith( "vnd.sun.star.tdoc" ):
1089                doc = getModelFromDocUrl(ctx, storageType)
1090        else:
1091            inv = args[0]
1092            try:
1093                doc = inv.ScriptContainer
1094                content = ctx.getServiceManager().createInstanceWithContext(
1095                    "com.sun.star.frame.TransientDocumentsDocumentContentFactory",
1096                    ctx).createDocumentContent(doc)
1097                storageType = content.getIdentifier().getContentIdentifier()
1098            except Exception as e:
1099                text = lastException2String()
1100                log.error( text )
1101
1102        isPackage = storageType.endswith( ":uno_packages" )
1103
1104        try:
1105#            urlHelper = ctx.ServiceManager.createInstanceWithArgumentsAndContext(
1106#                "com.sun.star.script.provider.ScriptURIHelper", (LANGUAGENAME, storageType), ctx)
1107            urlHelper = MyUriHelper( ctx, storageType )
1108            log.debug( "got urlHelper " + str( urlHelper ) )
1109
1110            rootUrl = expandUri( urlHelper.getRootStorageURI() )
1111            log.debug( storageType + " transformed to " + rootUrl )
1112
1113            ucbService = "com.sun.star.ucb.SimpleFileAccess"
1114            sfa = ctx.ServiceManager.createInstanceWithContext( ucbService, ctx )
1115            if not sfa:
1116                log.debug("PythonScriptProvider couldn't instantiate " +ucbService)
1117                raise RuntimeException(
1118                    "PythonScriptProvider couldn't instantiate " +ucbService, self)
1119            self.provCtx = ProviderContext(
1120                storageType, sfa, urlHelper, ScriptContext( uno.getComponentContext(), doc, inv ) )
1121            if isPackage:
1122                mapPackageName2Path = getPackageName2PathMap( sfa, storageType )
1123                self.provCtx.setPackageAttributes( mapPackageName2Path , rootUrl )
1124                self.dirBrowseNode = PackageBrowseNode( self.provCtx, LANGUAGENAME, rootUrl )
1125            else:
1126                self.dirBrowseNode = DirBrowseNode( self.provCtx, LANGUAGENAME, rootUrl, 0 )
1127
1128        except Exception as e:
1129            text = lastException2String()
1130            log.debug( "PythonScriptProvider could not be instantiated because of : " + text )
1131            raise e
1132
1133    def getName( self ):
1134        return self.dirBrowseNode.getName()
1135
1136    def getChildNodes( self ):
1137        return self.dirBrowseNode.getChildNodes()
1138
1139    def hasChildNodes( self ):
1140        return self.dirBrowseNode.hasChildNodes()
1141
1142    def getType( self ):
1143        return self.dirBrowseNode.getType()
1144
1145    def getScript( self, uri ):
1146        log.debug( "DirBrowseNode getScript " + uri + " invoked" )
1147
1148        raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 )
1149
1150    def getScript( self, scriptUri ):
1151        try:
1152            log.debug( "getScript " + scriptUri + " invoked")
1153
1154            storageUri = self.provCtx.getStorageUrlFromPersistentUrl(
1155                self.provCtx.uriHelper.getStorageURI(scriptUri) );
1156            log.debug( "getScript: storageUri = " + storageUri)
1157            fileUri = storageUri[0:storageUri.find( "$" )]
1158            funcName = storageUri[storageUri.find( "$" )+1:len(storageUri)]
1159
1160            mod = self.provCtx.getModuleByUrl( fileUri )
1161            log.debug( " got mod " + str(mod) )
1162
1163            func = mod.__dict__[ funcName ]
1164
1165            log.debug( "got func " + str( func ) )
1166            return PythonScript( func, mod )
1167        except Exception as e:
1168            text = lastException2String()
1169            log.error( text )
1170            raise ScriptFrameworkErrorException( text, self, scriptUri, LANGUAGENAME, 0 )
1171
1172    # XPropertySet
1173
1174    def getPropertyValue( self, name ):
1175        return self.dirBrowseNode.getPropertyValue( name )
1176
1177    def setPropertyValue( self, name, value ):
1178        return self.dirBrowseNode.setPropertyValue( name, value )
1179
1180    def getPropertySetInfo( self ):
1181        return self.dirBrowseNode.getPropertySetInfo()
1182
1183    # XInvocation
1184
1185    def getIntrospection( self ):
1186        return self.dirBrowseNode.getIntrospection()
1187
1188    def invoke( self, name, params, outparamindex, outparams ):
1189        return self.dirBrowseNode.invoke( name, params, outparamindex, outparams)
1190
1191    def setValue( self, name, value ):
1192        return self.dirBrowseNode.setValue( name, value )
1193
1194    def getValue( self, name ):
1195        return self.dirBrowseNode.getValue( name )
1196
1197    def hasMethod( self, name ):
1198        return self.dirBrowseNode.hasMethod( name )
1199
1200    def hasProperty( self, name ):
1201        return self.dirBrowseNode.hasProperty( name )
1202
1203    # XServiceInfo
1204    def getSupportedServices( self ):
1205        return g_ImplementationHelper.getSupportedServices(g_implName)
1206
1207    def supportsService( self, ServiceName ):
1208        return g_ImplementationHelper.supportsService( g_implName, ServiceName )
1209
1210    def getImplementationName(self):
1211        return g_implName
1212
1213    def getByName( self, name ):
1214        log.debug( "getByName called" + str( name ))
1215        return None
1216
1217
1218    def getElementNames( self ):
1219        log.debug( "getElementNames called")
1220        return ()
1221
1222    def hasByName( self, name ):
1223        try:
1224            log.debug( "hasByName called " + str( name ))
1225            uri = expandUri(name)
1226            ret = self.provCtx.isUrlInPackage( uri )
1227            log.debug( "hasByName " + uri + " " +str( ret ) )
1228            return ret
1229        except Exception as e:
1230            text = lastException2String()
1231            log.debug( "Error in hasByName:" +  text )
1232            return False
1233
1234    def removeByName( self, name ):
1235        log.debug( "removeByName called" + str( name ))
1236        uri = expandUri( name )
1237        if self.provCtx.isUrlInPackage( uri ):
1238            self.provCtx.removePackageByUrl( uri )
1239        else:
1240            log.debug( "removeByName unknown uri " + str( name ) + ", ignoring" )
1241            raise NoSuchElementException( uri + "is not in package" , self )
1242        log.debug( "removeByName called" + str( uri ) + " successful" )
1243
1244    def insertByName( self, name, value ):
1245        log.debug( "insertByName called " + str( name ) + " " + str( value ))
1246        uri = expandUri( name )
1247        if isPyFileInPath( self.provCtx.sfa, uri ):
1248            self.provCtx.addPackageByUrl( uri )
1249        else:
1250            # package is no python package ...
1251            log.debug( "insertByName: no python files in " + str( uri ) + ", ignoring" )
1252            raise IllegalArgumentException( uri + " does not contain .py files", self, 1 )
1253        log.debug( "insertByName called " + str( uri ) + " successful" )
1254
1255    def replaceByName( self, name, value ):
1256        log.debug( "replaceByName called " + str( name ) + " " + str( value ))
1257        removeByName( name )
1258        insertByName( name )
1259        log.debug( "replaceByName called" + str( uri ) + " successful" )
1260
1261    def getElementType( self ):
1262        log.debug( "getElementType called" )
1263        return uno.getTypeByName( "void" )
1264
1265    def hasElements( self ):
1266        log.debug( "hasElements got called")
1267        return False
1268
1269g_ImplementationHelper.addImplementation( \
1270        PythonScriptProvider,g_implName, \
1271    ("com.sun.star.script.provider.LanguageScriptProvider",
1272     "com.sun.star.script.provider.ScriptProviderFor"+ LANGUAGENAME,),)
1273
1274
1275log.debug( "pythonscript finished initializing" )
1276