LibreOffice Module scripting (master) 1
pythonscript.py
Go to the documentation of this file.
1# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
2#
3# This file is part of the LibreOffice project.
4#
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/.
8#
9# This file incorporates work covered by the following license notice:
10#
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 .
18#
19# XScript implementation for python
20import uno
21import unohelper
22import sys
23import os
24import types
25import time
26import ast
27import platform
28from com.sun.star.uri.RelativeUriExcessParentSegments import RETAIN
29from urllib.parse import unquote
30
32 NONE = 0 # production level
33 ERROR = 1 # for script developers
34 DEBUG = 2 # for script framework developers
35
36PYSCRIPT_LOG_ENV = "PYSCRIPT_LOG_LEVEL"
37PYSCRIPT_LOG_STDOUT_ENV = "PYSCRIPT_LOG_STDOUT"
38
39# Configuration ----------------------------------------------------
40LogLevel.use = LogLevel.NONE
41if os.environ.get(PYSCRIPT_LOG_ENV) == "ERROR":
42 LogLevel.use = LogLevel.ERROR
43elif os.environ.get(PYSCRIPT_LOG_ENV) == "DEBUG":
44 LogLevel.use = LogLevel.DEBUG
45
46# True, writes to stdout (difficult on windows)
47# False, writes to user/Scripts/python/log.txt
48LOG_STDOUT = os.environ.get(PYSCRIPT_LOG_STDOUT_ENV, "1") != "0"
49
50ENABLE_EDIT_DIALOG=False # offers a minimal editor for editing.
51#-------------------------------------------------------------------
52
53def encfile(uni):
54 return uni.encode( sys.getfilesystemencoding())
55
57 (excType,excInstance,excTraceback) = sys.exc_info()
58 ret = str(excType) + ": "+str(excInstance) + "\n" + \
60 return ret
61
62def logLevel2String( level ):
63 ret = " NONE"
64 if level == LogLevel.ERROR:
65 ret = "ERROR"
66 elif level >= LogLevel.DEBUG:
67 ret = "DEBUG"
68 return ret
69
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
83
85 def __init__(self , target ):
86 self.target = target
87
88 def isDebugLevel( self ):
89 return self.use >= self.DEBUG
90
91 def debug( self, msg ):
92 if self.isDebugLevel():
93 self.log( self.DEBUG, msg )
94
95 def isErrorLevel( self ):
96 return self.use >= self.ERROR
97
98 def error( self, msg ):
99 if self.isErrorLevel():
100 self.log( self.ERROR, msg )
101
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")
115
117
118log.debug( "pythonscript loading" )
119
120#from com.sun.star.lang import typeOfXServiceInfo, typeOfXTypeProvider
121from com.sun.star.uno import RuntimeException
122from com.sun.star.lang import IllegalArgumentException
123from com.sun.star.container import NoSuchElementException
124from com.sun.star.lang import XServiceInfo
125from com.sun.star.io import IOException
126from com.sun.star.ucb import CommandAbortedException, XCommandEnvironment, XProgressHandler, Command
127from com.sun.star.task import XInteractionHandler
128from com.sun.star.beans import XPropertySet, Property
129from com.sun.star.container import XNameContainer
130from com.sun.star.xml.sax import XDocumentHandler, InputSource
131from com.sun.star.uno import Exception as UnoException
132from com.sun.star.script import XInvocation
133from com.sun.star.awt import XActionListener
134
135from com.sun.star.script.provider import XScriptProvider, XScript, XScriptContext, ScriptFrameworkErrorException
136from com.sun.star.script.browse import XBrowseNode
137from com.sun.star.script.browse.BrowseNodeTypes import SCRIPT, CONTAINER, ROOT
138from com.sun.star.util import XModifyListener
139
140LANGUAGENAME = "Python"
141GLOBAL_SCRIPTCONTEXT_NAME = "XSCRIPTCONTEXT"
142CALLABLE_CONTAINER_NAME = "g_exportedScripts"
143
144# pythonloader looks for a static g_ImplementationHelper variable
145g_ImplementationHelper = unohelper.ImplementationHelper()
146g_implName = "org.libreoffice.pyuno.LanguageScriptProviderFor"+LANGUAGENAME
147
148
149
150BLOCK_SIZE = 65536
151def 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
160
161def toIniName( str ):
162 if platform.system() == "Windows":
163 return str + ".ini"
164 else:
165 return str + "rc"
166
167
168""" definition: storageURI is the system dependent, absolute file url, where the script is stored on disk
169 scriptURI is the system independent uri
170"""
171class MyUriHelper:
172
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 )
188
189 def getRootStorageURI( self ):
190 return self.m_baseUri
191
192 def getStorageURI( self, scriptURI ):
193 return self.scriptURI2StorageUri(scriptURI)
194
195 def getScriptURI( self, storageURI ):
196 return self.storageURI2ScriptUri(storageURI)
197
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 )
203
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
209
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)
215
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( "|", "/" )
221
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
229
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 )
235
236 if not xFileUri.hasRelativePath():
237 message = "pythonscript: an absolute uri is invalid '" + sFileUri+ "'"
238 log.debug( message )
239 raise RuntimeException( message, self.ctx )
240
241 # absolute path to the .py file
242 xAbsScriptUri = self.m_uriRefFac.makeAbsolute(xBaseUri, xFileUri, True, RETAIN)
243 sAbsScriptUri = xAbsScriptUri.getUriReference()
244
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 )
250
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 )
262
263
265 def __init__( self, lastRead, module ):
266 self.lastRead = lastRead
267 self.module = module
268
269def 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
277
279 if code.endswith(b"\n"):
280 code = code + b"\n"
281 code = code.replace(b"\r", b"")
282 return code
283
284
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 )
292
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 )
298
299
301 def __init__( self, ctx, doc, inv ):
302 self.ctx = ctx
303 self.doc = doc
304 self.inv = inv
305
306 # XScriptContext
307 def getDocument(self):
308 if self.doc:
309 return self.doc
310 return self.getDesktop().getCurrentComponent()
311
312 def getDesktop(self):
313 return self.ctx.ServiceManager.createInstanceWithContext(
314 "com.sun.star.frame.Desktop", self.ctx )
315
317 return self.ctx
318
320 return self.inv
321
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
338#
339# if load:
340# log.debug( "opening >" + url + "<" )
341#
342# code = readTextFromStream( sfa.openFileRead( url ) )
343
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
352
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
362
363 def getTransientPartFromUrl( self, url ):
364 rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1)
365 return rest[0:rest.find("/")]
366
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)]
371
372
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
379
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
390
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
399
400 def setPackageAttributes( self, mapPackageName2Path, rootUrl ):
401 self.mapPackageName2Path = mapPackageName2Path
402 self.rootUrl = rootUrl
403
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
412
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
422
423 def getFuncsByUrl( self, url ):
424 src = readTextFromStream( self.sfa.openFileRead( url ) )
425 checkForPythonPathBesideScript( url[0:url.rfind('/')] )
426 src = ensureSourceState( src )
427
428 try:
429 code = ast.parse( src )
430 except:
431 log.isDebugLevel() and log.debug( "pythonscript: getFuncsByUrl: exception while parsing: " + lastException2String())
432 raise
433
434 allFuncs = []
435
436 if code is None:
437 return allFuncs
438
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
454
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
466
467 return allFuncs
468
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
478
479 if load:
480 log.debug( "opening >" + url + "<" )
481
482 src = readTextFromStream( self.sfa.openFileRead( url ) )
483 checkForPythonPathBesideScript( url[0:url.rfind('/')] )
484 src = ensureSourceState( src )
485
486 # execute the module
487 entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") )
488 entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.scriptContext
489
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
500
501#--------------------------------------------------
502def isScript( candidate ):
503 ret = False
504 if isinstance( candidate, type(isScript) ):
505 ret = True
506 return ret
507
508#-------------------------------------------------------
509class 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
515
516 def getName( self ):
517 return self.funcName
518
519 def getChildNodes(self):
520 return ()
521
522 def hasChildNodes(self):
523 return False
524
525 def getType( self):
526 return SCRIPT
527
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 )
536
537 log.debug( "ScriptBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) )
538 except:
539 log.error( "ScriptBrowseNode.getPropertyValue error " + lastException2String())
540 raise
541
542 return ret
543 def setPropertyValue( self, name, value ):
544 log.debug( "ScriptBrowseNode.setPropertyValue called " + name + "=" +str(value ) )
546 log.debug( "ScriptBrowseNode.getPropertySetInfo called " )
547 return None
548
549 def getIntrospection( self ):
550 return None
551
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 )
558
559 self.editor = dlgprov.createDialog(
560 "vnd.sun.star.script:" +
561 "ScriptBindingLibrary.MacroEditor?location=application")
562
563 code = readTextFromStream(self.provCtx.sfa.openFileRead(self.uri))
564 code = ensureSourceState( code )
565 self.editor.getControl("EditorTextField").setText(code)
566
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)
571
572 self.editor.execute()
573
574 return None
575
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()
587
588 for i in values:
589 if isScript( i ):
590 i()
591 break
592
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() )
609
610
611 def setValue( self, name, value ):
612 return None
613
614 def getValue( self, name ):
615 return None
616
617 def hasMethod( self, name ):
618 return False
619
620 def hasProperty( self, name ):
621 return False
622
623
624#-------------------------------------------------------
625class 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
631
632 def getName( self ):
633 return self.name
634
635 def getChildNodes(self):
636 ret = ()
637 try:
638 self.funcnames = self.provCtx.getFuncsByUrl( self.uri )
639
640 scriptNodeList = []
641 for i in self.funcnames:
642 scriptNodeList.append(
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
652
653 def hasChildNodes(self):
654 try:
655 return len(self.getChildNodes()) > 0
656 except:
657 return False
658
659 def getType( self):
660 return CONTAINER
661
662
663
664class DirBrowseNode( unohelper.Base, XBrowseNode ):
665 def __init__( self, provCtx, name, rootUrl ):
666 self.provCtx = provCtx
667 self.name = name
668 self.rootUrl = rootUrl
669
670 def getName( self ):
671 return self.name
672
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 ()
692
693 def hasChildNodes( self ):
694 return True
695
696 def getType( self ):
697 return CONTAINER
698
699 def getScript( self, uri ):
700 log.debug( "DirBrowseNode getScript " + uri + " invoked" )
701 raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 )
702
703
704class ManifestHandler( XDocumentHandler, unohelper.Base ):
705 def __init__( self, rootUrl ):
706 self.rootUrl = rootUrl
707
708 def startDocument( self ):
709 self.urlList = []
710
711 def endDocument( self ):
712 pass
713
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" ) )
719
720 def endElement( self, name ):
721 pass
722
723 def characters ( self, chars ):
724 pass
725
726 def ignoreableWhitespace( self, chars ):
727 pass
728
729 def setDocumentLocator( self, locator ):
730 pass
731
732def 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
744
745# extracts META-INF directory from
746def 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
764
765
767 def __init__( self, paths, transientPathElement ):
768 self.paths = paths
769 self.transientPathElement = transientPathElement
770
771class DummyInteractionHandler( unohelper.Base, XInteractionHandler ):
772 def __init__( self ):
773 pass
774 def handle( self, event):
775 log.debug( "pythonscript: DummyInteractionHandler.handle " + str( event ) )
776
777class DummyProgressHandler( unohelper.Base, XProgressHandler ):
778 def __init__( self ):
779 pass
780
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 ) )
787
788class CommandEnvironment(unohelper.Base, XCommandEnvironment):
789 def __init__( self ):
793 return self.interactionHandler
795 return self.progressHandler
796
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 ) )
805
806def 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
817
818 c = Command()
819 c.Handle = -1
820 c.Name = "getPropertyValues"
821 c.Argument = uno.Any("[]com.sun.star.beans.Property", (p,))
822
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
830
832 ret = storageType
833 if( storageType == "share:uno_packages" ):
834 ret = "shared"
835 if( storageType == "user:uno_packages" ):
836 ret = "user"
837 return ret
838
839def getPackageName2PathMap( sfa, storageType ):
840 ret = {}
841 packageManagerFactory = uno.getComponentContext().getValueByName(
842 "/singletons/com.sun.star.deployment.thePackageManagerFactory" )
843 packageManager = packageManagerFactory.getPackageManager(
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))+")" )
850
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
861
863 lastSlash = aStr.rindex("/")
864 penultimateSlash = aStr.rindex("/",0,lastSlash-1)
865 return aStr[ penultimateSlash+1:lastSlash ]
866
867def lastElement( aStr):
868 return aStr[ aStr.rfind( "/" )+1:len(aStr)]
869
870class PackageBrowseNode( unohelper.Base, XBrowseNode ):
871 def __init__( self, provCtx, name, rootUrl ):
872 self.provCtx = provCtx
873 self.name = name
874 self.rootUrl = rootUrl
875
876 def getName( self ):
877 return self.name
878
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 )
891
892 def hasChildNodes( self ):
893 return len( self.provCtx.mapPackageName2Path ) > 0
894
895 def getType( self ):
896 return CONTAINER
897
898 def getScript( self, uri ):
899 log.debug( "PackageBrowseNode getScript " + uri + " invoked" )
900 raise IllegalArgumentException( "PackageBrowseNode couldn't instantiate script " + uri , self , 0 )
901
902
903
904
905class PythonScript( unohelper.Base, XScript ):
906 def __init__( self, func, mod, args ):
907 self.func = func
908 self.mod = mod
909 self.args = args
910
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, (), ()
940
941def 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
949
950#--------------------------------------------------------------
951class 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 )
960
961 doc = None
962 inv = None
963 storageType = ""
964
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 )
980
981 isPackage = storageType.endswith( ":uno_packages" )
982
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 ) )
988
989 rootUrl = expandUri( urlHelper.getRootStorageURI() )
990 log.debug( storageType + " transformed to " + rootUrl )
991
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)
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 )
1006
1007 except Exception as e:
1008 text = lastException2String()
1009 log.debug( "PythonScriptProvider could not be instantiated because of : " + text )
1010 raise e
1011
1012 def getName( self ):
1013 return self.dirBrowseNode.getName()
1014
1015 def getChildNodes( self ):
1016 return self.dirBrowseNode.getChildNodes()
1017
1018 def hasChildNodes( self ):
1019 return self.dirBrowseNode.hasChildNodes()
1020
1021 def getType( self ):
1022 return self.dirBrowseNode.getType()
1023
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
1042
1043 def getScript( self, scriptUri ):
1044 try:
1045 log.debug( "getScript " + scriptUri + " invoked")
1046
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)]
1052
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) )
1057
1058 mod = self.provCtx.getModuleByUrl( fileUri )
1059 log.debug( " got mod " + str(mod) )
1060
1061 func = mod.__dict__[ funcName ]
1062
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 )
1069
1070
1071 # XServiceInfo
1073 return g_ImplementationHelper.getSupportedServices(g_implName)
1074
1075 def supportsService( self, ServiceName ):
1076 return g_ImplementationHelper.supportsService( g_implName, ServiceName )
1077
1079 return g_implName
1080
1081 def getByName( self, name ):
1082 log.debug( "getByName called" + str( name ))
1083 return None
1084
1085
1086 def getElementNames( self ):
1087 log.debug( "getElementNames called")
1088 return ()
1089
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
1101
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" )
1111
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" )
1122
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" )
1129
1130 def getElementType( self ):
1131 log.debug( "getElementType called" )
1132 return uno.getTypeByName( "void" )
1133
1134 def hasElements( self ):
1135 log.debug( "hasElements got called")
1136 return False
1137
1138g_ImplementationHelper.addImplementation( \
1139 PythonScriptProvider,g_implName, \
1140 ("com.sun.star.script.provider.LanguageScriptProvider",
1141 "com.sun.star.script.provider.ScriptProviderFor"+ LANGUAGENAME,),)
1142
1143
1144log.debug( "pythonscript finished initializing" )
1145
1146# vim: set shiftwidth=4 softtabstop=4 expandtab:
def __init__(self, provCtx, name, rootUrl)
def __init__(self, provCtx, uri, name)
def debug(self, msg)
Definition: pythonscript.py:91
def isErrorLevel(self)
Definition: pythonscript.py:95
def __init__(self, target)
Definition: pythonscript.py:85
def isDebugLevel(self)
Definition: pythonscript.py:88
def error(self, msg)
Definition: pythonscript.py:98
def log(self, level, msg)
def __init__(self, rootUrl)
def startElement(self, name, attlist)
def setDocumentLocator(self, locator)
def characters(self, chars)
def ignoreableWhitespace(self, chars)
def __init__(self, lastRead, module)
def scriptURI2StorageUri(self, scriptURI)
def __init__(self, ctx, location)
def getStorageURI(self, scriptURI)
def getScriptURI(self, storageURI)
def storageURI2ScriptUri(self, storageURI)
def __init__(self, provCtx, name, rootUrl)
def __init__(self, paths, transientPathElement)
def getStorageUrlFromPersistentUrl(self, url)
def addPackageByUrl(self, url)
def removePackageByUrl(self, url)
def getPackageNameFromUrl(self, url)
def setPackageAttributes(self, mapPackageName2Path, rootUrl)
def getTransientPartFromUrl(self, url)
def __init__(self, storageType, sfa, uriHelper, scriptContext)
def getPersistentUrlFromStorageUrl(self, url)
def getScript(self, scriptUri)
def replaceByName(self, name, value)
def insertByName(self, name, value)
def supportsService(self, ServiceName)
def __init__(self, ctx, *args)
def getFunctionArguments(self, func_signature)
def invoke(self, args, out, outindex)
def __init__(self, func, mod, args)
def setPropertyValue(self, name, value)
def __init__(self, provCtx, uri, fileName, funcName)
def getPropertyValue(self, name)
def invoke(self, name, params, outparamindex, outparams)
def actionPerformed(self, event)
def setValue(self, name, value)
def __init__(self, ctx, doc, inv)
def getModelFromDocUrl(ctx, url)
def isPyFileInPath(sfa, path)
def hasChanged(oldDate, newDate)
def lastException2String()
Definition: pythonscript.py:56
def encfile(uni)
Definition: pythonscript.py:53
def mapStorageType2PackageContext(storageType)
def getPathsFromPackage(rootUrl, sfa)
def getPackageName2PathMap(sfa, storageType)
def ensureSourceState(code)
def expandUri(uri)
def checkForPythonPathBesideScript(url)
def readTextFromStream(inputStream)
def lastElement(aStr)
def penultimateElement(aStr)
def isScript(candidate)
def toIniName(str)
def getLogTarget()
Definition: pythonscript.py:70
def logLevel2String(level)
Definition: pythonscript.py:62
def absolutize(path, relativeUrl)
def _uno_extract_printable_stacktrace(trace)
def fileUrlToSystemPath(url)
def getTypeByName(typeName)
def fileUrlToSystemPath(url)