Python 操作 Word 案例

今天替老婆做一个小小的功能:

需求是这样的,她在银行对一个检查事项需要出具几份文档,例如《报告A》,《报告B》,《报告C》等。

但是这几份报告里面其实很多内容是重复的,例如人名,或者一些评价等等若干信息。

她希望可以不重复输入这些东西,而制作一些模板,来批量完成这项工作。这样对于大量的检查来说可以省掉不少工作量。

我隐约记得 word 里面有一插入域的功能,通过不同的 word 模板里面插入一些域作为钩子,然后引用到一个 access 文件,这样来进行批量的更新,但是由于其实我还是不太懂 word,于是浅尝辄止。

然后正好 python 的 office api 也是很快需要派上用场的,而且 python 都比较容易绿化所以干脆就写一个 python 的替换器吧。


简单的设想是这样的,做一个配置文件 conf.xlsx

模板文件 输出目录 替换字段 替换内容
form1.docx out a a for aster
form2.docx   b b for bullets
    c c for ceasar
    d d for demon

稍微简单解释一下:

第一列是模板的文件,可以是若干个 word 文件,通过相对路径或者绝对路径指定。word 文件里面可以填写若干个 {{替换字段}} 这样的标签用于匹配替换。

第二列是输出目录,替换之后的文件将会被输出到指定的目录另存,也可以接受相对目录或者绝对路径的目录。

第三第四列是替换的字典,key 是第三列“替换字段”,value 第四列是替换内容。然后对于所有的模板文件中的 {{key}} 将会被全部替换成 value。

这样用起来已经可以省掉相当多的工作了,下面是实施这个脚本。


首先是找一个恰当的 python office api,以前没搞过,但是一下子就找到一个合适的了:

Python for Windows extensions

下载合适的版本进行安装,然后具体的使用可以 import win32com 这个包。

为了简化操作,我还在百度文库找到了一个简易版 word 封装:

# easyword.py
#!/usr/bin/env python  
# -*- coding: utf-8 -*-  
import win32com.client
import os
#--------------------------------------------------------------------------
class easyWord:
    '''
    Some convenience methods for Excel documents accessed
    through COM.
    '''
    def __init__(self,visible=False):
        self.wdApp = win32com.client.DispatchEx('Word.Application')
        self.wdApp.Visible = visible
        self.wdApp.DisplayAlerts = False

    def new(self,filename=None):
        '''
        Create a new Word document. If 'filename' specified,
        use the file as a template.
        '''
        if filename:
            return self.wdApp.Documents.Add(filename)
        else:
            return self.wdApp.Documents.Add()

    def open(self,filename):
        '''
        Open an existing Word document for editing.
        '''
        return self.wdApp.Documents.Open(filename)

    def visible(self,visible=True):
        self.wdApp.Visible = visible

    def find(self,text,MatchWildcards=False):
        '''
        Find the string
        '''
        find = self.wdApp.Selection.Find
        find.ClearFormatting()
        find.Execute(text, False, False, MatchWildcards, False, False, True, 0)
        return self.wdApp.Selection.Text

    def replaceAll(self,oldStr,newStr):
        '''
        Find the oldStr and replace with the newStr.
        '''
        find = self.wdApp.Selection.Find
        find.ClearFormatting()
        find.Replacement.ClearFormatting()
        find.Execute(oldStr, False, False, False, False, False, True, 1, True, newStr, 2)   

    def updateToc(self):
        for tocitem in self.wdApp.ActiveDocument.TablesOfContents:
            tocitem.Update()

    def save(self):
        '''
        Save the active document
        '''
        self.wdApp.ActiveDocument.Save()

    def saveAs(self,filename,delete_existing=True):
        '''
        Save the active document as a different filename.
        If 'delete_existing' is specified and the file already
        exists, it will be deleted before saving.
        '''
        if delete_existing and os.path.exists(filename):
            os.remove(filename)
        self.wdApp.ActiveDocument.SaveAs(FileName=filename)

    def close(self):
        '''
        Close the active workbook.
        '''
        self.wdApp.ActiveDocument.Close()

    def quit(self):
        '''
        Quit Word
        '''
        return self.wdApp.Quit()

这个作为一个模块,我们通过 from easyword import easyWord 即可导入这个类,这下就简单了。

然后实现一个方法 replaceWord:

# replace.py
import os
from easyword import easyWord

def replaceWord(data, tmpl_path=[], export_path=['export']):
    for path in tmpl_path:
        path = os.path.relpath(path)
        if os.path.exists(path):
            w = easyWord()
            w.open(os.path.abspath(path))
            print('打开:%s' % os.path.abspath(path))
            for k in data:
                w.replaceAll('{{%s}}'%k, data[k])
            for ex in export_path:
                ex = os.path.join(os.path.abspath(ex), path)
                if not os.path.exists(os.path.dirname(ex)):
                    os.makedirs(os.path.dirname(ex))
                print('输出:%s' % ex)
                w.saveAs(ex)
            w.close()
            w.quit()

然后我们调用 replaceWord 函数:

data 是替换的字典 {'替换字段': '替换内容', ...}

tmpl_path 是模板文件的列表(支持相对或者绝对路径)

export_path 是输出目录的路径

举个例子:

replaceWord(
    {
        'a':'a for apple',
        'b':'b for bullets',
        'c':'c for ceaser',
        'd': 'd for damn',
        'e': 'e for eclipse',
    },
    (
        'form1.docx',
        'form2.docx',
    ),
    (
        'a/a',
        'b/b',
    )
)

这样调用就可以实现一个批量操作。

好了,最后一步,我们需要从指定的 conf.xlsx 来读取这些参数并且按照参数执行这个过程。

同样,在百度轻易找到了一个建议封装的 Excel 类库:

# easyexcel.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from win32com.client import Dispatch
import win32com.client

class easyExcel:
      """A utility to make it easier to get at Excel.    Remembering
      to save the data is your problem, as is    error handling.
      Operates on one workbook at a time."""

      def __init__(self, filename=None):
          self.xlApp = win32com.client.DispatchEx('Excel.Application')
          if filename:
              self.filename = filename
              self.xlBook = self.xlApp.Workbooks.Open(filename)
          else:
              self.xlBook = self.xlApp.Workbooks.Add()
              self.filename = ''  

      def save(self, newfilename=None):
          if newfilename:
              self.filename = newfilename
              self.xlBook.SaveAs(newfilename)
          else:
              self.xlBook.Save()    

      def close(self):
          self.xlBook.Close(SaveChanges=0)
          del self.xlApp

      def getCell(self, sheet, row, col):
          "Get value of one cell"
          sht = self.xlBook.Worksheets(sheet)
          return sht.Cells(row, col).Value

      def setCell(self, sheet, row, col, value):
          "set value of one cell"
          sht = self.xlBook.Worksheets(sheet)
          sht.Cells(row, col).Value = value

      def getRange(self, sheet, row1, col1, row2, col2):
          "return a 2d array (i.e. tuple of tuples)"
          sht = self.xlBook.Worksheets(sheet)
          return sht.Range(sht.Cells(row1, col1), sht.Cells(row2, col2)).Value

      def addPicture(self, sheet, pictureName, Left, Top, Width, Height):
          "Insert a picture in sheet"
          sht = self.xlBook.Worksheets(sheet)
          sht.Shapes.AddPicture(pictureName, 1, 1, Left, Top, Width, Height)

      def cpSheet(self, before):
          "copy sheet"
          shts = self.xlBook.Worksheets
          shts(1).Copy(None,shts(1))

于是我们可以通过如下方式获取到配置(在 replace.py 的后面加上这一段代码,然后就可以直接跑出结果来了):

# replace.py

from easyexcel import easyExcel

x = easyExcel(os.path.abspath('conf.xlsx'))

tmpl_path = []
while x.getCell('sheet1', len(tmpl_path)+2, 1):
    tmpl_path.append(x.getCell('sheet1', len(tmpl_path)+2, 1))

export_path = []
while x.getCell('sheet1', len(export_path)+2, 2):
    export_path.append(x.getCell('sheet1', len(export_path)+2, 2))

if not export_path:
    export_path = ['export']

data = {}
while x.getCell('sheet1', len(data)+2, 3):
    data[x.getCell('sheet1', len(data)+2, 3)] = x.getCell('sheet1', len(data)+2, 4)

x.close()

replaceWord(data, tmpl_path, export_path)

【转载请附】愿以此功德,回向 >>

原文链接:http://www.huangwenchao.com.cn/2014/02/python-word.html【Python 操作 Word 案例】

《Python 操作 Word 案例》有2个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注