diff options
Diffstat (limited to 'scripting/source/pyprov')
-rw-r--r-- | scripting/source/pyprov/mailmerge.README | 18 | ||||
-rw-r--r-- | scripting/source/pyprov/mailmerge.component | 28 | ||||
-rw-r--r-- | scripting/source/pyprov/mailmerge.py | 533 | ||||
-rw-r--r-- | scripting/source/pyprov/msgbox.py | 241 | ||||
-rw-r--r-- | scripting/source/pyprov/pythonscript.py | 1146 | ||||
-rw-r--r-- | scripting/source/pyprov/scriptproviderforpython.rdb | 28 |
6 files changed, 1994 insertions, 0 deletions
diff --git a/scripting/source/pyprov/mailmerge.README b/scripting/source/pyprov/mailmerge.README new file mode 100644 index 0000000000..6b4fb5ba4f --- /dev/null +++ b/scripting/source/pyprov/mailmerge.README @@ -0,0 +1,18 @@ +Easiest way I find to test this is to... + +1) + +a) install fakemail and run it +b) tools->options->writer->mail merge email +c) localhost 8025 + +2) + +a) type some text into writer that will exercise utf-8, e.g. "Caolán's test" +b) tools->mail merge wizard->next->email message->select address book +c) create, add one user with your own email address, ok +d) next, next, text, send merged document as email +e) and test all of plain text, html and the various attachment options + +fake mail will dump the mail it gets into its pwd, if all that works, you can +then try with your own normal mail server. diff --git a/scripting/source/pyprov/mailmerge.component b/scripting/source/pyprov/mailmerge.component new file mode 100644 index 0000000000..8a93253412 --- /dev/null +++ b/scripting/source/pyprov/mailmerge.component @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.Python" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="org.openoffice.pyuno.MailMessage"> + <service name="com.sun.star.mail.MailMessage"/> + </implementation> + <implementation name="org.openoffice.pyuno.MailServiceProvider"> + <service name="com.sun.star.mail.MailServiceProvider"/> + </implementation> +</component> diff --git a/scripting/source/pyprov/mailmerge.py b/scripting/source/pyprov/mailmerge.py new file mode 100644 index 0000000000..3c781c52f2 --- /dev/null +++ b/scripting/source/pyprov/mailmerge.py @@ -0,0 +1,533 @@ +# Caolan McNamara caolanm@redhat.com +# a simple email mailmerge component + +# manual installation for hackers, not necessary for users +# cp mailmerge.py /usr/lib/libreoffice/program +# cd /usr/lib/libreoffice/program +# ./unopkg add --shared mailmerge.py +# edit ~/.openoffice.org2/user/registry/data/org/openoffice/Office/Writer.xcu +# and change EMailSupported to as follows... +# <prop oor:name="EMailSupported" oor:type="xs:boolean"> +# <value>true</value> +# </prop> + +import unohelper +import uno +import re +import os +import encodings.idna + +#to implement com::sun::star::mail::XMailServiceProvider +#and +#to implement com.sun.star.mail.XMailMessage + +from com.sun.star.mail import XMailServiceProvider +from com.sun.star.mail import XMailService +from com.sun.star.mail import XSmtpService +from com.sun.star.mail import XConnectionListener +from com.sun.star.mail import XAuthenticator +from com.sun.star.mail import XMailMessage +from com.sun.star.mail.MailServiceType import SMTP +from com.sun.star.mail.MailServiceType import POP3 +from com.sun.star.mail.MailServiceType import IMAP +from com.sun.star.uno import XCurrentContext +from com.sun.star.lang import IllegalArgumentException +from com.sun.star.lang import EventObject +from com.sun.star.lang import XServiceInfo +from com.sun.star.mail import SendMailMessageFailedException + +from email.mime.base import MIMEBase +from email.message import Message +from email.charset import Charset +from email.charset import QP +from email.encoders import encode_base64 +from email.header import Header +from email.mime.multipart import MIMEMultipart +from email.utils import formatdate +from email.utils import parseaddr +from socket import _GLOBAL_DEFAULT_TIMEOUT + +import sys, ssl, smtplib, imaplib, poplib +dbg = False + +# pythonloader looks for a static g_ImplementationHelper variable +g_ImplementationHelper = unohelper.ImplementationHelper() +g_providerImplName = "org.openoffice.pyuno.MailServiceProvider" +g_messageImplName = "org.openoffice.pyuno.MailMessage" + +def prepareTLSContext(xComponent, xContext, isTLSRequested): + xConfigProvider = xContext.ServiceManager.createInstance("com.sun.star.configuration.ConfigurationProvider") + prop = uno.createUnoStruct('com.sun.star.beans.PropertyValue') + prop.Name = "nodepath" + prop.Value = "/org.openoffice.Office.Security/Net" + xSettings = xConfigProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", + (prop,)) + isAllowedInsecure = xSettings.getByName("AllowInsecureProtocols") + tlscontext = None + if isTLSRequested: + if dbg: + print("SSL config: " + str(ssl.get_default_verify_paths()), file=sys.stderr) + tlscontext = ssl.create_default_context() + # SSLv2/v3 is already disabled by default. + # This check does not work, because OpenSSL 3 defines SSL_OP_NO_SSLv2 + # as 0, so even though _ssl__SSLContext_impl() tries to set it, + # getting the value from SSL_CTX_get_options() doesn't lead to setting + # the python-level flag. + #assert (tlscontext.options & ssl.Options.OP_NO_SSLv2) != 0 + assert (tlscontext.options & ssl.Options.OP_NO_SSLv3) != 0 + if not(isAllowedInsecure): + if not(isTLSRequested): + if dbg: + print("mailmerge.py: insecure connection not allowed by configuration", file=sys.stderr) + raise IllegalArgumentException("insecure connection not allowed by configuration", xComponent, 1) + tlscontext.options |= ssl.Options.OP_NO_TLSv1 | ssl.Options.OP_NO_TLSv1_1 + return tlscontext + +class PyMailSMTPService(unohelper.Base, XSmtpService): + def __init__( self, ctx ): + self.ctx = ctx + self.listeners = [] + self.supportedtypes = ('Insecure', 'Ssl') + self.server = None + self.connectioncontext = None + self.notify = EventObject(self) + if dbg: + print("PyMailSMTPService init", file=sys.stderr) + print("python version is: " + sys.version, file=sys.stderr) + def addConnectionListener(self, xListener): + if dbg: + print("PyMailSMTPService addConnectionListener", file=sys.stderr) + self.listeners.append(xListener) + def removeConnectionListener(self, xListener): + if dbg: + print("PyMailSMTPService removeConnectionListener", file=sys.stderr) + self.listeners.remove(xListener) + def getSupportedConnectionTypes(self): + if dbg: + print("PyMailSMTPService getSupportedConnectionTypes", file=sys.stderr) + return self.supportedtypes + def connect(self, xConnectionContext, xAuthenticator): + self.connectioncontext = xConnectionContext + if dbg: + print("PyMailSMTPService connect", file=sys.stderr) + server = xConnectionContext.getValueByName("ServerName").strip() + if dbg: + print("ServerName: " + server, file=sys.stderr) + port = int(xConnectionContext.getValueByName("Port")) + if dbg: + print("Port: " + str(port), file=sys.stderr) + tout = xConnectionContext.getValueByName("Timeout") + if dbg: + print(isinstance(tout,int), file=sys.stderr) + if not isinstance(tout,int): + tout = _GLOBAL_DEFAULT_TIMEOUT + if dbg: + print("Timeout: " + str(tout), file=sys.stderr) + connectiontype = xConnectionContext.getValueByName("ConnectionType") + if dbg: + print("ConnectionType: " + connectiontype, file=sys.stderr) + tlscontext = prepareTLSContext(self, self.ctx, connectiontype.upper() == 'SSL' or port == 465) + if port == 465: + self.server = smtplib.SMTP_SSL(server, port, timeout=tout, context=tlscontext) + else: + self.server = smtplib.SMTP(server, port,timeout=tout) + + if dbg: + self.server.set_debuglevel(1) + + if connectiontype.upper() == 'SSL' and port != 465: + # STRIPTLS: smtplib raises an exception if result is not 220 + self.server.starttls(context=tlscontext) + + user = xAuthenticator.getUserName() + password = xAuthenticator.getPassword() + if user != '': + if dbg: + print("Logging in, username of: " + user, file=sys.stderr) + self.server.login(user, password) + + for listener in self.listeners: + listener.connected(self.notify) + def disconnect(self): + if dbg: + print("PyMailSMTPService disconnect", file=sys.stderr) + if self.server: + self.server.quit() + self.server = None + for listener in self.listeners: + listener.disconnected(self.notify) + def isConnected(self): + if dbg: + print("PyMailSMTPService isConnected", file=sys.stderr) + return self.server != None + def getCurrentConnectionContext(self): + if dbg: + print("PyMailSMTPService getCurrentConnectionContext", file=sys.stderr) + return self.connectioncontext + def sendMailMessage(self, xMailMessage): + COMMASPACE = ', ' + + if dbg: + print("PyMailSMTPService sendMailMessage", file=sys.stderr) + recipients = xMailMessage.getRecipients() + sendermail = xMailMessage.SenderAddress + sendername = xMailMessage.SenderName + subject = xMailMessage.Subject + ccrecipients = xMailMessage.getCcRecipients() + bccrecipients = xMailMessage.getBccRecipients() + if dbg: + print("PyMailSMTPService subject: " + subject, file=sys.stderr) + print("PyMailSMTPService from: " + sendername, file=sys.stderr) + print("PyMailSMTPService from: " + sendermail, file=sys.stderr) + print("PyMailSMTPService send to: %s" % (recipients,), file=sys.stderr) + + attachments = xMailMessage.getAttachments() + + textmsg = Message() + + content = xMailMessage.Body + flavors = content.getTransferDataFlavors() + if dbg: + print("PyMailSMTPService flavors len: %d" % (len(flavors),), file=sys.stderr) + + #Use first flavor that's sane for an email body + for flavor in flavors: + if flavor.MimeType.find('text/html') != -1 or flavor.MimeType.find('text/plain') != -1: + if dbg: + print("PyMailSMTPService mimetype is: " + flavor.MimeType, file=sys.stderr) + textbody = content.getTransferData(flavor) + + if len(textbody): + mimeEncoding = re.sub("charset=.*", "charset=UTF-8", flavor.MimeType) + if mimeEncoding.find('charset=UTF-8') == -1: + mimeEncoding = mimeEncoding + "; charset=UTF-8" + textmsg['Content-Type'] = mimeEncoding + textmsg['MIME-Version'] = '1.0' + + try: + #it's a string, get it as utf-8 bytes + textbody = textbody.encode('utf-8') + except: + #it's a bytesequence, get raw bytes + textbody = textbody.value + textbody = textbody.decode('utf-8') + c = Charset('utf-8') + c.body_encoding = QP + textmsg.set_payload(textbody, c) + + break + + if (len(attachments)): + msg = MIMEMultipart() + msg.epilogue = '' + msg.attach(textmsg) + else: + msg = textmsg + + hdr = Header(sendername, 'utf-8') + hdr.append('<'+sendermail+'>','us-ascii') + msg['Subject'] = subject + msg['From'] = hdr + msg['To'] = COMMASPACE.join(recipients) + if len(ccrecipients): + msg['Cc'] = COMMASPACE.join(ccrecipients) + if xMailMessage.ReplyToAddress != '': + msg['Reply-To'] = xMailMessage.ReplyToAddress + + mailerstring = "LibreOffice via Caolan's mailmerge component" + try: + ctx = uno.getComponentContext() + aConfigProvider = ctx.ServiceManager.createInstance("com.sun.star.configuration.ConfigurationProvider") + prop = uno.createUnoStruct('com.sun.star.beans.PropertyValue') + prop.Name = "nodepath" + prop.Value = "/org.openoffice.Setup/Product" + aSettings = aConfigProvider.createInstanceWithArguments("com.sun.star.configuration.ConfigurationAccess", + (prop,)) + mailerstring = aSettings.getByName("ooName") + " " + \ + aSettings.getByName("ooSetupVersion") + " via Caolan's mailmerge component" + except: + pass + + msg['X-Mailer'] = mailerstring + msg['Date'] = formatdate(localtime=True) + + for attachment in attachments: + content = attachment.Data + flavors = content.getTransferDataFlavors() + flavor = flavors[0] + ctype = flavor.MimeType + maintype, subtype = ctype.split('/', 1) + msgattachment = MIMEBase(maintype, subtype) + data = content.getTransferData(flavor) + msgattachment.set_payload(data.value) + encode_base64(msgattachment) + fname = attachment.ReadableName + try: + msgattachment.add_header('Content-Disposition', 'attachment', \ + filename=fname) + except: + msgattachment.add_header('Content-Disposition', 'attachment', \ + filename=('utf-8','',fname)) + if dbg: + print(("PyMailSMTPService attachmentheader: ", str(msgattachment)), file=sys.stderr) + + msg.attach(msgattachment) + + uniquer = {} + for key in recipients: + uniquer[key] = True + if len(ccrecipients): + for key in ccrecipients: + uniquer[key] = True + if len(bccrecipients): + for key in bccrecipients: + uniquer[key] = True + truerecipients = uniquer.keys() + + if dbg: + print(("PyMailSMTPService recipients are: ", truerecipients), file=sys.stderr) + + self.server.sendmail(sendermail, truerecipients, msg.as_string()) + +class PyMailIMAPService(unohelper.Base, XMailService): + def __init__( self, ctx ): + self.ctx = ctx + self.listeners = [] + self.supportedtypes = ('Insecure', 'Ssl') + self.server = None + self.connectioncontext = None + self.notify = EventObject(self) + if dbg: + print("PyMailIMAPService init", file=sys.stderr) + def addConnectionListener(self, xListener): + if dbg: + print("PyMailIMAPService addConnectionListener", file=sys.stderr) + self.listeners.append(xListener) + def removeConnectionListener(self, xListener): + if dbg: + print("PyMailIMAPService removeConnectionListener", file=sys.stderr) + self.listeners.remove(xListener) + def getSupportedConnectionTypes(self): + if dbg: + print("PyMailIMAPService getSupportedConnectionTypes", file=sys.stderr) + return self.supportedtypes + def connect(self, xConnectionContext, xAuthenticator): + if dbg: + print("PyMailIMAPService connect", file=sys.stderr) + + self.connectioncontext = xConnectionContext + server = xConnectionContext.getValueByName("ServerName") + if dbg: + print(server, file=sys.stderr) + port = int(xConnectionContext.getValueByName("Port")) + if dbg: + print(port, file=sys.stderr) + connectiontype = xConnectionContext.getValueByName("ConnectionType") + if dbg: + print(connectiontype, file=sys.stderr) + tlscontext = prepareTLSContext(self, self.ctx, connectiontype.upper() == 'SSL') + print("BEFORE", file=sys.stderr) + if connectiontype.upper() == 'SSL': + self.server = imaplib.IMAP4_SSL(server, port, ssl_context=tlscontext) + else: + self.server = imaplib.IMAP4(server, port) + print("AFTER", file=sys.stderr) + + user = xAuthenticator.getUserName() + password = xAuthenticator.getPassword() + if user != '': + if dbg: + print("Logging in, username of: " + user, file=sys.stderr) + self.server.login(user, password) + + for listener in self.listeners: + listener.connected(self.notify) + def disconnect(self): + if dbg: + print("PyMailIMAPService disconnect", file=sys.stderr) + if self.server: + self.server.logout() + self.server = None + for listener in self.listeners: + listener.disconnected(self.notify) + def isConnected(self): + if dbg: + print("PyMailIMAPService isConnected", file=sys.stderr) + return self.server != None + def getCurrentConnectionContext(self): + if dbg: + print("PyMailIMAPService getCurrentConnectionContext", file=sys.stderr) + return self.connectioncontext + +class PyMailPOP3Service(unohelper.Base, XMailService): + def __init__( self, ctx ): + self.ctx = ctx + self.listeners = [] + self.supportedtypes = ('Insecure', 'Ssl') + self.server = None + self.connectioncontext = None + self.notify = EventObject(self) + if dbg: + print("PyMailPOP3Service init", file=sys.stderr) + def addConnectionListener(self, xListener): + if dbg: + print("PyMailPOP3Service addConnectionListener", file=sys.stderr) + self.listeners.append(xListener) + def removeConnectionListener(self, xListener): + if dbg: + print("PyMailPOP3Service removeConnectionListener", file=sys.stderr) + self.listeners.remove(xListener) + def getSupportedConnectionTypes(self): + if dbg: + print("PyMailPOP3Service getSupportedConnectionTypes", file=sys.stderr) + return self.supportedtypes + def connect(self, xConnectionContext, xAuthenticator): + if dbg: + print("PyMailPOP3Service connect", file=sys.stderr) + + self.connectioncontext = xConnectionContext + server = xConnectionContext.getValueByName("ServerName") + if dbg: + print(server, file=sys.stderr) + port = int(xConnectionContext.getValueByName("Port")) + if dbg: + print(port, file=sys.stderr) + connectiontype = xConnectionContext.getValueByName("ConnectionType") + if dbg: + print(connectiontype, file=sys.stderr) + tlscontext = prepareTLSContext(self, self.ctx, connectiontype.upper() == 'SSL') + print("BEFORE", file=sys.stderr) + if connectiontype.upper() == 'SSL': + self.server = poplib.POP3_SSL(server, port, context=tlscontext) + else: + tout = xConnectionContext.getValueByName("Timeout") + if dbg: + print(isinstance(tout,int), file=sys.stderr) + if not isinstance(tout,int): + tout = _GLOBAL_DEFAULT_TIMEOUT + if dbg: + print("Timeout: " + str(tout), file=sys.stderr) + self.server = poplib.POP3(server, port, timeout=tout) + print("AFTER", file=sys.stderr) + + user = xAuthenticator.getUserName() + password = xAuthenticator.getPassword() + if dbg: + print("Logging in, username of: " + user, file=sys.stderr) + self.server.user(user) + self.server.pass_(password) + + for listener in self.listeners: + listener.connected(self.notify) + def disconnect(self): + if dbg: + print("PyMailPOP3Service disconnect", file=sys.stderr) + if self.server: + self.server.quit() + self.server = None + for listener in self.listeners: + listener.disconnected(self.notify) + def isConnected(self): + if dbg: + print("PyMailPOP3Service isConnected", file=sys.stderr) + return self.server != None + def getCurrentConnectionContext(self): + if dbg: + print("PyMailPOP3Service getCurrentConnectionContext", file=sys.stderr) + return self.connectioncontext + +class PyMailServiceProvider(unohelper.Base, XMailServiceProvider, XServiceInfo): + def __init__( self, ctx ): + if dbg: + print("PyMailServiceProvider init", file=sys.stderr) + self.ctx = ctx + def create(self, aType): + if dbg: + print("PyMailServiceProvider create with", aType, file=sys.stderr) + if aType == SMTP: + return PyMailSMTPService(self.ctx); + elif aType == POP3: + return PyMailPOP3Service(self.ctx); + elif aType == IMAP: + return PyMailIMAPService(self.ctx); + else: + print("PyMailServiceProvider, unknown TYPE " + aType, file=sys.stderr) + + def getImplementationName(self): + return g_providerImplName + + def supportsService(self, ServiceName): + return g_ImplementationHelper.supportsService(g_providerImplName, ServiceName) + + def getSupportedServiceNames(self): + return g_ImplementationHelper.getSupportedServiceNames(g_providerImplName) + +class PyMailMessage(unohelper.Base, XMailMessage): + def __init__( self, ctx, sTo='', sFrom='', Subject='', Body=None, aMailAttachment=None ): + if dbg: + print("PyMailMessage init", file=sys.stderr) + self.ctx = ctx + + self.recipients = [sTo] + self.ccrecipients = [] + self.bccrecipients = [] + self.aMailAttachments = [] + if aMailAttachment != None: + self.aMailAttachments.append(aMailAttachment) + + self.SenderName, self.SenderAddress = parseaddr(sFrom) + self.ReplyToAddress = sFrom + self.Subject = Subject + self.Body = Body + if dbg: + print("post PyMailMessage init", file=sys.stderr) + def addRecipient( self, recipient ): + if dbg: + print("PyMailMessage.addRecipient: " + recipient, file=sys.stderr) + self.recipients.append(recipient) + def addCcRecipient( self, ccrecipient ): + if dbg: + print("PyMailMessage.addCcRecipient: " + ccrecipient, file=sys.stderr) + self.ccrecipients.append(ccrecipient) + def addBccRecipient( self, bccrecipient ): + if dbg: + print("PyMailMessage.addBccRecipient: " + bccrecipient, file=sys.stderr) + self.bccrecipients.append(bccrecipient) + def getRecipients( self ): + if dbg: + print("PyMailMessage.getRecipients: " + str(self.recipients), file=sys.stderr) + return tuple(self.recipients) + def getCcRecipients( self ): + if dbg: + print("PyMailMessage.getCcRecipients: " + str(self.ccrecipients), file=sys.stderr) + return tuple(self.ccrecipients) + def getBccRecipients( self ): + if dbg: + print("PyMailMessage.getBccRecipients: " + str(self.bccrecipients), file=sys.stderr) + return tuple(self.bccrecipients) + def addAttachment( self, aMailAttachment ): + if dbg: + print("PyMailMessage.addAttachment", file=sys.stderr) + self.aMailAttachments.append(aMailAttachment) + def getAttachments( self ): + if dbg: + print("PyMailMessage.getAttachments", file=sys.stderr) + return tuple(self.aMailAttachments) + + def getImplementationName(self): + return g_messageImplName + + def supportsService(self, ServiceName): + return g_ImplementationHelper.supportsService(g_messageImplName, ServiceName) + + def getSupportedServiceNames(self): + return g_ImplementationHelper.getSupportedServiceNames(g_messageImplName) + +g_ImplementationHelper.addImplementation( \ + PyMailServiceProvider, g_providerImplName, + ("com.sun.star.mail.MailServiceProvider",),) +g_ImplementationHelper.addImplementation( \ + PyMailMessage, g_messageImplName, + ("com.sun.star.mail.MailMessage",),) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/scripting/source/pyprov/msgbox.py b/scripting/source/pyprov/msgbox.py new file mode 100644 index 0000000000..f9c93df174 --- /dev/null +++ b/scripting/source/pyprov/msgbox.py @@ -0,0 +1,241 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +# prepare Python environment - Add the path of this class +from os import path +from sys import modules +from sys import path as syspath + +# pyUNO program itself +import uno, unohelper + +# UNO GUI toolkit +from com.sun.star.awt.WindowClass import TOP, SIMPLE +from com.sun.star.awt.PushButtonType import STANDARD as standard +from com.sun.star.awt.PushButtonType import OK as ok +from com.sun.star.awt.PushButtonType import CANCEL as cancel +from com.sun.star.awt.PushButtonType import HELP as help +from com.sun.star.awt.TextAlign import CENTER as center +from com.sun.star.awt.TextAlign import LEFT as left +from com.sun.star.awt.TextAlign import RIGHT as right + +# used UNO listeners +from com.sun.star.awt import XActionListener + +class MsgBox(unohelper.Base): + """Inspect UNO object, link to sdk and recursive calls""" + + def __init__(self, aContext): + """acontext : a Valid UNO context + """ + + self.VERSION = '0.1' + self.ctx = aContext + self.smgr = aContext.ServiceManager + # UI Dialog object + self.dialog=None + # List of opened Listeners + self.lst_listeners={} + #UI parameters + self.ButtonSize = 50 + self.boxSize = 200 + self.lineHeight = 10 + self.fromBroxSize = False + self.numberOfLines = -1 + + self.Buttons = [] + self.Response = '' + + return + + ##################################################### + # GUI definition # + ##################################################### + def _createBox(self): + """Create the Box""" + + # computes parameters of the message dialog + if self.numberOfLines == -1: + #calculate + numberOfLines = len(self.message.split(chr(10))) + else: + numberOfLines = self.numberOfLines + + numberOfButtons = len(self.Buttons) + self.ButtonSpace = self.ButtonSize/2 + if self.fromBroxSize: + # button size is calculated from boxsize + size = (2 * self.boxSize) / (3 * numberOfButtons + 1) + self.ButtonSize = size + self.ButtonSpace = self.ButtonSize/2 + else: + # boxsize is calculated from buttonsize + self.boxSize = numberOfButtons * (self.ButtonSize + + self.ButtonSpace) + self.ButtonSpace + + # create the dialog model and set the properties + dialog_model = self.smgr.createInstanceWithContext( + 'com.sun.star.awt.UnoControlDialogModel', + self.ctx) + dialog_model.PositionX = 50 + dialog_model.Step = 1 + dialog_model.TabIndex = 7 + dialog_model.Width = self.boxSize#numberOfButtons * (self.ButtonSize + + # self.ButtonSpace) + 25 + dialog_model.Height = 10 + self.lineHeight * numberOfLines + 10 + 12 + 10 + dialog_model.PositionY = 63 + dialog_model.Sizeable = True + dialog_model.Closeable = False + + dialog = self.smgr.createInstanceWithContext( + 'com.sun.star.awt.UnoControlDialog', self.ctx) + + # label Label0 + label = dialog_model.createInstance( + 'com.sun.star.awt.UnoControlFixedTextModel') + label.PositionX = 10 + label.TabIndex = 9 + label.Width = dialog_model.Width - label.PositionX + label.Height = self.lineHeight* numberOfLines + label.PositionY = 10 + label.Align = left + label.MultiLine = True + label.Label = self.message + dialog_model.insertByName('Label0', label) + + nb = 0 + for buttonName in self.Buttons: + nb +=1 + button = dialog_model.createInstance( + 'com.sun.star.awt.UnoControlButtonModel') + button.PositionX = nb * self.ButtonSpace + (nb-1)* self.ButtonSize + button.TabIndex = 8 + button.Height = 12 + button.Width = self.ButtonSize + button.PositionY = 10 + label.Height + 10 + button.PushButtonType = standard + if nb == 1: + button.DefaultButton = True + else: + button.DefaultButton = False + button.Label = buttonName + dialog_model.insertByName('Btn' + str(nb), button ) + + if not dialog.getModel(): + dialog.setModel(dialog_model) + + # UNO toolkit definition + toolkit = self.smgr.createInstanceWithContext('com.sun.star.awt.Toolkit', self.ctx) + a_rect = uno.createUnoStruct( 'com.sun.star.awt.Rectangle' ) + a_rect.X = 50 + dialog.setTitle ( self.title ) + a_rect.Width = 270 + a_rect.Height = 261 + a_rect.Y = 63 + win_descriptor = uno.createUnoStruct('com.sun.star.awt.WindowDescriptor') + win_descriptor.Type = TOP + win_descriptor.ParentIndex = -1 + win_descriptor.Bounds = a_rect + peer = toolkit.createWindow( win_descriptor ) + dialog.createPeer( toolkit, peer ) + + return dialog + + def _addListeners(self): + """Add listeners to dialog""" + nb = 0 + for buttonName in self.Buttons: + nb +=1 + a_control = self.dialog.getControl('Btn'+str(nb)) + the_listener = ButtonListener(self) + a_control.addActionListener(the_listener) + self.lst_listeners['Btn'+str(nb)] = the_listener + return + + def _removeListeners(self): + """ remove listeners on exiting""" + nb = 0 + for buttonName in self.Buttons: + nb +=1 + a_control = self.dialog.getControl('Btn'+str(nb)) + a_control.removeActionListener(self.lst_listeners['Btn'+str(nb)]) + return + + def show(self, message, decoration, title): + self.message = message + self.decoration = decoration + self.title = title + # Create GUI + self.dialog = self._createBox() + self._addListeners() + #execute the dialog --> blocking call + self.dialog.execute() + #end --> release listeners and dispose dialog + self._removeListeners() + self.dialog.dispose() + return self.Response + + def addButton(self, caption): + self.Buttons.append(caption) + return + + def renderFromBoxSize(self, size = 150): + self.boxSize = size + self.fromBroxSize = True + return + + def renderFromButtonSize(self, size = 50): + self.ButtonSize = size + self.fromBroxSize = False + return + +class ButtonListener(unohelper.Base, XActionListener): + """Stops the MessageBox, sets the button label as returned value""" + def __init__(self, caller): + self.caller = caller + + def disposing(self, eventObject): + pass + + def actionPerformed(self, actionEvent): + button = actionEvent.Source + self.caller.Response = button.Model.Label + self.caller.dialog.endExecute() + return + +### TEST +if __name__ == '__main__': + # get the uno component context from the PyUNO runtime + localContext = uno.getComponentContext() + + # create the UnoUrlResolver + resolver = localContext.ServiceManager.createInstanceWithContext( + "com.sun.star.bridge.UnoUrlResolver", localContext ) + + # connect to the running office + # LibO has to be launched in listen mode as + # ./soffice "--accept=socket,host=localhost,port=2002;urp;" + ctx = resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" ) + myBox = MsgBox(ctx) + myBox.addButton("Yes") + myBox.addButton("No") + myBox.addButton("May be") + myBox.renderFromBoxSize(150) + myBox.numberOflines = 2 + + print(myBox.show("A very long message A very long message A very long message A very long message A very long message A very long message A very long message A very long message A very long message A very long message " + chr(10)+chr(10)+"Do you agree ?",0,"Dialog title")) + + myBox = MsgBox(ctx) + myBox.addButton("oK") + myBox.renderFromButtonSize() + myBox.numberOflines = 2 + + print(myBox.show("A small message",0,"Dialog title")) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/scripting/source/pyprov/pythonscript.py b/scripting/source/pyprov/pythonscript.py new file mode 100644 index 0000000000..00d96d9b0d --- /dev/null +++ b/scripting/source/pyprov/pythonscript.py @@ -0,0 +1,1146 @@ +# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# +# XScript implementation for python +import uno +import unohelper +import sys +import os +import types +import time +import ast +import platform +from com.sun.star.uri.RelativeUriExcessParentSegments import RETAIN +from urllib.parse import unquote + +class LogLevel: + NONE = 0 # production level + ERROR = 1 # for script developers + DEBUG = 2 # for script framework developers + +PYSCRIPT_LOG_ENV = "PYSCRIPT_LOG_LEVEL" +PYSCRIPT_LOG_STDOUT_ENV = "PYSCRIPT_LOG_STDOUT" + +# Configuration ---------------------------------------------------- +LogLevel.use = LogLevel.NONE +if os.environ.get(PYSCRIPT_LOG_ENV) == "ERROR": + LogLevel.use = LogLevel.ERROR +elif os.environ.get(PYSCRIPT_LOG_ENV) == "DEBUG": + LogLevel.use = LogLevel.DEBUG + +# True, writes to stdout (difficult on windows) +# False, writes to user/Scripts/python/log.txt +LOG_STDOUT = os.environ.get(PYSCRIPT_LOG_STDOUT_ENV, "1") != "0" + +ENABLE_EDIT_DIALOG=False # offers a minimal editor for editing. +#------------------------------------------------------------------- + +def encfile(uni): + return uni.encode( sys.getfilesystemencoding()) + +def lastException2String(): + (excType,excInstance,excTraceback) = sys.exc_info() + ret = str(excType) + ": "+str(excInstance) + "\n" + \ + uno._uno_extract_printable_stacktrace( excTraceback ) + return ret + +def logLevel2String( level ): + ret = " NONE" + if level == LogLevel.ERROR: + ret = "ERROR" + elif level >= LogLevel.DEBUG: + ret = "DEBUG" + return ret + +def getLogTarget(): + ret = sys.stdout + if not LOG_STDOUT: + try: + pathSubst = uno.getComponentContext().ServiceManager.createInstance( + "com.sun.star.util.PathSubstitution" ) + userInstallation = pathSubst.getSubstituteVariableValue( "user" ) + if len( userInstallation ) > 0: + systemPath = uno.fileUrlToSystemPath( userInstallation + "/Scripts/python/log.txt" ) + ret = open( systemPath , "a" ) + except: + print("Exception during creation of pythonscript logfile: "+ lastException2String() + "\n, delegating log to stdout\n") + return ret + +class Logger(LogLevel): + def __init__(self , target ): + self.target = target + + def isDebugLevel( self ): + return self.use >= self.DEBUG + + def debug( self, msg ): + if self.isDebugLevel(): + self.log( self.DEBUG, msg ) + + def isErrorLevel( self ): + return self.use >= self.ERROR + + def error( self, msg ): + if self.isErrorLevel(): + self.log( self.ERROR, msg ) + + def log( self, level, msg ): + if self.use >= level: + try: + self.target.write( + time.asctime() + + " [" + + logLevel2String( level ) + + "] " + + msg + + "\n" ) + self.target.flush() + except: + print("Error during writing to stdout: " +lastException2String() + "\n") + +log = Logger( getLogTarget() ) + +log.debug( "pythonscript loading" ) + +#from com.sun.star.lang import typeOfXServiceInfo, typeOfXTypeProvider +from com.sun.star.uno import RuntimeException +from com.sun.star.lang import IllegalArgumentException +from com.sun.star.container import NoSuchElementException +from com.sun.star.lang import XServiceInfo +from com.sun.star.io import IOException +from com.sun.star.ucb import CommandAbortedException, XCommandEnvironment, XProgressHandler, Command +from com.sun.star.task import XInteractionHandler +from com.sun.star.beans import XPropertySet, Property +from com.sun.star.container import XNameContainer +from com.sun.star.xml.sax import XDocumentHandler, InputSource +from com.sun.star.uno import Exception as UnoException +from com.sun.star.script import XInvocation +from com.sun.star.awt import XActionListener + +from com.sun.star.script.provider import XScriptProvider, XScript, XScriptContext, ScriptFrameworkErrorException +from com.sun.star.script.browse import XBrowseNode +from com.sun.star.script.browse.BrowseNodeTypes import SCRIPT, CONTAINER, ROOT +from com.sun.star.util import XModifyListener + +LANGUAGENAME = "Python" +GLOBAL_SCRIPTCONTEXT_NAME = "XSCRIPTCONTEXT" +CALLABLE_CONTAINER_NAME = "g_exportedScripts" + +# pythonloader looks for a static g_ImplementationHelper variable +g_ImplementationHelper = unohelper.ImplementationHelper() +g_implName = "org.libreoffice.pyuno.LanguageScriptProviderFor"+LANGUAGENAME + + + +BLOCK_SIZE = 65536 +def readTextFromStream( inputStream ): + # read the file + code = uno.ByteSequence( b"" ) + while True: + read,out = inputStream.readBytes( None , BLOCK_SIZE ) + code = code + out + if read < BLOCK_SIZE: + break + return code.value + +def toIniName( str ): + if platform.system() == "Windows": + return str + ".ini" + else: + return str + "rc" + + +""" definition: storageURI is the system dependent, absolute file url, where the script is stored on disk + scriptURI is the system independent uri +""" +class MyUriHelper: + + def __init__( self, ctx, location ): + self.ctx = ctx + self.s_UriMap = \ + { "share" : "vnd.sun.star.expand:$BRAND_BASE_DIR/$BRAND_SHARE_SUBDIR/Scripts/python" , \ + "share:uno_packages" : "vnd.sun.star.expand:$UNO_SHARED_PACKAGES_CACHE/uno_packages", \ + "user" : "vnd.sun.star.expand:${$BRAND_INI_DIR/" + toIniName( "bootstrap") + "::UserInstallation}/user/Scripts/python" , \ + "user:uno_packages" : "vnd.sun.star.expand:$UNO_USER_PACKAGES_CACHE/uno_packages" } + self.m_uriRefFac = ctx.ServiceManager.createInstanceWithContext("com.sun.star.uri.UriReferenceFactory",ctx) + if location.startswith( "vnd.sun.star.tdoc" ): + self.m_baseUri = location + "/Scripts/python" + self.m_scriptUriLocation = "document" + else: + self.m_baseUri = expandUri( self.s_UriMap[location] ) + self.m_scriptUriLocation = location + log.debug( "initialized urihelper with baseUri="+self.m_baseUri + ",m_scriptUriLocation="+self.m_scriptUriLocation ) + + def getRootStorageURI( self ): + return self.m_baseUri + + def getStorageURI( self, scriptURI ): + return self.scriptURI2StorageUri(scriptURI) + + def getScriptURI( self, storageURI ): + return self.storageURI2ScriptUri(storageURI) + + def storageURI2ScriptUri( self, storageURI ): + if not storageURI.startswith( self.m_baseUri ): + message = "pythonscript: storage uri '" + storageURI + "' not in base uri '" + self.m_baseUri + "'" + log.debug( message ) + raise RuntimeException( message, self.ctx ) + + ret = "vnd.sun.star.script:" + \ + storageURI[len(self.m_baseUri)+1:].replace("/","|") + \ + "?language=" + LANGUAGENAME + "&location=" + self.m_scriptUriLocation + log.debug( "converting storageURI="+storageURI + " to scriptURI=" + ret ) + return ret + + def scriptURI2StorageUri( self, scriptURI ): + try: + # base path to the python script location + sBaseUri = self.m_baseUri + "/" + xBaseUri = self.m_uriRefFac.parse(sBaseUri) + + # path to the .py file + "$functionname, arguments, etc + xStorageUri = self.m_uriRefFac.parse(scriptURI) + # getName will apply url-decoding to the name, so encode back + sStorageUri = xStorageUri.getName().replace("%", "%25") + sStorageUri = sStorageUri.replace( "|", "/" ) + + # path to the .py file, relative to the base + funcNameStart = sStorageUri.find("$") + if funcNameStart != -1: + sFileUri = sStorageUri[0:funcNameStart] + sFuncName = sStorageUri[funcNameStart+1:] + else: + sFileUri = sStorageUri + + xFileUri = self.m_uriRefFac.parse(sFileUri) + if not xFileUri: + message = "pythonscript: invalid relative uri '" + sFileUri+ "'" + log.debug( message ) + raise RuntimeException( message, self.ctx ) + + if not xFileUri.hasRelativePath(): + message = "pythonscript: an absolute uri is invalid '" + sFileUri+ "'" + log.debug( message ) + raise RuntimeException( message, self.ctx ) + + # absolute path to the .py file + xAbsScriptUri = self.m_uriRefFac.makeAbsolute(xBaseUri, xFileUri, True, RETAIN) + sAbsScriptUri = xAbsScriptUri.getUriReference() + + # ensure py file is under the base path + if not sAbsScriptUri.startswith(sBaseUri): + message = "pythonscript: storage uri '" + sAbsScriptUri + "' not in base uri '" + self.m_baseUri + "'" + log.debug( message ) + raise RuntimeException( message, self.ctx ) + + ret = sAbsScriptUri + if funcNameStart != -1: + ret = ret + "$" + sFuncName + log.debug( "converting scriptURI="+scriptURI + " to storageURI=" + ret ) + return ret + except UnoException as e: + log.error( "error during converting scriptURI="+scriptURI + ": " + e.Message) + raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + e.Message, self.ctx ) + except Exception as e: + log.error( "error during converting scriptURI="+scriptURI + ": " + str(e)) + raise RuntimeException( "pythonscript:scriptURI2StorageUri: " + str(e), self.ctx ) + + +class ModuleEntry: + def __init__( self, lastRead, module ): + self.lastRead = lastRead + self.module = module + +def hasChanged( oldDate, newDate ): + return newDate.Year > oldDate.Year or \ + newDate.Month > oldDate.Month or \ + newDate.Day > oldDate.Day or \ + newDate.Hours > oldDate.Hours or \ + newDate.Minutes > oldDate.Minutes or \ + newDate.Seconds > oldDate.Seconds or \ + newDate.NanoSeconds > oldDate.NanoSeconds + +def ensureSourceState( code ): + if code.endswith(b"\n"): + code = code + b"\n" + code = code.replace(b"\r", b"") + return code + + +def checkForPythonPathBesideScript( url ): + if url.startswith( "file:" ): + path = unohelper.fileUrlToSystemPath( url+"/pythonpath.zip" ); + log.log( LogLevel.DEBUG, "checking for existence of " + path ) + if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path: + log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" ) + sys.path.append( path ) + + path = unohelper.fileUrlToSystemPath( url+"/pythonpath" ); + log.log( LogLevel.DEBUG, "checking for existence of " + path ) + if 1 == os.access( encfile(path), os.F_OK) and not path in sys.path: + log.log( LogLevel.DEBUG, "adding " + path + " to sys.path" ) + sys.path.append( path ) + + +class ScriptContext(unohelper.Base): + def __init__( self, ctx, doc, inv ): + self.ctx = ctx + self.doc = doc + self.inv = inv + + # XScriptContext + def getDocument(self): + if self.doc: + return self.doc + return self.getDesktop().getCurrentComponent() + + def getDesktop(self): + return self.ctx.ServiceManager.createInstanceWithContext( + "com.sun.star.frame.Desktop", self.ctx ) + + def getComponentContext(self): + return self.ctx + + def getInvocationContext(self): + return self.inv + +#---------------------------------- +# Global Module Administration +# does not fit together with script +# engine lifetime management +#---------------------------------- +#g_scriptContext = ScriptContext( uno.getComponentContext(), None ) +#g_modules = {} +#def getModuleByUrl( url, sfa ): +# entry = g_modules.get(url) +# load = True +# lastRead = sfa.getDateTimeModified( url ) +# if entry: +# if hasChanged( entry.lastRead, lastRead ): +# log.debug("file " + url + " has changed, reloading") +# else: +# load = False +# +# if load: +# log.debug( "opening >" + url + "<" ) +# +# code = readTextFromStream( sfa.openFileRead( url ) ) + + # execute the module +# entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") ) +# entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = g_scriptContext +# entry.module.__file__ = url +# exec code in entry.module.__dict__ +# g_modules[ url ] = entry +# log.debug( "mapped " + url + " to " + str( entry.module ) ) +# return entry.module + +class ProviderContext: + def __init__( self, storageType, sfa, uriHelper, scriptContext ): + self.storageType = storageType + self.sfa = sfa + self.uriHelper = uriHelper + self.scriptContext = scriptContext + self.modules = {} + self.rootUrl = None + self.mapPackageName2Path = None + + def getTransientPartFromUrl( self, url ): + rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1) + return rest[0:rest.find("/")] + + def getPackageNameFromUrl( self, url ): + rest = url.replace( self.rootUrl , "",1 ).replace( "/","",1) + start = rest.find("/") +1 + return rest[start:rest.find("/",start)] + + + def removePackageByUrl( self, url ): + items = self.mapPackageName2Path.items() + for i in items: + if url in i[1].paths: + self.mapPackageName2Path.pop(i[0]) + break + + def addPackageByUrl( self, url ): + packageName = self.getPackageNameFromUrl( url ) + transientPart = self.getTransientPartFromUrl( url ) + log.debug( "addPackageByUrl : " + packageName + ", " + transientPart + "("+url+")" + ", rootUrl="+self.rootUrl ) + if packageName in self.mapPackageName2Path: + package = self.mapPackageName2Path[ packageName ] + package.paths = package.paths + (url, ) + else: + package = Package( (url,), transientPart) + self.mapPackageName2Path[ packageName ] = package + + def isUrlInPackage( self, url ): + values = self.mapPackageName2Path.values() + for i in values: +# print ("checking " + url + " in " + str(i.paths)) + if url in i.paths: + return True +# print ("false") + return False + + def setPackageAttributes( self, mapPackageName2Path, rootUrl ): + self.mapPackageName2Path = mapPackageName2Path + self.rootUrl = rootUrl + + def getPersistentUrlFromStorageUrl( self, url ): + # package name is the second directory + ret = url + if self.rootUrl: + pos = len( self.rootUrl) +1 + ret = url[0:pos]+url[url.find("/",pos)+1:len(url)] + log.debug( "getPersistentUrlFromStorageUrl " + url + " -> "+ ret) + return ret + + def getStorageUrlFromPersistentUrl( self, url): + ret = url + if self.rootUrl: + pos = len(self.rootUrl)+1 + packageName = url[pos:url.find("/",pos+1)] + package = self.mapPackageName2Path[ packageName ] + ret = url[0:pos]+ package.transientPathElement + "/" + url[pos:len(url)] + log.debug( "getStorageUrlFromPersistentUrl " + url + " -> "+ ret) + return ret + + def getFuncsByUrl( self, url ): + src = readTextFromStream( self.sfa.openFileRead( url ) ) + checkForPythonPathBesideScript( url[0:url.rfind('/')] ) + src = ensureSourceState( src ) + + try: + code = ast.parse( src ) + except: + log.isDebugLevel() and log.debug( "pythonscript: getFuncsByUrl: exception while parsing: " + lastException2String()) + raise + + allFuncs = [] + + if code is None: + return allFuncs + + g_exportedScripts = [] + for node in ast.iter_child_nodes(code): + if isinstance(node, ast.FunctionDef): + allFuncs.append(node.name) + elif isinstance(node, ast.Assign): + for target in node.targets: + try: + identifier = target.id + except AttributeError: + identifier = "" + pass + if identifier == "g_exportedScripts": + for value in node.value.elts: + g_exportedScripts.append(value.id) + return g_exportedScripts + +# Python 2 only +# for node in code.node.nodes: +# if node.__class__.__name__ == 'Function': +# allFuncs.append(node.name) +# elif node.__class__.__name__ == 'Assign': +# for assignee in node.nodes: +# if assignee.name == 'g_exportedScripts': +# for item in node.expr.nodes: +# if item.__class__.__name__ == 'Name': +# g_exportedScripts.append(item.name) +# return g_exportedScripts + + return allFuncs + + def getModuleByUrl( self, url ): + entry = self.modules.get(url) + load = True + lastRead = self.sfa.getDateTimeModified( url ) + if entry: + if hasChanged( entry.lastRead, lastRead ): + log.debug( "file " + url + " has changed, reloading" ) + else: + load = False + + if load: + log.debug( "opening >" + url + "<" ) + + src = readTextFromStream( self.sfa.openFileRead( url ) ) + checkForPythonPathBesideScript( url[0:url.rfind('/')] ) + src = ensureSourceState( src ) + + # execute the module + entry = ModuleEntry( lastRead, types.ModuleType("ooo_script_framework") ) + entry.module.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.scriptContext + + code = None + if url.startswith( "file:" ): + code = compile( src, encfile(uno.fileUrlToSystemPath( url ) ), "exec" ) + else: + code = compile( src, url, "exec" ) + exec(code, entry.module.__dict__) + entry.module.__file__ = url + self.modules[ url ] = entry + log.debug( "mapped " + url + " to " + str( entry.module ) ) + return entry.module + +#-------------------------------------------------- +def isScript( candidate ): + ret = False + if isinstance( candidate, type(isScript) ): + ret = True + return ret + +#------------------------------------------------------- +class ScriptBrowseNode( unohelper.Base, XBrowseNode , XPropertySet, XInvocation, XActionListener ): + def __init__( self, provCtx, uri, fileName, funcName ): + self.fileName = fileName + self.funcName = funcName + self.provCtx = provCtx + self.uri = uri + + def getName( self ): + return self.funcName + + def getChildNodes(self): + return () + + def hasChildNodes(self): + return False + + def getType( self): + return SCRIPT + + def getPropertyValue( self, name ): + ret = None + try: + if name == "URI": + ret = self.provCtx.uriHelper.getScriptURI( + self.provCtx.getPersistentUrlFromStorageUrl( self.uri + "$" + self.funcName ) ) + elif name == "Editable" and ENABLE_EDIT_DIALOG: + ret = not self.provCtx.sfa.isReadOnly( self.uri ) + + log.debug( "ScriptBrowseNode.getPropertyValue called for " + name + ", returning " + str(ret) ) + except: + log.error( "ScriptBrowseNode.getPropertyValue error " + lastException2String()) + raise + + return ret + def setPropertyValue( self, name, value ): + log.debug( "ScriptBrowseNode.setPropertyValue called " + name + "=" +str(value ) ) + def getPropertySetInfo( self ): + log.debug( "ScriptBrowseNode.getPropertySetInfo called " ) + return None + + def getIntrospection( self ): + return None + + def invoke( self, name, params, outparamindex, outparams ): + if name == "Editable": + servicename = "com.sun.star.awt.DialogProvider" + ctx = self.provCtx.scriptContext.getComponentContext() + dlgprov = ctx.ServiceManager.createInstanceWithContext( + servicename, ctx ) + + self.editor = dlgprov.createDialog( + "vnd.sun.star.script:" + + "ScriptBindingLibrary.MacroEditor?location=application") + + code = readTextFromStream(self.provCtx.sfa.openFileRead(self.uri)) + code = ensureSourceState( code ) + self.editor.getControl("EditorTextField").setText(code) + + self.editor.getControl("RunButton").setActionCommand("Run") + self.editor.getControl("RunButton").addActionListener(self) + self.editor.getControl("SaveButton").setActionCommand("Save") + self.editor.getControl("SaveButton").addActionListener(self) + + self.editor.execute() + + return None + + def actionPerformed( self, event ): + try: + if event.ActionCommand == "Run": + code = self.editor.getControl("EditorTextField").getText() + code = ensureSourceState( code ) + mod = types.ModuleType("ooo_script_framework") + mod.__dict__[GLOBAL_SCRIPTCONTEXT_NAME] = self.provCtx.scriptContext + exec(code, mod.__dict__) + values = mod.__dict__.get( CALLABLE_CONTAINER_NAME , None ) + if not values: + values = mod.__dict__.values() + + for i in values: + if isScript( i ): + i() + break + + elif event.ActionCommand == "Save": + toWrite = uno.ByteSequence( + self.editor.getControl("EditorTextField").getText().encode( + sys.getdefaultencoding()) ) + copyUrl = self.uri + ".orig" + self.provCtx.sfa.move( self.uri, copyUrl ) + out = self.provCtx.sfa.openFileWrite( self.uri ) + out.writeBytes( toWrite ) + out.close() + self.provCtx.sfa.kill( copyUrl ) +# log.debug("Save is not implemented yet") +# text = self.editor.getControl("EditorTextField").getText() +# log.debug("Would save: " + text) + except: + # TODO: add an error box here! + log.error( lastException2String() ) + + + def setValue( self, name, value ): + return None + + def getValue( self, name ): + return None + + def hasMethod( self, name ): + return False + + def hasProperty( self, name ): + return False + + +#------------------------------------------------------- +class FileBrowseNode( unohelper.Base, XBrowseNode ): + def __init__( self, provCtx, uri , name ): + self.provCtx = provCtx + self.uri = uri + self.name = name + self.funcnames = None + + def getName( self ): + return self.name + + def getChildNodes(self): + ret = () + try: + self.funcnames = self.provCtx.getFuncsByUrl( self.uri ) + + scriptNodeList = [] + for i in self.funcnames: + scriptNodeList.append( + ScriptBrowseNode( + self.provCtx, self.uri, self.name, i )) + ret = tuple( scriptNodeList ) + log.debug( "returning " +str(len(ret)) + " ScriptChildNodes on " + self.uri ) + except: + text = lastException2String() + log.error( "Error while evaluating " + self.uri + ":" + text ) + raise + return ret + + def hasChildNodes(self): + try: + return len(self.getChildNodes()) > 0 + except: + return False + + def getType( self): + return CONTAINER + + + +class DirBrowseNode( unohelper.Base, XBrowseNode ): + def __init__( self, provCtx, name, rootUrl ): + self.provCtx = provCtx + self.name = name + self.rootUrl = rootUrl + + def getName( self ): + return self.name + + def getChildNodes( self ): + try: + log.debug( "DirBrowseNode.getChildNodes called for " + self.rootUrl ) + contents = self.provCtx.sfa.getFolderContents( self.rootUrl, True ) + browseNodeList = [] + for i in contents: + if i.endswith( ".py" ): + log.debug( "adding filenode " + i ) + browseNodeList.append( + FileBrowseNode( self.provCtx, i, i[i.rfind("/")+1:len(i)-3] ) ) + elif self.provCtx.sfa.isFolder( i ) and not i.endswith("/pythonpath"): + log.debug( "adding DirBrowseNode " + i ) + browseNodeList.append( DirBrowseNode( self.provCtx, i[i.rfind("/")+1:len(i)],i)) + return tuple( browseNodeList ) + except Exception as e: + text = lastException2String() + log.error( "DirBrowseNode error: " + str(e) + " while evaluating " + self.rootUrl) + log.error( text) + return () + + def hasChildNodes( self ): + return True + + def getType( self ): + return CONTAINER + + def getScript( self, uri ): + log.debug( "DirBrowseNode getScript " + uri + " invoked" ) + raise IllegalArgumentException( "DirBrowseNode couldn't instantiate script " + uri , self , 0 ) + + +class ManifestHandler( XDocumentHandler, unohelper.Base ): + def __init__( self, rootUrl ): + self.rootUrl = rootUrl + + def startDocument( self ): + self.urlList = [] + + def endDocument( self ): + pass + + def startElement( self , name, attlist): + if name == "manifest:file-entry": + if attlist.getValueByName( "manifest:media-type" ) == "application/vnd.sun.star.framework-script": + self.urlList.append( + self.rootUrl + "/" + attlist.getValueByName( "manifest:full-path" ) ) + + def endElement( self, name ): + pass + + def characters ( self, chars ): + pass + + def ignoreableWhitespace( self, chars ): + pass + + def setDocumentLocator( self, locator ): + pass + +def isPyFileInPath( sfa, path ): + ret = False + contents = sfa.getFolderContents( path, True ) + for i in contents: + if sfa.isFolder(i): + ret = isPyFileInPath(sfa,i) + else: + if i.endswith(".py"): + ret = True + if ret: + break + return ret + +# extracts META-INF directory from +def getPathsFromPackage( rootUrl, sfa ): + ret = () + try: + fileUrl = rootUrl + "/META-INF/manifest.xml" + inputStream = sfa.openFileRead( fileUrl ) + parser = uno.getComponentContext().ServiceManager.createInstance( "com.sun.star.xml.sax.Parser" ) + handler = ManifestHandler( rootUrl ) + parser.setDocumentHandler( handler ) + parser.parseStream( InputSource( inputStream , "", fileUrl, fileUrl ) ) + for i in tuple(handler.urlList): + if not isPyFileInPath( sfa, i ): + handler.urlList.remove(i) + ret = tuple( handler.urlList ) + except UnoException: + text = lastException2String() + log.debug( "getPathsFromPackage " + fileUrl + " Exception: " +text ) + pass + return ret + + +class Package: + def __init__( self, paths, transientPathElement ): + self.paths = paths + self.transientPathElement = transientPathElement + +class DummyInteractionHandler( unohelper.Base, XInteractionHandler ): + def __init__( self ): + pass + def handle( self, event): + log.debug( "pythonscript: DummyInteractionHandler.handle " + str( event ) ) + +class DummyProgressHandler( unohelper.Base, XProgressHandler ): + def __init__( self ): + pass + + def push( self,status ): + log.debug( "pythonscript: DummyProgressHandler.push " + str( status ) ) + def update( self,status ): + log.debug( "pythonscript: DummyProgressHandler.update " + str( status ) ) + def pop( self, event ): + log.debug( "pythonscript: DummyProgressHandler.push " + str( event ) ) + +class CommandEnvironment(unohelper.Base, XCommandEnvironment): + def __init__( self ): + self.progressHandler = DummyProgressHandler() + self.interactionHandler = DummyInteractionHandler() + def getInteractionHandler( self ): + return self.interactionHandler + def getProgressHandler( self ): + return self.progressHandler + +#maybe useful for debugging purposes +#class ModifyListener( unohelper.Base, XModifyListener ): +# def __init__( self ): +# pass +# def modified( self, event ): +# log.debug( "pythonscript: ModifyListener.modified " + str( event ) ) +# def disposing( self, event ): +# log.debug( "pythonscript: ModifyListener.disposing " + str( event ) ) + +def getModelFromDocUrl(ctx, url): + """Get document model from document url.""" + doc = None + args = ("Local", "Office") + ucb = ctx.getServiceManager().createInstanceWithArgumentsAndContext( + "com.sun.star.ucb.UniversalContentBroker", args, ctx) + identifier = ucb.createContentIdentifier(url) + content = ucb.queryContent(identifier) + p = Property() + p.Name = "DocumentModel" + p.Handle = -1 + + c = Command() + c.Handle = -1 + c.Name = "getPropertyValues" + c.Argument = uno.Any("[]com.sun.star.beans.Property", (p,)) + + env = CommandEnvironment() + try: + ret = content.execute(c, 0, env) + doc = ret.getObject(1, None) + except Exception as e: + log.isErrorLevel() and log.error("getModelFromDocUrl: %s" % url) + return doc + +def mapStorageType2PackageContext( storageType ): + ret = storageType + if( storageType == "share:uno_packages" ): + ret = "shared" + if( storageType == "user:uno_packages" ): + ret = "user" + return ret + +def getPackageName2PathMap( sfa, storageType ): + ret = {} + packageManagerFactory = uno.getComponentContext().getValueByName( + "/singletons/com.sun.star.deployment.thePackageManagerFactory" ) + packageManager = packageManagerFactory.getPackageManager( + mapStorageType2PackageContext(storageType)) +# packageManager.addModifyListener( ModifyListener() ) + log.debug( "pythonscript: getPackageName2PathMap start getDeployedPackages" ) + packages = packageManager.getDeployedPackages( + packageManager.createAbortChannel(), CommandEnvironment( ) ) + log.debug( "pythonscript: getPackageName2PathMap end getDeployedPackages (" + str(len(packages))+")" ) + + for i in packages: + log.debug( "inspecting package " + i.Name + "("+i.Identifier.Value+")" ) + transientPathElement = penultimateElement( i.URL ) + j = expandUri( i.URL ) + paths = getPathsFromPackage( j, sfa ) + if len( paths ) > 0: + # map package name to url, we need this later + log.debug( "adding Package " + transientPathElement + " " + str( paths ) ) + ret[ lastElement( j ) ] = Package( paths, transientPathElement ) + return ret + +def penultimateElement( aStr ): + lastSlash = aStr.rindex("/") + penultimateSlash = aStr.rindex("/",0,lastSlash-1) + return aStr[ penultimateSlash+1:lastSlash ] + +def lastElement( aStr): + return aStr[ aStr.rfind( "/" )+1:len(aStr)] + +class PackageBrowseNode( unohelper.Base, XBrowseNode ): + def __init__( self, provCtx, name, rootUrl ): + self.provCtx = provCtx + self.name = name + self.rootUrl = rootUrl + + def getName( self ): + return self.name + + def getChildNodes( self ): + items = self.provCtx.mapPackageName2Path.items() + browseNodeList = [] + for i in items: + if len( i[1].paths ) == 1: + browseNodeList.append( + DirBrowseNode( self.provCtx, i[0], i[1].paths[0] )) + else: + for j in i[1].paths: + browseNodeList.append( + DirBrowseNode( self.provCtx, i[0]+"."+lastElement(j), j ) ) + return tuple( browseNodeList ) + + def hasChildNodes( self ): + return len( self.provCtx.mapPackageName2Path ) > 0 + + def getType( self ): + return CONTAINER + + def getScript( self, uri ): + log.debug( "PackageBrowseNode getScript " + uri + " invoked" ) + raise IllegalArgumentException( "PackageBrowseNode couldn't instantiate script " + uri , self , 0 ) + + + + +class PythonScript( unohelper.Base, XScript ): + def __init__( self, func, mod, args ): + self.func = func + self.mod = mod + self.args = args + + def invoke(self, args, out, outindex ): + log.debug( "PythonScript.invoke " + str( args ) ) + try: + if (self.args): + args += self.args + ret = self.func( *args ) + except UnoException as e: + # UNO Exception continue to fly ... + text = lastException2String() + complete = "Error during invoking function " + \ + str(self.func.__name__) + " in module " + \ + self.mod.__file__ + " (" + text + ")" + log.debug( complete ) + # some people may beat me up for modifying the exception text, + # but otherwise office just shows + # the type name and message text with no more information, + # this is really bad for most users. + e.Message = e.Message + " (" + complete + ")" + raise + except Exception as e: + # General python exception are converted to uno RuntimeException + text = lastException2String() + complete = "Error during invoking function " + \ + str(self.func.__name__) + " in module " + \ + self.mod.__file__ + " (" + text + ")" + log.debug( complete ) + raise RuntimeException( complete , self ) + log.debug( "PythonScript.invoke ret = " + str( ret ) ) + return ret, (), () + +def expandUri( uri ): + if uri.startswith( "vnd.sun.star.expand:" ): + uri = uri.replace( "vnd.sun.star.expand:", "",1) + uri = uno.getComponentContext().getByName( + "/singletons/com.sun.star.util.theMacroExpander" ).expandMacros( unquote(uri) ) + if uri.startswith( "file:" ): + uri = uno.absolutize("",uri) # necessary to get rid of .. in uri + return uri + +#-------------------------------------------------------------- +class PythonScriptProvider( unohelper.Base, XBrowseNode, XScriptProvider, XNameContainer): + def __init__( self, ctx, *args ): + if log.isDebugLevel(): + mystr = "" + for i in args: + if len(mystr) > 0: + mystr = mystr +"," + mystr = mystr + str(i) + log.debug( "Entering PythonScriptProvider.ctor" + mystr ) + + doc = None + inv = None + storageType = "" + + if isinstance(args[0], str): + storageType = args[0] + if storageType.startswith( "vnd.sun.star.tdoc" ): + doc = getModelFromDocUrl(ctx, storageType) + else: + inv = args[0] + try: + doc = inv.ScriptContainer + content = ctx.getServiceManager().createInstanceWithContext( + "com.sun.star.frame.TransientDocumentsDocumentContentFactory", + ctx).createDocumentContent(doc) + storageType = content.getIdentifier().getContentIdentifier() + except Exception as e: + text = lastException2String() + log.error( text ) + + isPackage = storageType.endswith( ":uno_packages" ) + + try: +# urlHelper = ctx.ServiceManager.createInstanceWithArgumentsAndContext( +# "com.sun.star.script.provider.ScriptURIHelper", (LANGUAGENAME, storageType), ctx) + urlHelper = MyUriHelper( ctx, storageType ) + log.debug( "got urlHelper " + str( urlHelper ) ) + + rootUrl = expandUri( urlHelper.getRootStorageURI() ) + log.debug( storageType + " transformed to " + rootUrl ) + + ucbService = "com.sun.star.ucb.SimpleFileAccess" + sfa = ctx.ServiceManager.createInstanceWithContext( ucbService, ctx ) + if not sfa: + log.debug("PythonScriptProvider couldn't instantiate " +ucbService) + raise RuntimeException( + "PythonScriptProvider couldn't instantiate " +ucbService, self) + self.provCtx = ProviderContext( + storageType, sfa, urlHelper, ScriptContext( uno.getComponentContext(), doc, inv ) ) + if isPackage: + mapPackageName2Path = getPackageName2PathMap( sfa, storageType ) + self.provCtx.setPackageAttributes( mapPackageName2Path , rootUrl ) + self.dirBrowseNode = PackageBrowseNode( self.provCtx, LANGUAGENAME, rootUrl ) + else: + self.dirBrowseNode = DirBrowseNode( self.provCtx, LANGUAGENAME, rootUrl ) + + except Exception as e: + text = lastException2String() + log.debug( "PythonScriptProvider could not be instantiated because of : " + text ) + raise e + + def getName( self ): + return self.dirBrowseNode.getName() + + def getChildNodes( self ): + return self.dirBrowseNode.getChildNodes() + + def hasChildNodes( self ): + return self.dirBrowseNode.hasChildNodes() + + def getType( self ): + return self.dirBrowseNode.getType() + + # retrieve function args in parenthesis + def getFunctionArguments(self, func_signature): + nOpenParenthesis = func_signature.find( "(" ) + if -1 == nOpenParenthesis: + function_name = func_signature + arguments = None + else: + function_name = func_signature[0:nOpenParenthesis] + arg_part = func_signature[nOpenParenthesis+1:len(func_signature)] + nCloseParenthesis = arg_part.find( ")" ) + if -1 == nCloseParenthesis: + raise IllegalArgumentException( "PythonLoader: mismatch parenthesis " + func_signature, self, 0 ) + arguments = arg_part[0:nCloseParenthesis].strip() + if arguments == "": + arguments = None + else: + arguments = tuple([x.strip().strip('"') for x in arguments.split(",")]) + return function_name, arguments + + def getScript( self, scriptUri ): + try: + log.debug( "getScript " + scriptUri + " invoked") + + storageUri = self.provCtx.getStorageUrlFromPersistentUrl( + self.provCtx.uriHelper.getStorageURI(scriptUri) ); + log.debug( "getScript: storageUri = " + storageUri) + fileUri = storageUri[0:storageUri.find( "$" )] + funcName = storageUri[storageUri.find( "$" )+1:len(storageUri)] + + # retrieve arguments in parenthesis + funcName, funcArgs = self.getFunctionArguments(funcName) + log.debug( " getScript : parsed funcname " + str(funcName) ) + log.debug( " getScript : func args " + str(funcArgs) ) + + mod = self.provCtx.getModuleByUrl( fileUri ) + log.debug( " got mod " + str(mod) ) + + func = mod.__dict__[ funcName ] + + log.debug( "got func " + str( func ) ) + return PythonScript( func, mod, funcArgs ) + except: + text = lastException2String() + log.error( text ) + raise ScriptFrameworkErrorException( text, self, scriptUri, LANGUAGENAME, 0 ) + + + # XServiceInfo + def getSupportedServices( self ): + return g_ImplementationHelper.getSupportedServices(g_implName) + + def supportsService( self, ServiceName ): + return g_ImplementationHelper.supportsService( g_implName, ServiceName ) + + def getImplementationName(self): + return g_implName + + def getByName( self, name ): + log.debug( "getByName called" + str( name )) + return None + + + def getElementNames( self ): + log.debug( "getElementNames called") + return () + + def hasByName( self, name ): + try: + log.debug( "hasByName called " + str( name )) + uri = expandUri(name) + ret = self.provCtx.isUrlInPackage( uri ) + log.debug( "hasByName " + uri + " " +str( ret ) ) + return ret + except: + text = lastException2String() + log.debug( "Error in hasByName:" + text ) + return False + + def removeByName( self, name ): + log.debug( "removeByName called" + str( name )) + uri = expandUri( name ) + if self.provCtx.isUrlInPackage( uri ): + self.provCtx.removePackageByUrl( uri ) + else: + log.debug( "removeByName unknown uri " + str( name ) + ", ignoring" ) + raise NoSuchElementException( uri + "is not in package" , self ) + log.debug( "removeByName called" + str( uri ) + " successful" ) + + def insertByName( self, name, value ): + log.debug( "insertByName called " + str( name ) + " " + str( value )) + uri = expandUri( name ) + if isPyFileInPath( self.provCtx.sfa, uri ): + self.provCtx.addPackageByUrl( uri ) + else: + # package is no python package ... + log.debug( "insertByName: no python files in " + str( uri ) + ", ignoring" ) + raise IllegalArgumentException( uri + " does not contain .py files", self, 1 ) + log.debug( "insertByName called " + str( uri ) + " successful" ) + + def replaceByName( self, name, value ): + log.debug( "replaceByName called " + str( name ) + " " + str( value )) + uri = expandUri( name ) + self.removeByName( name ) + self.insertByName( name, value ) + log.debug( "replaceByName called" + str( uri ) + " successful" ) + + def getElementType( self ): + log.debug( "getElementType called" ) + return uno.getTypeByName( "void" ) + + def hasElements( self ): + log.debug( "hasElements got called") + return False + +g_ImplementationHelper.addImplementation( \ + PythonScriptProvider,g_implName, \ + ("com.sun.star.script.provider.LanguageScriptProvider", + "com.sun.star.script.provider.ScriptProviderFor"+ LANGUAGENAME,),) + + +log.debug( "pythonscript finished initializing" ) + +# vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/scripting/source/pyprov/scriptproviderforpython.rdb b/scripting/source/pyprov/scriptproviderforpython.rdb new file mode 100644 index 0000000000..09a37e4da1 --- /dev/null +++ b/scripting/source/pyprov/scriptproviderforpython.rdb @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . +--> +<components xmlns="http://openoffice.org/2010/uno-components"> + <component loader="com.sun.star.loader.Python" + uri="vnd.openoffice.pymodule:pythonscript"> + <implementation + name="org.libreoffice.pyuno.LanguageScriptProviderForPython"> + <service name="com.sun.star.script.provider.ScriptProviderForPython"/> + <service name="com.sun.star.script.provider.LanguageScriptProvider"/> + </implementation> + </component> +</components> |