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