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