51  Python-docx

51.1 占位符替换

页眉和正文的文本对象不同

51.1.1 页眉和正文的普通文本

from docx import Document

dept_name = "这里是科室名称"
major_complaint = "这里是主诉"

replacemen_dict = {'dept-name': dept_name,
                   'major-complaint': major_complaint,
                   }

doc = Document('data/结构化病历模板-入院记录.docx')



# 替换页眉中的占位符

for section in doc.sections:
    header = section.header
    for pg in header.paragraphs:
        if "{{dept-name}}" in pg.text:
            pg.text = pg.text.replace("{{dept-name}}", dept_name)


# 替换段落中的占位符
for placeholder, replace_text in replacemen_dict.items():
    for pg in doc.paragraphs:
        # print(pg.text)
        # print(f"{{{{{placeholder}}}}}")
        if f"{{{{{placeholder}}}}}" in pg.text:
            pg.text = pg.text.replace(f"{{{{{placeholder}}}}}", replace_text)

doc.save('data/output.docx')

51.1.2 正文的特殊字符

比如复选框,单选框等

from docx import Document

doc = Document('data/demo-template.docx')

# 遍历文档中的所有 paragraph 和 run
for paragraph in doc.paragraphs:
    for run in paragraph.runs:
        # 检查 run 中是否包含占位符
        if "{复选框已选中状态}" in run.text:
            # 获取占位符的索引
            index = run.text.find("{复选框已选中状态}")
            
            # 替换占位符为复选框(使用Unicode字符)
            run.text = run.text.replace("{复选框已选中状态}", "")
            
            # 在占位符位置后插入选中状态的复选框(使用Unicode字符)
            run.add_text(u'\u2611')  # Unicode字符:☑

# 保存文档
doc.save("data/demo.docx")

51.1.3 表格中的占位符

前提是保证点位符属于同一个运行(run),可以采用对连续字符应用样式。这里因为,: 如果字符是连续的并且具有相同的格式,则它们通常会被包含在同一个运行中。例如,如果您在Word中键入一段文字并对其应用相同的字体、字号和颜色,则这些字符通常会被视为同一个运

import json

with open('data/json_path_value_dict.json', 'r', encoding='utf-8') as f:
    json_data = json.load(f)

json_data
{'input-text-number.GBS-Score.sbp-score.name': '收缩压得分',
 'input-text-number.GBS-Score.sbp-score.value': 2,
 'input-text-number.GBS-Score.sbun-score.name': '血尿素氮得分',
 'input-text-number.GBS-Score.sbun-score.value': 2,
 'input-text-number.GBS-Score.hgb-m-score.name': '血红蛋白得分-男',
 'input-text-number.GBS-Score.hgb-m-score.value': None,
 'input-text-number.GBS-Score.hgb-f-score.name': '血红蛋白得分-女',
 'input-text-number.GBS-Score.hgb-f-score.value': None,
 'input-text-number.GBS-Score.hgb-score.name': '血红蛋白得分',
 'input-text-number.GBS-Score.hgb-score.value': 3,
 'input-text-number.GBS-Score.other-signs-score.name': '其他表现得分',
 'input-text-number.GBS-Score.other-signs-score.value': 2,
 'input-text-number.GBS-Score.total-score.name': 'GBS评分总分',
 'input-text-number.GBS-Score.total-score.value': 9,
 'input-text-number.ROCKALL-Score.age-score.name': '年龄得分',
 'input-text-number.ROCKALL-Score.age-score.value': 1,
 'input-text-number.ROCKALL-Score.shock-score.name': '休克状况得分',
 'input-text-number.ROCKALL-Score.shock-score.value': 2,
 'input-text-number.ROCKALL-Score.combordity-score.name': '伴发疾病得分',
 'input-text-number.ROCKALL-Score.combordity-score.value': 0,
 'input-text-number.ROCKALL-Score.endoscopy-sign-score.name': '内镜诊断得分',
 'input-text-number.ROCKALL-Score.endoscopy-sign-score.value': None,
 'input-text-number.ROCKALL-Score.recent-bleeding-sign-score.name': '近期出血征象得分',
 'input-text-number.ROCKALL-Score.recent-bleeding-sign-score.value': None,
 'input-text-number.ROCKALL-Score.total-score.name': 'ROCKALL评分总分',
 'input-text-number.ROCKALL-Score.total-score.value': 3,
 'input-text-number.detail-table.doctor.name': '主管医生',
 'input-text-number.detail-table.doctor.value': None,
 'input-text-number.detail-table.name.name': '姓名',
 'input-text-number.detail-table.name.value': None,
 'input-text-number.detail-table.age.name': '年龄',
 'input-text-number.detail-table.age.value': None,
 'input-text-number.detail-table.patid.name': '住院号',
 'input-text-number.detail-table.patid.value': None,
 'input-text-number.detail-table.icu-stay.name': 'ICU住院日',
 'input-text-number.detail-table.icu-stay.value': None,
 'input-text-number.detail-table.gbs-score.name': 'GBS评分',
 'input-text-number.detail-table.gbs-score.value': 2,
 'input-text-number.detail-table.rockall-score.name': 'ROCKALL评分',
 'input-text-number.detail-table.rockall-score.value': 3,
 'input-text-number.detail-table.blood-report-time.name': '血常规报告时间',
 'input-text-number.detail-table.blood-report-time.value': 7,
 'input-text-number.detail-table.aptt-report-time.name': '止凝血报告时间',
 'input-text-number.detail-table.aptt-report-time.value': 10,
 'input-text-number.detail-table.chemistry-report-time.name': '生化报告时间',
 'input-text-number.detail-table.chemistry-report-time.value': 1,
 'input-text-number.detail-table.first-blood-bag-time.name': '第1袋发血时间',
 'input-text-number.detail-table.first-blood-bag-time.value': 9,
 'input-text-number.detail-table.gastric-department-consult-time.name': '消化内科会诊时间',
 'input-text-number.detail-table.gastric-department-consult-time.value': 17,
 'input-text-number.detail-table.mdt-time.name': 'MDT时间',
 'input-text-number.detail-table.mdt-time.value': 4,
 'input-text-number.detail-table.endoscopy-arrival-time.name': '胃镜到达现场时间',
 'input-text-number.detail-table.endoscopy-arrival-time.value': 2,
 'input-text-number.detail-table.ulcer-site.name': '溃疡部位',
 'input-text-number.detail-table.ulcer-site.value': None,
 'input-text-number.detail-table.other-endoscopy-view.name': '其他胃镜结果',
 'input-text-number.detail-table.other-endoscopy-view.value': None,
 'input-text-number.detail-table.bed-endoscopy-duration.name': '床旁胃镜时间',
 'input-text-number.detail-table.bed-endoscopy-duration.value': 2,
 'input-text-number.detail-table.red-blood-cell-infusion-units.name': '输红细胞总量',
 'input-text-number.detail-table.red-blood-cell-infusion-units.value': 1,
 'input-text-number.detail-table.plasma-infusion-volume.name': '输血浆总量',
 'input-text-number.detail-table.plasma-infusion-volume.value': 0,
 'input-text-number.detail-table.platelet-infusion-volume.name': '输血小板总量',
 'input-text-number.detail-table.platelet-infusion-volume.value': 2,
 'input-text-number.detail-table.cold-precipitation-volume.name': '输冷沉淀总量',
 'input-text-number.detail-table.cold-precipitation-volume.value': 30,
 'input-text-number.detail-table.fibrinogen-infusion-volume.name': '输纤维蛋白原总量',
 'input-text-number.detail-table.fibrinogen-infusion-volume.value': 1.5,
 'input-text-number.detail-table.acid-suppressor-name.name': '抑酸药物名称',
 'input-text-number.detail-table.acid-suppressor-name.value': '拉唑',
 'input-text-number.detail-table.surgery-name.name': '外科手术名称',
 'input-text-number.detail-table.surgery-name.value': '胃大部切除',
 'input-text-number.detail-table.intervention-name.name': '介入手术名称',
 'input-text-number.detail-table.intervention-name.value': '介入栓塞',
 'input-text-number.detail-table.utlimate-confirmed-bleeding-site.name': '最终诊断出血部分',
 'input-text-number.detail-table.utlimate-confirmed-bleeding-site.value': None,
 'input-text-number.detail-table.department-transferred-for.name': '转出科室名称',
 'input-text-number.detail-table.department-transferred-for.value': None,
 'input-text-number.detail-table.total-fee.name': '总费用',
 'input-text-number.detail-table.total-fee.value': 5064.36,
 'radio-group.GBS-Score.sbp-value.name': '收缩压值',
 'radio-group.GBS-Score.sbp-value.value': '2分:90 ~ 100',
 'radio-group.GBS-Score.sbun-value.name': '血尿素氮值',
 'radio-group.GBS-Score.sbun-value.value': '2分:6.5 ~ 7.9',
 'radio-group.GBS-Score.hgb-m-value.name': '血红蛋白值-男',
 'radio-group.GBS-Score.hgb-m-value.value': '3分:110 ~ 119',
 'radio-group.GBS-Score.hgb-f-value.name': '血红蛋白值-女',
 'radio-group.GBS-Score.hgb-f-value.value': None,
 'radio-group.GBS-Score.risk-category.name': 'GBS评分危险度分级',
 'radio-group.GBS-Score.risk-category.value': '≥6分:中高危',
 'radio-group.ROCKALL-Score.age-value.name': '年龄值',
 'radio-group.ROCKALL-Score.age-value.value': '1分:60 ~ 79',
 'radio-group.ROCKALL-Score.shock-value.name': '休克状况值',
 'radio-group.ROCKALL-Score.shock-value.value': '2分:低血压(收缩压<100mmHg,心率≥100次/分)',
 'radio-group.ROCKALL-Score.recent-bleeding-sign.name': '近期出血征象',
 'radio-group.ROCKALL-Score.recent-bleeding-sign.value': None,
 'radio-group.ROCKALL-Score.risk-category.name': 'ROCKALL评分危险度分级',
 'radio-group.ROCKALL-Score.risk-category.value': '3~4分:中危',
 'radio-group.detail-table.patient-source.name': '病人来源',
 'radio-group.detail-table.patient-source.value': '普通病房',
 'radio-group.detail-table.gbs-risk.name': 'GBS风险分级',
 'radio-group.detail-table.gbs-risk.value': '低危',
 'radio-group.detail-table.rockall-risk.name': 'ROCKALL风险分级',
 'radio-group.detail-table.rockall-risk.value': '中危',
 'radio-group.detail-table.intubation.name': '气管插管',
 'radio-group.detail-table.intubation.value': '是',
 'radio-group.detail-table.cvc.name': '深静脉置管',
 'radio-group.detail-table.cvc.value': '是',
 'radio-group.detail-table.bedside-endoscopy.name': '床旁胃镜',
 'radio-group.detail-table.bedside-endoscopy.value': '否',
 'radio-group.detail-table.808-schema.name': '是否采用808方案',
 'radio-group.detail-table.808-schema.value': '是',
 'radio-group.detail-table.eddoscropy-over-time.name': '胃镜完善时间',
 'radio-group.detail-table.eddoscropy-over-time.value': '2小时内',
 'radio-group.detail-table.surgery-initialization-time.name': '手术时间',
 'radio-group.detail-table.surgery-initialization-time.value': '24小时内',
 'radio-group.detail-table.intervention-initialization-time.name': '介入时间',
 'radio-group.detail-table.intervention-initialization-time.value': '24小时内',
 'radio-group.detail-table.post-care-rebleeding.name': '治疗再出血',
 'radio-group.detail-table.post-care-rebleeding.value': '是',
 'check.GBS-Score.other-signs-pulse.name': '其他表现-脉搏',
 'check.GBS-Score.other-signs-pulse.value': 1,
 'check.GBS-Score.other-signs-black-stools.name': '其他表现-黑便',
 'check.GBS-Score.other-signs-black-stools.value': 1,
 'check.GBS-Score.other-signs-faintness.name': '其他表现-晕厥',
 'check.GBS-Score.other-signs-faintness.value': False,
 'check.GBS-Score.other-signs-liver-disease.name': '其他表现-肝病',
 'check.GBS-Score.other-signs-liver-disease.value': False,
 'check.GBS-Score.other-signs-heart-failure.name': '其他表现-心衰',
 'check.GBS-Score.other-signs-heart-failure.value': False,
 'check.ROCKALL-Score.combordity-none.name': '伴发疾病-无',
 'check.ROCKALL-Score.combordity-none.value': 1,
 'check.ROCKALL-Score.combordity-heart.name': '伴发疾病-心衰',
 'check.ROCKALL-Score.combordity-heart.value': 0,
 'check.ROCKALL-Score.combordity-liver-kidney-tumor.name': '伴发疾病-肝肾肿瘤',
 'check.ROCKALL-Score.combordity-liver-kidney-tumor.value': 0,
 'check.ROCKALL-Score.endoscopy-sign-none.name': '内镜诊断-无',
 'check.ROCKALL-Score.endoscopy-sign-none.value': False,
 'check.ROCKALL-Score.endoscopy-sign-ulcer.name': '内镜诊断-溃疡',
 'check.ROCKALL-Score.endoscopy-sign-ulcer.value': False,
 'check.ROCKALL-Score.endoscopy-sign-tumor.name': '内镜诊断-肿瘤',
 'check.ROCKALL-Score.endoscopy-sign-tumor.value': False,
 'check.ROCKALL-Score.recent-bleeding-sign-none.name': '近期出血征象-无',
 'check.ROCKALL-Score.recent-bleeding-sign-none.value': False,
 'check.ROCKALL-Score.recent-bleeding-sign-exist.name': '近期出血征象-有',
 'check.ROCKALL-Score.recent-bleeding-sign-exist.value': False,
 'check.detail-table.mdt-yes.name': 'MDT-是',
 'check.detail-table.mdt-yes.value': 1,
 'check.detail-table.mdt-no.name': 'MDT-否',
 'check.detail-table.mdt-no.value': 1,
 'check.detail-table.endoscopy-site-room.name': '胃镜检查地点-胃镜室',
 'check.detail-table.endoscopy-site-room.value': 1,
 'check.detail-table.endoscopy-site-bedside.name': '胃镜检查地点-床边',
 'check.detail-table.endoscopy-site-bedside.value': 1,
 'check.detail-table.endoscopy-view-varicose-veins.name': '胃镜检查结果-静脉曲张',
 'check.detail-table.endoscopy-view-varicose-veins.value': 1,
 'check.detail-table.endoscopy-view-varicose-veins-esophagus.name': '胃镜检查结果-静脉曲张-食管',
 'check.detail-table.endoscopy-view-varicose-veins-esophagus.value': 1,
 'check.detail-table.endoscopy-view-varicose-veins-fundus.name': '胃镜检查结果-静脉曲张-胃底',
 'check.detail-table.endoscopy-view-varicose-veins-fundus.value': 1,
 'check.detail-table.endoscopy-view-ulcer.name': '胃镜检查结果-溃疡',
 'check.detail-table.endoscopy-view-ulcer.value': 1,
 'check.detail-table.endoscopy-view-others.name': '胃镜检查结果-其他',
 'check.detail-table.endoscopy-view-others.value': 1,
 'check.detail-table.outcome-discharge.name': '患者转归-出院',
 'check.detail-table.outcome-discharge.value': 1,
 'check.detail-table.outcome-transfer.name': '患者转归-转科',
 'check.detail-table.outcome-transfer.value': 1,
 'check.detail-table.outcome-death.name': '患者转归-死亡',
 'check.detail-table.outcome-death.value': 1,
 'date-time.detail-table.in-datetime.name': '入科日期',
 'date-time.detail-table.in-datetime.value': False,
 'date-time.detail-table.out-datetime.name': '出科时间',
 'date-time.detail-table.out-datetime.value': False,
 'key': 'f1d9beae-f215-42c5-b305-8db672d76809',
 'table_name': '消化道出血病人列表的每个病人的数据详情',
 'dept': '重症医学科三区',
 'patid': '1027386',
 'name': '林文海',
 'submit_time': '2024-05-14 16:10:42',
 'link_key': '9c79ec44-2f2d-4255-9914-ca6967b7d0e8',
 'gender': '男',
 'age': 56}
from docx import Document
import re

doc = Document('data/gastric-bleeding-score-template-with-placedholders.docx')

json_path_pattern = r'\{[a-zA-Z-_.]+@{0,}.*?\}'

# 读取文档中的所有表格
for table in doc.tables:
    for row in table.rows:
        for cell in row.cells:
            # 遍历单元格中的所有段落
            for paragraph in cell.paragraphs:
                # 遍历段落中的所有运行并打印文本内容
                for run in paragraph.runs:
                    # print(f"run.text: {run.text}")
                    matched = re.findall(json_path_pattern, run.text)
                    if matched:
                        # print(f"matched: {matched}")
                        for placeholder in matched:   
                            # 在json_data中的键
                            placeholder_key = placeholder.strip('{}')

                            
                            # 获取替换值
                            placeholder_replacement = json_data.get(placeholder_key, '')

                            # 如果为None或者False
                            if not placeholder_replacement:  
                                placeholder_replacement = ''
        
                            # 全部转换为字符型
                            placeholder_replacement = str(placeholder_replacement)

                            # 处理复选框

                            if 'check.' in placeholder: 
                                if placeholder_replacement == "1":
                                    placeholder_replacement = u'\u2611' # 复选模式选中状态
                                else:
                                    placeholder_replacement = u'\u2610' # 复选模式未选中状态
                                    
                            # 处理单选框
                            if 'radio-group.' in placeholder:

                                # radio-group的占位符比较特殊,需要调整下,如: {radio-group.detail-table.patient-source.value@急诊},即将选中时对应的value放在@后面
                                
                                # 调整placeholder_key
                                placeholder_key, placeholder_radio_value = placeholder_key.split("@")

                                # 获取替换值
                                placeholder_replacement = json_data.get(placeholder_key, '')

                                if placeholder_replacement == placeholder_radio_value:
                                    placeholder_replacement = u'\u2611' # 复选模式选中状态
                                else:
                                    placeholder_replacement = u'\u2610' # 复选模式未选中状态
                                    
                                    # 也可设置为单选框未选中状态的字符
                            
                                
                                
                            print(f"\n\nrun.text: {run.text}, \
                                    placeholder: {placeholder}, \
                                    placeholder_key: {placeholder_key}, \
                                    placeholder_replacement: {placeholder_replacement}")
                            

                            run.text = run.text.replace(placeholder, placeholder_replacement)

# 保存文档
doc.save("data/gastric-bleeding-score.docx")


run.text: {dept}危险性上消化道出血病人资料表,                                     placeholder: {dept},                                     placeholder_key: dept,                                     placeholder_replacement: 重症医学科三区


run.text: {date-time.detail-table.in-datetime.value},                                     placeholder: {date-time.detail-table.in-datetime.value},                                     placeholder_key: date-time.detail-table.in-datetime.value,                                     placeholder_replacement: 


run.text: {input-text-number.detail-table.doctor.value},                                     placeholder: {input-text-number.detail-table.doctor.value},                                     placeholder_key: input-text-number.detail-table.doctor.value,                                     placeholder_replacement: 


run.text: 姓名:{input-text-number.detail-table.name.value}    性别:{gender}  年龄:{age}  住院号:{patid}      ,                                     placeholder: {input-text-number.detail-table.name.value},                                     placeholder_key: input-text-number.detail-table.name.value,                                     placeholder_replacement: 


run.text: 姓名:    性别:{gender}  年龄:{age}  住院号:{patid}      ,                                     placeholder: {gender},                                     placeholder_key: gender,                                     placeholder_replacement: 男


run.text: 姓名:    性别:男  年龄:{age}  住院号:{patid}      ,                                     placeholder: {age},                                     placeholder_key: age,                                     placeholder_replacement: 56


run.text: 姓名:    性别:男  年龄:56  住院号:{patid}      ,                                     placeholder: {patid},                                     placeholder_key: patid,                                     placeholder_replacement: 1027386


run.text: {radio-group.detail-table.patient-source.value@急诊} ,                                     placeholder: {radio-group.detail-table.patient-source.value@急诊},                                     placeholder_key: radio-group.detail-table.patient-source.value,                                     placeholder_replacement: ☐


run.text: {radio-group.detail-table.patient-source.value@外院转诊} ,                                     placeholder: {radio-group.detail-table.patient-source.value@外院转诊},                                     placeholder_key: radio-group.detail-table.patient-source.value,                                     placeholder_replacement: ☐


run.text: {radio-group.detail-table.patient-source.value@普通病房},                                     placeholder: {radio-group.detail-table.patient-source.value@普通病房},                                     placeholder_key: radio-group.detail-table.patient-source.value,                                     placeholder_replacement: ☑


run.text: {check.detail-table.outcome-death.value},                                     placeholder: {check.detail-table.outcome-death.value},                                     placeholder_key: check.detail-table.outcome-death.value,                                     placeholder_replacement: ☑