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