如何使用 python-docx 将复选框表单插入 .docx 文件?

Posted

技术标签:

【中文标题】如何使用 python-docx 将复选框表单插入 .docx 文件?【英文标题】:How can I insert a checkbox form into a .docx file using python-docx? 【发布时间】:2018-03-13 11:40:55 【问题描述】:

我一直在使用 python 来实现自定义解析器,并使用解析后的数据来格式化要在内部分发的 word 文档。到目前为止,所有的格式设置都简单明了,但我完全不知道如何将复选框插入单个表格单元格。

我尝试在 python-docx 中使用 python 对象函数(使用get_or_add_tcPr() 等),当我尝试打开文件时,这会导致 MS Word 引发以下错误,“无法打开文件 xxxx,因为内容有问题详细信息:文件已损坏,无法打开”。

在为此苦苦挣扎了一段时间后,我转向了第二种方法,涉及为输出文档操作 word/document.xml 文件。我已经为保存为replacementXML 的复选框检索了我认为正确的xml,并将填充文本插入到单元格中以充当可以搜索和替换的标签searchXML。以下似乎在 linux (Fedora 25) 环境中使用 python 运行,但是当我尝试打开文档时 word 文档显示相同的错误,但是这次文档是可恢复的并恢复为填充文本。我已经能够让它与手动制作的文档一起使用并使用一个空的表格单元格,所以我相信这应该是可能的。注意:我已经在 searchXML 变量中包含了表格单元格的整个 xml 元素,但我尝试使用正则表达式并缩短字符串。不只是使用精确匹配,因为我知道这可能会因单元格而异。

searchXML = r'<w:tc><w:tcPr><w:tcW w:type="dxa" w:w="4320"/><w:gridSpan w:val="2"/></w:tcPr><w:p><w:pPr><w:jc w:val="right"/></w:pPr><w:r><w:rPr><w:sz w:val="16"/></w:rPr><w:t>IN_CHECKB</w:t></w:r></w:p></w:tc>'

def addCheckboxes(): 
    os.system("mkdir unzipped")
    os.system("unzip tempdoc.docx -d unzipped/")

    with open('unzipped/word/document.xml', encoding="ISO-8859-1") as file:
        filedata = file.read()

    rep_count = 0
    while re.search(searchXML, filedata):
        filedata = replaceXML(filedata, rep_count)
        rep_count += 1

    with open('unzipped/word/document.xml', 'w') as file:
        file.write(filedata)

    os.system("zip -r ../buildcfg/tempdoc.docx unzipped/*")
    os.system("rm -rf unzipped")

def replaceXML(filedata, rep_count):
    replacementXML = r'<w:tc><w:tcPr><w:tcW w:w="4320" w:type="dxa"/><w:gridSpan w:val="2"/></w:tcPr><w:p w:rsidR="00D2569D" w:rsidRDefault="00FD6FDF"><w:pPr><w:jc w:val="right"/></w:pPr><w:r><w:rPr><w:sz w:val="16"/>
                       </w:rPr><w:fldChar w:fldCharType="begin"><w:ffData><w:name w:val="Check1"/><w:enabled/><w:calcOnExit w:val="0"/><w:checkBox><w:sizeAuto/><w:default w:val="0"/></w:checkBox></w:ffData></w:fldChar>
                       </w:r><w:bookmarkStart w:id="' + rep_count + '" w:name="Check' + rep_count + '"/><w:r><w:rPr><w:sz w:val="16"/></w:rPr><w:instrText xml:space="preserve"> FORMCHECKBOX </w:instrText></w:r><w:r>
                       <w:rPr><w:sz w:val="16"/></w:rPr></w:r><w:r><w:rPr><w:sz w:val="16"/></w:rPr><w:fldChar w:fldCharType="end"/></w:r><w:bookmarkEnd w:id="' + rep_count + '"/></w:p></w:tc>'
    filedata = re.sub(searchXML, replacementXML, filedata, 1)

    rerturn filedata

我有一种强烈的感觉,即通过 python-docx 库有一种更简单(而且正确!)的方法,但由于某种原因,我似乎无法做到正确。

有没有一种方法可以轻松地将复选框字段插入 MS Word 文档中的表格单元格?如果是的话,我会怎么做?如果没有,有没有比操作 .xml 文件更好的方法?

更新:我已经能够使用 python-docx 成功地将 XML 注入到文档中,但是复选框和添加的 XML 没有出现。

我已将以下 XML 添加到表格单元格中:

<w:tc>
  <w:tcPr>
    <w:tcW w:type="dxa" w:w="4320"/>
    <w:gridSpan w:val="2"/>
  </w:tcPr>
  <w:p>
    <w:r>
      <w:bookmarkStart w:id="0" w:name="testName">
        <w:complexType w:name="CT_FFCheckBox">
          <w:sequence>
            <w:choice>
              <w:element w:name="size" w:type="CT_HpsMeasure"/>
              <w:element w:name="sizeAuto" w:type="CT_OnOff"/>
            </w:choice>
            <w:element w:name="default" w:type="CT_OnOff" w:minOccurs="0"/>
            <w:element w:name="checked" w:type="CT_OnOff" w:minOccurs="0"/>
          </w:sequence>
        </w:complexType>
      </w:bookmarkStart>
      <w:bookmarkEnd w:id="0" w:name="testName"/>
    </w:r>
  </w:p>
</w:tc>

使用以下 python-docx 代码:

run = p.add_run()
tag = run._r
start = docx.oxml.shared.OxmlElement('w:bookmarkStart')
start.set(docx.oxml.ns.qn('w:id'), '0')
start.set(docx.oxml.ns.qn('w:name'), n)
tag.append(start)

ctype = docx.oxml.OxmlElement('w:complexType')
ctype.set(docx.oxml.ns.qn('w:name'), 'CT_FFCheckBox')
seq = docx.oxml.OxmlElement('w:sequence')
choice = docx.oxml.OxmlElement('w:choice')
el = docx.oxml.OxmlElement('w:element')
el.set(docx.oxml.ns.qn('w:name'), 'size')
el.set(docx.oxml.ns.qn('w:type'), 'CT_HpsMeasure')
el2 = docx.oxml.OxmlElement('w:element')
el2.set(docx.oxml.ns.qn('w:name'), 'sizeAuto')
el2.set(docx.oxml.ns.qn('w:type'), 'CT_OnOff')

choice.append(el)
choice.append(el2)

el3 = docx.oxml.OxmlElement('w:element')
el3.set(docx.oxml.ns.qn('w:name'), 'default')
el3.set(docx.oxml.ns.qn('w:type'), 'CT_OnOff')
el3.set(docx.oxml.ns.qn('w:minOccurs'), '0')
el4 = docx.oxml.OxmlElement('w:element')
el4.set(docx.oxml.ns.qn('w:name'), 'checked')
el4.set(docx.oxml.ns.qn('w:type'), 'CT_OnOff')
el4.set(docx.oxml.ns.qn('w:minOccurs'), '0')

seq.append(choice)
seq.append(el3)
seq.append(el4)

ctype.append(seq)
start.append(ctype)

end = docx.oxml.shared.OxmlElement('w:bookmarkEnd')
end.set(docx.oxml.ns.qn('w:id'), '0')
end.set(docx.oxml.ns.qn('w:name'), n)
tag.append(end)

似乎找不到 XML 没有反映在输出文档中的原因,但会根据我找到的任何内容进行更新。

【问题讨论】:

【参考方案1】:

经过@scanny 的大量挖掘和帮助,我终于能够完成这项工作。

可以使用以下函数将复选框插入python-docx 中的任何段落。我在表格的特定单元格中插入了一个复选框。

def addCheckbox(para, box_id, name, checked):

  run = para.add_run()
  tag = run._r
  fldchar = docx.oxml.shared.OxmlElement('w:fldChar')
  fldchar.set(docx.oxml.ns.qn('w:fldCharType'), 'begin')

  ffdata = docx.oxml.shared.OxmlElement('w:ffData')
  name = docx.oxml.shared.OxmlElement('w:name')
  name.set(docx.oxml.ns.qn('w:val'), cb_name)
  enabled = docx.oxml.shared.OxmlElement('w:enabled')
  calconexit = docx.oxml.shared.OxmlElement('w:calcOnExit')
  calconexit.set(docx.oxml.ns.qn('w:val'), '0')

  checkbox = docx.oxml.shared.OxmlElement('w:checkBox')
  sizeauto = docx.oxml.shared.OxmlElement('w:sizeAuto')
  default = docx.oxml.shared.OxmlElement('w:default')

  if checked:
    default.set(docx.oxml.ns.qn('w:val'), '1')
  else:
    default.set(docx.oxml.ns.qn('w:val'), '0')

  checkbox.append(sizeauto)
  checkbox.append(default)
  ffdata.append(name)
  ffdata.append(enabled)
  ffdata.append(calconexit)
  ffdata.append(checkbox)
  fldchar.append(ffdata)
  tag.append(fldchar)

  run2 = para.add_run()
  tag2 = run2._r
  start = docx.oxml.shared.OxmlElement('w:bookmarkStart')
  start.set(docx.oxml.ns.qn('w:id'), str(box_id))
  start.set(docx.oxml.ns.qn('w:name'), name)
  tag2.append(start)

  run3 = para.add_run()
  tag3 = run3._r
  instr = docx.oxml.OxmlElement('w:instrText')
  instr.text = 'FORMCHECKBOX'
  tag3.append(instr)

  run4 = para.add_run()
  tag4 = run4._r
  fld2 = docx.oxml.shared.OxmlElement('w:fldChar')
  fld2.set(docx.oxml.ns.qn('w:fldCharType'), 'end')
  tag4.append(fld2)

  run5 = para.add_run()
  tag5 = run5._r
  end = docx.oxml.shared.OxmlElement('w:bookmarkEnd')
  end.set(docx.oxml.ns.qn('w:id'), str(box_id))
  end.set(docx.oxml.ns.qn('w:name'), name)
  tag5.append(end)

  return

fldData.text 对象似乎是随机的,但它是从生成的 XML 表单中获取的,它是一个带有现有复选框的 word 文档。如果不设置此文本,该函数将失败。我尚未确认,但我听说过一种情况,即开发人员任意更改字符串但保存后会恢复为原始生成的值。

【讨论】:

嗨,它是否像我尝试打印'FORMCHECKBOX'文本而不是复选框时那样工作【参考方案2】:

这些变通方法的关键是要有一个有效的 XML 示例,并且能够比较您生成的 XML。如果您生成与工作示例匹配的 XML,那么它每次都会工作。 opc-diag 便于检查 Word 文档中的 XML。使用非常小的文档(例如单段或两行表格,用于分析目的)可以更轻松地了解 Word 是如何构建 XML 的。

需要注意的重要一点是,Word 文档中的 XML 元素是顺序敏感的,这意味着任何其他元素中的子元素通常具有它们必须出现的固定顺序。如果你把它换掉,你会得到你提到的“修复”错误。

我发现在python-docx 中操作 XML 更容易,因为它会为您处理所有解压缩和重新压缩以及许多其他细节。

要正确排序,您需要熟悉您正在使用的元素的 XML 模式规范。这里有一个例子: http://python-docx.readthedocs.io/en/latest/dev/analysis/features/text/paragraph-format.html

完整架构位于ref/xsd/ 下的代码树中。大多数文本元素都在wml.xsd 文件中(wml 代表文字处理标记语言)。

您可以通过搜索"python-docx" workaround function 找到其他所谓的“解决方法”的示例。请特别注意parse_xml() 函数和OxmlElement 对象,它们允许您分别创建新的XML 子树和单个元素。 XML 元素可以使用常规的lxml._Element 方法定位; python-docx 中的所有 XML 元素都基于 lxml。 http://lxml.de/api/lxml.etree._Element-class.html

【讨论】:

感谢您的回复scanny!我现在正在研究模式并尝试将其应用于更简单的(python 生成的)word doc。我将更新我的进展和出现的任何其他问题。另外,感谢您在社区中如此活跃。如果不是您的回复,我不会在这个问题上取得任何进展! 嗨@scanny,我一直试图让 parse_xml() 函数为复选框工作,但我收到一个 lxml.etree.XMLSyntaxError 抱怨未定义命名空间。我理解错误,但对 XML 不是很熟悉,我不知道如何正确添加定义。我正在使用您提供的架构中的 XML。我使用了一些更简单的文档来理解复选标记的顺序,它似乎需要在单元格中放置一个段落。使用'cell._tc._add_p'然后为复选框插入xml是否正确?非常感谢任何帮助! 完美!会的! 我已根据我的进度更新了问题。知道为什么文档没有反映我添加的 XML 吗? 我完全明白!我昨天无法做到,因为我低于最低“分数”。我非常感谢您的所有帮助!

以上是关于如何使用 python-docx 将复选框表单插入 .docx 文件?的主要内容,如果未能解决你的问题,请参考以下文章

使用选择表单将复选框值插入 mysql 上特定列的 PHP 代码

python-docx 插入点

Python-docx - 从 URL 将图片插入 docx

如何在 Django 表单中插入复选框

如何将多个复选框值插入表中?

如果选中复选框,如何将自动值插入编辑组件