xref: /trunk/main/scripting/source/pyprov/pythonscript.py (revision cfa9efbce197aa899e4f64e62fe1b13b5898dd99)
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                if params is None or not params:
688                    raise IllegalArgumentException( "invoke with Creatable needs the name in params" )
689                newUri = self.parent.rootUrl + "/" + params[0] + ".py"
690                self.provCtx.sfa.move( self.uri(), newUri )
691                self.name = params[0]
692                return self, (), ()
693        except Exception as e:
694            log.error( "FileBrowseNode.invoke error " + lastException2String() )
695            raise
696        return None, (), ()
697
698    def setValue( self, name, value ):
699        return None
700
701    def getValue( self, name ):
702        log.debug( "FileBrowseNode.getValue() called" )
703        return None
704
705    def hasMethod( self, name ):
706        return False
707
708    def hasProperty( self, name ):
709        return False
710
711
712class DirBrowseNode( unohelper.Base, XBrowseNode, XPropertySet, XInvocation ):
713    def __init__( self, provCtx, name, rootUrl, depth ):
714        self.provCtx = provCtx
715        self.name = name
716        self.rootUrl = rootUrl
717        self.depth = depth
718        log.debug( "DirBrowseNode constructor for " + name + "," + rootUrl )
719
720    def getName( self ):
721        return self.name
722
723    def getChildNodes( self ):
724        try:
725            log.debug( "DirBrowseNode.getChildNodes called for " + self.rootUrl )
726            contents = self.provCtx.sfa.getFolderContents( self.rootUrl, True )
727            browseNodeList = []
728            for i in contents:
729                if i.endswith( ".py" ):
730                    log.debug( "adding filenode " + i )
731                    browseNodeList.append(
732                        FileBrowseNode( self.provCtx, self, i[i.rfind("/")+1:len(i)-3] ) )
733                elif self.provCtx.sfa.isFolder( i ) and not i.endswith("/pythonpath"):
734                    log.debug( "adding DirBrowseNode " + i )
735                    browseNodeList.append(
736                        DirBrowseNode( self.provCtx, i[i.rfind("/")+1:len(i)], i, self.depth + 1 ) )
737            return tuple( browseNodeList )
738        except Exception as e:
739            text = lastException2String()
740            log.error( "DirBrowseNode error: " + str(e) + " while evaluating " + self.rootUrl)
741            log.error( text)
742            return ()
743
744    def hasChildNodes( self ):
745        return True
746
747    def getType( self ):
748        return CONTAINER
749
750    # XScriptProvider
751
752    def getScript( self, uri ):
753        log.debug( "DirBrowseNode getScript " + uri + " invoked" )
754        raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 )
755
756    # XPropertySet
757
758    def getPropertyValue( self, name ):
759        ret = None
760        try:
761            if name == "Creatable":
762                ret = True
763            elif name == "Deletable":
764                ret = self.depth > 0 and not self.provCtx.sfa.isReadOnly( self.rootUrl )
765            elif name == "Renamable":
766                ret = self.depth > 0 and not self.provCtx.sfa.isReadOnly( self.rootUrl )
767
768            log.debug( "DirBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
769        except Exception as e:
770            log.error( "DirBrowseNode.getPropertyValue error " + lastException2String())
771            raise
772
773        return ret
774
775    def setPropertyValue( self, name, value ):
776        log.debug( "DirBrowseNode.setPropertyValue called " + name + "=" +str(value ) )
777
778    def getPropertySetInfo( self ):
779        log.debug( "DirBrowseNode.getPropertySetInfo called "  )
780        return None
781
782    # XInvocation
783
784    def getIntrospection( self ):
785        log.debug( "DirBrowseNode.getIntrospection() called" )
786        return None
787
788    def invoke( self, name, params, outparamindex, outparams ):
789        log.debug("DirBrowseNode.invoke called for " + name + "," + str(params) + "," + str(outparamindex) + "," + str(outparams))
790        try:
791            if name == "Creatable":
792                if params is None or not params:
793                    raise IllegalArgumentException( "invoke with Creatable needs the name in params" )
794                if self.depth == 0:
795                    subFolderUrl = self.rootUrl + "/" + params[0]
796                    self.provCtx.sfa.createFolder( subFolderUrl )
797                    childNode = DirBrowseNode( self.provCtx, subFolderUrl[subFolderUrl.rfind("/")+1:len(subFolderUrl)], subFolderUrl, self.depth + 1 )
798                    return childNode, (), ()
799                else:
800                    scriptUrl = self.rootUrl + "/" + params[0] + ".py"
801                    # Creates an empty file
802                    self.provCtx.sfa.writeFile( scriptUrl, EmptyInputStream() )
803                    childNode = FileBrowseNode( self.provCtx, self, params[0] )
804                    return childNode, (), ()
805            elif name == "Deletable":
806                self.provCtx.sfa.kill( self.rootUrl )
807                return True, (), ()
808            elif name == "Renamable":
809                if params is None or not params:
810                    raise IllegalArgumentException( "invoke with Renamable needs the name in params" )
811                newUrl = self.rootUrl[0:self.rootUrl.rfind("/")+1] + params[0]
812                self.provCtx.sfa.move( self.rootUrl, newUrl )
813                self.rootUrl = newUrl
814                self.name = params[0]
815                return self, (), ()
816        except Exception as e:
817            log.error( "DirBrowseNode.invoke error: " + lastException2String())
818            raise
819        return None, (), ()
820
821    def setValue( self, name, value ):
822        return None
823
824    def getValue( self, name ):
825        log.debug( "DirBrowseNode.getValue() called" )
826        return None
827
828    def hasMethod( self, name ):
829        return False
830
831    def hasProperty( self, name ):
832        return False
833
834
835class ManifestHandler( XDocumentHandler, unohelper.Base ):
836    def __init__( self, rootUrl ):
837        self.rootUrl = rootUrl
838
839    def startDocument( self ):
840        self.urlList = []
841
842    def endDocument( self ):
843        pass
844
845    def startElement( self , name, attlist):
846        if name == "manifest:file-entry":
847            if attlist.getValueByName( "manifest:media-type" ) == "application/vnd.sun.star.framework-script":
848                self.urlList.append(
849                    self.rootUrl + "/" + attlist.getValueByName( "manifest:full-path" ) )
850
851    def endElement( self, name ):
852        pass
853
854    def characters ( self, chars ):
855        pass
856
857    def ignoreableWhitespace( self, chars ):
858        pass
859
860    def setDocumentLocator( self, locator ):
861        pass
862
863def isPyFileInPath( sfa, path ):
864    ret = False
865    contents = sfa.getFolderContents( path, True )
866    for i in contents:
867        if sfa.isFolder(i):
868            ret = isPyFileInPath(sfa,i)
869        else:
870            if i.endswith(".py"):
871                ret = True
872        if ret:
873            break
874    return ret
875
876# extracts META-INF directory from
877def getPathesFromPackage( rootUrl, sfa ):
878    ret = ()
879    try:
880        fileUrl = rootUrl + "/META-INF/manifest.xml"
881        inputStream = sfa.openFileRead( fileUrl )
882        parser = uno.getComponentContext().ServiceManager.createInstance( "com.sun.star.xml.sax.Parser" )
883        handler = ManifestHandler( rootUrl )
884        parser.setDocumentHandler( handler )
885        parser.parseStream( InputSource( inputStream , "", fileUrl, fileUrl ) )
886        for i in tuple(handler.urlList):
887            if not isPyFileInPath( sfa, i ):
888                handler.urlList.remove(i)
889        ret = tuple( handler.urlList )
890    except UnoException as e:
891        text = lastException2String()
892        log.debug( "getPathesFromPackage " + fileUrl + " Exception: " +text )
893        pass
894    return ret
895
896
897class Package:
898    def __init__( self, pathes, transientPathElement ):
899        self.pathes = pathes
900        self.transientPathElement = transientPathElement
901
902class DummyInteractionHandler( unohelper.Base, XInteractionHandler ):
903    def __init__( self ):
904        pass
905    def handle( self, event):
906        log.debug( "pythonscript: DummyInteractionHandler.handle " + str( event ) )
907
908class DummyProgressHandler( unohelper.Base, XProgressHandler ):
909    def __init__( self ):
910        pass
911
912    def push( self,status ):
913        log.debug( "pythonscript: DummyProgressHandler.push " + str( status ) )
914    def update( self,status ):
915        log.debug( "pythonscript: DummyProgressHandler.update " + str( status ) )
916    def pop( self ):
917        log.debug( "pythonscript: DummyProgressHandler.push " + str( event ) )
918
919class CommandEnvironment(unohelper.Base, XCommandEnvironment):
920    def __init__( self ):
921        self.progressHandler = DummyProgressHandler()
922        self.interactionHandler = DummyInteractionHandler()
923    def getInteractionHandler( self ):
924        return self.interactionHandler
925    def getProgressHandler( self ):
926        return self.progressHandler
927
928#maybe useful for debugging purposes
929#class ModifyListener( unohelper.Base, XModifyListener ):
930#    def __init__( self ):
931#        pass
932#    def modified( self, event ):
933#        log.debug( "pythonscript: ModifyListener.modified " + str( event ) )
934#    def disposing( self, event ):
935#        log.debug( "pythonscript: ModifyListener.disposing " + str( event ) )
936
937def getModelFromDocUrl(ctx, url):
938    """Get document model from document url."""
939    doc = None
940    args = ("Local", "Office")
941    ucb = ctx.getServiceManager().createInstanceWithArgumentsAndContext(
942        "com.sun.star.ucb.UniversalContentBroker", args, ctx)
943    identifier = ucb.createContentIdentifier(url)
944    content = ucb.queryContent(identifier)
945    p = Property()
946    p.Name = "DocumentModel"
947    p.Handle = -1
948
949    c = Command()
950    c.Handle = -1
951    c.Name = "getPropertyValues"
952    c.Argument = uno.Any("[]com.sun.star.beans.Property", (p,))
953
954    env = CommandEnvironment()
955    try:
956        ret = content.execute(c, 0, env)
957        doc = ret.getObject(1, None)
958    except Exception as e:
959        log.isErrorLevel() and log.error("getModelFromDocUrl: %s" % url)
960    return doc
961
962def mapStorageType2PackageContext( storageType ):
963    ret = storageType
964    if( storageType == "share:uno_packages" ):
965        ret = "shared"
966    if( storageType == "user:uno_packages" ):
967        ret = "user"
968    return ret
969
970def getPackageName2PathMap( sfa, storageType ):
971    ret = {}
972    packageManagerFactory = uno.getComponentContext().getValueByName(
973        "/singletons/com.sun.star.deployment.thePackageManagerFactory" )
974    packageManager = packageManagerFactory.getPackageManager(
975        mapStorageType2PackageContext(storageType))
976#    packageManager.addModifyListener( ModifyListener() )
977    log.debug( "pythonscript: getPackageName2PathMap start getDeployedPackages" )
978    packages = packageManager.getDeployedPackages(
979        packageManager.createAbortChannel(), CommandEnvironment( ) )
980    log.debug( "pythonscript: getPackageName2PathMap end getDeployedPackages (" + str(len(packages))+")" )
981
982    for i in packages:
983        log.debug( "inspecting package " + i.Name + "("+i.Identifier.Value+")" )
984        transientPathElement = penultimateElement( i.URL )
985        j = expandUri( i.URL )
986        pathes = getPathesFromPackage( j, sfa )
987        if len( pathes ) > 0:
988            # map package name to url, we need this later
989            log.isErrorLevel() and log.error( "adding Package " + transientPathElement + " " + str( pathes ) )
990            ret[ lastElement( j ) ] = Package( pathes, transientPathElement )
991    return ret
992
993def penultimateElement( aStr ):
994    lastSlash = aStr.rindex("/")
995    penultimateSlash = aStr.rindex("/",0,lastSlash-1)
996    return  aStr[ penultimateSlash+1:lastSlash ]
997
998def lastElement( aStr):
999    return aStr[ aStr.rfind( "/" )+1:len(aStr)]
1000
1001class PackageBrowseNode( unohelper.Base, XBrowseNode ):
1002    def __init__( self, provCtx, name, rootUrl ):
1003        self.provCtx = provCtx
1004        self.name = name
1005        self.rootUrl = rootUrl
1006
1007    def getName( self ):
1008        return self.name
1009
1010    def getChildNodes( self ):
1011        items = list(self.provCtx.mapPackageName2Path.items())
1012        browseNodeList = []
1013        for i in items:
1014            if len( i[1].pathes ) == 1:
1015                browseNodeList.append(
1016                    DirBrowseNode( self.provCtx, i[0], i[1].pathes[0], 0 ))
1017            else:
1018                for j in i[1].pathes:
1019                    browseNodeList.append(
1020                        DirBrowseNode( self.provCtx, i[0]+"."+lastElement(j), j, 0 ) )
1021        return tuple( browseNodeList )
1022
1023    def hasChildNodes( self ):
1024        return len( self.mapPackageName2Path ) > 0
1025
1026    def getType( self ):
1027        return CONTAINER
1028
1029    def getScript( self, uri ):
1030        log.debug( "DirBrowseNode getScript " + uri + " invoked" )
1031        raise IllegalArgumentException( "PackageBrowseNode couldn't instantiate script " + uri , self , 0 )
1032
1033
1034
1035
1036class PythonScript( unohelper.Base, XScript ):
1037    def __init__( self, func, mod ):
1038        self.func = func
1039        self.mod = mod
1040    def invoke(self, args, out, outindex ):
1041        log.debug( "PythonScript.invoke " + str( args ) )
1042        try:
1043            ret = self.func( *args )
1044        except UnoException as e:
1045            # UNO Exception continue to fly ...
1046            text = lastException2String()
1047            complete = "Error during invoking function " + \
1048                str(self.func.__name__) + " in module " + \
1049                self.mod.__file__ + " (" + text + ")"
1050            log.debug( complete )
1051            # some people may beat me up for modifying the exception text,
1052            # but otherwise office just shows
1053            # the type name and message text with no more information,
1054            # this is really bad for most users.
1055            e.Message = e.Message + " (" + complete + ")"
1056            raise
1057        except Exception as e:
1058            # General python exception are converted to uno RuntimeException
1059            text = lastException2String()
1060            complete = "Error during invoking function " + \
1061                str(self.func.__name__) + " in module " + \
1062                self.mod.__file__ + " (" + text + ")"
1063            log.debug( complete )
1064            raise RuntimeException( complete , self )
1065        log.debug( "PythonScript.invoke ret = " + str( ret ) )
1066        return ret, (), ()
1067
1068def expandUri(  uri ):
1069    if uri.startswith( "vnd.sun.star.expand:" ):
1070        uri = uri.replace( "vnd.sun.star.expand:", "",1)
1071        uri = uno.getComponentContext().getByName(
1072                    "/singletons/com.sun.star.util.theMacroExpander" ).expandMacros( uri )
1073    if uri.startswith( "file:" ):
1074        uri = uno.absolutize("",uri)   # necessary to get rid of .. in uri
1075    return uri
1076
1077#--------------------------------------------------------------
1078class PythonScriptProvider( unohelper.Base, XBrowseNode, XScriptProvider, XNameContainer, XPropertySet, XInvocation):
1079    def __init__( self, ctx, *args ):
1080        if log.isDebugLevel():
1081            mystr = ""
1082            for i in args:
1083                if len(mystr) > 0:
1084                    mystr = mystr +","
1085                mystr = mystr + str(i)
1086            log.debug( "Entering PythonScriptProvider.ctor with args " + mystr )
1087
1088        doc = None
1089        inv = None
1090        storageType = ""
1091
1092        if isinstance(args[0],unicode ):
1093            storageType = args[0]
1094            if storageType.startswith( "vnd.sun.star.tdoc" ):
1095                doc = getModelFromDocUrl(ctx, storageType)
1096        else:
1097            inv = args[0]
1098            try:
1099                doc = inv.ScriptContainer
1100                content = ctx.getServiceManager().createInstanceWithContext(
1101                    "com.sun.star.frame.TransientDocumentsDocumentContentFactory",
1102                    ctx).createDocumentContent(doc)
1103                storageType = content.getIdentifier().getContentIdentifier()
1104            except Exception as e:
1105                text = lastException2String()
1106                log.error( text )
1107
1108        isPackage = storageType.endswith( ":uno_packages" )
1109
1110        try:
1111#            urlHelper = ctx.ServiceManager.createInstanceWithArgumentsAndContext(
1112#                "com.sun.star.script.provider.ScriptURIHelper", (LANGUAGENAME, storageType), ctx)
1113            urlHelper = MyUriHelper( ctx, storageType )
1114            log.debug( "got urlHelper " + str( urlHelper ) )
1115
1116            rootUrl = expandUri( urlHelper.getRootStorageURI() )
1117            log.debug( storageType + " transformed to " + rootUrl )
1118
1119            ucbService = "com.sun.star.ucb.SimpleFileAccess"
1120            sfa = ctx.ServiceManager.createInstanceWithContext( ucbService, ctx )
1121            if not sfa:
1122                log.debug("PythonScriptProvider couldn't instantiate " +ucbService)
1123                raise RuntimeException(
1124                    "PythonScriptProvider couldn't instantiate " +ucbService, self)
1125            self.provCtx = ProviderContext(
1126                storageType, sfa, urlHelper, ScriptContext( uno.getComponentContext(), doc, inv ) )
1127            if isPackage:
1128                mapPackageName2Path = getPackageName2PathMap( sfa, storageType )
1129                self.provCtx.setPackageAttributes( mapPackageName2Path , rootUrl )
1130                self.dirBrowseNode = PackageBrowseNode( self.provCtx, LANGUAGENAME, rootUrl )
1131            else:
1132                self.dirBrowseNode = DirBrowseNode( self.provCtx, LANGUAGENAME, rootUrl, 0 )
1133
1134        except Exception as e:
1135            text = lastException2String()
1136            log.debug( "PythonScriptProvider could not be instantiated because of : " + text )
1137            raise e
1138
1139    def getName( self ):
1140        return self.dirBrowseNode.getName()
1141
1142    def getChildNodes( self ):
1143        return self.dirBrowseNode.getChildNodes()
1144
1145    def hasChildNodes( self ):
1146        return self.dirBrowseNode.hasChildNodes()
1147
1148    def getType( self ):
1149        return self.dirBrowseNode.getType()
1150
1151    def getScript( self, uri ):
1152        log.debug( "DirBrowseNode getScript " + uri + " invoked" )
1153
1154        raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 )
1155
1156    def getScript( self, scriptUri ):
1157        try:
1158            log.debug( "getScript " + scriptUri + " invoked")
1159
1160            storageUri = self.provCtx.getStorageUrlFromPersistentUrl(
1161                self.provCtx.uriHelper.getStorageURI(scriptUri) );
1162            log.debug( "getScript: storageUri = " + storageUri)
1163            fileUri = storageUri[0:storageUri.find( "$" )]
1164            funcName = storageUri[storageUri.find( "$" )+1:len(storageUri)]
1165
1166            mod = self.provCtx.getModuleByUrl( fileUri )
1167            log.debug( " got mod " + str(mod) )
1168
1169            func = mod.__dict__[ funcName ]
1170
1171            log.debug( "got func " + str( func ) )
1172            return PythonScript( func, mod )
1173        except Exception as e:
1174            text = lastException2String()
1175            log.error( text )
1176            raise ScriptFrameworkErrorException( text, self, scriptUri, LANGUAGENAME, 0 )
1177
1178    # XPropertySet
1179
1180    def getPropertyValue( self, name ):
1181        return self.dirBrowseNode.getPropertyValue( name )
1182
1183    def setPropertyValue( self, name, value ):
1184        return self.dirBrowseNode.setPropertyValue( name, value )
1185
1186    def getPropertySetInfo( self ):
1187        return self.dirBrowseNode.getPropertySetInfo()
1188
1189    # XInvocation
1190
1191    def getIntrospection( self ):
1192        return self.dirBrowseNode.getIntrospection()
1193
1194    def invoke( self, name, params, outparamindex, outparams ):
1195        return self.dirBrowseNode.invoke( name, params, outparamindex, outparams)
1196
1197    def setValue( self, name, value ):
1198        return self.dirBrowseNode.setValue( name, value )
1199
1200    def getValue( self, name ):
1201        return self.dirBrowseNode.getValue( name )
1202
1203    def hasMethod( self, name ):
1204        return self.dirBrowseNode.hasMethod( name )
1205
1206    def hasProperty( self, name ):
1207        return self.dirBrowseNode.hasProperty( name )
1208
1209    # XServiceInfo
1210    def getSupportedServices( self ):
1211        return g_ImplementationHelper.getSupportedServices(g_implName)
1212
1213    def supportsService( self, ServiceName ):
1214        return g_ImplementationHelper.supportsService( g_implName, ServiceName )
1215
1216    def getImplementationName(self):
1217        return g_implName
1218
1219    def getByName( self, name ):
1220        log.debug( "getByName called" + str( name ))
1221        return None
1222
1223
1224    def getElementNames( self ):
1225        log.debug( "getElementNames called")
1226        return ()
1227
1228    def hasByName( self, name ):
1229        try:
1230            log.debug( "hasByName called " + str( name ))
1231            uri = expandUri(name)
1232            ret = self.provCtx.isUrlInPackage( uri )
1233            log.debug( "hasByName " + uri + " " +str( ret ) )
1234            return ret
1235        except Exception as e:
1236            text = lastException2String()
1237            log.debug( "Error in hasByName:" +  text )
1238            return False
1239
1240    def removeByName( self, name ):
1241        log.debug( "removeByName called" + str( name ))
1242        uri = expandUri( name )
1243        if self.provCtx.isUrlInPackage( uri ):
1244            self.provCtx.removePackageByUrl( uri )
1245        else:
1246            log.debug( "removeByName unknown uri " + str( name ) + ", ignoring" )
1247            raise NoSuchElementException( uri + "is not in package" , self )
1248        log.debug( "removeByName called" + str( uri ) + " successful" )
1249
1250    def insertByName( self, name, value ):
1251        log.debug( "insertByName called " + str( name ) + " " + str( value ))
1252        uri = expandUri( name )
1253        if isPyFileInPath( self.provCtx.sfa, uri ):
1254            self.provCtx.addPackageByUrl( uri )
1255        else:
1256            # package is no python package ...
1257            log.debug( "insertByName: no python files in " + str( uri ) + ", ignoring" )
1258            raise IllegalArgumentException( uri + " does not contain .py files", self, 1 )
1259        log.debug( "insertByName called " + str( uri ) + " successful" )
1260
1261    def replaceByName( self, name, value ):
1262        log.debug( "replaceByName called " + str( name ) + " " + str( value ))
1263        removeByName( name )
1264        insertByName( name )
1265        log.debug( "replaceByName called" + str( uri ) + " successful" )
1266
1267    def getElementType( self ):
1268        log.debug( "getElementType called" )
1269        return uno.getTypeByName( "void" )
1270
1271    def hasElements( self ):
1272        log.debug( "hasElements got called")
1273        return False
1274
1275g_ImplementationHelper.addImplementation( \
1276        PythonScriptProvider,g_implName, \
1277    ("com.sun.star.script.provider.LanguageScriptProvider",
1278     "com.sun.star.script.provider.ScriptProviderFor"+ LANGUAGENAME,),)
1279
1280
1281log.debug( "pythonscript finished initializing" )
1282