注意: 虽然 JavaScript 对于本网站并非必不可少,但您与内容的交互将受到限制。请启用 JavaScript 以获得完整体验。

使用 Python 避免文档成本

介绍

本文展示了我如何将 Python、COM、DocBook、OpenJade 和 Word 集成在一起,为 BEACON(一个可视化编程环境)创建一个文档工具。这个文档工具用于我公司软件开发方法中的代码审查,并带来了显著的(超过 100 万美元)成本节约。

在开始这个项目之前,我没有任何使用 SGML、XML 或其他文档标记语言的经验。只有当我通过改编 Mark Hammond 的书《Win32 上的 Python 编程》中的直接 PythonCOM 到 Word 接口,几乎要重新发明标记语言的概念时,我才意识到从设计和维护的角度来看,应该有更好的方法来做这件事。

一次网络搜索为我提供了关于 XML、HTML 和 SGML 等标记语言的速成课程。我的搜索还让我深入了解了 DocBook SGML(一种流行的开放标准)和 OpenJade(一个可以将 DocBook SGML 转换为 Word 富文本的开源软件包)。

这种安排并不完美,但我很快意识到,我可以用它来节省一年的开发和维护成本,并更快地响应新的任务。

核心数据流

我的主要任务是将从我组织中分散的各种来源挖掘的任意数据转换为看起来合理的 Microsoft Word 97 报告。

我决定最好通过一个核心应用程序管道来处理,这些应用程序使用通用的数据约定相互协作。这个管道将由一个 Python 生成器应用程序控制,该应用程序将驱动一组前端转换器、一个内容插入器和一个后处理格式化程序来生成报告。Python 最近被选为我们部门用于自动化测试脚本的后继语言。我注意到了 Python 在测试之外的潜力,所以我获得了我的经理的许可,将其用于此任务。

此应用程序中的前端转换器从各种数据源收集内容(图片、表格、段落)并将其放入字典中。内容插入器创建一个 Word 文档并将字典中的值插入其中。后处理格式化程序获取结果并根据最新的公司 Word 格式样式模板对其进行修改。

此流程旨在应对我们部门内不同团队和公司级标准的需求变化。例如,报告的布局由生成器应用程序中的布局类确定,该布局类可以用其他类替换以支持新型报告。

我需要创建的第一个前端转换器是从一个名为 BEACON 的航空航天行业软件可视化编程工具构建的递归属性列表中获取图片、表格和数据。通过这个转换器,我能够获得大量的样本数据,适合测试 Word 内容插入器。

插入器的问题

第一个版本的内容插入器基于《Win32 上的 Python 编程》中演示的原则,其中详细描述了 Python 如何使用 Word 97 COM 对象模型创建和操作 Word 文档。此实现通过直接通过 COM 接口驱动 Word 将内容插入到 Word 文档中。

不幸的是,COM 接口太慢,无法处理从 BEACON 源代码中提取的大量表格单元格。

更糟糕的是,我为处理不同部分、标题、段落和短语的不同样式要求而编写的类的重用问题。

为了解决这些问题,我考虑编写 ASCII 文本文件来为标准表单指定边距、字体、标题级别和插入点。但是,发明我自己的文本指定排版标准将耗时且成本高昂,无论是在开发还是维护方面。

我真正需要的是一个 Python API,可以快速为有限的一组文档在 Word 97 中生成排版精美的副本,但在 2001 年,没有可用的东西可以做到这一点。我唯一的选择是找到一个开放的排版标准,该标准可以在文档生成后转换为 Word 97 格式。

寻找标准

为了找到解决方案,我花了一些时间调查可用的开源排版解决方案。最流行的两个是 TeXDocBook,两者都由用 C 编写的开源实现支持。

选择 DocBook 是因为它具有更明确定义和记录的排版元素生产规则。《DocBook,权威指南》在详细解释这些规则方面确实是一个宝藏。

DocBook 提供了一组以 DSSSL(文档样式语义和规范语言)编写的文档类型定义 (.DTD) 和文档样式表 (.DSL) 文件。DocBook 分为两种版本,一种用于 SGML,另一种用于 XML 文档编码。SGML 和 XML 都具有类似的标签嵌套结构以及 DTD 和 DSL 的相同逻辑结构。但是,XML 规则在 2001 年仍在开发中,因此我选择了更可靠和成熟的 SGML 规则集。

DocBook 还提供了编写本地文档类型定义和样式表的能力,称为Local.DTDLocal.DSL分别。这些允许在 DocBook 提供的基础上引入额外的文档元素及其呈现。

例如,我公司的一些团队希望最终 Word 文档中的功能相当于 SGML 中任意的 Word 字段代码支持。

为了支持这一点,我编写了一个本地 DocBook 样式表和定义文件对(Local.DSL, Local.DTD)以发出 RTF 中对应于所需字段代码的序列。RTF 需要未转义的字符{, }, 和\\, 所以我修改了 OpenJade,以将样式表中的 Unicode 映射到这些未转义的字符。0xFFFD, 0xFFFE, 和0xFFFF在样式表中映射到这些未转义的字符。

我还找到了 docbook-apps 邮件列表中的一篇 存档帖子,这对于将 DocBook 下载组件的内容对齐到可行的层次结构中非常有帮助。

DocBook 的 Python API 示例

为了支持使用 DocBook 开发必要的内容插入器,我需要一个 Python API,该 API 可用于快速生成 SGML 格式的文档。为此 API 选择的设计为 DocBook Python 模块中的每个文档元素类型提供抽象类。这些抽象类可以在定义特定文档结构的代码中继承,并且可以任意嵌套,以便每个类都映射到输出文档结构的不同级别或部分。

例如,假设我们想生成以下表格作为 Word 文档的一部分

名称 类型
statex 整数
statey 长整型

用于此表格的 SGML 文本以Local.DSLLocal.DTD的形式编写

<!DOCTYPE informaltable SYSTEM "C:\Local.dtd">
<informaltable frame='all'>
<tgroup cols='2' colsep='1' rowsep='1' align='center'>
<colspec colname='Name' colwidth='75' align='left'></colspec>
<colspec colname='Type' colwidth='64' align='center'></colspec>
<thead>
<row>
<entry><emphasis role='bold'>Name</emphasis></entry>
<entry><emphasis role='bold'>Type</emphasis></entry>
</row>
</thead>
<tbody>
<row>
<entry><phrase role='xe' condition='italic'>statex</phrase></entry>
<entry>Integer</entry>
</row>
<row>
<entry><phrase role='xe' condition='italic'>statey</phrase></entry>
<entry>Long</entry>
</row>
</tbody>
</tgroup>
</informaltable>

这是通过基于 DocBook 类树生成上述 SGML 的 Python 列表

from DocBook import DocBook

class ItalicIndexPhrase (DocBook.Rules.Phrase):

    "italic indexible text phrase"
    TITLE    = DocBook.Rules.Phrase
    def __init__        (self, text):
        DocBook.Rules.Phrase.__init__ (self, 'xe', 'italic')
        self.data = [ text ]

class NameCell          (DocBook.Rules.Entry):

    "table row cell describing name of identifier (italic and indexible text!)"
    TITLE    = DocBook.Rules.Entry

    def __init__        (self, text):
        DocBook.Rules.Entry.__init__ (self)
        self.data = [ ItalicIndexPhrase (text) ]

class StorageCell       (DocBook.Rules.Entry):

    "table row cell describing storage type of identifier (ordinary text)"
    TITLE    = DocBook.Rules.Entry
    def __init__        (self, text):
        DocBook.Rules.Entry.__init__ (self)
        self.data = text

class TRow              (DocBook.Rules.Row):

    "each row in application's informal table body"
    TITLE    = DocBook.Rules.Row
    def __init__        (self, binding):
        (identifier, storage) = binding
        DocBook.Rules.Row.__init__ (self, [ NameCell    (identifier),
                                            StorageCell (storage)
                                          ])

class TBody             (DocBook.Rules.TBody):

    "application's informal table body"
    TITLE    = DocBook.Rules.TBody

    def __init__        (self, items):
        DocBook.Rules.TBody.__init__ (self, map (TRow, items))


class TGroup            (DocBook.Rules.TGroup):

    "application's informal table group"

    COLSPECS = [ DocBook.Rules.ColSpec ('Name', 75, 'left'),
                 DocBook.Rules.ColSpec ('Type', 64, 'center')
               ]

    SHAPE    = [ '2', '1', '1', 'center' ]
    TBODY    = TBody


class InformalTable     (DocBook.Rules.InformalTable):

    "application's informal table"
    TGROUP   = TGroup

class Example           (DocBook):

    'example application of DocBook formatting class'
    SECTION  = str  (InformalTable)
    def __call__    (self):
        self.data = [ InformalTable ()(self.data) ]
        return DocBook.__call__ (self)

if __name__ == '__main__':
    print Example ([('statex', 'Integer'), ('statey', 'Long')]) ()

OpenJade 接口

OpenJade 是一个开源产品,它提供了一种从 SGML 编码文档获取 Microsoft 富文本格式 (RTF) 的方法。它读取 DocBook DSSSL 样式表和用户的本地 DSSSL 样式表(如果有)。DSSSL 在用户的 SGML 源文本上执行,以编写一个最终文档以加载到用户的文字处理器中。

对于这个项目,我们希望自动生成 Microsoft Word 可读的文件,因此 OpenJade 被设置为发出 Microsoft Word 富文本文件。OpenJade 作为命令行应用程序运行,因此可以使用 Python 代码通过Popen4Python 标准库调用轻松控制。

使用 PythonCOM 的 Word 自动化进行后处理

OpenJade 创建的 Microsoft 富文本格式文件在整体外观上非常吸引人。但是,它们不符合公司格式化 Word 文档文件的许多标准。

编写了一个本地 DSSSL 样式表 (Local.DSL) 来覆盖几个默认的 DocBook DSSSL 设置,并使它们与公司标准保持一致。但是,这并没有解决在文档中设置标准化的 Word 样式标识符名称的需求。

为了解决这个问题,在文档管道的最后阶段需要一个重新格式化程序。它以 COM 对象的形式访问 Word,以便遍历生成的 RTF 文档对象模型中各个级别的表格、图形、标题和节级样式标识符。在遍历过程中,它会重命名样式标识符,以符合我们本地制图部门作为标准分发的 Microsoft Word 文档模板 (.DOT) 文件中提供的标识符。

完成此转换后,后处理器将完成的文档以 Microsoft Word 文档格式保存。

所有后处理任务都不是特别困难。一旦充分理解了 Win32 应用程序的 COM 接口,该应用程序就会在 Python 开发人员手中退化为另一个库。

投资回报

用于推导此处提出的投资回报 (ROI) 数字的假设是保守的。

我花费了 2001 年的大部分时间使用本文中的想法开发了一个系统,以自动将 BEACON 可视化编程语言文件中的内容直接转换为 Word 文档。2002 年,我还对软件进行了重大修订。我在开发、维护和支持方面的总努力大约是两年期间的一半时间。

在 2002 年至 2003 年间,我的部门有 5 个正在进行的项目,这些项目处于不同的开发阶段,复杂度各异,从 30 个可视化编程文件到多达 150 个,平均可能为 75 个。

在这两年中,对于每个项目,至少有 2 次主要的强制发布,其中每个文件的重要内容都必须经过同行评审:由不少于 3 位工程师(主持人、作者和检查员)同时进行详细检查。

每次发布都需要将每个可视化编程文件渲染为可查看的硬拷贝格式,其中包含其所有图表;以及一个交叉引用的表格,其中包含每个图表中的所有标识符,包括存储类、范围、初始值、文档和其他字段。

可视化编程语言 GUI 应用程序 BEACON 没有全面的硬拷贝生成器。相反,需要一名入门级工程师在适度监督下,通过在 UNIX 上运行 BEACON 并通过 UNIX 到 Win32 X 终端模拟器检查文件,手动将文本从 X 终端传输到打开的 Word 文档中。

最不复杂的文件(约占 20%)需要半天时间。大部分文件(60%)平均需要一整天时间。最复杂的文件(约占 20%)至少需要 2 天时间。

这浪费了大量的工程劳动力,这些劳动力本可以更好地用于提高我部门的软件产品质量。

Each project release:       1/5 * 75 *  4 hours     =           60  hours
                            3/5 * 75 *  8 hours     =          360  hours
                            1/5 * 75 * 16 hours     =          240  hours
                                                            -------------
                                                               660  hours

Two major releases per year:        * 2             =        1 320  hours
Five projects needing releases:     * 5             =        6 600  hours
Two year period (2002-2003)         * 2             =       13 200  hours
Total effort avoided:                                       13 200  hours

Automated releases over 2 year period:                         160  hours
My effort (12 * 140 hours per labor month):                  1 680  hours
Total investment:                                            1 840  hours

Net effort avoided, 2002-3:                                 11 360  hours
Net cost avoided by customers 2002-3 at $100/hour        1 136 000  dollars
Net labor years avoided at 1680 hours/year:                   6.76  years
Head count avoided per year:                                  3.38  people

ROI (Total effort avoided / total invested) 2002-3:           7.17

从上表可以看出,仅为正式发布给客户生成文档的自动化所带来的投资回报,显然帮助我的部门避免了大量的人工成本。

Python 和 DocBook 结合在一起,被证明是消除现实业务流程瓶颈的强大组合。

结论

我的部门决定采用 Python 并允许我将其与另一个开放标准 DocBook 一起使用,这已被中长期内的巨额投资回报所证实,即使仅仅在避免的文档成本方面也是如此。