在Windows系统下使用PHP生成Word文档的教程


Posted in PHP onJuly 03, 2015

准备工作

首先,请确保在你的Windows系统中已经安装并配置好了一个典型的WAMP环境。由于Interop纯粹是一个Windows的特性,我们将在Windows平台下搭建Apache和PHP。在这个实例中,我使用了EasyPHP 14.1,这款软件安装和配置都十分容易。

接下来,我们要安装Microsoft Office。版本不是严格要求的。我正在使用的是Office2013专业版,但是任何2007之后的Office版本都应该可以使用。

我们然后需要去确保开发Interop应用(又被称作PIA,优先交互组件)的库是安装好的。为了确保这个,我们可以打开资源管理器,然后找到<Windows目录>\assembly,我们将会看到下面安装好的PIAs分支:

在Windows系统下使用PHP生成Word文档的教程

我们可以看到一个 Microsoft.Office.Interop.Word 条目(在这个截图中有下划线)。 这就是我们在这个示例中将要使用的 PIA。请特别注意它的“名称”,“版本”和“公钥标记”。我们将要在PHP脚本中用到它们。

在这个目录中,我们还可以看到其它用于编程(不仅是PHP,还有VB.net, C#等)的PIAs(包括整个Office家族)。

如果这个列表没有包含 Microsoft.Office.Interop 的整个包,我们可以重新安装Office并且在安装中包含PIA;我们也可以手动下载安装这个包。安装的详细步骤可以查阅这个MSDN页面。

注意:只有Microsoft Office 2010 PIA Redistributable 可以被单独下载安装。这个包中的 PIA 版本是14.0.0。版本15只能通过安装Office获得。

最后,我们需要在文件 php.ini 中启用 PHP 扩展 php_com_dotnet.dll,并且重启服务器。

现在我们可以开始编程了。

HTML表单

由于该demo主要关注与后台的处理,所以我们这里就用一个简单的HTML表单做前台的展示,看起来应该是这样的:

在Windows系统下使用PHP生成Word文档的教程

我们有一个文本框用于输入“Name”,一个“Gender”的单选按钮组,一个“Age”的域值控制还有一个文本域来写“Message”,最后,还需要一个“Submit”按钮。

将该文件命名为“index.html”,保存在虚拟主机的根目录下,这样我们可以直接通过URL访问该文件,例如:http://test/test/interop

后台

后台的PHP文件是我们所要讨论的核心部分。我先将代码贴到下面,接下来在一步一步的进行解释
 

<?php
 
$inputs = $_POST;
$inputs['printdate']=''; 
// A dummy value to avoid a PHP notice as we don't have "printdate" in the POST variables. 
 
$assembly = 'Microsoft.Office.Interop.Word, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c';
$class = 'Microsoft.Office.Interop.Word.ApplicationClass';
 
$w = new DOTNET($assembly, $class);
$w->visible = true;
 
$fn = __DIR__ . '\\template.docx';
 
$d = $w->Documents->Open($fn);
 
echo "Document opened.<br><hr>";
 
$flds = $d->Fields;
$count = $flds->Count;
echo "There are $count fields in this document.<br>";
echo "<ul>";
$mapping = setupfields();
 
foreach ($flds as $index => $f)
{
  $f->Select();
  $key = $mapping[$index];
  $value = $inputs[$key];
  if ($key == 'gender')
  {
    if ($value == 'm')
      $value = 'Mr.';
    else
      $value = 'Ms.';
  }
   
  if($key=='printdate')
    $value= date ('Y-m-d H:i:s');
 
  $w->Selection->TypeText($value);
  echo "<li>Mappig field $index: $key with value $value</li>";
}
echo "</ul>";
 
echo "Mapping done!<br><hr>";
echo "Printing. Please wait...<br>";
 
$d->PrintOut();
sleep(3);
echo "Done!";
 
$w->Quit(false);
$w=null;
 
 
 
function setupfields()
{
  $mapping = array();
  $mapping[0] = 'gender';
  $mapping[1] = 'name';
  $mapping[2] = 'age';
  $mapping[3] = 'msg';
  $mapping[4] = 'printdate';
   
 
  return $mapping;
}

在设置完用来获取表单中传过来的值的变量$inputs之后,我们要创建一个虚拟值用来存放printdate——我们稍后会讨论为何需要这个变量——现在,我们看到这4行比较关键的代码:
 

$assembly = 'Microsoft.Office.Interop.Word, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c';
$class = 'Microsoft.Office.Interop.Word.ApplicationClass';
 
$w = new DOTNET($assembly, $class);
$w->visible = true;

在PHP中的COM操纵需要在一个assembly里请求一个class的实例。在我们的案例中,我见将要操作Word。如果考虑到我们上一个截图中展示的代码,我们将能够构造出一个完整签名的Word PIA:

  •     “Name”,“Version”,“Public Key Token”是在当我们浏览“c:\Windows\assembly”时所展示的信息
  •     “Cultrue”总是neutrual的。

调用类编译后的文件后缀名为通常为ApplicationClass.

通过设置下面两个步骤,我们可以初始化一个word对象:

首先,word对象可以保存在后台或者通过将visible属性设置为true,使它在前台展示出来。

然后,我们打开将要处理的文档,把它实例化为一个$d变量。

在文档对象中,基于html表单的文本来添加文档的内容,这里可以设置一些参数。
最不好的方式是对php页面上所有内容进行硬编码,然后将它们添加到word对象中。我强烈建议不采用此种方式,原因有:

1 代码没有灵活性,php内容的任何变化都需要重新修改脚本;
2 违反了控制层、展示层的分离;
3  如果需要设置word内容的格式(对齐,字体,样式,等),这种方式大大增加了代码行数,并且以编程的方式来修改样式是非常麻烦的。

另一种方式是使用“搜索-替换”。PHP内置的这种功能非常强大。我们可以创建一个word文档,在那些需要被替换的占位内容周围放置一些分隔符。比如,我们创建一个文档包含如下内容:

{{name}}

在PHP中,我们只需使用从表单提交中获取的“Name”值来替换。这种方式避免了第一选项中的那些缺点。我们只需要找到正确的分隔符,在这个例子中,除了使用的模板是word文档,我们更像是做一个模板渲染。

第三个选项是我的建议,并是Word中的高级主题。我们将用域来表示占位符,在我们的PHP代码中,我们会直接更新了相应的表单值的字段。 

这种方法灵活,快速,符合Word的最佳实践。这也避免了文件的全文搜索,这有助于提高性能。请注意,此选项有它的缺点了。 

总之,自从首次亮相,Word从来没有支持命名索引的字段。尽管我们对于我们在Word文档中创建的字段提供了一个名字,我们还是要用数字下标来访问每个字段。这也解释了为什么我们要使用专用的功能(setupfields)做表单字段的字段索引和名之间的映射手册

学习如何在word文档中插入字段(点击这里查看一个定制好的版本),请参阅相关 Word 帮助主题和手册。对于这个demo,我们有一个具备5个MERGEFIELD字段的文档。此外,我们将文档和PHP脚本放在一个目录下,以方便获取。

请注意,printdate字段并没有一个相应的窗体字段。这就是为什么我们要在$inputs数组中添加一个假的printdate作为key。没有这个key,脚本依然可以执行,但是会有提示说明$inputs数组中不存在索引printdate。

在使用表单数据更新完字段的值之后,我们将会使用下面的命令打印文档:
 

$d->PrintOut();

PrintOut方法有几个可选参数,这里,我们使用最简单的格式。这将会给链接到我们Windows机器的默认打印机打印一份副本。

我们可以通过使用PrintPreview进行打印预览。在纯自动化的情景下,当然,我们直接使用PrintOut进行打印。

在退出word应用程序之前,我们还需要稍作等待,因为打印工作需要时间来完全退出后台。如果没有delay(3),$w->Quit将会立刻得到执行,并且打印工作立刻被终止。

最终,我们调用 $w->Quit(false) 来选择通过我们的PHP脚本调用关闭word应用程序。这里提供的唯一参数是用来指明我们是否希望在退出前保存更改。我们确实对文档进行了更改,但是我们不希望保存它们,因为我们希望能为其他用户的输入提供一份干净的模板。
 

当我们完成编码之后,我们可以加载表单页面,输入一些内容并提交表单。下面的截图展示了PHP脚本的输出,同时更新了Word文档:

在Windows系统下使用PHP生成Word文档的教程

在Windows系统下使用PHP生成Word文档的教程

提高编码速度并更好的理解PIA

PHP是一种弱类型的语言。一个COM对象是一种Object类型。在我们的PHP编码过程中,在一个对象中我们无法使用代码自动提示和完成功能,在一个Word应用,一个文档甚至一个字段中同样如此。我们不知道它有哪些特性,或者它支持哪些方法。

这将大幅度的降低我们开发的速度。为了使开发更快,首先,我建议我们在c#中开发功能应当迁移至我们的PHP编码。我推荐一款免费的C# IDE 叫做"#develop",你可以在这里下载。相比VS,我更喜欢这一款软件,因为#develop体积更小,更简洁,响应更快。

C#代码迁移至PHP一点也不吓人。先让我展示一些C#的代码:
 

Word.Application w=new Word.Application();

w.Visible=true;

             

String path=Application.StartupPath+"\\template.docx";

             

Word.Document d=w.Documents.Open(path) as Word.Document;

             

Word.Fields flds=d.Fields;

int len=flds.Count;

             

foreach (Word.Field f in flds)

{

    f.Select();

    int i=f.Index;

    w.Selection.TypeText("...");

}

我们可以看到,C#的代码和我们之前展示的PHP的代码基础一模一样。由于C#是一种强类型语言,所以我们可以看到有些类型转换的语句,我们不得不显性的给我们的变量赋一种类型。

有了代码的类型,我们可以尽情的享受代码的自动提示和代码自动完成功能,这样我们开发的速度将有大幅度提高。

另一种可以给予我们更快速度进行PHP开发的方式是使用Word的宏命令。我们先操作一遍我们需要重复的动作,然后用一个宏将其录制下来。一个宏其实是Visual Basic,同样也可以非常容易的翻译成PHP。

最重要的是,Office PIA微软官方文档,特别是文档中对于每个Office应用的命名空间,总会是我们所需要的最想进的参考。比较常用的3个应用如下:

  •     Excel 2013:http://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel(v=office.15).aspx
  •     Word 2013:http://msdn.microsoft.com/en-us/library/microsoft.office.interop.word(v=office.15).aspx
  •     PowerPoint2013:http://msdn.microsoft.com/en-us/library/microsoft.office.interop.powerpoint(v=office.15).aspx

结语

在这篇文章中,我们演示了如何使用PHP COM库和Microsoft Office Interop功能来倩影一个Word文档。

Windows和Office在我们的日常生活中可以说是被广泛的使用。能够知道和了解Office或者Windows的强大之处还有PHP,对于任何一个在Windows平台上进行PHP开发的程序员都是十分必要的。

使用PHP的COM扩展,掌握这一组合的大门就被打开了。

如果你对于这部分的编程比较感兴趣,请留下你的评论,我们将会考虑在这个话题上写更多的文章。我十分期待更多现实生活的应用开发能使用这种方式。

PHP 相关文章推荐
PHP脚本数据库功能详解(上)
Oct 09 PHP
获得Google PR值的PHP代码
Jan 28 PHP
Discuz 5.0 中读取纯真IP数据库函数分析
Mar 16 PHP
php字符串分割函数explode的实例代码
Feb 07 PHP
php中把美国时间转为北京时间的自定义函数分享
Jul 28 PHP
THINKPHP项目开发中的日志记录实例分析
Dec 01 PHP
ECSHOP在PHP5.5及高版本上报错的解决方法
Aug 31 PHP
图文详解phpstorm配置Xdebug进行调试PHP教程
Jun 13 PHP
YII框架中搜索分页jQuery写法详解
Dec 19 PHP
php基于dom实现的图书xml格式数据示例
Feb 03 PHP
thinkphp分页集成实例
Jul 24 PHP
PHP基于mcript扩展实现对称加密功能示例
Feb 21 PHP
深入理解PHP中的Streams工具
Jul 03 #PHP
列举PHP的Yii 2框架的开发优势
Jul 03 #PHP
PHP中异常处理的一些方法整理
Jul 03 #PHP
在PHP程序中使用Rust扩展的方法
Jul 03 #PHP
PHP整合七牛实现上传文件
Jul 03 #PHP
ThinkPHP自定义函数解决模板标签加减运算的方法
Jul 03 #PHP
5款适合PHP使用的HTML编辑器推荐
Jul 03 #PHP
You might like
不用mod_rewrite直接用php实现伪静态化页面代码
2008/10/04 PHP
php设计模式 Chain Of Responsibility (职责链模式)
2011/06/26 PHP
163的邮件用phpmailer发送(实例详解)
2013/06/24 PHP
微信支付开发订单查询实例
2016/07/12 PHP
PHP经典算法集锦【经典收藏】
2016/09/14 PHP
javascript window.opener的用法分析
2010/04/07 Javascript
javascript跨域刷新实现代码
2011/01/01 Javascript
JavaScript自执行闭包的小例子
2013/06/29 Javascript
Javascript让DEDECMS告别手写Tag
2014/09/01 Javascript
jquery实现实时改变网页字体大小、字体背景色和颜色的方法
2015/08/05 Javascript
JavaScript记录光标在编辑器中位置的实现方法
2016/04/22 Javascript
javascript实现简易计算器的代码
2016/05/31 Javascript
微信小程序 vidao实现视频播放和弹幕的功能
2016/11/02 Javascript
Node.JS中事件轮询(Event Loop)的解析
2017/02/25 Javascript
jQuery布局组件EasyUI Layout使用方法详解
2017/02/28 Javascript
Ionic项目中Native Camera的使用方法
2017/06/07 Javascript
JavaScript基本语法_动力节点Java学院整理
2017/06/26 Javascript
JavaScript选取(picking)和反选(rejecting)对象的属性方法
2017/08/16 Javascript
微信头像地址失效踩坑记附带解决方案
2019/09/23 Javascript
[02:40]DOTA2英雄基础教程 巨牙海民
2013/12/23 DOTA
深入解析Python中的WSGI接口
2015/05/11 Python
Python实现列表删除重复元素的三种常用方法分析
2017/11/24 Python
Python产生一个数值范围内的不重复的随机数的实现方法
2019/08/21 Python
python3格式化字符串 f-string的高级用法(推荐)
2020/03/04 Python
详细分析Python可变对象和不可变对象
2020/07/09 Python
Django中Q查询及Q()对象 F查询及F()对象用法
2020/07/09 Python
Python用摘要算法生成token及检验token的示例代码
2020/12/01 Python
美国流行背包品牌:JanSport(杰斯伯)
2018/03/02 全球购物
CHARLES & KEITH英国官网:新加坡时尚品牌
2018/07/04 全球购物
介绍一下write命令
2012/09/24 面试题
SOA的常见陷阱或者误解是什么
2014/10/05 面试题
2015新年联欢晚会开场白
2014/12/14 职场文书
小学生法制教育心得体会
2016/01/14 职场文书
承诺书的内容有哪些,怎么写?
2019/06/21 职场文书
详解分布式系统中如何用python实现Paxos
2021/05/18 Python
spring cloud eureka 服务启动失败的原因分析及解决方法
2022/03/17 Java/Android