使用 Python 启动、停止和连接 OpenOffice

作者:Mitch Frazier

通过 pyuno,您可以使用 Python 编写 OpenOffice 脚本。 Pyuno 允许您在 OpenOffice 内部创建宏,并且还允许您创建外部 Python 脚本与正在运行的 OpenOffice 实例进行通信。 如果您想开始使用 pyuno,请做好面对挫败的准备:文档零散且通常难以找到,并且嵌入在 OpenOffice 中的 Python 版本已经停留在 2.3 版本很长时间了。

Pyuno 是实现 OpenOffice UNO 接口的 Python 层。 UNO 是一个独立于应用程序编程语言的 OpenOffice 接口。 UNO 在 IDL 中指定,并且 pyuno 的唯一文档通常是 UNO 的 IDL 文档

这里提供的代码在 OpenOffice 外部运行,并展示了如何在“无头”模式下启动、停止和连接 OpenOffice。 以“无头”模式运行 OpenOffice 意味着 OpenOffice 不显示窗口,而是等待 UNO 操作(通过 TCP/IP 端口)。 这里提供的代码使用 Python 2.5 版本和 OpenOffice 2.4 版本进行了测试。 我还使用 OpenOffice 3.0 版本进行了测试,但只有在使用 OpenOffice 自带的 Python 版本时才能运行。

代码主要由 Python 类组成OORunner该类具有连接到 OpenOffice、启动 OpenOffice 和关闭 OpenOffice 的方法。 该代码还跟踪它启动的所有 OpenOffice 副本,并在退出时关闭所有副本。 此代码基于我在这里找到的一些代码,我重构了连接并添加了从 Python 启动和停止 OpenOffice 的能力。 文件名是ooutils.py.

# OpenOffice utils.
#
# Based on code from:
#   PyODConverter (Python OpenDocument Converter) v1.0.0 - 2008-05-05
#   Copyright (C) 2008 Mirko Nasato <mirko@artofsolving.com>
#   Licensed under the GNU LGPL v2.1 - or any later version.
#   https://gnu.ac.cn/licenses/lgpl-2.1.html
#

import sys
import os
import time
import atexit


OPENOFFICE_PORT = 8100

# Find OpenOffice.
_oopaths=(
        ('/usr/lib64/ooo-2.0/program',   '/usr/lib64/ooo-2.0/program'),
        ('/opt/openoffice.org3/program', '/opt/openoffice.org/basis3.0/program'),
     )

for p in _oopaths:
    if os.path.exists(p[0]):
        OPENOFFICE_PATH    = p[0]
        OPENOFFICE_BIN     = os.path.join(OPENOFFICE_PATH, 'soffice')
        OPENOFFICE_LIBPATH = p[1]

        # Add to path so we can find uno.
        if sys.path.count(OPENOFFICE_LIBPATH) == 0:
            sys.path.insert(0, OPENOFFICE_LIBPATH)
        break


import uno
from com.sun.star.beans import PropertyValue
from com.sun.star.connection import NoConnectException


class OORunner:
    """
    Start, stop, and connect to OpenOffice.
    """
    def __init__(self, port=OPENOFFICE_PORT):
        """ Create OORunner that connects on the specified port. """
        self.port = port


    def connect(self, no_startup=False):
        """
        Connect to OpenOffice.
        If a connection cannot be established try to start OpenOffice.
        """
        localContext = uno.getComponentContext()
        resolver     = localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", localContext)
        context      = None
        did_start    = False

        n = 0
        while n < 6:
            try:
                context = resolver.resolve("uno:socket,host=localhost,port=%d;urp;StarOffice.ComponentContext" % self.port)
                break
            except NoConnectException:
                pass

            # If first connect failed then try starting OpenOffice.
            if n == 0:
                # Exit loop if startup not desired.
                if no_startup:
                     break
                self.startup()
                did_start = True

            # Pause and try again to connect
            time.sleep(1)
            n += 1

        if not context:
            raise Exception, "Failed to connect to OpenOffice on port %d" % self.port

        desktop = context.ServiceManager.createInstanceWithContext("com.sun.star.frame.Desktop", context)

        if not desktop:
            raise Exception, "Failed to create OpenOffice desktop on port %d" % self.port

        if did_start:
            _started_desktops[self.port] = desktop

        return desktop


    def startup(self):
        """
        Start a headless instance of OpenOffice.
        """
        args = [OPENOFFICE_BIN,
                '-accept=socket,host=localhost,port=%d;urp;StarOffice.ServiceManager' % self.port,
                '-norestore',
                '-nofirststartwizard',
                '-nologo',
                '-headless',
                ]
        env  = {'PATH'       : '/bin:/usr/bin:%s' % OPENOFFICE_PATH,
                'PYTHONPATH' : OPENOFFICE_LIBPATH,
                }

        try:
            pid = os.spawnve(os.P_NOWAIT, args[0], args, env)
        except Exception, e:
            raise Exception, "Failed to start OpenOffice on port %d: %s" % (self.port, e.message)

        if pid <= 0:
            raise Exception, "Failed to start OpenOffice on port %d" % self.port


    def shutdown(self):
        """
        Shutdown OpenOffice.
        """
        try:
            if _started_desktops.get(self.port):
                _started_desktops[self.port].terminate()
                del _started_desktops[self.port]
        except Exception, e:
            pass



# Keep track of started desktops and shut them down on exit.
_started_desktops = {}

def _shutdown_desktops():
    """ Shutdown all OpenOffice desktops that were started by the program. """
    for port, desktop in _started_desktops.items():
        try:
            if desktop:
                desktop.terminate()
        except Exception, e:
            pass


atexit.register(_shutdown_desktops)


def oo_shutdown_if_running(port=OPENOFFICE_PORT):
    """ Shutdown OpenOffice if it's running on the specified port. """
    oorunner = OORunner(port)
    try:
        desktop = oorunner.connect(no_startup=True)
        desktop.terminate()
    except Exception, e:
        pass


def oo_properties(**args):
    """
    Convert args to OpenOffice property values.
    """
    props = []
    for key in args:
        prop       = PropertyValue()
        prop.Name  = key
        prop.Value = args[key]
        props.append(prop)

    return tuple(props)

使用该类非常简单:您只需创建它的一个实例并调用connect()在该实例上。 Connect 返回 OpenOffice 的桌面对象。 这是您用于与 OpenOffice 进行大多数交互的对象。

   oor     = ooutils.OORunner()
   desktop = oor.connect()
   # Do something with the "desktop"

正如我提到的,还有启动和关闭方法,但您实际上不需要调用它们:connect 方法在无法连接时将调用启动方法,并且 atexit 代码将在退出时关闭所有已启动的 OpenOffice 副本。 还有一个函数oo_shutdown_if_running()可以调用该函数来关闭正在运行的桌面。

下周我将向您展示如何使用此处提供的代码编写 Python 程序将电子表格转换为 CSV 文件。

Mitch Frazier 是 Emerson Electric Co. 的一名嵌入式系统程序员。 自 2000 年代初以来,Mitch 一直是Linux Journal 的贡献者和朋友。

加载 Disqus 评论