xref: /trunk/main/scripting/source/pyprov/pythonscript.py (revision 27dee00ef8b391d4d520b7edb65b4093b02f83ed) !
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 types
28import time
29import ast
30import re
31
32try:
33    unicode
34except NameError:
35    unicode = str
36
37class LogLevel:
38    NONE = 0   # production level
39    ERROR = 1  # for script developers
40    DEBUG = 2  # for script framework developers
41
42PYSCRIPT_LOG_ENV = "PYSCRIPT_LOG_LEVEL"
43PYSCRIPT_LOG_STDOUT_ENV = "PYSCRIPT_LOG_STDOUT"
44
45# Configuration ----------------------------------------------------
46LogLevel.use = LogLevel.NONE
47if os.environ.get(PYSCRIPT_LOG_ENV) == "ERROR":
48    LogLevel.use = LogLevel.ERROR
49elif os.environ.get(PYSCRIPT_LOG_ENV) == "DEBUG":
50    LogLevel.use = LogLevel.DEBUG
51
52# True, writes to stdout (difficult on windows)
53# False, writes to user/Scripts/python/log.txt
54LOG_STDOUT = os.environ.get(PYSCRIPT_LOG_STDOUT_ENV, "1") != "0"
55
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, FontDescriptor
140from com.sun.star.awt.FontPitch import FIXED
141
142from com.sun.star.script.provider import XScriptProvider, XScript, XScriptContext, ScriptFrameworkErrorException
143from com.sun.star.script.browse import XBrowseNode
144from com.sun.star.script.browse.BrowseNodeTypes import SCRIPT, CONTAINER, ROOT
145from com.sun.star.util import XModifyListener
146
147LANGUAGENAME = "Python"
148GLOBAL_SCRIPTCONTEXT_NAME = "XSCRIPTCONTEXT"
149CALLABLE_CONTAINER_NAME =  "g_exportedScripts"
150
151# pythonloader looks for a static g_ImplementationHelper variable
152g_ImplementationHelper = unohelper.ImplementationHelper()
153g_implName = "org.openoffice.pyuno.LanguageScriptProviderFor"+LANGUAGENAME
154
155
156
157BLOCK_SIZE = 65536
158def readTextFromStream( inputStream ):
159    # read the file
160    code = uno.ByteSequence( b"" )
161    while True:
162        read,out = inputStream.readBytes( None , BLOCK_SIZE )
163        code = code + out
164        if read < BLOCK_SIZE:
165            break
166    if sys.version_info[0] > 2:
167        return str( code.value, 'utf-8' )
168    else:
169        return code.value
170
171def toIniName( str ):
172    # TODO: what is the official way to get to know whether i am on the windows platform ?
173    if( hasattr(sys , "dllhandle") ):
174        return str + ".ini"
175    return str + "rc"
176
177class EmptyInputStream( unohelper.Base, XInputStream ):
178    def __init__( self ):
179        pass
180
181    def closeInput(self):
182        pass
183
184    def readBytes( self, seq, n ):
185        return 0, ""
186
187    def readSomeBytes( self, seq, n ):
188        return 0, ""
189
190    def skipBytes( self, n ):
191        pass
192
193    def available( self ):
194        return 0
195
196class BytesInputStream( unohelper.Base, XInputStream ):
197    def __init__( self, bytes ):
198        self.bytes = bytes
199        self.position = 0
200
201    def closeInput(self):
202        pass
203
204    def readBytes( self, seq, n ):
205        size = self.available()
206        if n < size:
207            size = n
208        curr = self.position
209        self.position += size
210        return size, uno.ByteSequence( self.bytes[curr:curr+size] )
211
212    def readSomeBytes( self, seq, n ):
213        return self.readBytes( seq, n )
214
215    def skipBytes( self, n ):
216        size = self.available()
217        if n < size:
218            size = n
219        self.position += size
220
221    def available( self ):
222        return len( self.bytes ) - self.position
223
224""" definition: storageURI is the system dependent, absolute file url, where the script is stored on disk
225                scriptURI is the system independent uri
226"""
227class MyUriHelper:
228
229    def __init__( self, ctx, location ):
230        self.s_UriMap = \
231        { "share" : "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" +  toIniName( "bootstrap") + "::BaseInstallation}/share/Scripts/python" , \
232          "share:uno_packages" : "vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE/uno_packages", \
233          "user" : "vnd.sun.star.expand:${$OOO_BASE_DIR/program/" + toIniName( "bootstrap") + "::UserInstallation}/user/Scripts/python" , \
234          "user:uno_packages" : "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages" }
235        self.m_uriRefFac = ctx.ServiceManager.createInstanceWithContext("com.sun.star.uri.UriReferenceFactory",ctx)
236        if location.startswith( "vnd.sun.star.tdoc" ):
237            self.m_baseUri = location + "/Scripts/python"
238            self.m_scriptUriLocation = "document"
239        else:
240            self.m_baseUri = expandUri( self.s_UriMap[location] )
241            self.m_scriptUriLocation = location
242        log.debug( "initialized urihelper with baseUri="+self.m_baseUri + ",m_scriptUriLocation="+self.m_scriptUriLocation )
243
244    def getRootStorageURI( self ):
245        return self.m_baseUri
246
247    def getStorageURI( self, scriptURI ):
248        return self.scriptURI2StorageUri(scriptURI)
249
250    def getScriptURI( self, storageURI ):
251        return self.storageURI2ScriptUri(storageURI)
252
253    def storageURI2ScriptUri( self, storageURI ):
254        if not storageURI.startswith( self.m_baseUri ):
255            message = "pythonscript: storage uri '" + storageURI + "' not in base uri '" + self.m_baseUri + "'"
256            log.debug( message )
257            raise RuntimeException( message )
258
259        ret = "vnd.sun.star.script:" + \
260              storageURI[len(self.m_baseUri)+1:].replace("/","|") + \
261              "?language=" + LANGUAGENAME + "&location=" + self.m_scriptUriLocation
262        log.debug( "converting storageURI="+storageURI + " to scriptURI=" + ret )
263        return ret
264
265    def scriptURI2StorageUri( self, scriptURI ):
266        try:
267            myUri = self.m_uriRefFac.parse(scriptURI)
268            ret = self.m_baseUri + "/" + myUri.getName().replace( "|", "/" )
269            log.debug( "converting scriptURI="+scriptURI + " to storageURI=" + ret )
270            return ret
271        except UnoException as e:
272            log.error( "error during converting scriptURI="+scriptURI + ": " + e.Message)
273            raise RuntimeException( "pythonscript:scriptURI2StorageUri: " +e.getMessage(), None )
274        except Exception as e:
275            log.error( "error during converting scriptURI="+scriptURI + ": " + str(e))
276            raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + str(e), None )
277
278
279class ModuleEntry:
280    def __init__( self, lastRead, module ):
281        self.lastRead = lastRead
282        self.module = module
283
284def hasChanged( oldDate, newDate ):
285    return newDate.Year > oldDate.Year or \
286           newDate.Month > oldDate.Month or \
287           newDate.Day > oldDate.Day or \
288           newDate.Hours > oldDate.Hours or \
289           newDate.Minutes > oldDate.Minutes or \
290           newDate.Seconds > oldDate.Seconds or \
291           newDate.HundredthSeconds > oldDate.HundredthSeconds
292
293def ensureSourceState( code ):
294    if not code.endswith( "\n" ):
295        code = code + "\n"
296    code = code.replace( "\r", "" )
297    return code
298
299
300# Heuristics for Python 2 constructs that are *syntax* errors under Python 3.
301# These fail at compile() time, so a runtime shim cannot fix them; the best we
302# can do is recognise the most common ones and tell the user the script needs
303# to be ported, instead of surfacing a bare SyntaxError traceback.
304PYTHON2_SYNTAX_HINTS = (
305    (re.compile( r"^\s*print\s+[^(=]" ),          "'print' statement (use the print() function)"),
306    (re.compile( r"^\s*exec\s+[^(=]" ),           "'exec' statement (use the exec() function)"),
307    (re.compile( r"\bexcept\b[^:]*,\s*\w+\s*:" ), "'except Exception, e:' syntax (use 'except Exception as e:')"),
308    (re.compile( r"\braise\s+\w+\s*," ),          "'raise Exc, args' syntax (use 'raise Exc(args)')"),
309    (re.compile( r"`[^`]+`" ),                     "backtick repr `x` (use repr(x))"),
310    (re.compile( r"<>" ),                          "'<>' operator (use '!=')"),
311    (re.compile( r"\b0[0-7]+\b" ),                 "old-style octal literal (use the 0o prefix)"),
312    (re.compile( r"\b[uU][rR]['\"]" ),             "'ur' string prefix (removed in Python 3)"),
313)
314
315def detectPython2Syntax( src, syntaxError ):
316    # Prefer the exact line the compiler flagged; fall back to scanning the source.
317    candidates = []
318    text = getattr( syntaxError, "text", None )
319    if text:
320        candidates.append( text )
321    else:
322        candidates = src.split( "\n" )
323    for line in candidates:
324        for pattern, description in PYTHON2_SYNTAX_HINTS:
325            if pattern.search( line ):
326                return description
327    return None
328
329
330def checkForPythonPathBesideScript( url ):
331    if url.startswith( "file:" ):
332        path = unohelper.fileUrlToSystemPath( url+"/pythonpath.zip" );
333        log.log( LogLevel.DEBUG,  "checking for existence of " + path )
334        if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path:
335            log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" )
336            sys.path.append( path )
337
338        path = unohelper.fileUrlToSystemPath( url+"/pythonpath" );
339        log.log( LogLevel.DEBUG,  "checking for existence of " + path )
340        if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path:
341            log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" )
342            sys.path.append( path )
343
344
345class ScriptContext(unohelper.Base):
346    def __init__( self, ctx, doc, inv ):
347        self.ctx = ctx
348        self.doc = doc
349        self.inv = inv
350
351   # XScriptContext
352    def getDocument(self):
353        if self.doc:
354            return self.doc
355        return self.getDesktop().getCurrentComponent()
356
357    def getDesktop(self):
358        return self.ctx.ServiceManager.createInstanceWithContext(
359            "com.sun.star.frame.Desktop", self.ctx )
360
361    def getComponentContext(self):
362        return self.ctx
363
364    def getInvocationContext(self):
365        return self.inv
366
367#----------------------------------
368# Global Module Administration
369# does not fit together with script
370# engine lifetime management
371#----------------------------------
372#g_scriptContext = ScriptContext( uno.getComponentContext(), None )
373#g_modules = {}
374#def getModuleByUrl( url, sfa ):
375#    entry =  g_modules.get(url)
376#    load = True
377#    lastRead = sfa.getDateTimeModified( url )
378#    if entry:
379#        if hasChanged( entry.lastRead, lastRead ):
380#            log.debug("file " + url + " has changed, reloading")
381#        else:
382#            load = False
383#
384#    if load:
385#        log.debug( "opening >" + url + "<" )
386#
387#        code = readTextFromStream( sfa.openFileRead( url ) )
388
389        # execute the module
390#        entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") )
391#        entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = g_scriptContext
392#        entry.module.__file__ = url
393#        exec code in entry.module.__dict__
394#        g_modules[ url ] = entry
395#        log.debug( "mapped " + url + " to " + str( entry.module ) )
396#    return entry.module
397
398class ProviderContext:
399    def __init__( self, storageType, sfa, uriHelper, scriptContext ):
400        self.storageType = storageType
401        self.sfa = sfa
402        self.uriHelper = uriHelper
403        self.scriptContext = scriptContext
404        self.modules = {}
405        self.rootUrl = None
406        self.mapPackageName2Path = None
407
408    def getTransientPartFromUrl( self, url ):
409        rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1)
410        return rest[0:rest.find("/")]
411
412    def getPackageNameFromUrl( self, url ):
413        rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1)
414        start = rest.find("/") +1
415        return rest[start:rest.find("/",start)]
416
417
418    def removePackageByUrl( self, url ):
419        items = list(self.mapPackageName2Path.items())
420        for i in items:
421            if url in i[1].pathes:
422                self.mapPackageName2Path.pop(i[0])
423                break
424
425    def addPackageByUrl( self, url ):
426        packageName = self.getPackageNameFromUrl( url )
427        transientPart = self.getTransientPartFromUrl( url )
428        log.debug( "addPackageByUrl : " + packageName + ", " + transientPart + "("+url+")" + ", rootUrl="+self.rootUrl )
429        if packageName in self.mapPackageName2Path:
430            package = self.mapPackageName2Path[ packageName ]
431            package.pathes = package.pathes + (url, )
432        else:
433            package = Package( (url,), transientPart)
434            self.mapPackageName2Path[ packageName ] = package
435
436    def isUrlInPackage( self, url ):
437        values = list(self.mapPackageName2Path.values())
438        for i in values:
439#           print "checking " + url + " in " + str(i.pathes)
440            if url in i.pathes:
441                return True
442#        print "false"
443        return False
444
445    def setPackageAttributes( self, mapPackageName2Path, rootUrl ):
446        self.mapPackageName2Path = mapPackageName2Path
447        self.rootUrl = rootUrl
448
449    def getPersistentUrlFromStorageUrl( self, url ):
450        # package name is the second directory
451        ret = url
452        if self.rootUrl:
453            pos = len( self.rootUrl) +1
454            ret = url[0:pos]+url[url.find("/",pos)+1:len(url)]
455        log.debug( "getPersistentUrlFromStorageUrl " + url +  " -> "+ ret)
456        return ret
457
458    def getStorageUrlFromPersistentUrl( self, url):
459        ret = url
460        if self.rootUrl:
461            pos = len(self.rootUrl)+1
462            packageName = url[pos:url.find("/",pos+1)]
463            package = self.mapPackageName2Path[ packageName ]
464            ret = url[0:pos]+ package.transientPathElement + "/" + url[pos:len(url)]
465        log.debug( "getStorageUrlFromPersistentUrl " + url + " -> "+ ret)
466        return ret
467
468    def getFuncsByUrl( self, url ):
469        src = readTextFromStream( self.sfa.openFileRead( url ) )
470        checkForPythonPathBesideScript( url[0:url.rfind('/')] )
471        src = ensureSourceState( src )
472
473        allFuncs = []
474        g_exportedScripts = []
475
476        a = ast.parse(src, url)
477
478        if isinstance(a, ast.Module):
479            for node in a.body:
480                if isinstance(node, ast.FunctionDef):
481                    allFuncs.append(node.name)
482                elif isinstance(node, ast.Assign):
483                    is_exported = False
484                    for subnode in node.targets:
485                        if isinstance(subnode, ast.Name) and \
486                            subnode.id == "g_exportedScripts":
487                            is_exported = True
488                            break
489                    if is_exported:
490                        value_node = node.value
491                        if isinstance(value_node, ast.List) or \
492                            isinstance(value_node, ast.Tuple):
493                            for elt in value_node.elts:
494                                if isinstance(elt, ast.Str):
495                                    g_exportedScripts.append(elt.s)
496                                elif isinstance(elt, ast.Name):
497                                    g_exportedScripts.append(elt.id)
498                        elif isinstance(value_node, ast.Str):
499                            g_exportedScripts.append(value_node.s)
500                        elif isinstance(value_node, ast.Name):
501                            g_exportedScripts.append(value_node.id)
502                        return g_exportedScripts
503        return allFuncs
504
505    def getModuleByUrl( self, url ):
506        entry =  self.modules.get(url)
507        load = True
508        lastRead = self.sfa.getDateTimeModified( url )
509        if entry:
510            if hasChanged( entry.lastRead, lastRead ):
511                log.debug( "file " + url + " has changed, reloading" )
512            else:
513                load = False
514
515        if load:
516            log.debug( "opening >" + url + "<" )
517
518            src = readTextFromStream( self.sfa.openFileRead( url ) )
519            checkForPythonPathBesideScript( url[0:url.rfind('/')] )
520            src = ensureSourceState( src )
521
522            # execute the module
523            entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") )
524            entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.scriptContext
525
526            code = None
527            scriptName = uno.fileUrlToSystemPath( url ) if url.startswith( "file:" ) else url
528            try:
529                code = compile( src, encfile(scriptName) if url.startswith( "file:" ) else url, "exec" )
530            except SyntaxError as e:
531                hint = detectPython2Syntax( src, e )
532                if hint:
533                    raise SyntaxError(
534                        "The script '" + scriptName + "' appears to use Python 2 syntax that is "
535                        "not valid in Python 3: " + hint + ". Please port the script to Python 3. "
536                        "(original error: " + str(e) + ")" )
537                raise
538            exec(code, entry.module.__dict__)
539            entry.module.__file__ = url
540            self.modules[ url ] = entry
541            log.debug( "mapped " + url + " to " + str( entry.module ) )
542        return  entry.module
543
544    # Forgets a stale module so a fresh copy can be loaded in the future.
545    # This is necessary because getModuleByUrl()'s self.sfa.getDateTimeModified()
546    # doesn't always work, eg. for embedded scripts it always returns timestamps
547    # with all zeroes, and even if it worked, the smallest granularity for ZIP file
548    # timestamps is 2 seconds, which isn't good enough.
549    def removeModuleByUrl( self, url ):
550        self.modules.pop( url, None )
551
552def createEditorDialog( ctx ):
553    smgr = ctx.ServiceManager
554
555    dialogModel = smgr.createInstanceWithContext(
556        "com.sun.star.awt.UnoControlDialogModel", ctx)
557    dialogModel.PositionX = 105
558    dialogModel.PositionY = 117
559    dialogModel.Width = 240
560    dialogModel.Height = 320
561    dialogModel.Closeable = True
562    dialogModel.Moveable = True
563    dialogModel.Title = "Python Macro Editor"
564
565    runButtonModel = dialogModel.createInstance(
566        "com.sun.star.awt.UnoControlButtonModel" )
567    runButtonModel.PositionX = 57
568    runButtonModel.PositionY = 300
569    runButtonModel.Width = 40
570    runButtonModel.Height = 14
571    runButtonModel.TabIndex = 0
572    runButtonModel.Label = "Run"
573
574    saveButtonModel = dialogModel.createInstance(
575        "com.sun.star.awt.UnoControlButtonModel" )
576    saveButtonModel.PositionX = 100
577    saveButtonModel.PositionY = 300
578    saveButtonModel.Width = 40
579    saveButtonModel.Height = 14
580    saveButtonModel.TabIndex = 1
581    saveButtonModel.Label = "Save"
582
583    closeButtonModel = dialogModel.createInstance(
584        "com.sun.star.awt.UnoControlButtonModel" )
585    closeButtonModel.PositionX = 143
586    closeButtonModel.PositionY = 300
587    closeButtonModel.Width = 40
588    closeButtonModel.Height = 14
589    closeButtonModel.TabIndex = 2
590    closeButtonModel.PushButtonType = 2  # CANCEL
591    closeButtonModel.Label = "Close"
592
593    fontDescriptor = FontDescriptor()
594    fontDescriptor.Name = "SomeFixedWidthFont"
595    fontDescriptor.Pitch = FIXED
596
597    textFieldModel = dialogModel.createInstance(
598        "com.sun.star.awt.UnoControlEditModel" )
599    textFieldModel.PositionX = 6
600    textFieldModel.PositionY = 6
601    textFieldModel.Width = 228
602    textFieldModel.Height = 288
603    textFieldModel.TabIndex = 3
604    textFieldModel.HScroll = True
605    textFieldModel.VScroll = True
606    textFieldModel.MultiLine = True
607    textFieldModel.FontDescriptor = fontDescriptor
608
609    dialogModel.insertByName( "RunButton", runButtonModel )
610    dialogModel.insertByName( "SaveButton", saveButtonModel )
611    dialogModel.insertByName( "CloseButton", closeButtonModel )
612    dialogModel.insertByName( "EditorTextField", textFieldModel )
613
614    # create the dialog control and set the model
615    controlContainer = smgr.createInstanceWithContext(
616        "com.sun.star.awt.UnoControlDialog", ctx);
617    controlContainer.setModel(dialogModel);
618
619    # create a peer
620    toolkit = smgr.createInstanceWithContext(
621        "com.sun.star.awt.ExtToolkit", ctx);
622
623    controlContainer.setVisible(False);
624    controlContainer.createPeer(toolkit, None);
625
626    return controlContainer
627
628
629#--------------------------------------------------
630def isScript( candidate ):
631    ret = False
632    if isinstance( candidate, type(isScript) ):
633        ret = True
634    return ret
635
636#-------------------------------------------------------
637class ScriptBrowseNode( unohelper.Base, XBrowseNode, XPropertySet, XInvocation, XActionListener ):
638    def __init__( self, provCtx, parent, fileName, funcName ):
639        self.parent = parent
640        self.fileName = fileName
641        self.funcName = funcName
642        self.provCtx = provCtx
643
644    def uri( self ):
645        return self.parent.uri()
646
647    def getName( self ):
648        return self.funcName
649
650    def getChildNodes(self):
651        return ()
652
653    def hasChildNodes(self):
654        return False
655
656    def getType( self):
657        return SCRIPT
658
659    def getPropertyValue( self, name ):
660        ret = None
661        try:
662            if name == "URI":
663                ret = self.provCtx.uriHelper.getScriptURI(
664                    self.provCtx.getPersistentUrlFromStorageUrl( self.uri() + "$" + self.funcName ) )
665            elif name == "Editable":
666                ret = not self.provCtx.sfa.isReadOnly( self.uri() )
667
668            log.debug( "ScriptBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
669        except Exception as e:
670            log.error( "ScriptBrowseNode.getPropertyValue error " + lastException2String())
671            raise
672
673        return ret
674    def setPropertyValue( self, name, value ):
675        log.debug( "ScriptBrowseNode.setPropertyValue called " + name + "=" +str(value ) )
676    def getPropertySetInfo( self ):
677        log.debug( "ScriptBrowseNode.getPropertySetInfo called "  )
678        return None
679
680    def getIntrospection( self ):
681        return None
682
683    def invoke( self, name, params, outparamindex, outparams ):
684        if name == "Editable":
685            ctx = self.provCtx.scriptContext.getComponentContext()
686
687            self.editor = createEditorDialog( ctx )
688
689            code = readTextFromStream(self.provCtx.sfa.openFileRead(self.uri()))
690            code = ensureSourceState( code )
691            self.editor.getControl("EditorTextField").setText(code)
692
693            self.editor.getControl("RunButton").setActionCommand("Run")
694            self.editor.getControl("RunButton").addActionListener(self)
695            self.editor.getControl("SaveButton").setActionCommand("Save")
696            self.editor.getControl("SaveButton").addActionListener(self)
697
698            self.editor.execute()
699
700        return None, (), ()
701
702    def actionPerformed( self, event ):
703        try:
704            if event.ActionCommand == "Run":
705                code = self.editor.getControl("EditorTextField").getText()
706                code = ensureSourceState( code )
707                mod = types.ModuleType("ooo_script_framework")
708                mod.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.provCtx.scriptContext
709                exec(code, mod.__dict__)
710                values = mod.__dict__.get( CALLABLE_CONTAINER_NAME , None )
711                if not values:
712                    values = list(mod.__dict__.values())
713
714                for i in values:
715                    if isScript( i ):
716                        i()
717                        break
718
719            elif event.ActionCommand == "Save":
720                toWrite = uno.ByteSequence(
721                    self.editor.getControl("EditorTextField").getText().encode("utf-8"))
722                log.debug( "Saving Python macro to URI " + self.uri() )
723                self.provCtx.sfa.writeFile( self.uri(), BytesInputStream( toWrite.value ) )
724                self.provCtx.removeModuleByUrl( self.uri() )
725        except Exception as e:
726            # TODO: add an error box here !
727            log.error( lastException2String() )
728
729
730    def setValue( self, name, value ):
731        return None
732
733    def getValue( self, name ):
734        return None
735
736    def hasMethod( self, name ):
737        return False
738
739    def hasProperty( self, name ):
740        return False
741
742
743#-------------------------------------------------------
744class FileBrowseNode( unohelper.Base, XBrowseNode, XPropertySet, XInvocation, XActionListener ):
745    def __init__( self, provCtx, parent, name ):
746        self.provCtx = provCtx
747        self.parent = parent
748        self.name = name
749        self.funcnames = None
750
751    def uri( self ):
752        return self.parent.rootUrl + "/" + self.name + ".py"
753
754    def getName( self ):
755        return self.name
756
757    def getChildNodes(self):
758        ret = ()
759        try:
760            self.funcnames = self.provCtx.getFuncsByUrl( self.uri() )
761
762            scriptNodeList = []
763            for i in self.funcnames:
764                scriptNodeList.append(
765                    ScriptBrowseNode(
766                    self.provCtx, self, self.name, i ))
767            ret = tuple( scriptNodeList )
768        except Exception as e:
769            text = lastException2String()
770            log.error( "FileBrowseNode.getChildNodes error while evaluating " + self.uri() + ":" + text )
771            raise
772        return ret
773
774    def hasChildNodes(self):
775        try:
776            return len(self.getChildNodes()) > 0
777        except Exception as e:
778            return False
779
780    def getType( self):
781        return CONTAINER
782
783    # XPropertySet
784
785    def getPropertyValue( self, name ):
786        ret = None
787        try:
788            if name == "Editable":
789                ret = not self.provCtx.sfa.isReadOnly( self.uri() )
790            elif name == "Deletable":
791                ret = not self.provCtx.sfa.isReadOnly( self.uri() )
792            elif name == "Renamable":
793                ret = not self.provCtx.sfa.isReadOnly( self.uri() )
794
795            log.debug( "FileBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
796        except Exception as e:
797            log.error( "FileBrowseNode.getPropertyValue error " + lastException2String())
798            raise
799
800        return ret
801
802    def setPropertyValue( self, name, value ):
803        log.debug( "FileBrowseNode.setPropertyValue called " + name + "=" +str(value ) )
804
805    def getPropertySetInfo( self ):
806        log.debug( "FileBrowseNode.getPropertySetInfo called "  )
807        return None
808
809    # XInvocation
810
811    def getIntrospection( self ):
812        log.debug( "FileBrowseNode.getIntrospection() called" )
813        return None
814
815    def invoke( self, name, params, outparamindex, outparams ):
816        log.debug("FileBrowseNode.invoke called for " + name + "," + str(params) + "," + str(outparamindex) + "," + str(outparams))
817        try:
818            if name == "Editable":
819                ctx = self.provCtx.scriptContext.getComponentContext()
820
821                self.editor = createEditorDialog( ctx )
822
823                code = readTextFromStream(self.provCtx.sfa.openFileRead(self.uri()))
824                code = ensureSourceState( code )
825                self.editor.getControl("EditorTextField").setText(code)
826
827                self.editor.getControl("RunButton").setActionCommand("Run")
828                self.editor.getControl("RunButton").addActionListener(self)
829                self.editor.getControl("SaveButton").setActionCommand("Save")
830                self.editor.getControl("SaveButton").addActionListener(self)
831
832                self.editor.execute()
833            elif name == "Deletable":
834                self.provCtx.sfa.kill( self.uri() )
835                return True, (), ()
836            elif name == "Renamable":
837                if params is None or not params:
838                    raise IllegalArgumentException( "invoke with Creatable needs the name in params" )
839                newUri = self.parent.rootUrl + "/" + params[0] + ".py"
840                self.provCtx.sfa.move( self.uri(), newUri )
841                self.name = params[0]
842                return self, (), ()
843        except Exception as e:
844            log.error( "FileBrowseNode.invoke error " + lastException2String() )
845            raise
846        return None, (), ()
847
848    def setValue( self, name, value ):
849        return None
850
851    def getValue( self, name ):
852        log.debug( "FileBrowseNode.getValue() called" )
853        return None
854
855    def hasMethod( self, name ):
856        return False
857
858    def hasProperty( self, name ):
859        return False
860
861    # XActionListener
862
863    def actionPerformed( self, event ):
864        try:
865            if event.ActionCommand == "Run":
866                code = self.editor.getControl("EditorTextField").getText()
867                code = ensureSourceState( code )
868                mod = types.ModuleType("ooo_script_framework")
869                mod.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.provCtx.scriptContext
870                exec(code, mod.__dict__)
871                values = mod.__dict__.get( CALLABLE_CONTAINER_NAME , None )
872                if not values:
873                    values = list(mod.__dict__.values())
874
875                for i in values:
876                    if isScript( i ):
877                        i()
878                        break
879
880            elif event.ActionCommand == "Save":
881                toWrite = uno.ByteSequence(
882                    self.editor.getControl("EditorTextField").getText().encode("utf-8"))
883                log.debug( "Saving Python macro to URI " + self.uri() )
884                self.provCtx.sfa.writeFile( self.uri(), BytesInputStream( toWrite.value ) )
885                self.provCtx.removeModuleByUrl( self.uri() )
886        except Exception as e:
887            # TODO: add an error box here !
888            log.error( lastException2String() )
889
890
891class DirBrowseNode( unohelper.Base, XBrowseNode, XPropertySet, XInvocation ):
892    def __init__( self, provCtx, name, rootUrl, depth ):
893        self.provCtx = provCtx
894        self.name = name
895        self.rootUrl = rootUrl
896        self.depth = depth
897        log.debug( "DirBrowseNode constructor for " + name + "," + rootUrl )
898
899    def getName( self ):
900        return self.name
901
902    def getChildNodes( self ):
903        try:
904            log.debug( "DirBrowseNode.getChildNodes called for " + self.rootUrl )
905            contents = self.provCtx.sfa.getFolderContents( self.rootUrl, True )
906            browseNodeList = []
907            for i in contents:
908                if i.endswith( ".py" ):
909                    log.debug( "adding filenode " + i )
910                    browseNodeList.append(
911                        FileBrowseNode( self.provCtx, self, i[i.rfind("/")+1:len(i)-3] ) )
912                elif self.provCtx.sfa.isFolder( i ) and not i.endswith("/pythonpath"):
913                    log.debug( "adding DirBrowseNode " + i )
914                    browseNodeList.append(
915                        DirBrowseNode( self.provCtx, i[i.rfind("/")+1:len(i)], i, self.depth + 1 ) )
916            return tuple( browseNodeList )
917        except Exception as e:
918            text = lastException2String()
919            log.error( "DirBrowseNode error: " + str(e) + " while evaluating " + self.rootUrl)
920            log.error( text)
921            return ()
922
923    def hasChildNodes( self ):
924        return True
925
926    def getType( self ):
927        return CONTAINER
928
929    # XScriptProvider
930
931    def getScript( self, uri ):
932        log.debug( "DirBrowseNode getScript " + uri + " invoked" )
933        raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 )
934
935    # XPropertySet
936
937    def getPropertyValue( self, name ):
938        ret = None
939        try:
940            if name == "Creatable":
941                ret = True
942            elif name == "Deletable":
943                ret = self.depth > 0 and not self.provCtx.sfa.isReadOnly( self.rootUrl )
944            elif name == "Renamable":
945                ret = self.depth > 0 and not self.provCtx.sfa.isReadOnly( self.rootUrl )
946
947            log.debug( "DirBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
948        except Exception as e:
949            log.error( "DirBrowseNode.getPropertyValue error " + lastException2String())
950            raise
951
952        return ret
953
954    def setPropertyValue( self, name, value ):
955        log.debug( "DirBrowseNode.setPropertyValue called " + name + "=" +str(value ) )
956
957    def getPropertySetInfo( self ):
958        log.debug( "DirBrowseNode.getPropertySetInfo called "  )
959        return None
960
961    # XInvocation
962
963    def getIntrospection( self ):
964        log.debug( "DirBrowseNode.getIntrospection() called" )
965        return None
966
967    def invoke( self, name, params, outparamindex, outparams ):
968        log.debug("DirBrowseNode.invoke called for " + name + "," + str(params) + "," + str(outparamindex) + "," + str(outparams))
969        try:
970            if name == "Creatable":
971                if params is None or not params:
972                    raise IllegalArgumentException( "invoke with Creatable needs the name in params" )
973                if self.depth == 0:
974                    subFolderUrl = self.rootUrl + "/" + params[0]
975                    self.provCtx.sfa.createFolder( subFolderUrl )
976                    childNode = DirBrowseNode( self.provCtx, subFolderUrl[subFolderUrl.rfind("/")+1:len(subFolderUrl)], subFolderUrl, self.depth + 1 )
977                    return childNode, (), ()
978                else:
979                    scriptUrl = self.rootUrl + "/" + params[0] + ".py"
980                    # Creates an empty file
981                    self.provCtx.sfa.writeFile( scriptUrl, EmptyInputStream() )
982                    childNode = FileBrowseNode( self.provCtx, self, params[0] )
983                    return childNode, (), ()
984            elif name == "Deletable":
985                self.provCtx.sfa.kill( self.rootUrl )
986                return True, (), ()
987            elif name == "Renamable":
988                if params is None or not params:
989                    raise IllegalArgumentException( "invoke with Renamable needs the name in params" )
990                newUrl = self.rootUrl[0:self.rootUrl.rfind("/")+1] + params[0]
991                self.provCtx.sfa.move( self.rootUrl, newUrl )
992                self.rootUrl = newUrl
993                self.name = params[0]
994                return self, (), ()
995        except Exception as e:
996            log.error( "DirBrowseNode.invoke error: " + lastException2String())
997            raise
998        return None, (), ()
999
1000    def setValue( self, name, value ):
1001        return None
1002
1003    def getValue( self, name ):
1004        log.debug( "DirBrowseNode.getValue() called" )
1005        return None
1006
1007    def hasMethod( self, name ):
1008        return False
1009
1010    def hasProperty( self, name ):
1011        return False
1012
1013
1014class ManifestHandler( XDocumentHandler, unohelper.Base ):
1015    def __init__( self, rootUrl ):
1016        self.rootUrl = rootUrl
1017
1018    def startDocument( self ):
1019        self.urlList = []
1020
1021    def endDocument( self ):
1022        pass
1023
1024    def startElement( self , name, attlist):
1025        if name == "manifest:file-entry":
1026            if attlist.getValueByName( "manifest:media-type" ) == "application/vnd.sun.star.framework-script":
1027                self.urlList.append(
1028                    self.rootUrl + "/" + attlist.getValueByName( "manifest:full-path" ) )
1029
1030    def endElement( self, name ):
1031        pass
1032
1033    def characters ( self, chars ):
1034        pass
1035
1036    def ignoreableWhitespace( self, chars ):
1037        pass
1038
1039    def setDocumentLocator( self, locator ):
1040        pass
1041
1042def isPyFileInPath( sfa, path ):
1043    ret = False
1044    contents = sfa.getFolderContents( path, True )
1045    for i in contents:
1046        if sfa.isFolder(i):
1047            ret = isPyFileInPath(sfa,i)
1048        else:
1049            if i.endswith(".py"):
1050                ret = True
1051        if ret:
1052            break
1053    return ret
1054
1055# extracts META-INF directory from
1056def getPathesFromPackage( rootUrl, sfa ):
1057    ret = ()
1058    try:
1059        fileUrl = rootUrl + "/META-INF/manifest.xml"
1060        inputStream = sfa.openFileRead( fileUrl )
1061        parser = uno.getComponentContext().ServiceManager.createInstance( "com.sun.star.xml.sax.Parser" )
1062        handler = ManifestHandler( rootUrl )
1063        parser.setDocumentHandler( handler )
1064        parser.parseStream( InputSource( inputStream , "", fileUrl, fileUrl ) )
1065        for i in tuple(handler.urlList):
1066            if not isPyFileInPath( sfa, i ):
1067                handler.urlList.remove(i)
1068        ret = tuple( handler.urlList )
1069    except UnoException as e:
1070        text = lastException2String()
1071        log.debug( "getPathesFromPackage " + fileUrl + " Exception: " +text )
1072        pass
1073    return ret
1074
1075
1076class Package:
1077    def __init__( self, pathes, transientPathElement ):
1078        self.pathes = pathes
1079        self.transientPathElement = transientPathElement
1080
1081class DummyInteractionHandler( unohelper.Base, XInteractionHandler ):
1082    def __init__( self ):
1083        pass
1084    def handle( self, event):
1085        log.debug( "pythonscript: DummyInteractionHandler.handle " + str( event ) )
1086
1087class DummyProgressHandler( unohelper.Base, XProgressHandler ):
1088    def __init__( self ):
1089        pass
1090
1091    def push( self,status ):
1092        log.debug( "pythonscript: DummyProgressHandler.push " + str( status ) )
1093    def update( self,status ):
1094        log.debug( "pythonscript: DummyProgressHandler.update " + str( status ) )
1095    def pop( self ):
1096        log.debug( "pythonscript: DummyProgressHandler.push " + str( event ) )
1097
1098class CommandEnvironment(unohelper.Base, XCommandEnvironment):
1099    def __init__( self ):
1100        self.progressHandler = DummyProgressHandler()
1101        self.interactionHandler = DummyInteractionHandler()
1102    def getInteractionHandler( self ):
1103        return self.interactionHandler
1104    def getProgressHandler( self ):
1105        return self.progressHandler
1106
1107#maybe useful for debugging purposes
1108#class ModifyListener( unohelper.Base, XModifyListener ):
1109#    def __init__( self ):
1110#        pass
1111#    def modified( self, event ):
1112#        log.debug( "pythonscript: ModifyListener.modified " + str( event ) )
1113#    def disposing( self, event ):
1114#        log.debug( "pythonscript: ModifyListener.disposing " + str( event ) )
1115
1116def getModelFromDocUrl(ctx, url):
1117    """Get document model from document url."""
1118    doc = None
1119    args = ("Local", "Office")
1120    ucb = ctx.getServiceManager().createInstanceWithArgumentsAndContext(
1121        "com.sun.star.ucb.UniversalContentBroker", args, ctx)
1122    identifier = ucb.createContentIdentifier(url)
1123    content = ucb.queryContent(identifier)
1124    p = Property()
1125    p.Name = "DocumentModel"
1126    p.Handle = -1
1127
1128    c = Command()
1129    c.Handle = -1
1130    c.Name = "getPropertyValues"
1131    c.Argument = uno.Any("[]com.sun.star.beans.Property", (p,))
1132
1133    env = CommandEnvironment()
1134    try:
1135        ret = content.execute(c, 0, env)
1136        doc = ret.getObject(1, None)
1137    except Exception as e:
1138        log.isErrorLevel() and log.error("getModelFromDocUrl: %s" % url)
1139    return doc
1140
1141def mapStorageType2PackageContext( storageType ):
1142    ret = storageType
1143    if( storageType == "share:uno_packages" ):
1144        ret = "shared"
1145    if( storageType == "user:uno_packages" ):
1146        ret = "user"
1147    return ret
1148
1149def getPackageName2PathMap( sfa, storageType ):
1150    ret = {}
1151    packageManagerFactory = uno.getComponentContext().getValueByName(
1152        "/singletons/com.sun.star.deployment.thePackageManagerFactory" )
1153    packageManager = packageManagerFactory.getPackageManager(
1154        mapStorageType2PackageContext(storageType))
1155#    packageManager.addModifyListener( ModifyListener() )
1156    log.debug( "pythonscript: getPackageName2PathMap start getDeployedPackages" )
1157    packages = packageManager.getDeployedPackages(
1158        packageManager.createAbortChannel(), CommandEnvironment( ) )
1159    log.debug( "pythonscript: getPackageName2PathMap end getDeployedPackages (" + str(len(packages))+")" )
1160
1161    for i in packages:
1162        log.debug( "inspecting package " + i.Name + "("+i.Identifier.Value+")" )
1163        transientPathElement = penultimateElement( i.URL )
1164        j = expandUri( i.URL )
1165        pathes = getPathesFromPackage( j, sfa )
1166        if len( pathes ) > 0:
1167            # map package name to url, we need this later
1168            log.isErrorLevel() and log.error( "adding Package " + transientPathElement + " " + str( pathes ) )
1169            ret[ lastElement( j ) ] = Package( pathes, transientPathElement )
1170    return ret
1171
1172def penultimateElement( aStr ):
1173    lastSlash = aStr.rindex("/")
1174    penultimateSlash = aStr.rindex("/",0,lastSlash-1)
1175    return  aStr[ penultimateSlash+1:lastSlash ]
1176
1177def lastElement( aStr):
1178    return aStr[ aStr.rfind( "/" )+1:len(aStr)]
1179
1180class PackageBrowseNode( unohelper.Base, XBrowseNode, XPropertySet, XInvocation ):
1181    def __init__( self, provCtx, name, rootUrl ):
1182        self.provCtx = provCtx
1183        self.name = name
1184        self.rootUrl = rootUrl
1185
1186    def getName( self ):
1187        return self.name
1188
1189    def getChildNodes( self ):
1190        items = list(self.provCtx.mapPackageName2Path.items())
1191        browseNodeList = []
1192        for i in items:
1193            if len( i[1].pathes ) == 1:
1194                browseNodeList.append(
1195                    DirBrowseNode( self.provCtx, i[0], i[1].pathes[0], 0 ))
1196            else:
1197                for j in i[1].pathes:
1198                    browseNodeList.append(
1199                        DirBrowseNode( self.provCtx, i[0]+"."+lastElement(j), j, 0 ) )
1200        return tuple( browseNodeList )
1201
1202    def hasChildNodes( self ):
1203        return len( self.mapPackageName2Path ) > 0
1204
1205    def getType( self ):
1206        return CONTAINER
1207
1208    def getScript( self, uri ):
1209        log.debug( "DirBrowseNode getScript " + uri + " invoked" )
1210        raise IllegalArgumentException( "PackageBrowseNode couldn't instantiate script " + uri , self , 0 )
1211
1212    # XPropertySet
1213
1214    def getPropertyValue( self, name ):
1215        ret = None
1216        log.debug( "PackageBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
1217        return ret
1218
1219    def setPropertyValue( self, name, value ):
1220        log.debug( "PackageBrowseNode.setPropertyValue " + name + "=" +str( value ) )
1221
1222    def getPropertySetInfo( self ):
1223        log.debug( "PackageBrowseNode.getPropertySetInfo called" )
1224        return None
1225
1226    # XInvocation
1227
1228    def getIntrospection( self ):
1229        log.debug( "PackageBrowseNode.getIntrospection() called" )
1230        return None
1231
1232    def invoke( self, name, params, outparamindex, outparams ):
1233        log.debug( "PackageBrowseNode.invoke called for " + name + "," + str( params ) + "," + str( outparamindex ) + "," + str( outparams ) )
1234        return None, (), ()
1235
1236    def setValue( self, name, value ):
1237        log.debug( "PackageBrowseNode.setValue" )
1238        return None
1239
1240    def getValue( self, name ):
1241        log.debug( "PackageBrowseNode.getValue" )
1242        return None
1243
1244    def hasMethod( self, name ):
1245        log.debug( "PackageBrowseNode.hasMethod" )
1246        return False
1247
1248    def hasProperty( self, name ):
1249        log.debug( "PackageBrowseNode.hasProperty" )
1250        return False
1251
1252
1253
1254class PythonScript( unohelper.Base, XScript ):
1255    def __init__( self, func, mod ):
1256        self.func = func
1257        self.mod = mod
1258    def invoke(self, args, out, outindex ):
1259        log.debug( "PythonScript.invoke " + str( args ) )
1260        try:
1261            ret = self.func( *args )
1262        except UnoException as e:
1263            # UNO Exception continue to fly ...
1264            text = lastException2String()
1265            complete = "Error during invoking function " + \
1266                str(self.func.__name__) + " in module " + \
1267                self.mod.__file__ + " (" + text + ")"
1268            log.debug( complete )
1269            # some people may beat me up for modifying the exception text,
1270            # but otherwise office just shows
1271            # the type name and message text with no more information,
1272            # this is really bad for most users.
1273            e.Message = e.Message + " (" + complete + ")"
1274            raise
1275        except Exception as e:
1276            # General python exception are converted to uno RuntimeException
1277            text = lastException2String()
1278            complete = "Error during invoking function " + \
1279                str(self.func.__name__) + " in module " + \
1280                self.mod.__file__ + " (" + text + ")"
1281            log.debug( complete )
1282            raise RuntimeException( complete , self )
1283        log.debug( "PythonScript.invoke ret = " + str( ret ) )
1284        return ret, (), ()
1285
1286def expandUri(  uri ):
1287    if uri.startswith( "vnd.sun.star.expand:" ):
1288        uri = uri.replace( "vnd.sun.star.expand:", "",1)
1289        uri = uno.getComponentContext().getByName(
1290                    "/singletons/com.sun.star.util.theMacroExpander" ).expandMacros( uri )
1291    if uri.startswith( "file:" ):
1292        uri = uno.absolutize("",uri)   # necessary to get rid of .. in uri
1293    return uri
1294
1295#--------------------------------------------------------------
1296class PythonScriptProvider( unohelper.Base, XBrowseNode, XScriptProvider, XNameContainer, XPropertySet, XInvocation):
1297    def __init__( self, ctx, *args ):
1298        if log.isDebugLevel():
1299            mystr = ""
1300            for i in args:
1301                if len(mystr) > 0:
1302                    mystr = mystr +","
1303                mystr = mystr + str(i)
1304            log.debug( "Entering PythonScriptProvider.ctor with args " + mystr )
1305
1306        doc = None
1307        inv = None
1308        storageType = ""
1309
1310        if isinstance(args[0],unicode ):
1311            storageType = args[0]
1312            if storageType.startswith( "vnd.sun.star.tdoc" ):
1313                doc = getModelFromDocUrl(ctx, storageType)
1314        else:
1315            inv = args[0]
1316            try:
1317                doc = inv.ScriptContainer
1318                content = ctx.getServiceManager().createInstanceWithContext(
1319                    "com.sun.star.frame.TransientDocumentsDocumentContentFactory",
1320                    ctx).createDocumentContent(doc)
1321                storageType = content.getIdentifier().getContentIdentifier()
1322            except Exception as e:
1323                text = lastException2String()
1324                log.error( text )
1325
1326        isPackage = storageType.endswith( ":uno_packages" )
1327
1328        try:
1329#            urlHelper = ctx.ServiceManager.createInstanceWithArgumentsAndContext(
1330#                "com.sun.star.script.provider.ScriptURIHelper", (LANGUAGENAME, storageType), ctx)
1331            urlHelper = MyUriHelper( ctx, storageType )
1332            log.debug( "got urlHelper " + str( urlHelper ) )
1333
1334            rootUrl = expandUri( urlHelper.getRootStorageURI() )
1335            log.debug( storageType + " transformed to " + rootUrl )
1336
1337            ucbService = "com.sun.star.ucb.SimpleFileAccess"
1338            sfa = ctx.ServiceManager.createInstanceWithContext( ucbService, ctx )
1339            if not sfa:
1340                log.debug("PythonScriptProvider couldn't instantiate " +ucbService)
1341                raise RuntimeException(
1342                    "PythonScriptProvider couldn't instantiate " +ucbService, self)
1343            self.provCtx = ProviderContext(
1344                storageType, sfa, urlHelper, ScriptContext( uno.getComponentContext(), doc, inv ) )
1345            if isPackage:
1346                mapPackageName2Path = getPackageName2PathMap( sfa, storageType )
1347                self.provCtx.setPackageAttributes( mapPackageName2Path , rootUrl )
1348                self.dirBrowseNode = PackageBrowseNode( self.provCtx, LANGUAGENAME, rootUrl )
1349            else:
1350                self.dirBrowseNode = DirBrowseNode( self.provCtx, LANGUAGENAME, rootUrl, 0 )
1351
1352        except Exception as e:
1353            text = lastException2String()
1354            log.debug( "PythonScriptProvider could not be instantiated because of : " + text )
1355            raise e
1356
1357    def getName( self ):
1358        return self.dirBrowseNode.getName()
1359
1360    def getChildNodes( self ):
1361        return self.dirBrowseNode.getChildNodes()
1362
1363    def hasChildNodes( self ):
1364        return self.dirBrowseNode.hasChildNodes()
1365
1366    def getType( self ):
1367        return self.dirBrowseNode.getType()
1368
1369    def getScript( self, scriptUri ):
1370        try:
1371            log.debug( "DirBrowseNode getScript " + scriptUri + " invoked")
1372
1373            storageUri = self.provCtx.getStorageUrlFromPersistentUrl(
1374                self.provCtx.uriHelper.getStorageURI(scriptUri) );
1375            log.debug( "getScript: storageUri = " + storageUri)
1376            fileUri = storageUri[0:storageUri.find( "$" )]
1377            funcName = storageUri[storageUri.find( "$" )+1:len(storageUri)]
1378
1379            mod = self.provCtx.getModuleByUrl( fileUri )
1380            log.debug( " got mod " + str(mod) )
1381
1382            func = mod.__dict__[ funcName ]
1383
1384            log.debug( "got func " + str( func ) )
1385            return PythonScript( func, mod )
1386        except Exception as e:
1387            text = lastException2String()
1388            log.error( text )
1389            raise ScriptFrameworkErrorException( text, self, scriptUri, LANGUAGENAME, 0 )
1390
1391    # XPropertySet
1392
1393    def getPropertyValue( self, name ):
1394        return self.dirBrowseNode.getPropertyValue( name )
1395
1396    def setPropertyValue( self, name, value ):
1397        return self.dirBrowseNode.setPropertyValue( name, value )
1398
1399    def getPropertySetInfo( self ):
1400        return self.dirBrowseNode.getPropertySetInfo()
1401
1402    # XInvocation
1403
1404    def getIntrospection( self ):
1405        return self.dirBrowseNode.getIntrospection()
1406
1407    def invoke( self, name, params, outparamindex, outparams ):
1408        return self.dirBrowseNode.invoke( name, params, outparamindex, outparams)
1409
1410    def setValue( self, name, value ):
1411        return self.dirBrowseNode.setValue( name, value )
1412
1413    def getValue( self, name ):
1414        return self.dirBrowseNode.getValue( name )
1415
1416    def hasMethod( self, name ):
1417        return self.dirBrowseNode.hasMethod( name )
1418
1419    def hasProperty( self, name ):
1420        return self.dirBrowseNode.hasProperty( name )
1421
1422    # XServiceInfo
1423    def getSupportedServices( self ):
1424        return g_ImplementationHelper.getSupportedServices(g_implName)
1425
1426    def supportsService( self, ServiceName ):
1427        return g_ImplementationHelper.supportsService( g_implName, ServiceName )
1428
1429    def getImplementationName(self):
1430        return g_implName
1431
1432    def getByName( self, name ):
1433        log.debug( "getByName called" + str( name ))
1434        return None
1435
1436
1437    def getElementNames( self ):
1438        log.debug( "getElementNames called")
1439        return ()
1440
1441    def hasByName( self, name ):
1442        try:
1443            log.debug( "hasByName called " + str( name ))
1444            uri = expandUri(name)
1445            ret = self.provCtx.isUrlInPackage( uri )
1446            log.debug( "hasByName " + uri + " " +str( ret ) )
1447            return ret
1448        except Exception as e:
1449            text = lastException2String()
1450            log.debug( "Error in hasByName:" +  text )
1451            return False
1452
1453    def removeByName( self, name ):
1454        log.debug( "removeByName called" + str( name ))
1455        uri = expandUri( name )
1456        if self.provCtx.isUrlInPackage( uri ):
1457            self.provCtx.removePackageByUrl( uri )
1458        else:
1459            log.debug( "removeByName unknown uri " + str( name ) + ", ignoring" )
1460            raise NoSuchElementException( uri + "is not in package" , self )
1461        log.debug( "removeByName called" + str( uri ) + " successful" )
1462
1463    def insertByName( self, name, value ):
1464        log.debug( "insertByName called " + str( name ) + " " + str( value ))
1465        uri = expandUri( name )
1466        if isPyFileInPath( self.provCtx.sfa, uri ):
1467            self.provCtx.addPackageByUrl( uri )
1468        else:
1469            # package is no python package ...
1470            log.debug( "insertByName: no python files in " + str( uri ) + ", ignoring" )
1471            raise IllegalArgumentException( uri + " does not contain .py files", self, 1 )
1472        log.debug( "insertByName called " + str( uri ) + " successful" )
1473
1474    def replaceByName( self, name, value ):
1475        log.debug( "replaceByName called " + str( name ) + " " + str( value ))
1476        removeByName( name )
1477        insertByName( name )
1478        log.debug( "replaceByName called" + str( uri ) + " successful" )
1479
1480    def getElementType( self ):
1481        log.debug( "getElementType called" )
1482        return uno.getTypeByName( "void" )
1483
1484    def hasElements( self ):
1485        log.debug( "hasElements got called")
1486        return False
1487
1488g_ImplementationHelper.addImplementation( \
1489        PythonScriptProvider,g_implName, \
1490    ("com.sun.star.script.provider.LanguageScriptProvider",
1491     "com.sun.star.script.provider.ScriptProviderFor"+ LANGUAGENAME,),)
1492
1493
1494log.debug( "pythonscript finished initializing" )
1495