nss: upgrade to release 3.73
[LibreOffice.git] / scripting / source / pyprov / pythonscript.py
blob4955c8c5488830ac00bcd195f9f98fc37488ca9f
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
30 class LogLevel:
31 NONE = 0 # production level
32 ERROR = 1 # for script developers
33 DEBUG = 2 # for script framework developers
35 PYSCRIPT_LOG_ENV = "PYSCRIPT_LOG_LEVEL"
36 PYSCRIPT_LOG_STDOUT_ENV = "PYSCRIPT_LOG_STDOUT"
38 # Configuration ----------------------------------------------------
39 LogLevel.use = LogLevel.NONE
40 if os.environ.get(PYSCRIPT_LOG_ENV) == "ERROR":
41 LogLevel.use = LogLevel.ERROR
42 elif os.environ.get(PYSCRIPT_LOG_ENV) == "DEBUG":
43 LogLevel.use = LogLevel.DEBUG
45 # True, writes to stdout (difficult on windows)
46 # False, writes to user/Scripts/python/log.txt
47 LOG_STDOUT = os.environ.get(PYSCRIPT_LOG_STDOUT_ENV, "1") != "0"
49 ENABLE_EDIT_DIALOG=False # offers a minimal editor for editing.
50 #-------------------------------------------------------------------
52 def encfile(uni):
53 return uni.encode( sys.getfilesystemencoding())
55 def lastException2String():
56 (excType,excInstance,excTraceback) = sys.exc_info()
57 ret = str(excType) + ": "+str(excInstance) + "\n" + \
58 uno._uno_extract_printable_stacktrace( excTraceback )
59 return ret
61 def logLevel2String( level ):
62 ret = " NONE"
63 if level == LogLevel.ERROR:
64 ret = "ERROR"
65 elif level >= LogLevel.DEBUG:
66 ret = "DEBUG"
67 return ret
69 def getLogTarget():
70 ret = sys.stdout
71 if not LOG_STDOUT:
72 try:
73 pathSubst = uno.getComponentContext().ServiceManager.createInstance(
74 "com.sun.star.util.PathSubstitution" )
75 userInstallation = pathSubst.getSubstituteVariableValue( "user" )
76 if len( userInstallation ) > 0:
77 systemPath = uno.fileUrlToSystemPath( userInstallation + "/Scripts/python/log.txt" )
78 ret = open( systemPath , "a" )
79 except:
80 print("Exception during creation of pythonscript logfile: "+ lastException2String() + "\n, delegating log to stdout\n")
81 return ret
83 class Logger(LogLevel):
84 def __init__(self , target ):
85 self.target = target
87 def isDebugLevel( self ):
88 return self.use >= self.DEBUG
90 def debug( self, msg ):
91 if self.isDebugLevel():
92 self.log( self.DEBUG, msg )
94 def isErrorLevel( self ):
95 return self.use >= self.ERROR
97 def error( self, msg ):
98 if self.isErrorLevel():
99 self.log( self.ERROR, msg )
101 def log( self, level, msg ):
102 if self.use >= level:
103 try:
104 self.target.write(
105 time.asctime() +
106 " [" +
107 logLevel2String( level ) +
108 "] " +
109 msg +
110 "\n" )
111 self.target.flush()
112 except:
113 print("Error during writing to stdout: " +lastException2String() + "\n")
115 log = Logger( getLogTarget() )
117 log.debug( "pythonscript loading" )
119 #from com.sun.star.lang import typeOfXServiceInfo, typeOfXTypeProvider
120 from com.sun.star.uno import RuntimeException
121 from com.sun.star.lang import IllegalArgumentException
122 from com.sun.star.container import NoSuchElementException
123 from com.sun.star.lang import XServiceInfo
124 from com.sun.star.io import IOException
125 from com.sun.star.ucb import CommandAbortedException, XCommandEnvironment, XProgressHandler, Command
126 from com.sun.star.task import XInteractionHandler
127 from com.sun.star.beans import XPropertySet, Property
128 from com.sun.star.container import XNameContainer
129 from com.sun.star.xml.sax import XDocumentHandler, InputSource
130 from com.sun.star.uno import Exception as UnoException
131 from com.sun.star.script import XInvocation
132 from com.sun.star.awt import XActionListener
134 from com.sun.star.script.provider import XScriptProvider, XScript, XScriptContext, ScriptFrameworkErrorException
135 from com.sun.star.script.browse import XBrowseNode
136 from com.sun.star.script.browse.BrowseNodeTypes import SCRIPT, CONTAINER, ROOT
137 from com.sun.star.util import XModifyListener
139 LANGUAGENAME = "Python"
140 GLOBAL_SCRIPTCONTEXT_NAME = "XSCRIPTCONTEXT"
141 CALLABLE_CONTAINER_NAME = "g_exportedScripts"
143 # pythonloader looks for a static g_ImplementationHelper variable
144 g_ImplementationHelper = unohelper.ImplementationHelper()
145 g_implName = "org.libreoffice.pyuno.LanguageScriptProviderFor"+LANGUAGENAME
149 BLOCK_SIZE = 65536
150 def readTextFromStream( inputStream ):
151 # read the file
152 code = uno.ByteSequence( b"" )
153 while True:
154 read,out = inputStream.readBytes( None , BLOCK_SIZE )
155 code = code + out
156 if read < BLOCK_SIZE:
157 break
158 return code.value
160 def toIniName( str ):
161 if platform.system() == "Windows":
162 return str + ".ini"
163 else:
164 return str + "rc"
167 """ definition: storageURI is the system dependent, absolute file url, where the script is stored on disk
168 scriptURI is the system independent uri
170 class MyUriHelper:
172 def __init__( self, ctx, location ):
173 self.ctx = ctx
174 self.s_UriMap = \
175 { "share" : "vnd.sun.star.expand:$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/Scripts/python" , \
176 "share:uno_packages" : "vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE/uno_packages", \
177 "user" : "vnd.sun.star.expand:${$BRAND_INI_DIR/" + toIniName( "bootstrap") + "::UserInstallation}/user/Scripts/python" , \
178 "user:uno_packages" : "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages" }
179 self.m_uriRefFac = ctx.ServiceManager.createInstanceWithContext("com.sun.star.uri.UriReferenceFactory",ctx)
180 if location.startswith( "vnd.sun.star.tdoc" ):
181 self.m_baseUri = location + "/Scripts/python"
182 self.m_scriptUriLocation = "document"
183 else:
184 self.m_baseUri = expandUri( self.s_UriMap[location] )
185 self.m_scriptUriLocation = location
186 log.debug( "initialized urihelper with baseUri="+self.m_baseUri + ",m_scriptUriLocation="+self.m_scriptUriLocation )
188 def getRootStorageURI( self ):
189 return self.m_baseUri
191 def getStorageURI( self, scriptURI ):
192 return self.scriptURI2StorageUri(scriptURI)
194 def getScriptURI( self, storageURI ):
195 return self.storageURI2ScriptUri(storageURI)
197 def storageURI2ScriptUri( self, storageURI ):
198 if not storageURI.startswith( self.m_baseUri ):
199 message = "pythonscript: storage uri '" + storageURI + "' not in base uri '" + self.m_baseUri + "'"
200 log.debug( message )
201 raise RuntimeException( message, self.ctx )
203 ret = "vnd.sun.star.script:" + \
204 storageURI[len(self.m_baseUri)+1:].replace("/","|") + \
205 "?language=" + LANGUAGENAME + "&location=" + self.m_scriptUriLocation
206 log.debug( "converting storageURI="+storageURI + " to scriptURI=" + ret )
207 return ret
209 def scriptURI2StorageUri( self, scriptURI ):
210 try:
211 # base path to the python script location
212 sBaseUri = self.m_baseUri + "/"
213 xBaseUri = self.m_uriRefFac.parse(sBaseUri)
215 # path to the .py file + "$functionname, arguments, etc
216 xStorageUri = self.m_uriRefFac.parse(scriptURI)
217 # getName will apply url-decoding to the name, so encode back
218 sStorageUri = xStorageUri.getName().replace("%", "%25")
219 sStorageUri = sStorageUri.replace( "|", "/" )
221 # path to the .py file, relative to the base
222 funcNameStart = sStorageUri.find("$")
223 if funcNameStart != -1:
224 sFileUri = sStorageUri[0:funcNameStart]
225 sFuncName = sStorageUri[funcNameStart+1:]
226 else:
227 sFileUri = sStorageUri
229 xFileUri = self.m_uriRefFac.parse(sFileUri)
230 if not xFileUri:
231 message = "pythonscript: invalid relative uri '" + sFileUri+ "'"
232 log.debug( message )
233 raise RuntimeException( message, self.ctx )
235 if not xFileUri.hasRelativePath():
236 message = "pythonscript: an absolute uri is invalid '" + sFileUri+ "'"
237 log.debug( message )
238 raise RuntimeException( message, self.ctx )
240 # absolute path to the .py file
241 xAbsScriptUri = self.m_uriRefFac.makeAbsolute(xBaseUri, xFileUri, True, RETAIN)
242 sAbsScriptUri = xAbsScriptUri.getUriReference()
244 # ensure py file is under the base path
245 if not sAbsScriptUri.startswith(sBaseUri):
246 message = "pythonscript: storage uri '" + sAbsScriptUri + "' not in base uri '" + self.m_baseUri + "'"
247 log.debug( message )
248 raise RuntimeException( message, self.ctx )
250 ret = sAbsScriptUri
251 if funcNameStart != -1:
252 ret = ret + "$" + sFuncName
253 log.debug( "converting scriptURI="+scriptURI + " to storageURI=" + ret )
254 return ret
255 except UnoException as e:
256 log.error( "error during converting scriptURI="+scriptURI + ": " + e.Message)
257 raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + e.Message, self.ctx )
258 except Exception as e:
259 log.error( "error during converting scriptURI="+scriptURI + ": " + str(e))
260 raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + str(e), self.ctx )
263 class ModuleEntry:
264 def __init__( self, lastRead, module ):
265 self.lastRead = lastRead
266 self.module = module
268 def hasChanged( oldDate, newDate ):
269 return newDate.Year > oldDate.Year or \
270 newDate.Month > oldDate.Month or \
271 newDate.Day > oldDate.Day or \
272 newDate.Hours > oldDate.Hours or \
273 newDate.Minutes > oldDate.Minutes or \
274 newDate.Seconds > oldDate.Seconds or \
275 newDate.NanoSeconds > oldDate.NanoSeconds
277 def ensureSourceState( code ):
278 if code.endswith(b"\n"):
279 code = code + b"\n"
280 code = code.replace(b"\r", b"")
281 return code
284 def checkForPythonPathBesideScript( url ):
285 if url.startswith( "file:" ):
286 path = unohelper.fileUrlToSystemPath( url+"/pythonpath.zip" );
287 log.log( LogLevel.DEBUG, "checking for existence of " + path )
288 if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path:
289 log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" )
290 sys.path.append( path )
292 path = unohelper.fileUrlToSystemPath( url+"/pythonpath" );
293 log.log( LogLevel.DEBUG, "checking for existence of " + path )
294 if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path:
295 log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" )
296 sys.path.append( path )
299 class ScriptContext(unohelper.Base):
300 def __init__( self, ctx, doc, inv ):
301 self.ctx = ctx
302 self.doc = doc
303 self.inv = inv
305 # XScriptContext
306 def getDocument(self):
307 if self.doc:
308 return self.doc
309 return self.getDesktop().getCurrentComponent()
311 def getDesktop(self):
312 return self.ctx.ServiceManager.createInstanceWithContext(
313 "com.sun.star.frame.Desktop", self.ctx )
315 def getComponentContext(self):
316 return self.ctx
318 def getInvocationContext(self):
319 return self.inv
321 #----------------------------------
322 # Global Module Administration
323 # does not fit together with script
324 # engine lifetime management
325 #----------------------------------
326 #g_scriptContext = ScriptContext( uno.getComponentContext(), None )
327 #g_modules = {}
328 #def getModuleByUrl( url, sfa ):
329 # entry = g_modules.get(url)
330 # load = True
331 # lastRead = sfa.getDateTimeModified( url )
332 # if entry:
333 # if hasChanged( entry.lastRead, lastRead ):
334 # log.debug("file " + url + " has changed, reloading")
335 # else:
336 # load = False
338 # if load:
339 # log.debug( "opening >" + url + "<" )
341 # code = readTextFromStream( sfa.openFileRead( url ) )
343 # execute the module
344 # entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") )
345 # entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = g_scriptContext
346 # entry.module.__file__ = url
347 # exec code in entry.module.__dict__
348 # g_modules[ url ] = entry
349 # log.debug( "mapped " + url + " to " + str( entry.module ) )
350 # return entry.module
352 class ProviderContext:
353 def __init__( self, storageType, sfa, uriHelper, scriptContext ):
354 self.storageType = storageType
355 self.sfa = sfa
356 self.uriHelper = uriHelper
357 self.scriptContext = scriptContext
358 self.modules = {}
359 self.rootUrl = None
360 self.mapPackageName2Path = None
362 def getTransientPartFromUrl( self, url ):
363 rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1)
364 return rest[0:rest.find("/")]
366 def getPackageNameFromUrl( self, url ):
367 rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1)
368 start = rest.find("/") +1
369 return rest[start:rest.find("/",start)]
372 def removePackageByUrl( self, url ):
373 items = self.mapPackageName2Path.items()
374 for i in items:
375 if url in i[1].paths:
376 self.mapPackageName2Path.pop(i[0])
377 break
379 def addPackageByUrl( self, url ):
380 packageName = self.getPackageNameFromUrl( url )
381 transientPart = self.getTransientPartFromUrl( url )
382 log.debug( "addPackageByUrl : " + packageName + ", " + transientPart + "("+url+")" + ", rootUrl="+self.rootUrl )
383 if packageName in self.mapPackageName2Path:
384 package = self.mapPackageName2Path[ packageName ]
385 package.paths = package.paths + (url, )
386 else:
387 package = Package( (url,), transientPart)
388 self.mapPackageName2Path[ packageName ] = package
390 def isUrlInPackage( self, url ):
391 values = self.mapPackageName2Path.values()
392 for i in values:
393 # print ("checking " + url + " in " + str(i.paths))
394 if url in i.paths:
395 return True
396 # print ("false")
397 return False
399 def setPackageAttributes( self, mapPackageName2Path, rootUrl ):
400 self.mapPackageName2Path = mapPackageName2Path
401 self.rootUrl = rootUrl
403 def getPersistentUrlFromStorageUrl( self, url ):
404 # package name is the second directory
405 ret = url
406 if self.rootUrl:
407 pos = len( self.rootUrl) +1
408 ret = url[0:pos]+url[url.find("/",pos)+1:len(url)]
409 log.debug( "getPersistentUrlFromStorageUrl " + url + " -> "+ ret)
410 return ret
412 def getStorageUrlFromPersistentUrl( self, url):
413 ret = url
414 if self.rootUrl:
415 pos = len(self.rootUrl)+1
416 packageName = url[pos:url.find("/",pos+1)]
417 package = self.mapPackageName2Path[ packageName ]
418 ret = url[0:pos]+ package.transientPathElement + "/" + url[pos:len(url)]
419 log.debug( "getStorageUrlFromPersistentUrl " + url + " -> "+ ret)
420 return ret
422 def getFuncsByUrl( self, url ):
423 src = readTextFromStream( self.sfa.openFileRead( url ) )
424 checkForPythonPathBesideScript( url[0:url.rfind('/')] )
425 src = ensureSourceState( src )
427 try:
428 code = ast.parse( src )
429 except:
430 log.isDebugLevel() and log.debug( "pythonscript: getFuncsByUrl: exception while parsing: " + lastException2String())
431 raise
433 allFuncs = []
435 if code is None:
436 return allFuncs
438 g_exportedScripts = []
439 for node in ast.iter_child_nodes(code):
440 if isinstance(node, ast.FunctionDef):
441 allFuncs.append(node.name)
442 elif isinstance(node, ast.Assign):
443 for target in node.targets:
444 try:
445 identifier = target.id
446 except AttributeError:
447 identifier = ""
448 pass
449 if identifier == "g_exportedScripts":
450 for value in node.value.elts:
451 g_exportedScripts.append(value.id)
452 return g_exportedScripts
454 # Python 2 only
455 # for node in code.node.nodes:
456 # if node.__class__.__name__ == 'Function':
457 # allFuncs.append(node.name)
458 # elif node.__class__.__name__ == 'Assign':
459 # for assignee in node.nodes:
460 # if assignee.name == 'g_exportedScripts':
461 # for item in node.expr.nodes:
462 # if item.__class__.__name__ == 'Name':
463 # g_exportedScripts.append(item.name)
464 # return g_exportedScripts
466 return allFuncs
468 def getModuleByUrl( self, url ):
469 entry = self.modules.get(url)
470 load = True
471 lastRead = self.sfa.getDateTimeModified( url )
472 if entry:
473 if hasChanged( entry.lastRead, lastRead ):
474 log.debug( "file " + url + " has changed, reloading" )
475 else:
476 load = False
478 if load:
479 log.debug( "opening >" + url + "<" )
481 src = readTextFromStream( self.sfa.openFileRead( url ) )
482 checkForPythonPathBesideScript( url[0:url.rfind('/')] )
483 src = ensureSourceState( src )
485 # execute the module
486 entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") )
487 entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.scriptContext
489 code = None
490 if url.startswith( "file:" ):
491 code = compile( src, encfile(uno.fileUrlToSystemPath( url ) ), "exec" )
492 else:
493 code = compile( src, url, "exec" )
494 exec(code, entry.module.__dict__)
495 entry.module.__file__ = url
496 self.modules[ url ] = entry
497 log.debug( "mapped " + url + " to " + str( entry.module ) )
498 return entry.module
500 #--------------------------------------------------
501 def isScript( candidate ):
502 ret = False
503 if isinstance( candidate, type(isScript) ):
504 ret = True
505 return ret
507 #-------------------------------------------------------
508 class ScriptBrowseNode( unohelper.Base, XBrowseNode , XPropertySet, XInvocation, XActionListener ):
509 def __init__( self, provCtx, uri, fileName, funcName ):
510 self.fileName = fileName
511 self.funcName = funcName
512 self.provCtx = provCtx
513 self.uri = uri
515 def getName( self ):
516 return self.funcName
518 def getChildNodes(self):
519 return ()
521 def hasChildNodes(self):
522 return False
524 def getType( self):
525 return SCRIPT
527 def getPropertyValue( self, name ):
528 ret = None
529 try:
530 if name == "URI":
531 ret = self.provCtx.uriHelper.getScriptURI(
532 self.provCtx.getPersistentUrlFromStorageUrl( self.uri + "$" + self.funcName ) )
533 elif name == "Editable" and ENABLE_EDIT_DIALOG:
534 ret = not self.provCtx.sfa.isReadOnly( self.uri )
536 log.debug( "ScriptBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
537 except:
538 log.error( "ScriptBrowseNode.getPropertyValue error " + lastException2String())
539 raise
541 return ret
542 def setPropertyValue( self, name, value ):
543 log.debug( "ScriptBrowseNode.setPropertyValue called " + name + "=" +str(value ) )
544 def getPropertySetInfo( self ):
545 log.debug( "ScriptBrowseNode.getPropertySetInfo called " )
546 return None
548 def getIntrospection( self ):
549 return None
551 def invoke( self, name, params, outparamindex, outparams ):
552 if name == "Editable":
553 servicename = "com.sun.star.awt.DialogProvider"
554 ctx = self.provCtx.scriptContext.getComponentContext()
555 dlgprov = ctx.ServiceManager.createInstanceWithContext(
556 servicename, ctx )
558 self.editor = dlgprov.createDialog(
559 "vnd.sun.star.script:" +
560 "ScriptBindingLibrary.MacroEditor?location=application")
562 code = readTextFromStream(self.provCtx.sfa.openFileRead(self.uri))
563 code = ensureSourceState( code )
564 self.editor.getControl("EditorTextField").setText(code)
566 self.editor.getControl("RunButton").setActionCommand("Run")
567 self.editor.getControl("RunButton").addActionListener(self)
568 self.editor.getControl("SaveButton").setActionCommand("Save")
569 self.editor.getControl("SaveButton").addActionListener(self)
571 self.editor.execute()
573 return None
575 def actionPerformed( self, event ):
576 try:
577 if event.ActionCommand == "Run":
578 code = self.editor.getControl("EditorTextField").getText()
579 code = ensureSourceState( code )
580 mod = types.ModuleType("ooo_script_framework")
581 mod.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.provCtx.scriptContext
582 exec(code, mod.__dict__)
583 values = mod.__dict__.get( CALLABLE_CONTAINER_NAME , None )
584 if not values:
585 values = mod.__dict__.values()
587 for i in values:
588 if isScript( i ):
590 break
592 elif event.ActionCommand == "Save":
593 toWrite = uno.ByteSequence(
594 self.editor.getControl("EditorTextField").getText().encode(
595 sys.getdefaultencoding()) )
596 copyUrl = self.uri + ".orig"
597 self.provCtx.sfa.move( self.uri, copyUrl )
598 out = self.provCtx.sfa.openFileWrite( self.uri )
599 out.writeBytes( toWrite )
600 out.close()
601 self.provCtx.sfa.kill( copyUrl )
602 # log.debug("Save is not implemented yet")
603 # text = self.editor.getControl("EditorTextField").getText()
604 # log.debug("Would save: " + text)
605 except:
606 # TODO: add an error box here!
607 log.error( lastException2String() )
610 def setValue( self, name, value ):
611 return None
613 def getValue( self, name ):
614 return None
616 def hasMethod( self, name ):
617 return False
619 def hasProperty( self, name ):
620 return False
623 #-------------------------------------------------------
624 class FileBrowseNode( unohelper.Base, XBrowseNode ):
625 def __init__( self, provCtx, uri , name ):
626 self.provCtx = provCtx
627 self.uri = uri
628 self.name = name
629 self.funcnames = None
631 def getName( self ):
632 return self.name
634 def getChildNodes(self):
635 ret = ()
636 try:
637 self.funcnames = self.provCtx.getFuncsByUrl( self.uri )
639 scriptNodeList = []
640 for i in self.funcnames:
641 scriptNodeList.append(
642 ScriptBrowseNode(
643 self.provCtx, self.uri, self.name, i ))
644 ret = tuple( scriptNodeList )
645 log.debug( "returning " +str(len(ret)) + " ScriptChildNodes on " + self.uri )
646 except:
647 text = lastException2String()
648 log.error( "Error while evaluating " + self.uri + ":" + text )
649 raise
650 return ret
652 def hasChildNodes(self):
653 try:
654 return len(self.getChildNodes()) > 0
655 except:
656 return False
658 def getType( self):
659 return CONTAINER
663 class DirBrowseNode( unohelper.Base, XBrowseNode ):
664 def __init__( self, provCtx, name, rootUrl ):
665 self.provCtx = provCtx
666 self.name = name
667 self.rootUrl = rootUrl
669 def getName( self ):
670 return self.name
672 def getChildNodes( self ):
673 try:
674 log.debug( "DirBrowseNode.getChildNodes called for " + self.rootUrl )
675 contents = self.provCtx.sfa.getFolderContents( self.rootUrl, True )
676 browseNodeList = []
677 for i in contents:
678 if i.endswith( ".py" ):
679 log.debug( "adding filenode " + i )
680 browseNodeList.append(
681 FileBrowseNode( self.provCtx, i, i[i.rfind("/")+1:len(i)-3] ) )
682 elif self.provCtx.sfa.isFolder( i ) and not i.endswith("/pythonpath"):
683 log.debug( "adding DirBrowseNode " + i )
684 browseNodeList.append( DirBrowseNode( self.provCtx, i[i.rfind("/")+1:len(i)],i))
685 return tuple( browseNodeList )
686 except Exception as e:
687 text = lastException2String()
688 log.error( "DirBrowseNode error: " + str(e) + " while evaluating " + self.rootUrl)
689 log.error( text)
690 return ()
692 def hasChildNodes( self ):
693 return True
695 def getType( self ):
696 return CONTAINER
698 def getScript( self, uri ):
699 log.debug( "DirBrowseNode getScript " + uri + " invoked" )
700 raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 )
703 class ManifestHandler( XDocumentHandler, unohelper.Base ):
704 def __init__( self, rootUrl ):
705 self.rootUrl = rootUrl
707 def startDocument( self ):
708 self.urlList = []
710 def endDocument( self ):
711 pass
713 def startElement( self , name, attlist):
714 if name == "manifest:file-entry":
715 if attlist.getValueByName( "manifest:media-type" ) == "application/vnd.sun.star.framework-script":
716 self.urlList.append(
717 self.rootUrl + "/" + attlist.getValueByName( "manifest:full-path" ) )
719 def endElement( self, name ):
720 pass
722 def characters ( self, chars ):
723 pass
725 def ignoreableWhitespace( self, chars ):
726 pass
728 def setDocumentLocator( self, locator ):
729 pass
731 def isPyFileInPath( sfa, path ):
732 ret = False
733 contents = sfa.getFolderContents( path, True )
734 for i in contents:
735 if sfa.isFolder(i):
736 ret = isPyFileInPath(sfa,i)
737 else:
738 if i.endswith(".py"):
739 ret = True
740 if ret:
741 break
742 return ret
744 # extracts META-INF directory from
745 def getPathsFromPackage( rootUrl, sfa ):
746 ret = ()
747 try:
748 fileUrl = rootUrl + "/META-INF/manifest.xml"
749 inputStream = sfa.openFileRead( fileUrl )
750 parser = uno.getComponentContext().ServiceManager.createInstance( "com.sun.star.xml.sax.Parser" )
751 handler = ManifestHandler( rootUrl )
752 parser.setDocumentHandler( handler )
753 parser.parseStream( InputSource( inputStream , "", fileUrl, fileUrl ) )
754 for i in tuple(handler.urlList):
755 if not isPyFileInPath( sfa, i ):
756 handler.urlList.remove(i)
757 ret = tuple( handler.urlList )
758 except UnoException:
759 text = lastException2String()
760 log.debug( "getPathsFromPackage " + fileUrl + " Exception: " +text )
761 pass
762 return ret
765 class Package:
766 def __init__( self, paths, transientPathElement ):
767 self.paths = paths
768 self.transientPathElement = transientPathElement
770 class DummyInteractionHandler( unohelper.Base, XInteractionHandler ):
771 def __init__( self ):
772 pass
773 def handle( self, event):
774 log.debug( "pythonscript: DummyInteractionHandler.handle " + str( event ) )
776 class DummyProgressHandler( unohelper.Base, XProgressHandler ):
777 def __init__( self ):
778 pass
780 def push( self,status ):
781 log.debug( "pythonscript: DummyProgressHandler.push " + str( status ) )
782 def update( self,status ):
783 log.debug( "pythonscript: DummyProgressHandler.update " + str( status ) )
784 def pop( self, event ):
785 log.debug( "pythonscript: DummyProgressHandler.push " + str( event ) )
787 class CommandEnvironment(unohelper.Base, XCommandEnvironment):
788 def __init__( self ):
789 self.progressHandler = DummyProgressHandler()
790 self.interactionHandler = DummyInteractionHandler()
791 def getInteractionHandler( self ):
792 return self.interactionHandler
793 def getProgressHandler( self ):
794 return self.progressHandler
796 #maybe useful for debugging purposes
797 #class ModifyListener( unohelper.Base, XModifyListener ):
798 # def __init__( self ):
799 # pass
800 # def modified( self, event ):
801 # log.debug( "pythonscript: ModifyListener.modified " + str( event ) )
802 # def disposing( self, event ):
803 # log.debug( "pythonscript: ModifyListener.disposing " + str( event ) )
805 def getModelFromDocUrl(ctx, url):
806 """Get document model from document url."""
807 doc = None
808 args = ("Local", "Office")
809 ucb = ctx.getServiceManager().createInstanceWithArgumentsAndContext(
810 "com.sun.star.ucb.UniversalContentBroker", args, ctx)
811 identifier = ucb.createContentIdentifier(url)
812 content = ucb.queryContent(identifier)
813 p = Property()
814 p.Name = "DocumentModel"
815 p.Handle = -1
817 c = Command()
818 c.Handle = -1
819 c.Name = "getPropertyValues"
820 c.Argument = uno.Any("[]com.sun.star.beans.Property", (p,))
822 env = CommandEnvironment()
823 try:
824 ret = content.execute(c, 0, env)
825 doc = ret.getObject(1, None)
826 except Exception as e:
827 log.isErrorLevel() and log.error("getModelFromDocUrl: %s" % url)
828 return doc
830 def mapStorageType2PackageContext( storageType ):
831 ret = storageType
832 if( storageType == "share:uno_packages" ):
833 ret = "shared"
834 if( storageType == "user:uno_packages" ):
835 ret = "user"
836 return ret
838 def getPackageName2PathMap( sfa, storageType ):
839 ret = {}
840 packageManagerFactory = uno.getComponentContext().getValueByName(
841 "/singletons/com.sun.star.deployment.thePackageManagerFactory" )
842 packageManager = packageManagerFactory.getPackageManager(
843 mapStorageType2PackageContext(storageType))
844 # packageManager.addModifyListener( ModifyListener() )
845 log.debug( "pythonscript: getPackageName2PathMap start getDeployedPackages" )
846 packages = packageManager.getDeployedPackages(
847 packageManager.createAbortChannel(), CommandEnvironment( ) )
848 log.debug( "pythonscript: getPackageName2PathMap end getDeployedPackages (" + str(len(packages))+")" )
850 for i in packages:
851 log.debug( "inspecting package " + i.Name + "("+i.Identifier.Value+")" )
852 transientPathElement = penultimateElement( i.URL )
853 j = expandUri( i.URL )
854 paths = getPathsFromPackage( j, sfa )
855 if len( paths ) > 0:
856 # map package name to url, we need this later
857 log.isErrorLevel() and log.error( "adding Package " + transientPathElement + " " + str( paths ) )
858 ret[ lastElement( j ) ] = Package( paths, transientPathElement )
859 return ret
861 def penultimateElement( aStr ):
862 lastSlash = aStr.rindex("/")
863 penultimateSlash = aStr.rindex("/",0,lastSlash-1)
864 return aStr[ penultimateSlash+1:lastSlash ]
866 def lastElement( aStr):
867 return aStr[ aStr.rfind( "/" )+1:len(aStr)]
869 class PackageBrowseNode( unohelper.Base, XBrowseNode ):
870 def __init__( self, provCtx, name, rootUrl ):
871 self.provCtx = provCtx
872 self.name = name
873 self.rootUrl = rootUrl
875 def getName( self ):
876 return self.name
878 def getChildNodes( self ):
879 items = self.provCtx.mapPackageName2Path.items()
880 browseNodeList = []
881 for i in items:
882 if len( i[1].paths ) == 1:
883 browseNodeList.append(
884 DirBrowseNode( self.provCtx, i[0], i[1].paths[0] ))
885 else:
886 for j in i[1].paths:
887 browseNodeList.append(
888 DirBrowseNode( self.provCtx, i[0]+"."+lastElement(j), j ) )
889 return tuple( browseNodeList )
891 def hasChildNodes( self ):
892 return len( self.provCtx.mapPackageName2Path ) > 0
894 def getType( self ):
895 return CONTAINER
897 def getScript( self, uri ):
898 log.debug( "PackageBrowseNode getScript " + uri + " invoked" )
899 raise IllegalArgumentException( "PackageBrowseNode couldn't instantiate script " + uri , self , 0 )
904 class PythonScript( unohelper.Base, XScript ):
905 def __init__( self, func, mod, args ):
906 self.func = func
907 self.mod = mod
908 self.args = args
910 def invoke(self, args, out, outindex ):
911 log.debug( "PythonScript.invoke " + str( args ) )
912 try:
913 if (self.args):
914 args += self.args
915 ret = self.func( *args )
916 except UnoException as e:
917 # UNO Exception continue to fly ...
918 text = lastException2String()
919 complete = "Error during invoking function " + \
920 str(self.func.__name__) + " in module " + \
921 self.mod.__file__ + " (" + text + ")"
922 log.debug( complete )
923 # some people may beat me up for modifying the exception text,
924 # but otherwise office just shows
925 # the type name and message text with no more information,
926 # this is really bad for most users.
927 e.Message = e.Message + " (" + complete + ")"
928 raise
929 except Exception as e:
930 # General python exception are converted to uno RuntimeException
931 text = lastException2String()
932 complete = "Error during invoking function " + \
933 str(self.func.__name__) + " in module " + \
934 self.mod.__file__ + " (" + text + ")"
935 log.debug( complete )
936 raise RuntimeException( complete , self )
937 log.debug( "PythonScript.invoke ret = " + str( ret ) )
938 return ret, (), ()
940 def expandUri( uri ):
941 if uri.startswith( "vnd.sun.star.expand:" ):
942 uri = uri.replace( "vnd.sun.star.expand:", "",1)
943 uri = uno.getComponentContext().getByName(
944 "/singletons/com.sun.star.util.theMacroExpander" ).expandMacros( uri )
945 if uri.startswith( "file:" ):
946 uri = uno.absolutize("",uri) # necessary to get rid of .. in uri
947 return uri
949 #--------------------------------------------------------------
950 class PythonScriptProvider( unohelper.Base, XBrowseNode, XScriptProvider, XNameContainer):
951 def __init__( self, ctx, *args ):
952 if log.isDebugLevel():
953 mystr = ""
954 for i in args:
955 if len(mystr) > 0:
956 mystr = mystr +","
957 mystr = mystr + str(i)
958 log.debug( "Entering PythonScriptProvider.ctor" + mystr )
960 doc = None
961 inv = None
962 storageType = ""
964 if isinstance(args[0], str):
965 storageType = args[0]
966 if storageType.startswith( "vnd.sun.star.tdoc" ):
967 doc = getModelFromDocUrl(ctx, storageType)
968 else:
969 inv = args[0]
970 try:
971 doc = inv.ScriptContainer
972 content = ctx.getServiceManager().createInstanceWithContext(
973 "com.sun.star.frame.TransientDocumentsDocumentContentFactory",
974 ctx).createDocumentContent(doc)
975 storageType = content.getIdentifier().getContentIdentifier()
976 except Exception as e:
977 text = lastException2String()
978 log.error( text )
980 isPackage = storageType.endswith( ":uno_packages" )
982 try:
983 # urlHelper = ctx.ServiceManager.createInstanceWithArgumentsAndContext(
984 # "com.sun.star.script.provider.ScriptURIHelper", (LANGUAGENAME, storageType), ctx)
985 urlHelper = MyUriHelper( ctx, storageType )
986 log.debug( "got urlHelper " + str( urlHelper ) )
988 rootUrl = expandUri( urlHelper.getRootStorageURI() )
989 log.debug( storageType + " transformed to " + rootUrl )
991 ucbService = "com.sun.star.ucb.SimpleFileAccess"
992 sfa = ctx.ServiceManager.createInstanceWithContext( ucbService, ctx )
993 if not sfa:
994 log.debug("PythonScriptProvider couldn't instantiate " +ucbService)
995 raise RuntimeException(
996 "PythonScriptProvider couldn't instantiate " +ucbService, self)
997 self.provCtx = ProviderContext(
998 storageType, sfa, urlHelper, ScriptContext( uno.getComponentContext(), doc, inv ) )
999 if isPackage:
1000 mapPackageName2Path = getPackageName2PathMap( sfa, storageType )
1001 self.provCtx.setPackageAttributes( mapPackageName2Path , rootUrl )
1002 self.dirBrowseNode = PackageBrowseNode( self.provCtx, LANGUAGENAME, rootUrl )
1003 else:
1004 self.dirBrowseNode = DirBrowseNode( self.provCtx, LANGUAGENAME, rootUrl )
1006 except Exception as e:
1007 text = lastException2String()
1008 log.debug( "PythonScriptProvider could not be instantiated because of : " + text )
1009 raise e
1011 def getName( self ):
1012 return self.dirBrowseNode.getName()
1014 def getChildNodes( self ):
1015 return self.dirBrowseNode.getChildNodes()
1017 def hasChildNodes( self ):
1018 return self.dirBrowseNode.hasChildNodes()
1020 def getType( self ):
1021 return self.dirBrowseNode.getType()
1023 # retrieve function args in parenthesis
1024 def getFunctionArguments(self, func_signature):
1025 nOpenParenthesis = func_signature.find( "(" )
1026 if -1 == nOpenParenthesis:
1027 function_name = func_signature
1028 arguments = None
1029 else:
1030 function_name = func_signature[0:nOpenParenthesis]
1031 arg_part = func_signature[nOpenParenthesis+1:len(func_signature)]
1032 nCloseParenthesis = arg_part.find( ")" )
1033 if -1 == nCloseParenthesis:
1034 raise IllegalArgumentException( "PythonLoader: mismatch parenthesis " + func_signature, self, 0 )
1035 arguments = arg_part[0:nCloseParenthesis].strip()
1036 if arguments == "":
1037 arguments = None
1038 else:
1039 arguments = tuple([x.strip().strip('"') for x in arguments.split(",")])
1040 return function_name, arguments
1042 def getScript( self, scriptUri ):
1043 try:
1044 log.debug( "getScript " + scriptUri + " invoked")
1046 storageUri = self.provCtx.getStorageUrlFromPersistentUrl(
1047 self.provCtx.uriHelper.getStorageURI(scriptUri) );
1048 log.debug( "getScript: storageUri = " + storageUri)
1049 fileUri = storageUri[0:storageUri.find( "$" )]
1050 funcName = storageUri[storageUri.find( "$" )+1:len(storageUri)]
1052 # retrieve arguments in parenthesis
1053 funcName, funcArgs = self.getFunctionArguments(funcName)
1054 log.debug( " getScript : parsed funcname " + str(funcName) )
1055 log.debug( " getScript : func args " + str(funcArgs) )
1057 mod = self.provCtx.getModuleByUrl( fileUri )
1058 log.debug( " got mod " + str(mod) )
1060 func = mod.__dict__[ funcName ]
1062 log.debug( "got func " + str( func ) )
1063 return PythonScript( func, mod, funcArgs )
1064 except:
1065 text = lastException2String()
1066 log.error( text )
1067 raise ScriptFrameworkErrorException( text, self, scriptUri, LANGUAGENAME, 0 )
1070 # XServiceInfo
1071 def getSupportedServices( self ):
1072 return g_ImplementationHelper.getSupportedServices(g_implName)
1074 def supportsService( self, ServiceName ):
1075 return g_ImplementationHelper.supportsService( g_implName, ServiceName )
1077 def getImplementationName(self):
1078 return g_implName
1080 def getByName( self, name ):
1081 log.debug( "getByName called" + str( name ))
1082 return None
1085 def getElementNames( self ):
1086 log.debug( "getElementNames called")
1087 return ()
1089 def hasByName( self, name ):
1090 try:
1091 log.debug( "hasByName called " + str( name ))
1092 uri = expandUri(name)
1093 ret = self.provCtx.isUrlInPackage( uri )
1094 log.debug( "hasByName " + uri + " " +str( ret ) )
1095 return ret
1096 except:
1097 text = lastException2String()
1098 log.debug( "Error in hasByName:" + text )
1099 return False
1101 def removeByName( self, name ):
1102 log.debug( "removeByName called" + str( name ))
1103 uri = expandUri( name )
1104 if self.provCtx.isUrlInPackage( uri ):
1105 self.provCtx.removePackageByUrl( uri )
1106 else:
1107 log.debug( "removeByName unknown uri " + str( name ) + ", ignoring" )
1108 raise NoSuchElementException( uri + "is not in package" , self )
1109 log.debug( "removeByName called" + str( uri ) + " successful" )
1111 def insertByName( self, name, value ):
1112 log.debug( "insertByName called " + str( name ) + " " + str( value ))
1113 uri = expandUri( name )
1114 if isPyFileInPath( self.provCtx.sfa, uri ):
1115 self.provCtx.addPackageByUrl( uri )
1116 else:
1117 # package is no python package ...
1118 log.debug( "insertByName: no python files in " + str( uri ) + ", ignoring" )
1119 raise IllegalArgumentException( uri + " does not contain .py files", self, 1 )
1120 log.debug( "insertByName called " + str( uri ) + " successful" )
1122 def replaceByName( self, name, value ):
1123 log.debug( "replaceByName called " + str( name ) + " " + str( value ))
1124 uri = expandUri( name )
1125 self.removeByName( name )
1126 self.insertByName( name, value )
1127 log.debug( "replaceByName called" + str( uri ) + " successful" )
1129 def getElementType( self ):
1130 log.debug( "getElementType called" )
1131 return uno.getTypeByName( "void" )
1133 def hasElements( self ):
1134 log.debug( "hasElements got called")
1135 return False
1137 g_ImplementationHelper.addImplementation( \
1138 PythonScriptProvider,g_implName, \
1139 ("com.sun.star.script.provider.LanguageScriptProvider",
1140 "com.sun.star.script.provider.ScriptProviderFor"+ LANGUAGENAME,),)
1143 log.debug( "pythonscript finished initializing" )
1145 # vim: set shiftwidth=4 softtabstop=4 expandtab: