891 lines
31 KiB
Python
Executable File
891 lines
31 KiB
Python
Executable File
#! /opt/openoffice4/program/python
|
||
# -*- coding: utf-8 -*-
|
||
# Office Basic macro source synchronizer.
|
||
# by imacat <imacat@mail.imacat.idv.tw>, 2016-08-31
|
||
|
||
# Copyright (c) 2016-2017 imacat.
|
||
#
|
||
# Licensed 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
|
||
#
|
||
# Unless required by applicable law or agreed to in writing, software
|
||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
# See the License for the specific language governing permissions and
|
||
# limitations under the License.
|
||
|
||
"""Synchronize Office Basic macro source.
|
||
|
||
obasync is an OpenOffice/LibreOffice Basic macro source synchronizer.
|
||
It synchronizes your Basic macros with your local project files.
|
||
|
||
Given the following source files:
|
||
|
||
* Directory: MyApp
|
||
* Files: MyMacros.vb Utils.vb Registry.vb Data.vb
|
||
|
||
Running "obasync" will synchronize them with the following Basic
|
||
macros:
|
||
|
||
* Library: MyApp
|
||
* Modules: MyMacros Utils Registry Data
|
||
|
||
If the Basic library MyApp does not exist, it will be created.
|
||
Missing modules will be added, and excess modules will be removed.
|
||
|
||
On the other hand, given the following Basic macros:
|
||
|
||
* Library: MyApp
|
||
* Modules: MyMacros Utils Registry Data
|
||
|
||
Running "obasync --get" will synchronize them with the following
|
||
source files:
|
||
|
||
* Directory: MyApp
|
||
* Files: MyMacros.vb Utils.vb Registry.vb Data.vb
|
||
|
||
Missing source files will be added, and excess source files will be
|
||
deleted.
|
||
|
||
SYNOPSIS
|
||
|
||
obasync [options] [DIRECTORY [LIBRARY]]
|
||
|
||
DIRECTORY The project source directory. Default to the current
|
||
working directory.
|
||
|
||
LIBRARY The name of the Basic library. Default to the same
|
||
name as the project source directory.
|
||
|
||
--get Download (check out) the macros from the
|
||
OpenOffice/LibreOffice Basic storage to the source
|
||
files, instead of upload (check in). By default it
|
||
uploads the source files onto the
|
||
OpenOffice/LibreOffice Basic storage.
|
||
|
||
--set-passwd Sets the password of the library after upload. Supply
|
||
nothing when prompting the new password to remove the
|
||
password protection. This does not work with --get.
|
||
|
||
-p, --port N The TCP port to communicate with
|
||
OpenOffice/LibreOffice. The default is 2002. You can
|
||
change it if port 2002 is already in use.
|
||
|
||
-x, --ext .EXT The file name extension of the source files. The
|
||
default is ".vb". This may be used for your
|
||
convenience of editor syntax highlighting.
|
||
|
||
-e, --encoding CS
|
||
The encoding of the source files. The default is
|
||
system-dependent. For example, on Traditional Chinese
|
||
MS-Windows, this will be CP950 (Big5). You can change
|
||
this to UTF-8 for convenience if you
|
||
obtain/synchronize your source code from other
|
||
sources.
|
||
|
||
-r, --run MODULE.MACRO
|
||
Run he specific macro after synchronization, for
|
||
convenience.
|
||
|
||
--user Store the macros in the user macro storage. (default)
|
||
|
||
--doc Store the macros in the document macro storage.
|
||
|
||
--target TARGET The target storage document if there are more than one
|
||
opened documents. You may specify a partial path, or
|
||
as "Untitied 1" (in your language) if it is a new
|
||
file.
|
||
|
||
-h, --help Show the help message and exit
|
||
|
||
-v, --version Show program’s version number and exit
|
||
"""
|
||
|
||
from __future__ import print_function
|
||
import argparse
|
||
import getpass
|
||
import os
|
||
import sys
|
||
import time
|
||
import locale
|
||
|
||
|
||
def append_uno_path():
|
||
"""Append the path of the uno module to the import path."""
|
||
for p in sys.path:
|
||
if os.path.exists(os.path.join(p, "uno.py")):
|
||
return
|
||
# For uno.py on MacOS
|
||
cand = "/Applications/OpenOffice.app/Contents/MacOS"
|
||
if os.path.exists(os.path.join(cand, "uno.py")):
|
||
sys.path.append(cand)
|
||
return
|
||
# Find uno.py for MS-Windows
|
||
cand = sys.executable
|
||
while cand != os.path.dirname(cand):
|
||
cand = os.path.dirname(cand)
|
||
if os.path.exists(os.path.join(cand, "uno.py")):
|
||
sys.path.append(cand)
|
||
return
|
||
|
||
append_uno_path()
|
||
import uno
|
||
from com.sun.star.connection import NoConnectException
|
||
from com.sun.star.lang import IllegalArgumentException
|
||
|
||
|
||
def main():
|
||
"""The main program."""
|
||
t_start = time.time()
|
||
global args
|
||
|
||
# Parses the arguments
|
||
parse_args()
|
||
|
||
# Downloads the macros from OpenOffice/LibreOffice Basic
|
||
if args.get:
|
||
oo = Office(args.port)
|
||
storage = find_storage(oo, args.storage_type, args.target)
|
||
modules = read_basic_modules(storage, args.library)
|
||
if len(modules) == 0:
|
||
print("ERROR: Library %s does not exist" % args.library,
|
||
file=sys.stderr)
|
||
return
|
||
update_source_dir(
|
||
args.projdir, modules, args.ext, args.encoding)
|
||
|
||
# Uploads the macros onto OpenOffice/LibreOffice Basic
|
||
else:
|
||
modules = read_in_source_dir(
|
||
args.projdir, args.ext, args.encoding)
|
||
if len(modules) == 0:
|
||
print("ERROR: Found no source macros in %s" %
|
||
args.projdir,
|
||
file=sys.stderr)
|
||
return
|
||
oo = Office(args.port)
|
||
storage = find_storage(oo, args.storage_type, args.target)
|
||
update_basic_modules(storage, args.library, modules, args.setpass)
|
||
if args.run is not None:
|
||
run_macro(storage, args.library, args.run)
|
||
|
||
print("Done. %02d:%02d elapsed." %
|
||
(int((time.time() - t_start) / 60),
|
||
(time.time() - t_start) % 60), file=sys.stderr)
|
||
|
||
|
||
def parse_args():
|
||
"""Parse the arguments."""
|
||
global args
|
||
|
||
parser = argparse.ArgumentParser(
|
||
description=("Synchronize the local Basic scripts"
|
||
" with OpenOffice/LibreOffice Basic."))
|
||
parser.add_argument(
|
||
"projdir", metavar="DIR", nargs="?", default=os.getcwd(),
|
||
help=("The project source directory"
|
||
" (default to the current directory)."))
|
||
parser.add_argument(
|
||
"library", metavar="LIBRARY", nargs="?",
|
||
help=("The Library to upload/download the macros"
|
||
" (default to the name of the directory)."))
|
||
parser.add_argument(
|
||
"--get", action="store_true",
|
||
help="Downloads the macros instead of upload.")
|
||
parser.add_argument(
|
||
"--set-passwd", dest="setpass", action="store_true",
|
||
help=("Sets the password of the library after upload. "
|
||
"This does not work with --get."))
|
||
parser.add_argument(
|
||
"-p", "--port", metavar="N", type=int, default=2002,
|
||
help=("The TCP port to communicate with "
|
||
"OpenOffice/LibreOffice with (default: %(default)s)"))
|
||
parser.add_argument(
|
||
"-x", "--ext", metavar=".EXT", default=".vb",
|
||
help=("The file name extension of the source files. "
|
||
"(default: %(default)s)"))
|
||
parser.add_argument(
|
||
"-e", "--encoding", metavar="CS",
|
||
default=locale.getpreferredencoding(),
|
||
help=("The encoding of the source files. "
|
||
"(default: %(default)s)"))
|
||
parser.add_argument(
|
||
"-r", "--run", metavar="MODULE.MACRO",
|
||
help="The macro to run after the upload, if any.")
|
||
parser.add_argument(
|
||
"--user", dest="storage_type",
|
||
action="store_const", const="user",
|
||
help="Store the macros in the user macro storage. (default)")
|
||
parser.add_argument(
|
||
"--doc", dest="storage_type",
|
||
action="store_const", const="doc",
|
||
help="Store the macros in the document macro storage.")
|
||
parser.add_argument(
|
||
"--target", metavar="TARGET",
|
||
help=("The target storage document if there are more than one"
|
||
" opened documents. You may specify a partial path, or"
|
||
" an \"Untitied 1\" (in your language) if it is a new"
|
||
" file."))
|
||
parser.add_argument(
|
||
"-v", "--version", action="version", version="%(prog)s 0.10")
|
||
args = parser.parse_args()
|
||
|
||
# Obtain the absolute path
|
||
args.projdir = os.path.abspath(args.projdir)
|
||
# Obtain the library name from the path
|
||
if args.library is None:
|
||
args.library = os.path.basename(args.projdir)
|
||
# Adjust the file name extension.
|
||
if args.ext[0] != ".":
|
||
args.ext = "." + args.ext
|
||
|
||
if args.storage_type is None:
|
||
args.storage_type = "user"
|
||
# For Python 2 only.
|
||
# Paths are understood locally, despite of the content encoding.
|
||
if sys.version_info.major == 2:
|
||
if args.target is not None:
|
||
args.target = args.target.decode(locale.getpreferredencoding())
|
||
|
||
if args.get and args.setpass:
|
||
print("ERROR: --get does not work with --set-passwd.",
|
||
file=sys.stderr)
|
||
sys.exit(1)
|
||
return
|
||
|
||
|
||
def find_storage(oo, type, target):
|
||
"""Finds the macro storage to store the macros.
|
||
|
||
Arguments:
|
||
type: The storage type, either "user" or "doc".
|
||
target: The file path to locate the storing document if there
|
||
are more than one opened documents. A partial path
|
||
is OK.
|
||
|
||
Returns:
|
||
The storage to save the macros, as a Storage object.
|
||
"""
|
||
if type == "user":
|
||
storage = Storage()
|
||
storage.type = type
|
||
storage.oo = oo
|
||
storage.doc = None
|
||
storage.libs = oo.service_manager.createInstance(
|
||
"com.sun.star.script.ApplicationScriptLibraryContainer")
|
||
return storage
|
||
elif type == "doc":
|
||
storage = Storage()
|
||
storage.type = type
|
||
storage.oo = oo
|
||
storage.doc = find_doc(oo, target)
|
||
storage.libs = storage.doc.getPropertyValue("BasicLibraries")
|
||
return storage
|
||
else:
|
||
return None
|
||
|
||
|
||
def find_doc(oo, target):
|
||
"""Find the target opened document by a partial path.
|
||
|
||
Arguments:
|
||
target: A partial path of the document.
|
||
|
||
Returns:
|
||
If there is only one opened document that matches the
|
||
target, it is returned. If the target is not specified,
|
||
but there is only one opened document, it is returned.
|
||
Otherwise, the program exits with an error.
|
||
"""
|
||
# Checks the opened documents
|
||
enum = oo.desktop.getComponents().createEnumeration()
|
||
opened = []
|
||
while enum.hasMoreElements():
|
||
component = enum.nextElement()
|
||
if component.supportsService(
|
||
"com.sun.star.document.OfficeDocument"):
|
||
opened.append(component)
|
||
if len(opened) == 0:
|
||
print("ERROR: Found no opened document to store the macros",
|
||
file=sys.stderr)
|
||
sys.exit(1)
|
||
file_content_provider = oo.service_manager.createInstance(
|
||
"com.sun.star.ucb.FileContentProvider")
|
||
# There are opened documents.
|
||
if target is None:
|
||
if len(opened) == 1:
|
||
return opened[0]
|
||
print("ERROR: There are more than one opened documens. "
|
||
"Please specify the file path.",
|
||
file=sys.stderr)
|
||
for path in get_doc_paths(opened, file_content_provider):
|
||
print("* %s" % path, file=sys.stderr)
|
||
sys.exit(1)
|
||
matched = []
|
||
for doc in opened:
|
||
if doc.hasLocation():
|
||
path = file_content_provider.getSystemPathFromFileURL(
|
||
doc.getLocation())
|
||
else:
|
||
path = doc.getTitle()
|
||
if path.find(target) >= 0:
|
||
matched.append(doc)
|
||
if len(matched) == 1:
|
||
return matched[0]
|
||
elif len(matched) == 0:
|
||
print("ERROR: Found no matching document to store the macros.",
|
||
file=sys.stderr)
|
||
print("Opened documents:", file=sys.stderr)
|
||
for path in get_doc_paths(opened, file_content_provider):
|
||
print("* %s" % path, file=sys.stderr)
|
||
sys.exit(1)
|
||
else:
|
||
print("ERROR: There are more than one matching documents.",
|
||
file=sys.stderr)
|
||
print("Matching documents:", file=sys.stderr)
|
||
for path in get_doc_paths(matched, file_content_provider):
|
||
print("* %s" % path, file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
|
||
def get_doc_paths(docs, file_content_provider):
|
||
"""Returns the paths (or the titles) of the documents.
|
||
|
||
Arguments:
|
||
docs: A list of office documents.
|
||
file_content_provider: A FileContentProvider service instance.
|
||
|
||
Returns:
|
||
A list of paths, or the titles if there is no path yet,
|
||
of the documents.
|
||
"""
|
||
paths = []
|
||
for doc in docs:
|
||
if doc.hasLocation():
|
||
paths.append(
|
||
file_content_provider.getSystemPathFromFileURL(
|
||
doc.getLocation()))
|
||
else:
|
||
paths.append(doc.getTitle())
|
||
return sorted(paths)
|
||
|
||
|
||
def read_in_source_dir(projdir, ext, encoding):
|
||
"""Read-in the source files.
|
||
|
||
Arguments:
|
||
projdir: The project source directory.
|
||
ext: The file name extension of the source files, beginning
|
||
with a single dot ".".
|
||
encoding: The encoding of the source file.
|
||
|
||
Returns:
|
||
The Basic modules that read, as a dictionary. The keys of the
|
||
dictionary are the module names, and the values of the
|
||
dictionary are the module contents.
|
||
"""
|
||
modules = {}
|
||
for entry in os.listdir(projdir):
|
||
path = os.path.join(projdir, entry)
|
||
if os.path.isfile(path) \
|
||
and entry.lower().endswith(ext.lower()):
|
||
modname = entry[0:-len(ext)]
|
||
modules[modname] = read_file(path, encoding)
|
||
return modules
|
||
|
||
|
||
def update_source_dir(projdir, modules, ext, encoding):
|
||
"""Update the source files.
|
||
|
||
Arguments:
|
||
projdir: The project source directory.
|
||
modules: The Basic modules, as a dictionary. The keys of
|
||
the dictionary are the module names, and the values of
|
||
the dictionary are the module contents.
|
||
ext: The file name extension of the source files, beginning
|
||
with a single dot ".".
|
||
encoding: The encoding of the source file.
|
||
"""
|
||
curmods = {}
|
||
is_in_sync = True
|
||
for entry in os.listdir(projdir):
|
||
path = os.path.join(projdir, entry)
|
||
if os.path.isfile(path) \
|
||
and entry.lower().endswith(ext.lower()):
|
||
modname = entry[0:-len(ext)]
|
||
curmods[modname] = entry
|
||
for modname in sorted(modules.keys()):
|
||
if modname not in curmods:
|
||
path = os.path.join(projdir, modname + ext)
|
||
write_file(path, modules[modname], encoding)
|
||
print("%s added." % (modname + ext), file=sys.stderr)
|
||
is_in_sync = False
|
||
else:
|
||
path = os.path.join(projdir, curmods[modname])
|
||
if update_file(path, modules[modname], encoding):
|
||
print("%s updated." % curmods[modname],
|
||
file=sys.stderr)
|
||
is_in_sync = False
|
||
for modname in sorted(curmods.keys()):
|
||
if modname not in modules:
|
||
path = os.path.join(projdir, curmods[modname])
|
||
os.remove(path)
|
||
print("%s removed." % curmods[modname], file=sys.stderr)
|
||
is_in_sync = False
|
||
if is_in_sync:
|
||
print("Everything is in sync.", file=sys.stderr)
|
||
return
|
||
|
||
|
||
def read_basic_modules(storage, libname):
|
||
"""
|
||
Read the OpenOffice/LibreOffice Basic macros from the macro
|
||
storage.
|
||
|
||
Arguments:
|
||
storage: The Basic macro storage, as a Storage object.
|
||
libname: The name of the Basic library.
|
||
|
||
Returns:
|
||
The Basic modules that read, as a dictionary. The keys of the
|
||
dictionary are the module names, and the values of the
|
||
dictionary are the module contents.
|
||
"""
|
||
modules = {}
|
||
if not storage.libs.hasByName(libname):
|
||
return modules
|
||
verify_library_password(storage, libname)
|
||
storage.libs.loadLibrary(libname)
|
||
library = storage.libs.getByName(libname)
|
||
for modname in library.getElementNames():
|
||
modules[modname] = library.getByName(modname)
|
||
return modules
|
||
|
||
|
||
def update_basic_modules(storage, libname, modules, setpass):
|
||
"""Update the OpenOffice/LibreOffice Basic macro storage.
|
||
|
||
Arguments:
|
||
storage: The Basic macro storage, as a Storage object.
|
||
libname: The name of the Basic library.
|
||
modules: The Basic modules, as a dictionary. The keys of
|
||
the dictionary are the module names, and the values of
|
||
the dictionary are the module contents.
|
||
"""
|
||
if not storage.libs.hasByName(libname):
|
||
storage.libs.createLibrary(libname)
|
||
print("Script library %s created." % libname, file=sys.stderr)
|
||
create_dialog_library(storage, libname)
|
||
if setpass:
|
||
set_library_password(storage, libname)
|
||
verify_library_password(storage, libname)
|
||
library = storage.libs.getByName(libname)
|
||
for modname in sorted(modules.keys()):
|
||
library.insertByName(modname, modules[modname])
|
||
print("Module %s added." % modname, file=sys.stderr)
|
||
else:
|
||
if setpass:
|
||
set_library_password(storage, libname)
|
||
verify_library_password(storage, libname)
|
||
storage.libs.loadLibrary(libname)
|
||
library = storage.libs.getByName(libname)
|
||
# As of OpenOffice 4.1.3, when there is no modules in the
|
||
# document storage, it returns a zero-length byte sequence
|
||
# instead of an empty string list.
|
||
# The byte sequence cannot be sorted directly.
|
||
curmods = library.getElementNames()
|
||
if len(curmods) == 0:
|
||
curmods = []
|
||
curmods = sorted(curmods)
|
||
for modname in sorted(modules.keys()):
|
||
if modname not in curmods:
|
||
library.insertByName(modname, modules[modname])
|
||
print("Module %s added." % modname, file=sys.stderr)
|
||
elif modules[modname] != library.getByName(modname):
|
||
library.replaceByName(modname, modules[modname])
|
||
print("Module %s updated." % modname, file=sys.stderr)
|
||
for modname in curmods:
|
||
if modname not in modules:
|
||
library.removeByName(modname)
|
||
print("Module %s removed." % modname, file=sys.stderr)
|
||
if storage.libs.isModified():
|
||
storage.libs.storeLibraries()
|
||
else:
|
||
print("Everything is in sync.", file=sys.stderr)
|
||
return
|
||
|
||
|
||
def create_dialog_library(storage, libname):
|
||
"""Create the dialog library.
|
||
|
||
Arguments:
|
||
storage: The Basic macro storage, as a Storage object.
|
||
libname: The name of the dialog library.
|
||
"""
|
||
if storage.type is "user":
|
||
libraries = storage.oo.service_manager.createInstance(
|
||
"com.sun.star.script.ApplicationDialogLibraryContainer")
|
||
else:
|
||
libraries = storage.doc.getPropertyValue("DialogLibraries")
|
||
libraries.createLibrary(libname)
|
||
print("Dialog library %s created." % libname, file=sys.stderr)
|
||
libraries.storeLibraries()
|
||
|
||
|
||
def verify_library_password(storage, libname):
|
||
"""Verify the password for the library.
|
||
|
||
Arguments:
|
||
storage: The Basic macro storage, as a Storage object.
|
||
libname: The name of the dialog library.
|
||
"""
|
||
if not storage.libs.isLibraryPasswordProtected(libname):
|
||
return
|
||
if storage.libs.isLibraryPasswordVerified(libname):
|
||
return
|
||
while True:
|
||
password = getpass.getpass("Password: ")
|
||
if storage.libs.verifyLibraryPassword(libname, password):
|
||
return
|
||
print("ERROR: Failed password for library %s." % libname,
|
||
file=sys.stderr)
|
||
return
|
||
|
||
|
||
def set_library_password(storage, libname):
|
||
"""Sets the password of the library.
|
||
|
||
Arguments:
|
||
storage: The Basic macro storage, as a Storage object.
|
||
libname: The name of the dialog library.
|
||
"""
|
||
if not storage.libs.isLibraryPasswordProtected(libname):
|
||
while True:
|
||
newpass = getpass.getpass("New password: ")
|
||
newpass2 = getpass.getpass("Repeat new password: ")
|
||
if newpass != newpass2:
|
||
print("ERROR: Two new passwords are not the same.",
|
||
file=sys.stderr)
|
||
continue
|
||
else:
|
||
break
|
||
storage.libs.changeLibraryPassword(libname, "", newpass)
|
||
return
|
||
else:
|
||
while True:
|
||
oldpass = getpass.getpass("Old password: ")
|
||
if oldpass == "":
|
||
print("ERROR: Please enter the old password.",
|
||
file=sys.stderr)
|
||
continue
|
||
# There is no easy way to verify the old password.
|
||
# The verifyLibraryPassword() method does not work when
|
||
# the password of the library was verified before.
|
||
# The changeLibraryPassword() method always success
|
||
# when the old password is the same as the new password.
|
||
# So I have to change the password to a temporary password
|
||
# to verify the old password, and then change it back.
|
||
# This has the risk that if the script crashes between,
|
||
# the password is changed, and the users do not know how
|
||
# to get back their password-protected library.
|
||
# But I suppose the users has the local files as their
|
||
# source repository.
|
||
tmppass = oldpass + "tmp"
|
||
try:
|
||
storage.libs.changeLibraryPassword(
|
||
libname, oldpass, tmppass)
|
||
except IllegalArgumentException:
|
||
print("ERROR: Incorrect old password.",
|
||
file=sys.stderr)
|
||
continue
|
||
else:
|
||
storage.libs.changeLibraryPassword(
|
||
libname, tmppass, oldpass)
|
||
break
|
||
while True:
|
||
newpass = getpass.getpass("New password: ")
|
||
newpass2 = getpass.getpass("Repeat new password: ")
|
||
if newpass != newpass2:
|
||
print("ERROR: Two new passwords are not the same.",
|
||
file=sys.stderr)
|
||
continue
|
||
else:
|
||
break
|
||
storage.libs.changeLibraryPassword(libname, oldpass, newpass)
|
||
return
|
||
|
||
|
||
def run_macro(storage, libname, macro):
|
||
"""Run a Basic macro.
|
||
|
||
Arguments:
|
||
storage: The Basic macro storage, as a Storage object.
|
||
libname: The name of the dialog library.
|
||
macro: The The macro to run.
|
||
"""
|
||
if storage.type is "user":
|
||
factory = storage.oo.service_manager.DefaultContext.getByName(
|
||
"/singletons/com.sun.star.script.provider."
|
||
"theMasterScriptProviderFactory")
|
||
provider = factory.createScriptProvider("")
|
||
script = provider.getScript(
|
||
"vnd.sun.star.script:%s.%s"
|
||
"?language=Basic&location=application" %
|
||
(args.library, args.run))
|
||
script.invoke((), (), ())
|
||
else:
|
||
provider = storage.doc.getScriptProvider()
|
||
script = provider.getScript(
|
||
"vnd.sun.star.script:%s.%s"
|
||
"?language=Basic&location=document" %
|
||
(args.library, args.run))
|
||
script.invoke((), (), ())
|
||
|
||
|
||
def read_file(path, encoding):
|
||
"""Read a file, and deals with Python 3 / 2 compatibility.
|
||
|
||
Arguments:
|
||
path: The full path of the file.
|
||
encoding: The encoding of the file.
|
||
|
||
Returns:
|
||
The content of the file.
|
||
"""
|
||
if sys.version_info.major == 2:
|
||
f = open(path)
|
||
content = f.read().decode(encoding).replace("\r\n", "\n")
|
||
f.close()
|
||
else:
|
||
f = open(path, encoding=encoding)
|
||
content = f.read().replace("\r\n", "\n")
|
||
f.close()
|
||
return content
|
||
|
||
|
||
def write_file(path, content, encoding):
|
||
"""Write to a file, and deals with Python 3 / 2 compatibility.
|
||
|
||
Arguments:
|
||
path: The full path of the file.
|
||
content: The content of the file.
|
||
encoding: The encoding of the file.
|
||
"""
|
||
if sys.version_info.major == 2:
|
||
f = open(path, "w")
|
||
f.write(content.encode(encoding))
|
||
f.close()
|
||
else:
|
||
f = open(path, "w", encoding=encoding)
|
||
f.write(content)
|
||
f.close()
|
||
return
|
||
|
||
|
||
def update_file(path, content, encoding):
|
||
"""Update a file, and deals with Python 3 / 2 compatibility.
|
||
|
||
The file will only be update if its content is different from
|
||
the supplied content.
|
||
|
||
Arguments:
|
||
path: The full path of the file.
|
||
content: The new content of the file.
|
||
encoding: The encoding of the file.
|
||
|
||
Returns:
|
||
True if the file is updated, or False otherwise.
|
||
"""
|
||
is_updated = False
|
||
if sys.version_info.major == 2:
|
||
f = open(path, "r+")
|
||
if content != f.read().decode(encoding).replace("\r\n", "\n"):
|
||
f.seek(0)
|
||
f.truncate(0)
|
||
f.write(content.encode(encoding))
|
||
is_updated = True
|
||
f.close()
|
||
else:
|
||
f = open(path, "r+", encoding=encoding)
|
||
if content != f.read().replace("\r\n", "\n"):
|
||
f.seek(0)
|
||
f.truncate(0)
|
||
f.write(content)
|
||
is_updated = True
|
||
f.close()
|
||
return is_updated
|
||
|
||
|
||
class Storage:
|
||
"""A Basic macro storage.
|
||
|
||
Attributes:
|
||
oo: The office connection, as an Office() object.
|
||
doc: The office document component to store the macros.
|
||
libs: The Basic macro storage of the document.
|
||
"""
|
||
pass
|
||
|
||
|
||
class Office:
|
||
"""The OpenOffice/LibreOffice connection.
|
||
|
||
Attributes:
|
||
port: The TCP port that is used to communicate with the
|
||
OpenOffice/LibreOffice process. OpenOffice/LibreOffice
|
||
will listen to this port.
|
||
bootstrap_context: The bootstrap context of the
|
||
OpenOffice/LibreOffice desktop.
|
||
service_manager: The service manager of the
|
||
OpenOffice/LibreOffce desktop.
|
||
desktop: The OpenOffice/LibreOffice desktop object.
|
||
"""
|
||
|
||
def __init__(self, port=2002):
|
||
"""Initialize the OpenOffice/LibreOffice connection.
|
||
|
||
Arguments:
|
||
port: The TCP port to communicate with
|
||
OpenOffice/LibreOffice with. OpenOffice/LibreOffice
|
||
will start to listen on this port if it is not
|
||
listening on this port not yet. The default is
|
||
port 2002. Change it to another port if port 2002 is
|
||
already in use.
|
||
"""
|
||
self.port = port
|
||
self.bootstrap_context = None
|
||
self.service_manager = None
|
||
self.desktop = None
|
||
self.__connect()
|
||
|
||
def __connect(self):
|
||
"""Connect to the running OpenOffice/LibreOffice process.
|
||
|
||
Run OpenOffice/LibreOffice in server listening mode if it is
|
||
not running yet.
|
||
"""
|
||
# Obtain the local context
|
||
local_context = uno.getComponentContext()
|
||
# Obtain the local service manager
|
||
local_service_manager = local_context.getServiceManager()
|
||
# Obtain the URL resolver
|
||
url_resolver = local_service_manager.createInstanceWithContext(
|
||
"com.sun.star.bridge.UnoUrlResolver", local_context)
|
||
# Obtain the context
|
||
url = ("uno:socket,host=localhost,port=%d;"
|
||
"urp;StarOffice.ComponentContext") % self.port
|
||
while True:
|
||
try:
|
||
self.bootstrap_context = url_resolver.resolve(url)
|
||
except NoConnectException:
|
||
self.__start_oo()
|
||
else:
|
||
break
|
||
# Obtain the service manager
|
||
self.service_manager = self.bootstrap_context.getServiceManager()
|
||
# Obtain the desktop service
|
||
self.desktop = self.service_manager.createInstanceWithContext(
|
||
"com.sun.star.frame.Desktop", self.bootstrap_context)
|
||
|
||
def __start_oo(self):
|
||
"""Start OpenOffice/LibreOffice in server listening mode."""
|
||
# For MS-Windows, which does not have fork()
|
||
if os.name == "nt":
|
||
from subprocess import Popen
|
||
soffice = os.path.join(
|
||
os.path.dirname(uno.__file__), "soffice.exe")
|
||
DETACHED_PROCESS = 0x00000008
|
||
Popen([soffice,
|
||
"-accept=socket,host=localhost,port=%d;urp;" %
|
||
self.port],
|
||
close_fds=True, creationflags=DETACHED_PROCESS)
|
||
time.sleep(2)
|
||
return
|
||
|
||
# For POSIX systems, including Linux and MacOSX
|
||
try:
|
||
pid = os.fork()
|
||
except OSError:
|
||
print("Failed to fork().", file=sys.stderr)
|
||
sys.exit(1)
|
||
if pid != 0:
|
||
time.sleep(2)
|
||
return
|
||
os.setsid()
|
||
soffice = self.__find_posix_soffice()
|
||
if soffice is None:
|
||
print("Failed to find the "
|
||
"OpenOffice/LibreOffice installation.",
|
||
file=sys.stderr)
|
||
sys.exit(1)
|
||
param = "-accept=socket,host=localhost,port=%d;urp;" % \
|
||
self.port
|
||
# LibreOffice on POSIX systems uses --accept instead of
|
||
# -accept now.
|
||
if self.__is_soffice_lo(soffice):
|
||
param = "-" + param
|
||
try:
|
||
os.execl(soffice, soffice, param)
|
||
except OSError:
|
||
print("%s: Failed to run the"
|
||
" OpenOffice/LibreOffice server." % soffice,
|
||
file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
def __find_posix_soffice(self):
|
||
"""Find soffice on POSIX systems (Linux or MacOSX).
|
||
|
||
Returns:
|
||
The found soffice executable, or None if not found.
|
||
"""
|
||
# Check soffice in the same directory of uno.py
|
||
# This works for Linux OpenOffice/LibreOffice local
|
||
# installation, and OpenOffice on MacOSX.
|
||
soffice = os.path.join(
|
||
os.path.dirname(uno.__file__), "soffice")
|
||
if os.path.exists(soffice):
|
||
return soffice
|
||
|
||
# Now we have LibreOffice on MacOSX and Linux
|
||
# OpenOffice/LibreOffice vender installation.
|
||
|
||
# LibreOffice on MacOSX.
|
||
soffice = "/Applications/LibreOffice.app/Contents/MacOS/soffice"
|
||
if os.path.exists(soffice):
|
||
return soffice
|
||
|
||
# Linux OpenOffice/LibreOffice vender installation.
|
||
soffice = "/usr/bin/soffice"
|
||
if os.path.exists(soffice):
|
||
return soffice
|
||
|
||
# Not found
|
||
return None
|
||
|
||
def __is_soffice_lo(self, soffice):
|
||
"""Check whether the soffice executable is LibreOffice.
|
||
|
||
LibreOffice on POSIX systems accepts "--accept" instead of
|
||
"-accept" now.
|
||
|
||
Returns:
|
||
True if soffice is LibreOffice, or False otherwise.
|
||
"""
|
||
# This works for most cases.
|
||
if soffice.lower().find("libreoffice") != -1:
|
||
return True
|
||
|
||
# Check the symbolic link at /usr/bin/soffice
|
||
if soffice == "/usr/bin/soffice" and os.path.islink(soffice):
|
||
if os.readlink(soffice).lower().find("libreoffice") != -1:
|
||
return True
|
||
|
||
# Not found
|
||
return False
|
||
|
||
if __name__ == "__main__":
|
||
main()
|