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