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