我一直在使用Python来实现自定义解析器,并使用解析后的数据来格式化要在内部分发的Word文档。到目前为止,所有格式设置都非常简单明了,但我完全不知道如何将复选框插入到各个表格单元格中。
我尝试在 python-docx 中使用 python 对象函数(使用
get_or_add_tcPr()
等),这会导致 MS Word 在我尝试打开文件时抛出以下错误:“无法打开文件 xxxx,因为有内容问题详细信息:文件已损坏且无法打开”。
在为此苦苦挣扎了一段时间后,我转向了第二种方法,涉及操作输出文档的 word/document.xml 文件。我已经检索了我认为保存为
replacementXML
的复选框的正确 xml,并将填充文本插入到单元格中以充当可搜索和替换的标签,searchXML
。以下内容似乎在 Linux (Fedora 25) 环境中使用 python 运行,但当我尝试打开文档时,word 文档显示相同的错误,但是这次文档是可恢复的并恢复为填充文本。我已经能够让它与手动制作的文档和使用空表格单元格一起使用,所以我相信这应该是可能的。注意:我已将表格单元格的整个 xml 元素包含在 searchXML
变量中,但我尝试使用正则表达式并缩短字符串。不仅仅是使用精确匹配,因为我知道这可能会因单元格而异。
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 未反映在输出文档中的原因,但会用我找到的任何内容进行更新。
经过大量挖掘和@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 文档中获取的,其中包含现有的复选框。如果不设置此文本,该函数将失败。我尚未确认,但我听说过一种情况,开发人员任意更改字符串,但一旦保存,它将恢复为原始生成的值。
这些解决方法函数的关键是要有一个有效的 XML 示例,并且能够比较您生成的 XML。如果您生成与工作示例相匹配的 XML,那么它每次都会工作。
opc-diag
用于检查 Word 文档中的 XML 非常方便。使用非常小的文档(例如用于分析目的的单段落或两行表)可以更轻松地弄清楚 Word 是如何构建 XML 的。
需要注意的重要一点是,Word 文档中的 XML 元素是“顺序敏感”的,这意味着任何其他元素中的子元素通常都有一个必须出现的设定顺序。如果你把它调换,你会得到你提到的“修复”错误。 我发现从内部操作 XML 更加容易
python-docx
,因为它会为您处理所有解压缩和重新压缩以及许多其他细节。
为了获得正确的排序,您需要熟悉您正在使用的元素的 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
def add_input_checkbox_content_control(paragraph, checked):
paragraph_tag = paragraph._element
sdt = oxml.shared.OxmlElement('w:sdt')
sdt_props = oxml.shared.OxmlElement('w:sdtPr')
w_checkbox = oxml.shared.OxmlElement('w14:checkbox')
w_checked = oxml.shared.OxmlElement('w14:checked')
w_checked.set(oxml.ns.qn('w14:val'), "1" if checked else "0")
w_checked_state = oxml.shared.OxmlElement('w14:checkedState')
w_checked_state.set(oxml.ns.qn('w14:font'), "MS Gothic")
w_checked_state.set(oxml.ns.qn('w14:val'), "2612") # unicode value for box with x in it
w_unchecked_state = oxml.shared.OxmlElement('w14:uncheckedState')
w_unchecked_state.set(oxml.ns.qn('w14:font'), "MS Gothic")
w_unchecked_state.set(oxml.ns.qn('w14:val'), "2610") # unicode value for empty box
w_checkbox.append(w_checked)
w_checkbox.append(w_checked_state)
w_checkbox.append(w_unchecked_state)
sdt_props.append(w_checkbox)
sdt_content = oxml.shared.OxmlElement('w:sdtContent')
sdt_content_run = oxml.OxmlElement('w:r')
# the box doesn't appear until clicked, so a default unicode box will need to be set as a placeholder
sdt_content_run.text = "\u2612" if checked else "\u2610"
sdt_content.append(sdt_content_run)
sdt.append(sdt_props)
sdt.append(sdt_content)
paragraph_tag.append(sdt)
import docx
try:
# Create an instance of a Word document
doc = docx.Document()
# Save the empty Word document
doc.save(r"C:\My Documents\checkbox_test_document.docx")
except PermissionError:
print("Need to close the open document")
try:
# Create an instance of a Word document
doc = docx.Document()
# Save the empty Word document (overwrites an existing document of the same name)
doc.save(r"C:\My Documents\checkbox_test_document.docx")
# Create a new instance of the empty Word document that has been created
doc = docx.Document(r"C:\My Documents\checkbox_test_document.docx")
# Raise an exception if a previously generated Word document is open
except PermissionError:
print("Need to close the open document")
check1 = "text for checkbox1"
check2 = "text for checkbox2"
checklist = [check1, check2]
def add_checlist_to_docx(document, checklist_item):
from docx.oxml.shared import OxmlElement, qn
paragraph = document.add_paragraph()
tag = paragraph._p
sdt = OxmlElement('w:sdt')
sdtPr = OxmlElement('w:sdtPr')
checkbox = OxmlElement('w14:checkbox')
checked = OxmlElement('w14:checked')
checked.set(qn('w14:val'), '0')
checkedState = OxmlElement('w14:checkedState')
checkedState.set(qn('w14:val'), '2612')
checkedState.set(qn('w14:font'), 'MS Gothic')
uncheckedState = OxmlElement('w14:uncheckedState')
uncheckedState.set(qn('w14:val'), '2610')
uncheckedState.set(qn('w14:font'), 'MS Gothic')
sdtContent = OxmlElement('w:sdtContent')
r_box = OxmlElement('w:r')
rPr = OxmlElement('w:rPr')
rFonts = OxmlElement('w:rFonts')
rFonts.set(qn('w:ascii'), 'MS Gothic')
rFonts.set(qn('w:eastAsia'), 'MS Gothic')
rFonts.set(qn('w:hAnsi'), 'MS Gothic')
rFonts.set(qn('w:hint'), 'eastAsia')
t_box = OxmlElement('w:t')
r_text = OxmlElement('w:r')
t_text = OxmlElement('w:t')
t_text.set(qn('xml:space'), 'preserve')
checkbox.append(checked)
checkbox.append(checkedState)
checkbox.append(uncheckedState)
sdtPr.append(checkbox)
sdt.append(sdtPr)
rPr.append(rFonts)
t_box.text = '☐'
r_box.append(rPr)
r_box.append(t_box)
sdtContent.append(r_box)
sdt.append(sdtContent)
tag.append(sdt)
t_text.text = checklist_item
r_text.append(t_text)
tag.append(r_text)
return
for check in checklist:
add_checlist_to_docx(doc, check)
doc.save(r"C:\My Documents\checkbox_test_document.docx")