第7章 *Microsoft Office编程
无论你做什么,总会有一个限制因素决定你完成它的速度和程度。你的工作就是对任务进行研究,找出那个限制因素。然后集中你所有的力量,消除这个瓶颈。
——Brain Tracy,2001年3月
(Eat That Frog,2001年,Berrett-Koehler)
本章内容:
简介;
使用Python进行COM客户端编程;
入门示例;
中级示例;
相关模块/包。
注意:本章中的示例都需要使用Windows操作系统,使用 Mac 系统的苹果电脑无法运行本章的Microsoft Office示例。
本章将与本书的大部分章节有所区别,不再关注网络开发、GUI、Web 或基于命令行的应用,而是使用 Python 做一些完全不同的事情:通过组件对象模型(COM)客户端编程控制专有软件,具体来说就是Microsoft Office应用。
7.1 简介
无论开发者是否喜欢,都无法否认他们生活在一个需要与基于Windows的PC进行交互的世界里。它可能只是间歇性出现的,也可能是你每天都必须处理的事情,不过无论你所面对的出现频率如何,Python总能够使我们的生活变得更简单。
在本章中,我们将学习通过使用Python进行COM客户端编程,从而能够控制诸如Word、Excel、PowerPoint和Outlook等Microsoft Office应用,并能够与之进行通信。COM是一个服务,通过该服务可以使PC应用与其他应用进行交互。具体而言,Office套件中那些知名的应用提供了COM服务,而COM客户端编程可以用来驱动这些应用。
传统意义上,COM 客户端一般使用两种非常强大但又非常不同的工具来编写,分别是Microsoft Visual Basic(VB)/Visual Basic for Application(VBA)和(Visual)C++。对于 COM编程而言,Python一般被视为一种可行的替代品,因为它比VB更加强大,又比C++开发有着更好的表现力和更少的时间消耗。
有一些更新的工具,如 IronPython、.NET、VSTO,也可以帮助你编写与 Office 工具通信的应用,不过如果你去研究其底层,就会发现它们同样是 COM,所以即使你使用了更加先进的工具,本章中的内容依旧可以适用。
本章既可以使COM开发者学会如何在其世界中应用Python,也可以让Python程序员学会如何创建 COM 客户端来自动执行任务,比如,生成 Excel 表格、创建 Word 文档、使用PowerPoint建立幻灯片演示以及通过Outlook发送邮件等。我们将不会讨论COM的原则或概念,或者思考“为什么是 COM”。此外,我们也不会学习有关 COM+、ATL、IDL、MFC、DCOM、ADO、.NET、IronPython、VSTO等工具的知识。
取而代之的是,我们将会让你专注于学习如何使用Python与Office应用通信,进行COM客户端编程。
7.2 使用Python进行COM客户端编程
在日常的业务环境中,你能够做的最有用的事情之一就是整合对Windows应用的支持。能够对这些应用进行数据读写通常是非常方便的。虽然你的部门可能并没有运行在Windows环境中,但是很有可能你的管理者或者其他项目组使用了Windows环境。Mark Hammond编写的Windows Extnesions for Python允许程序员在原生环境中与Windows应用进行交互。
Windows编程的领域正在不断扩大,其中大多数都来自于Windows Extensions for Python包。它包括:Windows API、派生进程、MFC GUI开发、Windows多线程编程、服务、远程访问、管道、服务器端COM编程以及事件。本章后续部分会重点讨论Windows领域中的一个重要部分:COM客户端编程。
7.2.1 客户端COM编程
我们可以使用 COM(其商业名称为 ActiveX)与诸如 Outlook、Excel等工具进行通信。对于开发者而言,其乐趣在于能够直接通过他们的 Python 代码来“控制”原生的Office应用。
具体来说,比如,在讨论COM对象的使用时,启动应用并允许代码访问应用的方法和数据,这称为 COM 客户端编程;而 COM 服务器端编程则是用于客户端访问的 COM对象的实现。
核心提示:Python和Microsoft COM(客户端)编程
Python在Windows 32位平台上包含了对COM的连通性,Microsoft的接口技术允许对象与其他对象进行通信,从而促进更高级别的应用与其他应用之间的通信,而不需要任何对语言或格式的依赖。在本节中我们会看到Python和COM(客户端编程)是如何结合起来,提供独特的时机用于创建能够直接与 Microsoft Office 应用(如 Word、Excel、PowerPoint和Outlook)进行通信的脚本的。
7.2.2 入门
本节的前提条件包括:使用一台运行32位或64位Windows系统的PC(或包含虚拟机的其他系统);必须安装有.NET 2.0(至少)、Python以及Python Extensions for Windows(可以从http://pywin32.sf.net获取该扩展);必须有至少一个可用的Microsoft应用用于尝试下面的示例。你可以在命令行中进行开发,也可以使用Extensions分发版本中的PythonWin IDE进行开发。
我必须承认自己并不是COM方面的专家,也不是Microsoft软件开发者,不过我有足够的能力来向你展示如何使用Python控制Office应用。当然,这里的例子还有很大的改进空间。所以恳请读者给我们写信,以普通读者的角度,提出意见、建议和改进方案。
本章后面几节是由一些示例应用组成的,这些例子可以让你对每种主要的 Office 应用编程有个初步认识;然后,会有几个中等难度的示例。在给出这些例子之前,需要指出客户端COM应用在执行中都遵循相似的步骤。这些应用进行交互的典型方式类似下面的步骤。
1.启动应用。
2.添加合适的文档以工作(或载入一个已经存在的文档)。
3.使应用可见(根据需要)。
4.执行文档所需的所有工作。
5.保存或放弃文档。
6.退出。
讨论就到这里,现在让我们看一些代码。接下来的一节会包含很多脚本,其中的每个脚本都会控制一种不同的Microsoft应用。所有脚本都会导入win32com.client模块以及一些Tk模块来控制应用的启动(和完成)。此外,和第5章一样,我们使用了.pyw扩展名,从而让不需要的DOS命令行窗口不再显示。
7.3 入门示例
本节将给出几个基础示例,使你在4个主流的Office应用开发中能够入门,这4个Office应用分别是:Excel、Word、PowerPoint和Outlook。
7.3.1 Excel
我们在第一个例子中使用的是Excel。在所有 Office套件中,我们发现Excel是可编程化最好的应用。向 Excel 中传输数据非常有用,因为你既可以利用表格的功能,又能够以一种很好的可打印格式显示数据。此外,从电子表格中读取数据并通过真实编程语言(如Python)的功能来执行,也是非常有用的。本节最后还会给出一个更复杂的例子,不过我们必须先要入门才可以,所以先从示例7-1开始。
示例7-1 Excel示例(excel.pyw)
本脚本会启动Excel并向电子表格的单元格中写入数据。
逐行解释
第1~6行、第31行
我们导入了Tkinter和tkMessageBox模块,仅用于在演示结束后使用showwarning消息框。在对话框出现(第26行)之前,我们使用了withdraw()方法不让Tk顶级窗口出现(第31行)。如果没有事先初始化顶级窗口,那么Tk会为你自动创建一个,而且不会将其隐藏,这样会在你的屏幕上造成一定的干扰。
第11~17行
当代码启动(或者“调度”)Excel后,我们添加了一个工作簿(一个包含了多个可写入数据的工作表的电子表格,这些工作表在工作簿中以标签的形式进行组织),然后取得了活动工作表(显示的那个工作表)的句柄。不要过分纠结于这些术语,因为“电子表格包含多个工作表”这种话很容易使人困惑。
核心提示:静态和动态调度
第13行使用了静态调度。在开始脚本之前,我们在PythonWin中运行了Makepy工具(启动IDE,选择Tools →COM Makepy utility,然后选择适合的应用对象库)。该工具会创建应用所需的对象并进行缓存。如果没有这个准备工作,对象和属性则需要在运行时构建,那样就是动态调度了。如果你希望动态运行,那么可以使用Dispatch()函数。
xl = win32com.client.Dispatch('%s.Application' % app)
Visible标记必须设为True,这样应用才能够在桌面上可见;而暂停可以让你看清演示中的每一步(第16行)。
第19~24行
在该脚本中的应用部分,首先在第一个单元格(左上角、A1或者(1, 1))写入演示的标题。然后跳过一行,并将“Line N”写到对应的单元格中(N为3~7之间的数字),每写入一行停顿1秒,从而可以让你看到实时更新(如果没有延时,单元格的更新将会发生得非常快。这就是脚本中贯穿了对sleep()函数的调用的原因)。
第26~32行
一个警告对话框会在演示结束后出现,指明你可以在观察到输出结果后退出。电子表格在关闭 时 并 不 会 进 行 保 存,这 里 使 用 了ss.Close([SaveChanges=]False),然后应用就会退出了。最后,脚本的“main”函数部分对Tk进行初始化,并运行应用的核心部分。
运行本脚本时会弹出一个Excel应用窗口,如图7-1所示。
图7-1 Python-to-Excel演示脚本(excel.pyw)
7.3.2 Word
下一个演示脚本使用的是Word。使用Word编写文档没有那么好的可编程性,因为不会涉及太多的数据。不过,你可以考虑使用 Word 生成套用信函。在示例 7-2 中,创建一个文档,并逐行写入文本。
Word的这个示例和Excel的那个示例非常相似。唯一的不同是,Excel中是写入单元格,而在Word中则是在文档的文本“范围”内插入字符串,并在每行写入后将光标移到下一行。这里必须手动给出行结束符:回车换行(\r\n)。
当运行脚本时,其运行结果如图7-2所示。
图7-2 Python-to-Word演示脚本(word.pyw)
示例7-2 Word示例(word.pyw)
本脚本会启动Word,并在文档中写入数据。
7.3.3 PowerPoint
在应用中使用PowerPoint并不十分常见,不过你可以考虑在匆忙赶制演示文稿时使用这种应用。比如,你可以在飞机上先将要点写入文本文件中,然后当晚上到达酒店后,使用脚本解析文件并自动生成一组幻灯片。更进一步,你还可以为这些幻灯片添加背景、动画等,所有一切都可以通过 COM 接口完成。另一个用例是在你必须自动生成或修改新的或已存在的演示文稿时。此时你可以通过shell脚本控制创建COM脚本,从而创建和调整每个演示文稿。下面让我们看一下示例7-3中的PowerPoint示例。
示例7-3 PowerPoint示例(ppoint.pyw)
本脚本会启动PowerPoint,并在幻灯片中将数据写入文本框中。
你看到的这个代码和之前的Excel以及Word演示都很相似。而PowerPoint与之不同的地方是写入数据的对象。不同于单个活动工作表或文档,PowerPoint 的情况比较麻烦,因为一个演示文稿中会包含很多张幻灯片,而每张幻灯片都可能有不同的布局(PowerPoint的最新版本中包含了30种不同的布局!)。在一张幻灯片中可以执行的操作需要依赖于你所选择的布局。
在该例子中,使用了标题和文本布局(第17行),把主标题(第19~20 行)填充到Shape[0](即Shape(1))中,而把文本部分(第22~26 行)填充到Shape[1](即Shape(2))中。需要注意,这里Shape的写法不同,是因为Python中是从0开始索引的,而在Windows软件中则是从1开始索引的。为了找出使用哪个常量,你需要有所有可用常量的一个列表。比如,ppLayoutText的常量值为2(整型),而 ppLayoutTitle 则是 1。你可以在大多数Microsoft VB/Office编程书籍中找到这些常量,也可以在网上通过搜索它们的名字进行查找。此外,你可以直接使用整型常量,而不必通过win32.constants命名它们。
PowerPoint示例的屏幕截图如图7-3所示。
图7-3 Python-to-PowerPoint演示脚本(ppoint.pyw)
7.3.4 Outlook
最后,我们给出Outlook演示,在Outlook中会使用比PowerPoint更多的常量。作为一个非常常见和通用的工具,Outlook的使用像Excel一样在应用中非常有意义。在Python程序中,可以轻松处理邮件地址、邮件消息及其他数据。示例7-4 是一个Outlook 示例,它比之前的几个例子功能更多一些。
示例7-4 Outlook示例(olook.pyw)
本脚本会启动Outlook,创建一封新邮件然后将其发送,并且可以让你通过Outbox和邮件本身打开进行查看。
在本示例中,我们使用了 Outlook 向我们自己发送一封邮件。为了使该演示可以正常运行,你需要关闭网络连接,从而使邮件消息不会真正发送出去,而可以在你的Outbox文件夹中看到它(如果你愿意,可以在查阅后删除该邮件)。在启动Outlook之后,我们创建了一封新的邮件,并填充了几个字段,比如接收人、主题、邮件正文内容等(第15~21行)。然后调用了send()方法(第 22行),使邮件假脱机进入Outbox,一旦邮件被传递到邮件服务器中,该邮件就会从Outbox文件夹移出,进入“已发送邮件”文件夹中。
和PowerPoint一样,这里也有很多可用的常量,olMailItem(其常量值为0)是用于邮件消息的一个常量。Outlook中其他常用的项目还包括:olAppointmentItem(1)、olContactItem (2)以及olTaskItem(3)。当然,还有更多的常量可用,你可以查阅VB/Office编程书籍或者在网上搜索常量及其值,以获取更多信息。
下一部分(第24~27行)使用了另一个常量:olFolderOutbox(4),用于打开Outbox文件夹并进行显示。我们会找到最新创建的几封邮件(希望包含我们刚刚创建的那封)进行显示。另一些常用的文件夹包括:olFolderInbox(6)、olFolderCalendar(9)、olFolderContacts(10)、olFolderDrafts(16)、olFolderSentMail(5)以及olFolderTasks(13)。如果你使用的是动态调度,你可能必须使用数值而不是常量名(参见前面的核心提示)。
图7-4所示为邮件窗口的截屏。
图7-4 Python-to-Outlook演示脚本(olook.pyw)
在我们更进一步之前,需要知道 Outlook 总是遭受各种各样的攻击,所以Microsoft内置了很多防护措施,来限制访问地址簿以及代表你发送邮件的能力。当尝试访问Outlook 数据时,屏幕上会显示类似图7-5 所示的弹窗,在这里你必须显式地给外部程序赋予权限。
接下来,当你尝试从一个外部程序发送邮件时,会出现图7-6所示的警告对话框;你必须等到时间条耗尽才能选择Yes按钮。
图7-5 Outlook地址簿访问警告
图7-6 Outlook邮件发送警告
当你通过所有安全检查后,其他一切事情都会很顺利地运行。有一些软件可以帮助你绕过这些检查,不过它们需要单独下载和安装。
在本书的配套网站http://corepython.com中,你可以找到一个应用脚本,它把这4个小示例都组合到了一起,在该脚本中会允许用户自己选择运行哪个示例。
7.4 中级示例
到目前为止,在本章中看到的例子都是用于让你对使用Python控制Microsoft Office产品进行入门的。现在让我们看几个在真实世界中有用的应用,其中的一些已经在我的日常工作中进行了使用。
7.4.1 Excel
在本例中,我们将把本章所讲的内容与第13章的内容结合起来。在第13章中,示例13-1的stock.py脚本使用Yahoo!金融服务并请求得到股票行情数据。而示例7-5会展示如何把Excel演示脚本与股票行情示例合并起来。最后,我们会得到一个应用,可以从网上下载股票行情,然后将其直接插入Excel中,而不必创建或使用CSV文件作为中转。
示例7-5 股票行情与Excel示例(estock.pyw)
本脚本从Yahoo!下载股票行情,然后把数据写入Excel中。
逐行解释
第1~13行
请先对第 13 章进行一些了解,在那里使用了一个简单的脚本用于从 Yahoo!金融服务获取股票行情。在本章中,我们会将该脚本的核心组件整合到本例中,将其数据导入到 Excel电子表格中。
第15~32行
核心函数的第一部分是启动 Excel(第 17~21 行),这里和之前的例子是一样的。然后将标题和时间戳写入单元格中(第23~29行),接下来写入列标题并将其加粗(第30行)。从表格的第6行开始,剩下的单元格用于写入真实的股票行情数据(第32行)。
第34~43行
同第13章的例子一样,打开URL(第34行),不过这里不再将数据写入标准输出,而是填充到单元格当中,每次一列数据,每个公司一行(第35~42行)。
第45~51行
脚本中剩下的行复制了之前用过的代码。
图7-7展示了脚本执行后写有真实数据的窗口。
需要注意的是,数据列丢失了数值字符串的原有格式,这是因为Excel使用了默认的单元格格式将其另存为数值型。原本小数点后包含两位数字的格式在这里会丢失,比如,尽管 Python 传入的是“34.20”,显示的却是“34.2”。对于“较上次收盘的变动”一列而言,除了小数点后位数丢失外,值前面表示上涨的“+”号也丢失了(对比 Excel 的输出以及第13章中示例13-1 [stock.py]的原始文本输出版本。这些问题会作为本章结尾处的练习进行解决)。
图7-7 Python-to-Excel 股票行情演示脚本(estock.pyw)
7.4.2 Outlook
起初,我们希望给读者一个Outlook脚本的示例,它用来操纵地址簿或者发送和接收邮件。不过,鉴于Outlook的安全问题,我们决定避免这些类别,而改为给出一个非常有用的例子。
像我们这些平常使用命令行构建应用的人,一般都会需要某种文本编辑器来协助我们工作。不考虑不同编辑器拥趸之间的争论,这些工具包括:Emacs、vi(及其更加现代化的替代品Vim或gVim)及一些其他编辑器。对于这些工具的用户而言,使用Outlook对话框窗口编辑邮件回复可能并不是他们所喜欢的方式。所以这里使用 Python 来满足他们的愿望。
本脚本受到了John Klassa于2001年创建的原始版本的启发,并且本脚本也很简单:当你在 Outlook 中回复邮件时,它会启动你所选择的编辑器,并将当前编辑对话框窗口中的邮件回复内容传进去,允许你使用自己喜欢的编辑器编辑剩余的邮件,然后在退出时,用你刚刚编辑的文本替代对话框窗口中的内容。最终你只需要单击Send按钮即可。
可以从命令行运行该工具。我们将其命名为outlook_edit.pyw。.pyw扩展名用于抑制终端的显示,其目的是运行一个用户命令行交互非必需的GUI应用。在看代码之前,先描述一下它是如何工作的。当它启动时,你会看到一个简单的用户界面,如图7-8所示。
图7-8 Outlook的邮件编辑器GUI控制面板(outlook_edit.pyw)
当你使用电子邮件时,可能有一封邮件需要你回复,因此你需要单击Reply按钮,然后弹出类似图7-9所示的窗口(当然,不包括其中的内容)。
图7-9 标准的Outlook回复对话框窗口
现在,相比于在这个简陋的对话框窗口中进行编辑而言,你可能更希望使用一个不同的编辑器(你所选择的编辑器)。当你设置好outlook_edit.pyw所使用的编辑器之后,单击GUI的Edit按钮。在本例中将其硬编码为gVim 7.3,不过你也可以使用环境变量,或者让用户通过命令行自己指定(参见本章结尾处的相关练习)。
对于本节中的图片,我们使用的是Outlook 2003。当该版本的Outlook检测到有外部脚本请求访问它时,它会显示如图 7-5 所示的警告对话框。当你选择同意后,一个新的 gVim窗口会打开,并且包含Outlook回复对话框中的内容,如图7-10所示。
图7-10 显示在gVim编辑器窗口中的Outlook对话框内容
此时,你可以添加你的回复,按照你的想法编辑邮件的剩余部分。这里将只进行一个快速且友好的回复(见图 7-11)。保存文件并退出编辑器之后,窗口会关闭,并且你回复的内容会被推送回你不愿意使用的Outlook回复对话框中(见图7-12)。中此时你所需要做的事情就是单击Send按钮,然后就完成了!
现在我们来看下脚本本身,如示例7-6所示。从代码的逐行解释中可以看出,该脚本分为4个主要部分:使用钩子进入Outlook,并得到当前正在活动的条目;清理Outlook对话框中的文本,并将其传入一个临时文件;使用该临时文本文件打开一个编辑器;读取编辑后的文本文件的内容,并将其传回Outlook的对话框窗口中。
图7-11 在gVim编辑器窗口中一个编辑的回复
图7-12 使用修改过的内容回到Outlook对话框中
示例7-6 Outlook编辑器示例(outlook_edit.pyw)
为什么要在Outlook的对话框窗口中新建或回复邮件呢?
逐行解释
第1~6行
尽管在本章的例子中 Tk 并没有起到多么巨大的作用,但是它为控制用户和目标 Office应用之间的接口提供了执行的外壳。因此,我们需要为这个应用提供一些 Tk 常量和控件。因为我们需要一些操作系统相关的条目,所以导入os模块(在这里实际上是nt)。而tempfile是还没有讨论过的一个Python模块,它可以提供一些工具和类,用于帮助开发者创建临时文件、文件名和目录。最后,我们需要到Office应用及其COM服务器的PC端连接。
第8~15行
本部分是代码中仅有的PC COM客户端代码行,在这里会获得一个正在运行的Outlook实例,并查找当前处于激活状态的对话框(应该是olMailItem)。如果 无法进行此查询,或者找不到当前条目,那么应用会无提示的退出。如果是这种情况,你将会发Edit辑按钮会立即再次出现而不是变灰(如果正常运行,编辑器窗口将会弹出)。
需要注意的是,这里选择使用动态调度(win32.Dispatch())而不是静态调度(win32.gencache.EnsureDispatch()),因为动态调度往往能够更快速地启动,另外我们不需要在脚本中使用任何缓存的常量值。
第16~22行
一旦当前对话框(撰写新邮件或邮件回复)窗口确定后,首先要做的事情就是抓取文本并将其写入临时文件中。不可否认的是,这里对于Unicode文本和注音字符的处理并不好,我们将会把所有非ASCII字符从对话框中过滤出去(本章结尾会有一个练习来解决这个问题,修改该脚本使其能够正确处理Unicode字符)。
默认情况下,UNIX风格的编辑器不会处理在PC中创建的文件里用做行结束符的回车-换行对,所以在编辑的预处理和后处理阶段都会对其进行额外的处理,在传输文件到编辑器之前会将其转换为只有换行符,然后再在编辑完成后转换回来。基于文本的现代编辑器会更加干净地处理\r\n,所以这不会再像过去那样是个问题了。
第24~26行
这里会有一些魔法发生:在设置好编辑器后(第 25 行指定了系统中 vim二进制文件的位置,而注释掉的第24行是Emacs的位置),启动编辑器,并将临时文件名作为参数(假设在命令行中,编辑器将目标文件名作为程序名之后的第一个参数)。这些操作会通过调用第26行中的os.spawnv()完成。
P_WAIT标记用于“暂停”主(父)进程,直到派生的(子)进程完成。换句话说,我们希望 Edit 按钮一直保持灰色,从而使用户不会同时尝试编辑多次。这听起来像是个限制,但是实际上它有助于用户集中注意力,而不会在整个桌面上存在很多个部分编辑的回复。
此外还有几个可以对spawnv()做的扩展,其中P_NOWAIT标记在POSIX和Windows系统中都可以使用(与P_WAIT正好相反,它将不会等待子进程结束,而是并行运行两个进程)。另外两个可能使用到的标记是 P_OVERLAY 和 P_DETACH,这两个标记都是只能用于Windows 的。P_OVERLAY 将使子进程替代父进程,如同 POSIX 的 exec()调用一样。而P_DETACH 则类似 P_NOWAIT,启动子进程后与父进程并行运行,只不过它是在后台运行的,与键盘或控制台是分离的。
第28~32行
接下来的代码会在编辑器关闭后打开用于更新的临时文件,取得其内容,删除临时文件,并替换对话框里的文本。请注意,我们只是将数据传回Outlook,它不会阻止Outlook清理消息;也就是说,这可能会产生一些副作用,包括(重新)添加签名、移除换行符等。
第34~44行
应用在main()函数中构建,main()会使用Tk(inter)来绘制一个拥有单个框架的用户界面,其中包括一个应用描述标签,以及两个按钮:Edit按钮会根据活动的Outlook 对话框派生编辑器,而Quit按钮会终止应用。
7.4.3 PowerPoint
最后一个更加现实的应用示例是Python用户向我请求了很多年的例子,我很高兴地说现在我终于可以在社区中展示这个例子了。如果你曾经看到过我在会议中发表演讲,很可能看到过我向观众展示我演讲的纯文本版本的策略,这可能会令一些没有听过我演讲的观众感到震惊。
对于那个纯文本文件,我会启动这个脚本,使用Python的功能自动生成一个PowerPoint演示文稿,完成风格模板,然后在观众的惊讶中启动幻灯片演示。不过,当你意识到它只是个简单的易于编写的Python脚本时,你就会觉得这其实并没有什么了不起的,甚至你自己也可以完成同样的事情。
其工作流程为:GUI 启动后(见图 7-13a)提示用户输入文本文件的地址。如果用户输入的是文件的一个合法位置,事情将会进展顺利;不过如果文件无法找到或者输入了“DEMO”,则会启动一个演示。如果给出了文件名但是因为某种原因应用无法打开,则会在文本框中写上DEMO字符串,以及文件无法打开的错误说明(见图7-13b)。
图7-13 Text-to-PowerPoint GUI控制面板(txt2ppt.pyw)
如图7-14所示,下一步是连接一个正在运行的PowerPoint应用(如果不存在则启动一个新的PowerPoint,并获得其句柄),创建标题幻灯片(基于全大写的幻灯片标题),然后基于伪Python语法的纯文本文件的内容创建其他幻灯片。
图7-14 PowerPoint创建demo演示文稿的标题幻灯片
图7-15所示的是处理中的脚本,创建演示文稿的最后一页幻灯片。当捕获该屏幕时,最后一行还没添加到幻灯片中(所以这不是代码中的bug)。
图7-15 创建demo演示文稿的最后一页幻灯片
最后,代码添加了一页辅助的幻灯片,以告知用户幻灯片放映要开始了(见图 7-16),并给出一个精巧的倒计时,从3数到0(截图取自倒计时刚开始数到2的时候)。然后开始幻灯片放映,而不需要任何额外的处理。图7-17描绘了其一般的样子(白底黑字)。
图7-16 启动幻灯片时的倒计时
图7-17 没有应用模板时,幻灯片启动后的效果
为了进行展示,现在我们要应用一个演示文稿模板(见图 7-18),给予其你所希望的外观,然后就可以从这里开始驾驭它了。
图7-18 应用模板后,完成的PowerPoint幻灯片放映效果
示例7-7是txt2ppt.pyw脚本的代码,接下来是其对应的代码解释。
示例7-7 Text-to-PowerPoint转换器(txt2ppt.pyw)
本脚本会根据一个类似Python代码格式的纯文本文件生成PowerPoint演示文稿。
逐行解释
第1~5行
令人惊讶的是,这里并没有导入太多东西。Python已经包含了几乎所有解决该问题需要的东西。类似Outlook对话框编辑器,我们需要引入一些基础的Tk功能来创建外壳GUI应用,以捕获用户输入。当然,你可以选择通过命令行接口达到此目的,不过你已经拥有了足够的知识使用这种方法自行创建。有时候让工具显示在桌面上供你使用更加便捷。
time.sleep()函数的使用纯粹是学术目的。我们只是用其减慢应用的速度。如果你愿意可以选择移除这些调用。这里使用它的原因和之前的Excel股票示例一样,都是为了减缓速度,因为代码通常执行得都很快,人们会怀疑它已经做了所有的事情或者认为这是特意安排的。
最后一行是代码的关键部分:PC库。
第7~21行
这段代码设置了两个通用的全局变量值。第一个变量设置了默认的缩进层次为4个空格,很像PEP 8风格指南中Python代码缩进的推荐方式,只不过这次定义的是演示文稿项目符号的缩进层次。第二个变量是一个幻灯片演示文稿的示例字符串,当你希望通过演示来了解脚本是如何工作时,或者将其作为期望的源文本文件无法被脚本找到时的备份时,都会使用到它。这个静态字符串也为你提供了一个构造源文本文件的例子。当你创建完演示文稿后,就不需要再查看这个字符串了。
第23~29行
主函数txt2ppt()的前几行会启动PowerPoint,创建新演示文稿,使PowerPoint应用在桌面上显示,暂停几秒,然后将幻灯片计数重置为1。
第30~54行
txt2ppt()函数有一个参数:组成演示文稿的源文本文件的所有行。可以让这个函数迭代一行或多行,之后一个幻灯片演示文稿就创建出来了。对于示例字符串中的各条目,我们使用cStringIO.StringIO对象来迭代其文本,而对于真实文件,我们则会对每行使用生成器表达式。当然如果你使用的是 Python 2.3或更老的版本,则需要更改“生成器表达式”为一个列表解析式。不过,这在内存使用上是存在弊端的,尤其是对于大文件而言,不过你能做什么呢?
回到处理器循环中,我们忽略了空白行,然后通过字符串分割在缩进上实现了一些魔法。这个代码片段将准确展示我们正在做的事情。
>>> 'slide title'.split(' ')
['slide title']
>>> ' 1st level bullet'.split(' ')
['', ' 1st level bullet']
>>> ' 2nd level bullet'.split(' ')
['', '', '2nd level bullet']
如果没有缩进,即按照缩进分割后列表里只有一个字符串,则意味着我们开始了一张新的幻灯片,并且这行文字是幻灯片的标题。如果列表长度大于1,则意味着我们至少有一层缩进,它依然是之前那张幻灯片上的材料(不用新建一张幻灯片)。对于前者来说,这个if语句的主要部分位于第35~47行。我们将首先关注这一块代码,然后才是剩下的代码。
下面的5行(第35~39行)决定了这是标题幻灯片还是标准的文本幻灯片。全大写字符是用于标题幻灯片的。我们仅仅通过比较其与全大写版本是否相同来进行判断。如果它们匹配,即该文本是全大写的,则意味着该幻灯片将使用标题布局,通过PC 常量 ppLayoutTitle进行设计。否则,这是一张拥有标题和文本正文的标准幻灯片(ppLayoutText)。
在我们决定了幻灯片的布局之后,第41行创建了新幻灯片,把PowerPoint指向(第42行)那张幻灯片(通过使其成为活动幻灯片),然后设置标题或主文本框的内容,使用首字母大写的形式(第43行)。请记住,Python是从0开始的(Shape[0]),而Microsoft更习惯从1开始(Shape(1))——任何语法都是可接受的。
剩下的内容将会在Shape[1](或Shape(2))部分中,我们将其称为正文(第44行);对于标题幻灯片而言,它将会是其子标题,而对于标准幻灯片而言,它将会是使用了项目符号的文本。
在该if语句块的剩下的代码中(第45~47行),我们对行数进行标记,说明这是本页幻灯片中写入的第一行,递增用于记录演示文稿中幻灯片总页数的计数器,然后暂停几秒从而让用户可以看到Python脚本是如何控制PowerPoint执行的。
再来看看else子句,我们移动到用于同一幻灯片剩下的列表执行的代码中,它将会去填充幻灯片的第二个文本框或正文。因为我们已经使用缩进来指明我们在哪里以及缩进的层级了,这些行首的空格不再需要,所以我们将其删除(str.lstrip()),并将文本插入正文中(第49~50行)。
剩下的代码块会将文本缩进为正确的项目符号层级(如果是标题幻灯片,则没有缩进,设置缩进层级为0对文本没有影响),递增本页幻灯片的行计数,在最后添加一个短的暂停以使其执行变缓(第51~54行)。
第56~62行
在所有主要的幻灯片都创建完毕后,我们在最后额外添加了一张标题幻灯片,通过动态改变文本从3到0倒计时,来宣布现在到幻灯片放映时间了。
第64~68行
这些行的主要目的是启动幻灯片放映。实际上只有最开始的两行(第64和65行)是做这件事的。第 66 行应用了模板。我们将其放在幻灯片放映开始之后,目的是让你能够看到它——这种方式更加令人印象深刻。这段代码的最后两行(第67~68行)重置了“it’s time for a slideshow”这页幻灯片,以及之前使用的倒计时。
第70~100行
_start()函数只有在我们使用命令行运行脚本时才有用。我们让 txt2ppt()能够在其他地方导入和使用,而_start()函数则需要GUI。先暂时跳到第90~100行,可以看到我们创建了一个Tk的GUI,包括一个文本输入框(含有一个标签,用于提示用户输入文件名或输入“DEMO”来查看演示)和一个Quit按钮。
因此,_start()函数从获取该输入框中的内容开始(第71行),然后会尝试打开该文件(第73行,参见本章结尾的相关练习)。如果文件打开成功,则它会略过except子句,调用txt2ppt()处理文件,在完成后关闭该文件(第86~87行)。
如果发生异常,处理程序会检查是否选中了demo(第77~79行)。如果是这样,则它会读取示例字符串到一个cStringIO.StringIO对象中(第76行),然后将其传递给txt2ppt();否则,这个演示仍然会运行,不过另外还会将错误消息插入文本框中,来告知用户失败发生的原因(第81~84行)。
7.4.4 总结
希望通过本章的学习,你可以理解如何使用Python进行COM客户端编程。尽管Microsoft Office 应用的 COM 服务器健壮性更好,功能更全面,但是你在这里所学到的东西已经可以应用到其他使用了COM服务器的应用中了,甚至是作为Microsoft Office替代品的StarOffice的开源版本。
由于Oracle收购了Sun Microsystems,也就是StarOffice和OpenOffice最初的合作赞助商,StarOffice的继任者将其改称为Oracle Open Office,使得开源社区的成员认为OpenOffice的状态会受到危及,从而又创建了LibreOffice分支。因为它们都基于相同的代码库,所以它们可以共享相同的COM风格接口,该接口称为通用网络对象(UNO)。可以使用PyUNO模块来驱动OpenOffice或LibreOffice应用处理文档,比如,编写PDF文件,转换Microsoft Word为OpenDocument文本(ODT)格式、HTML等。
7.5 相关模块/包
Python Extensions for Windows
xlrd、xlwt(Python 3版本可用)
http://www.lexicon.net/sjmachin/xlrd.htm
http://pypi.python.org/pypi/xlwt
http://pypi.python.org/pypi/xlrd
pyExcelerator
http://sourceforge.net/projects/pyexcelerator/
PyUNO
http://udk.openoffice.org/python/python-bridge.html
7.6 练习
7-1 Web服务。使用Yahoo! 股票行情示例(stock.py),修改该应用,使其保存行情数据到文件中而不是在屏幕中显示。选做题:修改脚本,以便用户可以选择在屏幕上显示行情数据还是将其保存到文件中。
7-2 Excel和Web页面。创建一个应用,从Excel电子表格中读取数据,并将其映射到等价的HTML表格中(如果愿意,可以使用第三方HTMLgen模块)。
7-3 Office应用和Web服务。对于任意已存在的Web服务,无论是REST风格的还是基于URL的,将其数据写入Excel电子表格中,或者比较好看的Word文档中。对其进行适当的格式化以便于打印。选做题:同时支持Excel和Word。
7-4 Outlook和Web服务。与练习7-3类似,除了将数据写到一封新的邮件消息中并使用Outlook发送外,其他工作均相同。选做题:改为使用常规的SMTP发送邮件,其他工作不变(可以参考第3章的内容)。
7-5 幻灯片放映生成器。在练习7-15~7-24中,你将为本章之前提到的幻灯片放映生成器 txt2ppt.pyw添加新的功能。本练习会让你去思考基础知识,但是使用非专有的格式。实现一个与txt2ppt.pyw类似的脚本,替换掉PowerPoint的接口,改用开源格式(比如HTML5)进行输出。可以查看LandSlide、DZSlides和HTML5Wow等项目去寻找一些灵感。你可以在http://en.wikipedia.org/wiki/Web-based_slideshow上找到更多此类项目。为用户创建一个纯文本规范格式,将其归档,并让这些用户使用该工具产出一些可以在台上使用的幻灯片。
7-6 Outlook、数据库和地址簿。编写程序,从Outlook地址簿中取得内容,并将需要的字段存储到数据库中。数据库可以是文本文件、DBM 文件,甚至是 RDBMS(可以参考第6章的内容)。选做题:进行相反的操作,从数据库中读取联系人信息(或允许用户直接输入),并在Outlook中创建或更新地址簿。
7-7 Microsoft Outlook 和邮件。开发程序,通过获取收件箱和/或其他重要文件夹中的内容备份邮件,将其在磁盘上以普通的“mbox”格式(或近似该格式)进行保存。
7-8 Outlook 日历。编写一个简单的脚本,创建新的 Outlook 约会。至少允许用户输入以下信息:开始的日期和时间、约会名称或主题以及约会的持续时间。
7-9 Outlook 日历。创建一个应用,转储你的所有约会内容到你指定的目标中,比如,屏幕、数据库、Excel等。选做题:为Outlook任务执行相同的操作。
7-10 多线程。修改Excel版本的股票行情下载脚本(estock.pyw),使用Python多线程并发下载数据。选做题:也可以使用win32process.beginthreadex()通过Visual C++线程来尝试本练习。
7-11 Excel单元格格式。在电子表格版本的股票行情下载脚本(estock.pyw)中,我们从图 7-7 中可以看出股票价格并不会默认显示到小数点后两位,即使我们传输的是结尾含有 0 的字符串。当 Excel 将其转换为数值时,它会为数值格式使用默认设置。
a)通过修改单元格的NumberFormat属性为0.00,让数值格式能够正确显示小数点后两位。
b)还看到“较上次收盘的变动”一列除了小数点后位数丢失外,还缺少了“+”号。不过,我们发现a)部分中对所有列进行的修改只能解决小数点后位数的问题,这个加号在任何数字中都会自动丢弃。这里的解决方法是修改该列的单元格格式为文本,而不是数值。可以通过将单元格的NumberFormat属性设置为@来进行修改。
c)不过,通过修改单元格的数值格式为文本,我们会失去数值自动产生的右对齐。作为 b)部分的附加操作,你必须现在设置单元格的 HorizontalAlignment 属性为PC Excel的常量xlRight。在你完成这三部分修改后,输出将会更加令人满意,如图7-19所示。
7-12 Python 3。示例7-8所示为第一个Excel示例的Python 3版本(excel3.pyw),及其相应的改变(使用斜体显示)。给出一个解决方案,将本章中所有其他脚本均移植到Python 3中。
图7-19 Python-to-Excel股票行情脚本改进(estock.pyw)
示例7-8 Excel示例的Python 3版本(excel3.pyw)
运行2to3工具,将原始excel.pyw脚本移植到Python 3版本。
下面两个练习与示例7-6相关(outlook_edit.pyw)。
7-13 支持Unicode。修改outlook_edit.pyw脚本,使其能够完美处理Unicode和注音字符。换句话说,不要将这些字符移除,而是将其保留,传递到编辑器中,并在编辑后的消息中能够接受它们,以使其可以在邮件消息中进行传输。
7-14 健壮性。通过允许用户根据自己的喜好从命令行指定编辑器使脚本更具灵活性。如果用户没有提供,则使用环境变量的设置,作为最后的手段,使用硬编码的编辑器进行设定。
下面一组练习与示例7-7相关(txt2ppt.pyw)。
7-15 忽略注释。修改脚本使其支持注释:如果文本文件中的一行以“#”开始,则假定该行并不存在,然后移动到下一行。
7-16 改进标题幻灯片设计。给出一个更好的方法来表达标题幻灯片。使用首字母大写风格固然很好,不过对于一些特定情况却并不希望这样显示。例如,用户创建了一个题为“Intro to TCP/IP”的演讲,如此使用则会包含几个错误:“to”首字母大写,“TCP/IP”中的“cp”和“p”变成小写后,它成为“Tcp/Ip”。
>>> 'Intro to TCP/IP'.title()
'Intro To Tcp/Ip'
7-17 副作用。如果当前文件夹中存在一个叫做“demo”的文本文件,那么在_start()函数执行时会发生什么呢?这是一个 bug 还是一个功能?我们可以用某种方式改进这个解决方案吗?如果可以,则编写代码;否则,说明原因。
7-18 模板规范。在目前的脚本中,所有演示文稿都应用的是下面这个设计模板:C:\Program Files\Microsoft Office\Templates\Presentation Designs\Stream.pot。这样会很无趣。
a)允许用户从该文件夹或你的安装目录下选择任何其他模板。
b)允许用户指定他自己的模板(及其位置),可以在GUI中添加新的输入框,或使用命令行,抑或是根据环境变量读取(由你选择)。选做题:使用所有可选方式进行模板选择,可以根据优先级顺序,也可以在用户界面中给用户一个下拉框来选择a)部分中的默认模板选项。
7-19 超链接。演讲可能需要在纯文本文件中包含链接功能。使这些链接在 PowerPoint中可以激活。提示:你需要设置Hyperlink.Address将其作为URL,当阅读者单击幻灯片中的链接时可以启动浏览器阅读(参见ActionSettings中的ppMouseClick)。
选做题:当链接在该行中不是唯一的文本时,只对URL文本支持超链接;也就是说,只对URL部分激活链接,而不会对该行的其他文本激活。
7-20 文本格式化。通过在源文本文件中支持一些轻量级标记格式,为演示文稿内容中的文本增加加粗、斜体、等宽字体(如 Courier)等效果。强烈推荐 reST(reStructuredText)、Markdown 或其他类似的工具,比如在 Wiki 风格的格式中,使用'monospaced'、bold、italic等。如果希望获得更多例子,可以参考http://en.wikipedia.org/wiki/Lightweight_markup_language。
7-21 文本格式化。添加对其他格式化服务的支持,比如,下划线、阴影、其他字体、文本颜色、对齐方式(左、中、右等)、字体大小、页眉和页脚以及PowerPoint支持的其他格式。
7-22 图像。我们需要为应用添加的一个重要功能是在幻灯片中显示图像。为了把问题简化,只需要你支持包含标题和单一图像(需要调整在演示文稿幻灯片中的大小并居中显示)的幻灯片。你需要指定一个定制化的语法,使用户可以嵌入图像的文件名,比如,“:IMG:C:/py/talk/images/cover.png”。提示:到目前为止,我们只使用了ppLayoutTitle 和 ppLayoutText 两种幻灯片布局,在本练习中,推荐使用ppLayoutTitleOnly。使用Shapes.AddPicture()插入图片,然后根据PageSetup.SlideHeight和 PageSetup.SlideWidth 提供的数据点以及图像的 Height 和 Width 属性,使用ScaleHeight()和ScaleWidth()调整图像的大小。
7-23 不同布局。对练习 7-22 的解决方案进行进一步的扩展,使你的脚本支持含有多张图像的幻灯片以及同时含有图像和项目符号文本的幻灯片。这意味着你需要使用其他布局风格。
7-24 嵌入视频。你可以添加的另一项先进功能是在演示文稿中嵌入YouTube视频剪辑(或其他Adobe Flash应用)。与练习7-23类似,你需要自己定义用于支持该功能的语法,比如“:VID:http://youtube.com/v/Tj5UmH5TdfI”。提示:这里再次推荐使用ppLayoutTitleOnly 布局。此外,你还需要使用 Shapes.AddOLDObject(),并选择“ShockwaveFlash.ShockwaveFlash.10”或你所使用的Flash播放器的其他版本。
