Avoid potential negative array index access to cached text.
[LibreOffice.git] / scripting / source / pyprov / pythonscript.py
blob00d96d9b0d3f32df5a6fe630cc14da314143219e
1 # -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
3 # This file is part of the LibreOffice project.
5 # This Source Code Form is subject to the terms of the Mozilla Public
6 # License, v. 2.0. If a copy of the MPL was not distributed with this
7 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 # This file incorporates work covered by the following license notice:
11 # Licensed to the Apache Software Foundation (ASF) under one or more
12 # contributor license agreements. See the NOTICE file distributed
13 # with this work for additional information regarding copyright
14 # ownership. The ASF licenses this file to you under the Apache
15 # License, Version 2.0 (the "License"); you may not use this file
16 # except in compliance with the License. You may obtain a copy of
17 # the License at http://www.apache.org/licenses/LICENSE-2.0 .
19 # XScript implementation for python
20 import uno
21 import unohelper
22 import sys
23 import os
24 import types
25 import time
26 import ast
27 import platform
28 from com.sun.star.uri.RelativeUriExcessParentSegments import RETAIN
29 from urllib.parse import unquote
31 class LogLevel:
32 NONE = 0 # production level
33 ERROR = 1 # for script developers
34 DEBUG = 2 # for script framework developers
36 PYSCRIPT_LOG_ENV = "PYSCRIPT_LOG_LEVEL"
37 PYSCRIPT_LOG_STDOUT_ENV = "PYSCRIPT_LOG_STDOUT"
39 # Configuration ----------------------------------------------------
40 LogLevel.use = LogLevel.NONE
41 if os.environ.get(PYSCRIPT_LOG_ENV) == "ERROR":
42 LogLevel.use = LogLevel.ERROR
43 elif os.environ.get(PYSCRIPT_LOG_ENV) == "DEBUG":
44 LogLevel.use = LogLevel.DEBUG
46 # True, writes to stdout (difficult on windows)
47 # False, writes to user/Scripts/python/log.txt
48 LOG_STDOUT = os.environ.get(PYSCRIPT_LOG_STDOUT_ENV, "1") != "0"
50 ENABLE_EDIT_DIALOG=False # offers a minimal editor for editing.
51 #-------------------------------------------------------------------
53 def encfile(uni):
54 return uni.encode( sys.getfilesystemencoding())
56 def lastException2String():
57 (excType,excInstance,excTraceback) = sys.exc_info()
58 ret = str(excType) + ": "+str(excInstance) + "\n" + \
59 uno._uno_extract_printable_stacktrace( excTraceback )
60 return ret
62 def logLevel2String( level ):
63 ret = " NONE"
64 if level == LogLevel.ERROR:
65 ret = "ERROR"
66 elif level >= LogLevel.DEBUG:
67 ret = "DEBUG"
68 return ret
70 def getLogTarget():
71 ret = sys.stdout
72 if not LOG_STDOUT:
73 try:
74 pathSubst = uno.getComponentContext().ServiceManager.createInstance(
75 "com.sun.star.util.PathSubstitution" )
76 userInstallation = pathSubst.getSubstituteVariableValue( "user" )
77 if len( userInstallation ) > 0:
78 systemPath = uno.fileUrlToSystemPath( userInstallation + "/Scripts/python/log.txt" )
79 ret = open( systemPath , "a" )
80 except:
81 print("Exception during creation of pythonscript logfile: "+ lastException2String() + "\n, delegating log to stdout\n")
82 return ret
84 class Logger(LogLevel):
85 def __init__(self , target ):
86 self.target = target
88 def isDebugLevel( self ):
89 return self.use >= self.DEBUG
91 def debug( self, msg ):
92 if self.isDebugLevel():
93 self.log( self.DEBUG, msg )
95 def isErrorLevel( self ):
96 return self.use >= self.ERROR
98 def error( self, msg ):
99 if self.isErrorLevel():
100 self.log( self.ERROR, msg )
102 def log( self, level, msg ):
103 if self.use >= level:
104 try:
105 self.target.write(
106 time.asctime() +
107 " [" +
108 logLevel2String( level ) +
109 "] " +
110 msg +
111 "\n" )
112 self.target.flush()
113 except:
114 print("Error during writing to stdout: " +lastException2String() + "\n")
116 log = Logger( getLogTarget() )
118 log.debug( "pythonscript loading" )
120 #from com.sun.star.lang import typeOfXServiceInfo, typeOfXTypeProvider
121 from com.sun.star.uno import RuntimeException
122 from com.sun.star.lang import IllegalArgumentException
123 from com.sun.star.container import NoSuchElementException
124 from com.sun.star.lang import XServiceInfo
125 from com.sun.star.io import IOException
126 from com.sun.star.ucb import CommandAbortedException, XCommandEnvironment, XProgressHandler, Command
127 from com.sun.star.task import XInteractionHandler
128 from com.sun.star.beans import XPropertySet, Property
129 from com.sun.star.container import XNameContainer
130 from com.sun.star.xml.sax import XDocumentHandler, InputSource
131 from com.sun.star.uno import Exception as UnoException
132 from com.sun.star.script import XInvocation
133 from com.sun.star.awt import XActionListener
135 from com.sun.star.script.provider import XScriptProvider, XScript, XScriptContext, ScriptFrameworkErrorException
136 from com.sun.star.script.browse import XBrowseNode
137 from com.sun.star.script.browse.BrowseNodeTypes import SCRIPT, CONTAINER, ROOT
138 from com.sun.star.util import XModifyListener
140 LANGUAGENAME = "Python"
141 GLOBAL_SCRIPTCONTEXT_NAME = "XSCRIPTCONTEXT"
142 CALLABLE_CONTAINER_NAME = "g_exportedScripts"
144 # pythonloader looks for a static g_ImplementationHelper variable
145 g_ImplementationHelper = unohelper.ImplementationHelper()
146 g_implName = "org.libreoffice.pyuno.LanguageScriptProviderFor"+LANGUAGENAME
150 BLOCK_SIZE = 65536
151 def readTextFromStream( inputStream ):
152 # read the file
153 code = uno.ByteSequence( b"" )
154 while True:
155 read,out = inputStream.readBytes( None , BLOCK_SIZE )
156 code = code + out
157 if read < BLOCK_SIZE:
158 break
159 return code.value
161 def toIniName( str ):
162 if platform.system() == "Windows":
163 return str + ".ini"
164 else:
165 return str + "rc"
168 """ definition: storageURI is the system dependent, absolute file url, where the script is stored on disk
169 scriptURI is the system independent uri
171 class MyUriHelper:
173 def __init__( self, ctx, location ):
174 self.ctx = ctx
175 self.s_UriMap = \
176 { "share" : "vnd.sun.star.expand:$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/Scripts/python" , \
177 "share:uno_packages" : "vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE/uno_packages", \
178 "user" : "vnd.sun.star.expand:${$BRAND_INI_DIR/" + toIniName( "bootstrap") + "::UserInstallation}/user/Scripts/python" , \
179 "user:uno_packages" : "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages" }
180 self.m_uriRefFac = ctx.ServiceManager.createInstanceWithContext("com.sun.star.uri.UriReferenceFactory",ctx)
181 if location.startswith( "vnd.sun.star.tdoc" ):
182 self.m_baseUri = location + "/Scripts/python"
183 self.m_scriptUriLocation = "document"
184 else:
185 self.m_baseUri = expandUri( self.s_UriMap[location] )
186 self.m_scriptUriLocation = location
187 log.debug( "initialized urihelper with baseUri="+self.m_baseUri + ",m_scriptUriLocation="+self.m_scriptUriLocation )
189 def getRootStorageURI( self ):
190 return self.m_baseUri
192 def getStorageURI( self, scriptURI ):
193 return self.scriptURI2StorageUri(scriptURI)
195 def getScriptURI( self, storageURI ):
196 return self.storageURI2ScriptUri(storageURI)
198 def storageURI2ScriptUri( self, storageURI ):
199 if not storageURI.startswith( self.m_baseUri ):
200 message = "pythonscript: storage uri '" + storageURI + "' not in base uri '" + self.m_baseUri + "'"
201 log.debug( message )
202 raise RuntimeException( message, self.ctx )
204 ret = "vnd.sun.star.script:" + \
205 storageURI[len(self.m_baseUri)+1:].replace("/","|") + \
206 "?language=" + LANGUAGENAME + "&location=" + self.m_scriptUriLocation
207 log.debug( "converting storageURI="+storageURI + " to scriptURI=" + ret )
208 return ret
210 def scriptURI2StorageUri( self, scriptURI ):
211 try:
212 # base path to the python script location
213 sBaseUri = self.m_baseUri + "/"
214 xBaseUri = self.m_uriRefFac.parse(sBaseUri)
216 # path to the .py file + "$functionname, arguments, etc
217 xStorageUri = self.m_uriRefFac.parse(scriptURI)
218 # getName will apply url-decoding to the name, so encode back
219 sStorageUri = xStorageUri.getName().replace("%", "%25")
220 sStorageUri = sStorageUri.replace( "|", "/" )
222 # path to the .py file, relative to the base
223 funcNameStart = sStorageUri.find("$")
224 if funcNameStart != -1:
225 sFileUri = sStorageUri[0:funcNameStart]
226 sFuncName = sStorageUri[funcNameStart+1:]
227 else:
228 sFileUri = sStorageUri
230 xFileUri = self.m_uriRefFac.parse(sFileUri)
231 if not xFileUri:
232 message = "pythonscript: invalid relative uri '" + sFileUri+ "'"
233 log.debug( message )
234 raise RuntimeException( message, self.ctx )
236 if not xFileUri.hasRelativePath():
237 message = "pythonscript: an absolute uri is invalid '" + sFileUri+ "'"
238 log.debug( message )
239 raise RuntimeException( message, self.ctx )
241 # absolute path to the .py file
242 xAbsScriptUri = self.m_uriRefFac.makeAbsolute(xBaseUri, xFileUri, True, RETAIN)
243 sAbsScriptUri = xAbsScriptUri.getUriReference()
245 # ensure py file is under the base path
246 if not sAbsScriptUri.startswith(sBaseUri):
247 message = "pythonscript: storage uri '" + sAbsScriptUri + "' not in base uri '" + self.m_baseUri + "'"
248 log.debug( message )
249 raise RuntimeException( message, self.ctx )
251 ret = sAbsScriptUri
252 if funcNameStart != -1:
253 ret = ret + "$" + sFuncName
254 log.debug( "converting scriptURI="+scriptURI + " to storageURI=" + ret )
255 return ret
256 except UnoException as e:
257 log.error( "error during converting scriptURI="+scriptURI + ": " + e.Message)
258 raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + e.Message, self.ctx )
259 except Exception as e:
260 log.error( "error during converting scriptURI="+scriptURI + ": " + str(e))
261 raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + str(e), self.ctx )
264 class ModuleEntry:
265 def __init__( self, lastRead, module ):
266 self.lastRead = lastRead
267 self.module = module
269 def hasChanged( oldDate, newDate ):
270 return newDate.Year > oldDate.Year or \
271 newDate.Month > oldDate.Month or \
272 newDate.Day > oldDate.Day or \
273 newDate.Hours > oldDate.Hours or \
274 newDate.Minutes > oldDate.Minutes or \
275 newDate.Seconds > oldDate.Seconds or \
276 newDate.NanoSeconds > oldDate.NanoSeconds
278 def ensureSourceState( code ):
279 if code.endswith(b"\n"):
280 code = code + b"\n"
281 code = code.replace(b"\r", b"")
282 return code
285 def checkForPythonPathBesideScript( url ):
286 if url.startswith( "file:" ):
287 path = unohelper.fileUrlToSystemPath( url+"/pythonpath.zip" );
288 log.log( LogLevel.DEBUG, "checking for existence of " + path )
289 if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path:
290 log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" )
291 sys.path.append( path )
293 path = unohelper.fileUrlToSystemPath( url+"/pythonpath" );
294 log.log( LogLevel.DEBUG, "checking for existence of " + path )
295 if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path:
296 log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" )
297 sys.path.append( path )
300 class ScriptContext(unohelper.Base):
301 def __init__( self, ctx, doc, inv ):
302 self.ctx = ctx
303 self.doc = doc
304 self.inv = inv
306 # XScriptContext
307 def getDocument(self):
308 if self.doc:
309 return self.doc
310 return self.getDesktop().getCurrentComponent()
312 def getDesktop(self):
313 return self.ctx.ServiceManager.createInstanceWithContext(
314 "com.sun.star.frame.Desktop", self.ctx )
316 def getComponentContext(self):
317 return self.ctx
319 def getInvocationContext(self):
320 return self.inv
322 #----------------------------------
323 # Global Module Administration
324 # does not fit together with script
325 # engine lifetime management
326 #----------------------------------
327 #g_scriptContext = ScriptContext( uno.getComponentContext(), None )
328 #g_modules = {}
329 #def getModuleByUrl( url, sfa ):
330 # entry = g_modules.get(url)
331 # load = True
332 # lastRead = sfa.getDateTimeModified( url )
333 # if entry:
334 # if hasChanged( entry.lastRead, lastRead ):
335 # log.debug("file " + url + " has changed, reloading")
336 # else:
337 # load = False
339 # if load:
340 # log.debug( "opening >" + url + "<" )
342 # code = readTextFromStream( sfa.openFileRead( url ) )
344 # execute the module
345 # entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") )
346 # entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = g_scriptContext
347 # entry.module.__file__ = url
348 # exec code in entry.module.__dict__
349 # g_modules[ url ] = entry
350 # log.debug( "mapped " + url + " to " + str( entry.module ) )
351 # return entry.module
353 class ProviderContext:
354 def __init__( self, storageType, sfa, uriHelper, scriptContext ):
355 self.storageType = storageType
356 self.sfa = sfa
357 self.uriHelper = uriHelper
358 self.scriptContext = scriptContext
359 self.modules = {}
360 self.rootUrl = None
361 self.mapPackageName2Path = None
363 def getTransientPartFromUrl( self, url ):
364 rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1)
365 return rest[0:rest.find("/")]
367 def getPackageNameFromUrl( self, url ):
368 rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1)
369 start = rest.find("/") +1
370 return rest[start:rest.find("/",start)]
373 def removePackageByUrl( self, url ):
374 items = self.mapPackageName2Path.items()
375 for i in items:
376 if url in i[1].paths:
377 self.mapPackageName2Path.pop(i[0])
378 break
380 def addPackageByUrl( self, url ):
381 packageName = self.getPackageNameFromUrl( url )
382 transientPart = self.getTransientPartFromUrl( url )
383 log.debug( "addPackageByUrl : " + packageName + ", " + transientPart + "("+url+")" + ", rootUrl="+self.rootUrl )
384 if packageName in self.mapPackageName2Path:
385 package = self.mapPackageName2Path[ packageName ]
386 package.paths = package.paths + (url, )
387 else:
388 package = Package( (url,), transientPart)
389 self.mapPackageName2Path[ packageName ] = package
391 def isUrlInPackage( self, url ):
392 values = self.mapPackageName2Path.values()
393 for i in values:
394 # print ("checking " + url + " in " + str(i.paths))
395 if url in i.paths:
396 return True
397 # print ("false")
398 return False
400 def setPackageAttributes( self, mapPackageName2Path, rootUrl ):
401 self.mapPackageName2Path = mapPackageName2Path
402 self.rootUrl = rootUrl
404 def getPersistentUrlFromStorageUrl( self, url ):
405 # package name is the second directory
406 ret = url
407 if self.rootUrl:
408 pos = len( self.rootUrl) +1
409 ret = url[0:pos]+url[url.find("/",pos)+1:len(url)]
410 log.debug( "getPersistentUrlFromStorageUrl " + url + " -> "+ ret)
411 return ret
413 def getStorageUrlFromPersistentUrl( self, url):
414 ret = url
415 if self.rootUrl:
416 pos = len(self.rootUrl)+1
417 packageName = url[pos:url.find("/",pos+1)]
418 package = self.mapPackageName2Path[ packageName ]
419 ret = url[0:pos]+ package.transientPathElement + "/" + url[pos:len(url)]
420 log.debug( "getStorageUrlFromPersistentUrl " + url + " -> "+ ret)
421 return ret
423 def getFuncsByUrl( self, url ):
424 src = readTextFromStream( self.sfa.openFileRead( url ) )
425 checkForPythonPathBesideScript( url[0:url.rfind('/')] )
426 src = ensureSourceState( src )
428 try:
429 code = ast.parse( src )
430 except:
431 log.isDebugLevel() and log.debug( "pythonscript: getFuncsByUrl: exception while parsing: " + lastException2String())
432 raise
434 allFuncs = []
436 if code is None:
437 return allFuncs
439 g_exportedScripts = []
440 for node in ast.iter_child_nodes(code):
441 if isinstance(node, ast.FunctionDef):
442 allFuncs.append(node.name)
443 elif isinstance(node, ast.Assign):
444 for target in node.targets:
445 try:
446 identifier = target.id
447 except AttributeError:
448 identifier = ""
449 pass
450 if identifier == "g_exportedScripts":
451 for value in node.value.elts:
452 g_exportedScripts.append(value.id)
453 return g_exportedScripts
455 # Python 2 only
456 # for node in code.node.nodes:
457 # if node.__class__.__name__ == 'Function':
458 # allFuncs.append(node.name)
459 # elif node.__class__.__name__ == 'Assign':
460 # for assignee in node.nodes:
461 # if assignee.name == 'g_exportedScripts':
462 # for item in node.expr.nodes:
463 # if item.__class__.__name__ == 'Name':
464 # g_exportedScripts.append(item.name)
465 # return g_exportedScripts
467 return allFuncs
469 def getModuleByUrl( self, url ):
470 entry = self.modules.get(url)
471 load = True
472 lastRead = self.sfa.getDateTimeModified( url )
473 if entry:
474 if hasChanged( entry.lastRead, lastRead ):
475 log.debug( "file " + url + " has changed, reloading" )
476 else:
477 load = False
479 if load:
480 log.debug( "opening >" + url + "<" )
482 src = readTextFromStream( self.sfa.openFileRead( url ) )
483 checkForPythonPathBesideScript( url[0:url.rfind('/')] )
484 src = ensureSourceState( src )
486 # execute the module
487 entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") )
488 entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.scriptContext
490 code = None
491 if url.startswith( "file:" ):
492 code = compile( src, encfile(uno.fileUrlToSystemPath( url ) ), "exec" )
493 else:
494 code = compile( src, url, "exec" )
495 exec(code, entry.module.__dict__)
496 entry.module.__file__ = url
497 self.modules[ url ] = entry
498 log.debug( "mapped " + url + " to " + str( entry.module ) )
499 return entry.module
501 #--------------------------------------------------
502 def isScript( candidate ):
503 ret = False
504 if isinstance( candidate, type(isScript) ):
505 ret = True
506 return ret
508 #-------------------------------------------------------
509 class ScriptBrowseNode( unohelper.Base, XBrowseNode , XPropertySet, XInvocation, XActionListener ):
510 def __init__( self, provCtx, uri, fileName, funcName ):
511 self.fileName = fileName
512 self.funcName = funcName
513 self.provCtx = provCtx
514 self.uri = uri
516 def getName( self ):
517 return self.funcName
519 def getChildNodes(self):
520 return ()
522 def hasChildNodes(self):
523 return False
525 def getType( self):
526 return SCRIPT
528 def getPropertyValue( self, name ):
529 ret = None
530 try:
531 if name == "URI":
532 ret = self.provCtx.uriHelper.getScriptURI(
533 self.provCtx.getPersistentUrlFromStorageUrl( self.uri + "$" + self.funcName ) )
534 elif name == "Editable" and ENABLE_EDIT_DIALOG:
535 ret = not self.provCtx.sfa.isReadOnly( self.uri )
537 log.debug( "ScriptBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
538 except:
539 log.error( "ScriptBrowseNode.getPropertyValue error " + lastException2String())
540 raise
542 return ret
543 def setPropertyValue( self, name, value ):
544 log.debug( "ScriptBrowseNode.setPropertyValue called " + name + "=" +str(value ) )
545 def getPropertySetInfo( self ):
546 log.debug( "ScriptBrowseNode.getPropertySetInfo called " )
547 return None
549 def getIntrospection( self ):
550 return None
552 def invoke( self, name, params, outparamindex, outparams ):
553 if name == "Editable":
554 servicename = "com.sun.star.awt.DialogProvider"
555 ctx = self.provCtx.scriptContext.getComponentContext()
556 dlgprov = ctx.ServiceManager.createInstanceWithContext(
557 servicename, ctx )
559 self.editor = dlgprov.createDialog(
560 "vnd.sun.star.script:" +
561 "ScriptBindingLibrary.MacroEditor?location=application")
563 code = readTextFromStream(self.provCtx.sfa.openFileRead(self.uri))
564 code = ensureSourceState( code )
565 self.editor.getControl("EditorTextField").setText(code)
567 self.editor.getControl("RunButton").setActionCommand("Run")
568 self.editor.getControl("RunButton").addActionListener(self)
569 self.editor.getControl("SaveButton").setActionCommand("Save")
570 self.editor.getControl("SaveButton").addActionListener(self)
572 self.editor.execute()
574 return None
576 def actionPerformed( self, event ):
577 try:
578 if event.ActionCommand == "Run":
579 code = self.editor.getControl("EditorTextField").getText()
580 code = ensureSourceState( code )
581 mod = types.ModuleType("ooo_script_framework")
582 mod.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.provCtx.scriptContext
583 exec(code, mod.__dict__)
584 values = mod.__dict__.get( CALLABLE_CONTAINER_NAME , None )
585 if not values:
586 values = mod.__dict__.values()
588 for i in values:
589 if isScript( i ):
591 break
593 elif event.ActionCommand == "Save":
594 toWrite = uno.ByteSequence(
595 self.editor.getControl("EditorTextField").getText().encode(
596 sys.getdefaultencoding()) )
597 copyUrl = self.uri + ".orig"
598 self.provCtx.sfa.move( self.uri, copyUrl )
599 out = self.provCtx.sfa.openFileWrite( self.uri )
600 out.writeBytes( toWrite )
601 out.close()
602 self.provCtx.sfa.kill( copyUrl )
603 # log.debug("Save is not implemented yet")
604 # text = self.editor.getControl("EditorTextField").getText()
605 # log.debug("Would save: " + text)
606 except:
607 # TODO: add an error box here!
608 log.error( lastException2String() )
611 def setValue( self, name, value ):
612 return None
614 def getValue( self, name ):
615 return None
617 def hasMethod( self, name ):
618 return False
620 def hasProperty( self, name ):
621 return False
624 #-------------------------------------------------------
625 class FileBrowseNode( unohelper.Base, XBrowseNode ):
626 def __init__( self, provCtx, uri , name ):
627 self.provCtx = provCtx
628 self.uri = uri
629 self.name = name
630 self.funcnames = None
632 def getName( self ):
633 return self.name
635 def getChildNodes(self):
636 ret = ()
637 try:
638 self.funcnames = self.provCtx.getFuncsByUrl( self.uri )
640 scriptNodeList = []
641 for i in self.funcnames:
642 scriptNodeList.append(
643 ScriptBrowseNode(
644 self.provCtx, self.uri, self.name, i ))
645 ret = tuple( scriptNodeList )
646 log.debug( "returning " +str(len(ret)) + " ScriptChildNodes on " + self.uri )
647 except:
648 text = lastException2String()
649 log.error( "Error while evaluating " + self.uri + ":" + text )
650 raise
651 return ret
653 def hasChildNodes(self):
654 try:
655 return len(self.getChildNodes()) > 0
656 except:
657 return False
659 def getType( self):
660 return CONTAINER
664 class DirBrowseNode( unohelper.Base, XBrowseNode ):
665 def __init__( self, provCtx, name, rootUrl ):
666 self.provCtx = provCtx
667 self.name = name
668 self.rootUrl = rootUrl
670 def getName( self ):
671 return self.name
673 def getChildNodes( self ):
674 try:
675 log.debug( "DirBrowseNode.getChildNodes called for " + self.rootUrl )
676 contents = self.provCtx.sfa.getFolderContents( self.rootUrl, True )
677 browseNodeList = []
678 for i in contents:
679 if i.endswith( ".py" ):
680 log.debug( "adding filenode " + i )
681 browseNodeList.append(
682 FileBrowseNode( self.provCtx, i, i[i.rfind("/")+1:len(i)-3] ) )
683 elif self.provCtx.sfa.isFolder( i ) and not i.endswith("/pythonpath"):
684 log.debug( "adding DirBrowseNode " + i )
685 browseNodeList.append( DirBrowseNode( self.provCtx, i[i.rfind("/")+1:len(i)],i))
686 return tuple( browseNodeList )
687 except Exception as e:
688 text = lastException2String()
689 log.error( "DirBrowseNode error: " + str(e) + " while evaluating " + self.rootUrl)
690 log.error( text)
691 return ()
693 def hasChildNodes( self ):
694 return True
696 def getType( self ):
697 return CONTAINER
699 def getScript( self, uri ):
700 log.debug( "DirBrowseNode getScript " + uri + " invoked" )
701 raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 )
704 class ManifestHandler( XDocumentHandler, unohelper.Base ):
705 def __init__( self, rootUrl ):
706 self.rootUrl = rootUrl
708 def startDocument( self ):
709 self.urlList = []
711 def endDocument( self ):
712 pass
714 def startElement( self , name, attlist):
715 if name == "manifest:file-entry":
716 if attlist.getValueByName( "manifest:media-type" ) == "application/vnd.sun.star.framework-script":
717 self.urlList.append(
718 self.rootUrl + "/" + attlist.getValueByName( "manifest:full-path" ) )
720 def endElement( self, name ):
721 pass
723 def characters ( self, chars ):
724 pass
726 def ignoreableWhitespace( self, chars ):
727 pass
729 def setDocumentLocator( self, locator ):
730 pass
732 def isPyFileInPath( sfa, path ):
733 ret = False
734 contents = sfa.getFolderContents( path, True )
735 for i in contents:
736 if sfa.isFolder(i):
737 ret = isPyFileInPath(sfa,i)
738 else:
739 if i.endswith(".py"):
740 ret = True
741 if ret:
742 break
743 return ret
745 # extracts META-INF directory from
746 def getPathsFromPackage( rootUrl, sfa ):
747 ret = ()
748 try:
749 fileUrl = rootUrl + "/META-INF/manifest.xml"
750 inputStream = sfa.openFileRead( fileUrl )
751 parser = uno.getComponentContext().ServiceManager.createInstance( "com.sun.star.xml.sax.Parser" )
752 handler = ManifestHandler( rootUrl )
753 parser.setDocumentHandler( handler )
754 parser.parseStream( InputSource( inputStream , "", fileUrl, fileUrl ) )
755 for i in tuple(handler.urlList):
756 if not isPyFileInPath( sfa, i ):
757 handler.urlList.remove(i)
758 ret = tuple( handler.urlList )
759 except UnoException:
760 text = lastException2String()
761 log.debug( "getPathsFromPackage " + fileUrl + " Exception: " +text )
762 pass
763 return ret
766 class Package:
767 def __init__( self, paths, transientPathElement ):
768 self.paths = paths
769 self.transientPathElement = transientPathElement
771 class DummyInteractionHandler( unohelper.Base, XInteractionHandler ):
772 def __init__( self ):
773 pass
774 def handle( self, event):
775 log.debug( "pythonscript: DummyInteractionHandler.handle " + str( event ) )
777 class DummyProgressHandler( unohelper.Base, XProgressHandler ):
778 def __init__( self ):
779 pass
781 def push( self,status ):
782 log.debug( "pythonscript: DummyProgressHandler.push " + str( status ) )
783 def update( self,status ):
784 log.debug( "pythonscript: DummyProgressHandler.update " + str( status ) )
785 def pop( self, event ):
786 log.debug( "pythonscript: DummyProgressHandler.push " + str( event ) )
788 class CommandEnvironment(unohelper.Base, XCommandEnvironment):
789 def __init__( self ):
790 self.progressHandler = DummyProgressHandler()
791 self.interactionHandler = DummyInteractionHandler()
792 def getInteractionHandler( self ):
793 return self.interactionHandler
794 def getProgressHandler( self ):
795 return self.progressHandler
797 #maybe useful for debugging purposes
798 #class ModifyListener( unohelper.Base, XModifyListener ):
799 # def __init__( self ):
800 # pass
801 # def modified( self, event ):
802 # log.debug( "pythonscript: ModifyListener.modified " + str( event ) )
803 # def disposing( self, event ):
804 # log.debug( "pythonscript: ModifyListener.disposing " + str( event ) )
806 def getModelFromDocUrl(ctx, url):
807 """Get document model from document url."""
808 doc = None
809 args = ("Local", "Office")
810 ucb = ctx.getServiceManager().createInstanceWithArgumentsAndContext(
811 "com.sun.star.ucb.UniversalContentBroker", args, ctx)
812 identifier = ucb.createContentIdentifier(url)
813 content = ucb.queryContent(identifier)
814 p = Property()
815 p.Name = "DocumentModel"
816 p.Handle = -1
818 c = Command()
819 c.Handle = -1
820 c.Name = "getPropertyValues"
821 c.Argument = uno.Any("[]com.sun.star.beans.Property", (p,))
823 env = CommandEnvironment()
824 try:
825 ret = content.execute(c, 0, env)
826 doc = ret.getObject(1, None)
827 except Exception as e:
828 log.isErrorLevel() and log.error("getModelFromDocUrl: %s" % url)
829 return doc
831 def mapStorageType2PackageContext( storageType ):
832 ret = storageType
833 if( storageType == "share:uno_packages" ):
834 ret = "shared"
835 if( storageType == "user:uno_packages" ):
836 ret = "user"
837 return ret
839 def getPackageName2PathMap( sfa, storageType ):
840 ret = {}
841 packageManagerFactory = uno.getComponentContext().getValueByName(
842 "/singletons/com.sun.star.deployment.thePackageManagerFactory" )
843 packageManager = packageManagerFactory.getPackageManager(
844 mapStorageType2PackageContext(storageType))
845 # packageManager.addModifyListener( ModifyListener() )
846 log.debug( "pythonscript: getPackageName2PathMap start getDeployedPackages" )
847 packages = packageManager.getDeployedPackages(
848 packageManager.createAbortChannel(), CommandEnvironment( ) )
849 log.debug( "pythonscript: getPackageName2PathMap end getDeployedPackages (" + str(len(packages))+")" )
851 for i in packages:
852 log.debug( "inspecting package " + i.Name + "("+i.Identifier.Value+")" )
853 transientPathElement = penultimateElement( i.URL )
854 j = expandUri( i.URL )
855 paths = getPathsFromPackage( j, sfa )
856 if len( paths ) > 0:
857 # map package name to url, we need this later
858 log.debug( "adding Package " + transientPathElement + " " + str( paths ) )
859 ret[ lastElement( j ) ] = Package( paths, transientPathElement )
860 return ret
862 def penultimateElement( aStr ):
863 lastSlash = aStr.rindex("/")
864 penultimateSlash = aStr.rindex("/",0,lastSlash-1)
865 return aStr[ penultimateSlash+1:lastSlash ]
867 def lastElement( aStr):
868 return aStr[ aStr.rfind( "/" )+1:len(aStr)]
870 class PackageBrowseNode( unohelper.Base, XBrowseNode ):
871 def __init__( self, provCtx, name, rootUrl ):
872 self.provCtx = provCtx
873 self.name = name
874 self.rootUrl = rootUrl
876 def getName( self ):
877 return self.name
879 def getChildNodes( self ):
880 items = self.provCtx.mapPackageName2Path.items()
881 browseNodeList = []
882 for i in items:
883 if len( i[1].paths ) == 1:
884 browseNodeList.append(
885 DirBrowseNode( self.provCtx, i[0], i[1].paths[0] ))
886 else:
887 for j in i[1].paths:
888 browseNodeList.append(
889 DirBrowseNode( self.provCtx, i[0]+"."+lastElement(j), j ) )
890 return tuple( browseNodeList )
892 def hasChildNodes( self ):
893 return len( self.provCtx.mapPackageName2Path ) > 0
895 def getType( self ):
896 return CONTAINER
898 def getScript( self, uri ):
899 log.debug( "PackageBrowseNode getScript " + uri + " invoked" )
900 raise IllegalArgumentException( "PackageBrowseNode couldn't instantiate script " + uri , self , 0 )
905 class PythonScript( unohelper.Base, XScript ):
906 def __init__( self, func, mod, args ):
907 self.func = func
908 self.mod = mod
909 self.args = args
911 def invoke(self, args, out, outindex ):
912 log.debug( "PythonScript.invoke " + str( args ) )
913 try:
914 if (self.args):
915 args += self.args
916 ret = self.func( *args )
917 except UnoException as e:
918 # UNO Exception continue to fly ...
919 text = lastException2String()
920 complete = "Error during invoking function " + \
921 str(self.func.__name__) + " in module " + \
922 self.mod.__file__ + " (" + text + ")"
923 log.debug( complete )
924 # some people may beat me up for modifying the exception text,
925 # but otherwise office just shows
926 # the type name and message text with no more information,
927 # this is really bad for most users.
928 e.Message = e.Message + " (" + complete + ")"
929 raise
930 except Exception as e:
931 # General python exception are converted to uno RuntimeException
932 text = lastException2String()
933 complete = "Error during invoking function " + \
934 str(self.func.__name__) + " in module " + \
935 self.mod.__file__ + " (" + text + ")"
936 log.debug( complete )
937 raise RuntimeException( complete , self )
938 log.debug( "PythonScript.invoke ret = " + str( ret ) )
939 return ret, (), ()
941 def expandUri( uri ):
942 if uri.startswith( "vnd.sun.star.expand:" ):
943 uri = uri.replace( "vnd.sun.star.expand:", "",1)
944 uri = uno.getComponentContext().getByName(
945 "/singletons/com.sun.star.util.theMacroExpander" ).expandMacros( unquote(uri) )
946 if uri.startswith( "file:" ):
947 uri = uno.absolutize("",uri) # necessary to get rid of .. in uri
948 return uri
950 #--------------------------------------------------------------
951 class PythonScriptProvider( unohelper.Base, XBrowseNode, XScriptProvider, XNameContainer):
952 def __init__( self, ctx, *args ):
953 if log.isDebugLevel():
954 mystr = ""
955 for i in args:
956 if len(mystr) > 0:
957 mystr = mystr +","
958 mystr = mystr + str(i)
959 log.debug( "Entering PythonScriptProvider.ctor" + mystr )
961 doc = None
962 inv = None
963 storageType = ""
965 if isinstance(args[0], str):
966 storageType = args[0]
967 if storageType.startswith( "vnd.sun.star.tdoc" ):
968 doc = getModelFromDocUrl(ctx, storageType)
969 else:
970 inv = args[0]
971 try:
972 doc = inv.ScriptContainer
973 content = ctx.getServiceManager().createInstanceWithContext(
974 "com.sun.star.frame.TransientDocumentsDocumentContentFactory",
975 ctx).createDocumentContent(doc)
976 storageType = content.getIdentifier().getContentIdentifier()
977 except Exception as e:
978 text = lastException2String()
979 log.error( text )
981 isPackage = storageType.endswith( ":uno_packages" )
983 try:
984 # urlHelper = ctx.ServiceManager.createInstanceWithArgumentsAndContext(
985 # "com.sun.star.script.provider.ScriptURIHelper", (LANGUAGENAME, storageType), ctx)
986 urlHelper = MyUriHelper( ctx, storageType )
987 log.debug( "got urlHelper " + str( urlHelper ) )
989 rootUrl = expandUri( urlHelper.getRootStorageURI() )
990 log.debug( storageType + " transformed to " + rootUrl )
992 ucbService = "com.sun.star.ucb.SimpleFileAccess"
993 sfa = ctx.ServiceManager.createInstanceWithContext( ucbService, ctx )
994 if not sfa:
995 log.debug("PythonScriptProvider couldn't instantiate " +ucbService)
996 raise RuntimeException(
997 "PythonScriptProvider couldn't instantiate " +ucbService, self)
998 self.provCtx = ProviderContext(
999 storageType, sfa, urlHelper, ScriptContext( uno.getComponentContext(), doc, inv ) )
1000 if isPackage:
1001 mapPackageName2Path = getPackageName2PathMap( sfa, storageType )
1002 self.provCtx.setPackageAttributes( mapPackageName2Path , rootUrl )
1003 self.dirBrowseNode = PackageBrowseNode( self.provCtx, LANGUAGENAME, rootUrl )
1004 else:
1005 self.dirBrowseNode = DirBrowseNode( self.provCtx, LANGUAGENAME, rootUrl )
1007 except Exception as e:
1008 text = lastException2String()
1009 log.debug( "PythonScriptProvider could not be instantiated because of : " + text )
1010 raise e
1012 def getName( self ):
1013 return self.dirBrowseNode.getName()
1015 def getChildNodes( self ):
1016 return self.dirBrowseNode.getChildNodes()
1018 def hasChildNodes( self ):
1019 return self.dirBrowseNode.hasChildNodes()
1021 def getType( self ):
1022 return self.dirBrowseNode.getType()
1024 # retrieve function args in parenthesis
1025 def getFunctionArguments(self, func_signature):
1026 nOpenParenthesis = func_signature.find( "(" )
1027 if -1 == nOpenParenthesis:
1028 function_name = func_signature
1029 arguments = None
1030 else:
1031 function_name = func_signature[0:nOpenParenthesis]
1032 arg_part = func_signature[nOpenParenthesis+1:len(func_signature)]
1033 nCloseParenthesis = arg_part.find( ")" )
1034 if -1 == nCloseParenthesis:
1035 raise IllegalArgumentException( "PythonLoader: mismatch parenthesis " + func_signature, self, 0 )
1036 arguments = arg_part[0:nCloseParenthesis].strip()
1037 if arguments == "":
1038 arguments = None
1039 else:
1040 arguments = tuple([x.strip().strip('"') for x in arguments.split(",")])
1041 return function_name, arguments
1043 def getScript( self, scriptUri ):
1044 try:
1045 log.debug( "getScript " + scriptUri + " invoked")
1047 storageUri = self.provCtx.getStorageUrlFromPersistentUrl(
1048 self.provCtx.uriHelper.getStorageURI(scriptUri) );
1049 log.debug( "getScript: storageUri = " + storageUri)
1050 fileUri = storageUri[0:storageUri.find( "$" )]
1051 funcName = storageUri[storageUri.find( "$" )+1:len(storageUri)]
1053 # retrieve arguments in parenthesis
1054 funcName, funcArgs = self.getFunctionArguments(funcName)
1055 log.debug( " getScript : parsed funcname " + str(funcName) )
1056 log.debug( " getScript : func args " + str(funcArgs) )
1058 mod = self.provCtx.getModuleByUrl( fileUri )
1059 log.debug( " got mod " + str(mod) )
1061 func = mod.__dict__[ funcName ]
1063 log.debug( "got func " + str( func ) )
1064 return PythonScript( func, mod, funcArgs )
1065 except:
1066 text = lastException2String()
1067 log.error( text )
1068 raise ScriptFrameworkErrorException( text, self, scriptUri, LANGUAGENAME, 0 )
1071 # XServiceInfo
1072 def getSupportedServices( self ):
1073 return g_ImplementationHelper.getSupportedServices(g_implName)
1075 def supportsService( self, ServiceName ):
1076 return g_ImplementationHelper.supportsService( g_implName, ServiceName )
1078 def getImplementationName(self):
1079 return g_implName
1081 def getByName( self, name ):
1082 log.debug( "getByName called" + str( name ))
1083 return None
1086 def getElementNames( self ):
1087 log.debug( "getElementNames called")
1088 return ()
1090 def hasByName( self, name ):
1091 try:
1092 log.debug( "hasByName called " + str( name ))
1093 uri = expandUri(name)
1094 ret = self.provCtx.isUrlInPackage( uri )
1095 log.debug( "hasByName " + uri + " " +str( ret ) )
1096 return ret
1097 except:
1098 text = lastException2String()
1099 log.debug( "Error in hasByName:" + text )
1100 return False
1102 def removeByName( self, name ):
1103 log.debug( "removeByName called" + str( name ))
1104 uri = expandUri( name )
1105 if self.provCtx.isUrlInPackage( uri ):
1106 self.provCtx.removePackageByUrl( uri )
1107 else:
1108 log.debug( "removeByName unknown uri " + str( name ) + ", ignoring" )
1109 raise NoSuchElementException( uri + "is not in package" , self )
1110 log.debug( "removeByName called" + str( uri ) + " successful" )
1112 def insertByName( self, name, value ):
1113 log.debug( "insertByName called " + str( name ) + " " + str( value ))
1114 uri = expandUri( name )
1115 if isPyFileInPath( self.provCtx.sfa, uri ):
1116 self.provCtx.addPackageByUrl( uri )
1117 else:
1118 # package is no python package ...
1119 log.debug( "insertByName: no python files in " + str( uri ) + ", ignoring" )
1120 raise IllegalArgumentException( uri + " does not contain .py files", self, 1 )
1121 log.debug( "insertByName called " + str( uri ) + " successful" )
1123 def replaceByName( self, name, value ):
1124 log.debug( "replaceByName called " + str( name ) + " " + str( value ))
1125 uri = expandUri( name )
1126 self.removeByName( name )
1127 self.insertByName( name, value )
1128 log.debug( "replaceByName called" + str( uri ) + " successful" )
1130 def getElementType( self ):
1131 log.debug( "getElementType called" )
1132 return uno.getTypeByName( "void" )
1134 def hasElements( self ):
1135 log.debug( "hasElements got called")
1136 return False
1138 g_ImplementationHelper.addImplementation( \
1139 PythonScriptProvider,g_implName, \
1140 ("com.sun.star.script.provider.LanguageScriptProvider",
1141 "com.sun.star.script.provider.ScriptProviderFor"+ LANGUAGENAME,),)
1144 log.debug( "pythonscript finished initializing" )
1146 # vim: set shiftwidth=4 softtabstop=4 expandtab: