|
|
@@ -94,6 +94,16 @@ def inject_styles_from_json(doc: Document, style_data: dict) -> None:
|
|
94
|
94
|
|
|
95
|
95
|
# 注入编号格式定义
|
|
96
|
96
|
inject_numbering_from_json(doc, style_data)
|
|
|
97
|
+
|
|
|
98
|
+ # 强制清除 python-docx 的样式缓存,确保后续使用的是新注入的样式
|
|
|
99
|
+ # 这一步很重要,因为 python-docx 会缓存样式对象
|
|
|
100
|
+ try:
|
|
|
101
|
+ # 清除样式字典缓存,强制重新从 XML 读取
|
|
|
102
|
+ if hasattr(doc.styles, '_element'):
|
|
|
103
|
+ # 触发样式重新加载
|
|
|
104
|
+ doc.styles._element = styles_element
|
|
|
105
|
+ except Exception:
|
|
|
106
|
+ pass
|
|
97
|
107
|
|
|
98
|
108
|
|
|
99
|
109
|
def inject_numbering_from_json(doc: Document, style_data: dict) -> None:
|
|
|
@@ -279,6 +289,10 @@ def blocks_to_docx_bytes(blocks: list[dict], style_map: dict, style_data: dict)
|
|
279
|
289
|
doc = Document()
|
|
280
|
290
|
inject_styles_from_json(doc, style_data)
|
|
281
|
291
|
|
|
|
292
|
+ # 验证并修正 Normal 样式的段后间距
|
|
|
293
|
+ # 这是为了确保样式正确应用,避免 python-docx 的默认值覆盖
|
|
|
294
|
+ _fix_normal_style_spacing(doc, style_data)
|
|
|
295
|
+
|
|
282
|
296
|
# 检查 blocks 中是否有常用的字体和字号,用于修改 Normal 样式
|
|
283
|
297
|
# 这样可以确保空行在 Word 中显示正确的字体
|
|
284
|
298
|
_update_normal_style_if_needed(doc, blocks)
|
|
|
@@ -305,6 +319,94 @@ def blocks_to_docx_bytes(blocks: list[dict], style_map: dict, style_data: dict)
|
|
305
|
319
|
return docx_bytes
|
|
306
|
320
|
|
|
307
|
321
|
|
|
|
322
|
+def _fix_normal_style_spacing(doc: Document, style_data: dict):
|
|
|
323
|
+ """验证并修正 Normal 样式的段后间距
|
|
|
324
|
+
|
|
|
325
|
+ 从 style_data 中读取 Normal 样式的段后间距定义,
|
|
|
326
|
+ 确保文档中的 Normal 样式与之一致
|
|
|
327
|
+
|
|
|
328
|
+ 关键修复:python-docx 默认模板中 Normal 样式的 styleId 可能是 "Normal" 而不是 "1",
|
|
|
329
|
+ 需要同时检查这两种情况
|
|
|
330
|
+ """
|
|
|
331
|
+ try:
|
|
|
332
|
+ # 查找 Normal 样式的定义
|
|
|
333
|
+ normal_style_def = None
|
|
|
334
|
+ for style_entry in style_data.get("styles", []):
|
|
|
335
|
+ if style_entry.get("name") == "Normal" or style_entry.get("style_id") == "1":
|
|
|
336
|
+ normal_style_def = style_entry
|
|
|
337
|
+ break
|
|
|
338
|
+
|
|
|
339
|
+ if not normal_style_def:
|
|
|
340
|
+ return
|
|
|
341
|
+
|
|
|
342
|
+ # 从 full_xml_definition 中提取段后间距
|
|
|
343
|
+ xml_def = normal_style_def.get("full_xml_definition", {})
|
|
|
344
|
+ pPr = xml_def.get("@children", {}).get("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}pPr", {})
|
|
|
345
|
+ spacing = pPr.get("@children", {}).get("{http://schemas.openxmlformats.org/wordprocessingml/2006/main}spacing", {})
|
|
|
346
|
+ spacing_attrib = spacing.get("@attrib", {})
|
|
|
347
|
+
|
|
|
348
|
+ # 检查是否定义了段后间距
|
|
|
349
|
+ after_key = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}after"
|
|
|
350
|
+ has_after = after_key in spacing_attrib
|
|
|
351
|
+ after_value = spacing_attrib.get(after_key, "0")
|
|
|
352
|
+
|
|
|
353
|
+ # 如果样式定义中没有 w:after 或者 w:after="0",确保文档中所有 Normal 样式也是 0
|
|
|
354
|
+ if not has_after or after_value == "0":
|
|
|
355
|
+ styles_element = doc.styles.element
|
|
|
356
|
+
|
|
|
357
|
+ # 需要修正的所有 Normal 样式 ID(python-docx 可能使用不同的 ID)
|
|
|
358
|
+ normal_style_ids = ["1", "Normal", "normal"]
|
|
|
359
|
+
|
|
|
360
|
+ for style_id in normal_style_ids:
|
|
|
361
|
+ normal_elem = styles_element.find(
|
|
|
362
|
+ f'.//{qn("w:style")}[@{qn("w:styleId")}="{style_id}"]'
|
|
|
363
|
+ )
|
|
|
364
|
+
|
|
|
365
|
+ if normal_elem is not None:
|
|
|
366
|
+ _apply_spacing_fix_to_style_elem(normal_elem)
|
|
|
367
|
+
|
|
|
368
|
+ # 也尝试通过名称查找(可能有其他命名的 Normal 样式)
|
|
|
369
|
+ for style_elem in styles_element.findall(qn("w:style")):
|
|
|
370
|
+ name_elem = style_elem.find(qn("w:name"))
|
|
|
371
|
+ if name_elem is not None:
|
|
|
372
|
+ name_val = name_elem.get(qn("w:val"))
|
|
|
373
|
+ if name_val and name_val.lower() == "normal":
|
|
|
374
|
+ _apply_spacing_fix_to_style_elem(style_elem)
|
|
|
375
|
+
|
|
|
376
|
+ except Exception as e:
|
|
|
377
|
+ # 如果修正失败,不影响文档生成,只是可能保留默认的 10 磅间距
|
|
|
378
|
+ print(f"警告: 修正 Normal 样式段后间距失败: {e}")
|
|
|
379
|
+ pass
|
|
|
380
|
+
|
|
|
381
|
+
|
|
|
382
|
+def _apply_spacing_fix_to_style_elem(style_elem):
|
|
|
383
|
+ """对单个样式元素应用间距修复"""
|
|
|
384
|
+ try:
|
|
|
385
|
+ # 找到或创建 pPr 节点
|
|
|
386
|
+ pPr_elem = style_elem.find(qn("w:pPr"))
|
|
|
387
|
+ if pPr_elem is None:
|
|
|
388
|
+ pPr_elem = OxmlElement("w:pPr")
|
|
|
389
|
+ # 插入到第一个位置(在 name 之后)
|
|
|
390
|
+ name_elem = style_elem.find(qn("w:name"))
|
|
|
391
|
+ if name_elem is not None:
|
|
|
392
|
+ idx = list(style_elem).index(name_elem) + 1
|
|
|
393
|
+ style_elem.insert(idx, pPr_elem)
|
|
|
394
|
+ else:
|
|
|
395
|
+ style_elem.insert(0, pPr_elem)
|
|
|
396
|
+
|
|
|
397
|
+ # 找到或创建 spacing 节点
|
|
|
398
|
+ spacing_elem = pPr_elem.find(qn("w:spacing"))
|
|
|
399
|
+ if spacing_elem is None:
|
|
|
400
|
+ spacing_elem = OxmlElement("w:spacing")
|
|
|
401
|
+ pPr_elem.append(spacing_elem)
|
|
|
402
|
+
|
|
|
403
|
+ # 确保 w:after="0"(明确设置为 0,而不是依赖默认值)
|
|
|
404
|
+ spacing_elem.set(qn("w:after"), "0")
|
|
|
405
|
+
|
|
|
406
|
+ except Exception:
|
|
|
407
|
+ pass
|
|
|
408
|
+
|
|
|
409
|
+
|
|
308
|
410
|
def _update_normal_style_if_needed(doc: Document, blocks: list[dict]):
|
|
309
|
411
|
"""更新 Normal 样式以匹配 blocks 中最常用的字体
|
|
310
|
412
|
|