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