原文:解决 PHPWord 中的 ProofErr 问题
一、PHPWord 简介
PHPWord 是 PHPOffice 中的一部分(另有 PhpSpreadsheet 用于处理 Excel),主要用于 PHP 项目中对 OOXML/OpenXML(Microsoft Office Open XML), OpenDocument/ODF(OASIS Open Document Format for Office Applications), RTF(Rich Text Format), HTML, PDF 等格式的文件进行读写操作。PHPOffice 的所有项目已经在 GitHub 上开源。
二、遇到的问题
PHPWord 作为处理文档的第三方扩展,在 OA 项目中被广泛使用。最近,公司内部使用的后台系统就遇到了这么一个问题。 起因是财务想要修改原有的发票模版,将落款单位由原先的固定值改为“我开户行”对应的公司名称(就是因为老板又注册了几家公司)。原有模版:
![f6f2fca120bf92fae1ec684326f77c67.png](https://i-blog.csdnimg.cn/blog_migrate/1e2fb0efff8eb1591ed7b03a4e65b941.jpeg)
翻出模版以后,第一想法当然就是把模版里的{{companyName}}
复制一份替换掉原先的苹果母公司
。替换、保存、上传后,手动生成了文档,打开一看,居然变成了:
![207996b9c02125730b860a92f197e23f.png](https://i-blog.csdnimg.cn/blog_migrate/b1f04d540aca563364e2d3d6159bd4e7.jpeg)
在经过多次替换后发现,只有 {{bank}}
这个占位符可以被正确识别进行替换,其他占位符均替换失败。那么,会不会是变量长度导致的异常呢?于是我取 {{candidateName}}
的前几个字母 {{can}}
进行了尝试,果然成功了
……吗?
事实证明,在每个占位符只取前 3~4 个字母的情况下,仍然出现了大批量的翻车事故。除了 {{bank}}
这个占位符,一如既往的坚挺。无奈之下,只能使用最低级的方法——打印内容。
三、查看输出
首先,看一下 PHPWord 在项目中的用法:
$templateProcessor = new TemplateProcessor($file_path);
$templateProcessor->setValue('{{invoiceTitle}}', $invoiceTitle);
$templateProcessor->saveAs($save_path);
其实很容易理解,读取一个模版,把对应的占位符用想输出的变量使用 setValue() 方法替换,然后保存,所以直接把 setValue() 后的 $templateProcessor 打印出来看看就可以了。通过比较替换成功/失败的两种情况:
# 正常情况
<w:r w:rsidR="00B06CB3">
<w:rPr>
<w:rFonts w:ascii="宋体" w:hAnsi="宋体"/>
<w:b/>
<w:bCs/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>{{bank}}</w:t>
</w:r>
# 错误情况
<w:r>
<w:rPr>
<w:rFonts w:ascii="宋体" w:hAnsi="宋体"/>
<w:b/>
<w:bCs/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>
{{
</w:t>
</w:r>
<w:proofErr w:type="spellStart"/>
<w:r>
<w:rPr>
<w:rFonts w:ascii="宋体" w:hAnsi="宋体"/>
<w:b/>
<w:bCs/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>
invoiceTitle
</w:t>
</w:r>
<w:bookmarkStart w:id="0" w:name="OLE_LINK3"/>
<w:bookmarkStart w:id="1" w:name="OLE_LINK4"/>
<w:proofErr w:type="spellEnd"/>
<w:r>
<w:rPr>
<w:rFonts w:ascii="宋体" w:hAnsi="宋体"/>
<w:b/>
<w:bCs/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>
}}
</w:t>
</w:r>
<w:bookmarkEnd w:id="0"/>
<w:bookmarkEnd w:id="1"/>
发现:正常情况下,{{bank}}
这个占位符作为一个整体,被一堆标签封闭起来,这些标签应该表示了字体、字号等信息。而{{invoiceTitle}}
这个标签,"{{", "invoiceTitle" 和 "}}" 则被分隔成三个部分,所以不能被正确识别进行替换。所以问题就变成了为什么这个占位符会被分隔开呢?
四、面向谷歌 debug
既然已经找到大括号和英文之间被prroofErr
这个标签给隔开了,那么就来看看这个到底是什么吧。在进行各个文本格式的简单科普之后,我了解了目前主流的几种文本格式,并且在一个 博客 中看到了相关解释:
If you are using Word to make your template, you may have problems periodically with TBS tags not merging due to being broken up by extra blocks of code inserted by Word. This extra code is not noticeable in Word, but sometimes it seems to mangle the TBS tag too much to be recognized. When this happens, you can do two things that will clean up your document.
1. Select any tags ([onshow;data;noerr;], e.g.) in your document and go to the review tab (I am using Word 2010 here, but 2007 is very similar). Click 'Language' > 'Set Proofing Language' (2010) or 'Set Language' in the 'Proofing' division (2007). Then check the box 'Do not check spelling or grammar'. This will prevent Word from adding those extra proofErr tags (and breaking up your TBS tags). It will also remove any that already exist in your document. (Or you can run spell check and ignore everything)
2. The w:rsID tags are a little trickier. There is a hidden option you can access through the macro editor (Alt + F11 will bring it up). Once you are in the macro editor, Ctrl + G will open the 'Immediate Window'. If you paste the following code into the Immediate Window:Application.Options.StoreRSIDOnSave = False
And then save your document (you do not need to save the macro with the document, it just applies to your Word session), it will remove all of the extra w:rsid tags breaking up your TBS tags.
Notes on editing: You will want to apply these changes at the end of editing, before you save. If you come back later and edit your document, it will forget that you don’t want spell checking and grammar. You can remind it by setting the language again, or by running Spell Check and telling it to ignore all of the problems it encounters. I edited my document more after running the macro I mentioned (in a new Word session, when my Immediate Window was clear again) and my w:rsid tags did not come back, but I suspect they would return if I edited enough.
There is one more potentially problematic tag:<w:bookmarkStart
w:id="0"
w:name="_GoBack"/>
<w:bookmarkEnd
w:id="0"/>
which will insert itself wherever it pleases (from what I understand, it has something to do with moving your screen back to your previous edit if you hit undo, so it attaches itself to the edit before last?). I haven’t done much experimenting with this yet, it may not interfere with TBS tags, and I’m not totally sure where it shows up.
Hope this helps.
其中第一点指出问题可能出在拼写检查,第二点指出问题可能出在宏编辑器。当然如果我当时再细心一点,思维再发散一点,应该不难看出除了{{bank}}
以外其他的占位符全都出现了红色波浪线,唯一能被正确替换的就是通过了拼写检查的占位符。
五、修复 bug
找出问题后修改就变得非常简单。只需要把这些个小驼峰命名法的单词换成下划线分隔,甚至于更偷懒的方法,直接打开模版文件,关闭拼写检查后保存上传。
六、个人感想
- 一些离奇的问题往往排查起来十分困难,但是了解以后修改起来十分简单
- 并不是说用谷歌的就比用百度的有逼格,但是在编程这一块,毕竟发源地在国外,早起步了十几二十年,沉淀下来的有帮助的东西总归是多一些
- 即使现在的翻译功能十分好用,良好的英文阅读能力在提升编程水平的路上会有重要帮助
七、一些题外话
看似洋洋洒洒写了一篇短文章,其实这个问题用几句话就可以简单概括。写这么多字,一来是重新锻炼一下自己的归纳总结能力,二来是最近部门里合作的几个后端同事水平不尽如人意,希望写下的这么一个对小问题的排查解决过程能对正在学习 PHP 的人一些帮助。 程序员真的是一个神奇的职业,既有毕业两年已经精通算法和 C 甚至单片机、FPGA 等软硬件通吃的大佬,也有拿着 13k 月薪的三年工作经验只会用 PHP 写写业务碰到问题只会两手一摊说“我不会” 的部分同事。“用代码改变世界”,共勉。