diff --git a/CHANGELOG b/CHANGELOG index 2332ccf..093ddeb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +v 0.8.0 [13-oct-2019] + - Generate manifest + - Add support for execute dialogs in documents. + + v 0.7.0 [27-sep-2019] - Add support for InputBox and documents diff --git a/VERSION b/VERSION index faef31a..a3df0a6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.0 +0.8.0 diff --git a/source/conf.py.example b/source/conf.py.example index 9e4f591..9be5aeb 100644 --- a/source/conf.py.example +++ b/source/conf.py.example @@ -205,6 +205,7 @@ PATHS = { 'regmerge': '/usr/lib/libreoffice/program/regmerge', 'soffice': ('soffice', PROGRAM, FILE_TEST), 'install': ('unopkg', 'add', '-v', '-f', '-s'), + 'profile': '/home/mau/.config/libreoffice/4/user', } @@ -427,20 +428,6 @@ FILE_ADDONS = f""" """ -NODE_ADDONS = '\n ' -if TYPE_EXTENSION > 1: - NODE_ADDONS = f'\n ' -if TYPE_EXTENSION == 3: - NODE_ADDONS += '\n ' - -FILE_MANIFEST = f""" - - - {NODE_ADDONS} - -""" - - FILE_UPDATE = '' if URL_XML_UPDATE: FILE_UPDATE = f""" @@ -625,9 +612,16 @@ FILE_SHORTCUTS = f""" """ +DATA_MANIFEST = [FILES['py'], f"Office/{FILES['shortcut']}", 'Addons.xcu'] +if TYPE_EXTENSION > 1: + DATA_MANIFEST.append(FILES['rdb']) +if TYPE_EXTENSION == 3: + DATA_MANIFEST.append('CalcAddIn.xcu') + + DATA = { 'py': FILE_PY, - 'manifest': FILE_MANIFEST, + 'manifest': DATA_MANIFEST, 'description': FILE_DESCRIPTION, 'addons': FILE_ADDONS, 'update': FILE_UPDATE, diff --git a/source/easymacro.py b/source/easymacro.py index 5261700..218abbf 100644 --- a/source/easymacro.py +++ b/source/easymacro.py @@ -576,6 +576,10 @@ class LODocument(object): def title(self, value): self.obj.setTitle(value) + @property + def uid(self): + return self.obj.RuntimeUID + @property def type(self): return self._type_doc @@ -2656,16 +2660,21 @@ class LODialog(object): def _create(self, properties): path = properties.pop('Path', '') if path: - dp = create_instance('com.sun.star.awt.DialogProvider2', True) + dp = create_instance('com.sun.star.awt.DialogProvider', True) return dp.createDialog(_path_url(path)) - if 'Library' in properties: - location = properties['Location'] + if 'Location' in properties: + location = properties.get('Location', 'application') + library = properties.get('Library', 'Standard') if location == 'user': location = 'application' - dp = create_instance('com.sun.star.awt.DialogProvider2', True) + dp = create_instance('com.sun.star.awt.DialogProvider', True) path = 'vnd.sun.star.script:{}.{}?location={}'.format( - properties['Library'], properties['Name'], location) + library, properties['Name'], location) + if location == 'document': + uid = get_document().uid + path = 'vnd.sun.star.tdoc:/{}/Dialogs/{}/{}.xml'.format( + uid, library, properties['Name']) return dp.createDialog(path) dlg = create_instance('com.sun.star.awt.UnoControlDialog', True) @@ -2678,8 +2687,20 @@ class LODialog(object): return dlg - def _init_controls(self): + def _get_type_control(self, name): + types = { + 'stardiv.Toolkit.UnoFixedTextControl': 'label', + 'stardiv.Toolkit.UnoButtonControl': 'button', + 'stardiv.Toolkit.UnoEditControl': 'text', + } + return types[name] + def _init_controls(self): + for control in self.obj.getControls(): + tipo = self._get_type_control(control.ImplementationName) + name = control.Model.Name + control = get_custom_class(tipo, control) + setattr(self, name, control) return @property @@ -2699,23 +2720,8 @@ class LODialog(object): self._connect_listeners() def _connect_listeners(self): - - return - - def _add_listeners(self, control): - if self.events is None: - return - - listeners = { - 'addActionListener': EventsButton, - 'addMouseListener': EventsMouse, - } - for key, value in listeners.items(): - if hasattr(control.obj, key): - if control.type == 'grid' and key == 'addMouseListener': - control.obj.addMouseListener(EventsMouseGrid(self.events)) - continue - getattr(control.obj, key)(listeners[key](self.events)) + for control in self.obj.getControls(): + add_listeners(self._events, control, control.Model.Name) return def open(self): @@ -2740,23 +2746,6 @@ class LODialog(object): } return services[control] - # ~ def _get_custom_class(self, tipo, obj): - # ~ classes = { - # ~ 'label': UnoLabel, - # ~ 'button': UnoButton, - # ~ 'text': UnoText, - # ~ 'listbox': UnoListBox, - # ~ 'grid': UnoGrid, - # ~ 'link': UnoLink, - # ~ 'tab': UnoTab, - # ~ 'roadmap': UnoRoadmap, - # ~ 'image': UnoImage, - # ~ 'radio': UnoRadio, - # ~ 'groupbox': UnoGroupBox, - # ~ 'tree': UnoTree, - # ~ } - # ~ return classes[tipo](obj) - def _set_column_model(self, columns): #~ https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1awt_1_1grid_1_1XGridColumn.html column_model = create_instance('com.sun.star.awt.grid.DefaultGridColumnModel', True) diff --git a/source/zaz.py b/source/zaz.py index 5669ec1..867ac42 100644 --- a/source/zaz.py +++ b/source/zaz.py @@ -19,11 +19,16 @@ import argparse import os +import re import sys +import zipfile +from datetime import datetime from pathlib import Path from shutil import copyfile from subprocess import call -import zipfile +from xml.etree import ElementTree as ET +from xml.dom.minidom import parseString + from conf import ( DATA, @@ -37,6 +42,83 @@ from conf import ( log) +class LiboXML(object): + TYPES = { + 'py': 'application/vnd.sun.star.uno-component;type=Python', + 'zip': 'application/binary', + 'xcu': 'application/vnd.sun.star.configuration-data', + 'rdb': 'application/vnd.sun.star.uno-typelibrary;type=RDB', + 'xcs': 'application/vnd.sun.star.configuration-schema', + 'help': 'application/vnd.sun.star.help', + } + NAME_SPACES = { + 'manifest_version': '1.2', + 'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0', + 'xmlns:loext': 'urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0', + } + + def __init__(self): + self._manifest = None + self._paths = [] + + def _save_path(self, attr): + self._paths.append(attr['{{{}}}full-path'.format(self.NAME_SPACES['manifest'])]) + return + + def _clean(self, name, nodes): + has_words = re.compile('\\w') + + if not re.search(has_words, str(nodes.tail)): + nodes.tail = '' + if not re.search(has_words, str(nodes.text)): + nodes.text = '' + + for node in nodes: + if name == 'manifest': + self._save_path(node.attrib) + if not re.search(has_words, str(node.tail)): + node.tail = '' + if not re.search(has_words, str(node.text)): + node.text = '' + return + + def new_manifest(self, data): + attr = { + 'manifest:version': self.NAME_SPACES['manifest_version'], + 'xmlns:manifest': self.NAME_SPACES['manifest'], + 'xmlns:loext': self.NAME_SPACES['xmlns:loext'], + } + self._manifest = ET.Element('manifest:manifest', attr) + return self.add_data_manifest(data) + + def parse_manifest(self, data): + ET.register_namespace('manifest', self.NAME_SPACES['manifest']) + self._manifest = ET.fromstring(data) + data = {'xmlns:loext': self.NAME_SPACES['xmlns:loext']} + self._manifest.attrib.update(**data) + self._clean('manifest', self._manifest) + return + + def add_data_manifest(self, data): + node_name = 'manifest:file-entry' + attr = { + 'manifest:full-path': '', + 'manifest:media-type': '', + } + for path in data: + if path in self._paths: + continue + ext = path.split('.')[-1] + attr['manifest:full-path'] = path + attr['manifest:media-type'] = self.TYPES.get(ext, '') + ET.SubElement(self._manifest, node_name, attr) + return self._get_xml(self._manifest) + + def _get_xml(self, doc): + xml = parseString(ET.tostring(doc, encoding='utf-8')) + return xml.toprettyxml(indent=' ', encoding='utf-8').decode('utf-8') + + def _exists(path): return os.path.exists(path) @@ -222,8 +304,11 @@ def _update_files(): target = _join(path_source, 'pythonpath', source) copyfile(source, target) + xml = LiboXML() + path = _join(path_source, DIRS['meta'], FILES['manifest']) - _save(path, DATA['manifest']) + data = xml.new_manifest(DATA['manifest']) + _save(path, data) path = _join(path_source, DIRS['office']) _mkdir(path) @@ -265,7 +350,96 @@ def _new(): return +def _get_info_path(path): + path, filename = os.path.split(path) + name, extension = os.path.splitext(filename) + return (path, filename, name, extension) + + +def _zip_embed(source, files): + PATH = 'Scripts/python/' + EASYMACRO = 'easymacro.' + + p, f, name, e = _get_info_path(source) + now = datetime.now().strftime('_%Y%m%d_%H%M%S') + path_source = _join(p, name + now + e) + copyfile(source, path_source) + target = source + + with zipfile.PyZipFile(EASYMACRO + 'zip', mode='w') as zf: + zf.writepy(EASYMACRO + 'py') + + xml = LiboXML() + + path_easymacro = PATH + EASYMACRO + 'zip' + names = [f[1] for f in files] + [path_easymacro] + nodes = [] + with zipfile.ZipFile(target, 'w', compression=zipfile.ZIP_DEFLATED) as zt: + with zipfile.ZipFile(path_source, compression=zipfile.ZIP_DEFLATED) as zs: + for name in zs.namelist(): + if FILES['manifest'] in name: + path_manifest = name + xml_manifest = zs.open(name).read() + elif name in names: + continue + else: + zt.writestr(name, zs.open(name).read()) + + data = [] + for path, name in files: + data.append(name) + zt.write(path, name) + + zt.write(EASYMACRO + 'zip', path_easymacro) + data.append(path_easymacro) + + xml.parse_manifest(xml_manifest) + xml_manifest = xml.add_data_manifest(data) + zt.writestr(path_manifest, xml_manifest) + + os.unlink(EASYMACRO + 'zip') + return + + +def _embed(args): + PATH = 'Scripts/python' + PYTHONPATH = 'pythonpath' + + doc = args.document + if not doc: + msg = '-d/--document Path file to embed is mandatory' + log.error(msg) + return + if not _exists(doc): + msg = 'Path file not exists' + log.error(msg) + return + + files = [] + if args.files: + files = args.files.split(',') + source = _join(PATHS['profile'], PATH) + content = os.listdir(source) + if PYTHONPATH in content: + content.remove(PYTHONPATH) + + if files: + files = [(_join(source, f), _join(PATH, f)) for f in files if f in content] + else: + files = [(_join(source, f), _join(PATH, f)) for f in content] + + _zip_embed(doc, files) + + log.info('Embedded macros successfully...') + return + + + def main(args): + if args.embed: + _embed(args) + return + if args.new: _new() return @@ -279,7 +453,7 @@ def main(args): if args.install: _install_and_test() - log.info('Extension make sucesfully...') + log.info('Extension make successfully...') return @@ -290,6 +464,10 @@ def _process_command_line_arguments(): default=False, required=False) parser.add_argument('-n', '--new', dest='new', action='store_true', default=False, required=False) + parser.add_argument('-e', '--embed', dest='embed', action='store_true', + default=False, required=False) + parser.add_argument('-d', '--document', dest='document', default='') + parser.add_argument('-f', '--files', dest='files', default='') return parser.parse_args()