ソースを参照

feat(export): 优化表格解析和样式提取逻辑

- 改进 colspan 处理:遍历所有行计算最大列数,而非仅使用首行列数
- 增强 _extract_table 函数:添加 doc 参数用于获取文档样式信息
- 优化单元格样式提取:优先使用 python-docx API 获取样式名称,降级到 XML 解析
- 完善段落样式处理:区分样式 ID 和样式名称,避免样式映射失败
- 修复表格目录格式:更新文档目录结构和页码引用以保持一致性
- 提升复杂表格的兼容性和样式准确度
chensiyu 1 ヶ月 前
コミット
488f046df5
共有2 個のファイルを変更した42 個の追加8 個の削除を含む
  1. 42 8
      app/services/word_parser.py
  2. BIN
      tmp/default.docx

+ 42 - 8
app/services/word_parser.py

@@ -422,12 +422,18 @@ def parse_word_to_blocks(docx_path: Path) -> list[dict]:
422 422
         elif elem_type == "table":
423 423
             # 表格块
424 424
             table = elem
425
-            table_content = _extract_table(table)
425
+            table_content = _extract_table(table, doc)
426 426
             parent_id = parent_stack[-1]['id'] if parent_stack else None
427 427
             
428 428
             # 计算表格元数据
429 429
             rows = table_content.get('rows', [])
430
-            cols = len(rows[0]['cells']) if rows else 0
430
+            # 遍历所有行,找出最大的列数(考虑 colspan)
431
+            max_cols = 0
432
+            for row_data in rows:
433
+                row_cols = sum(cell.get('colspan', 1) for cell in row_data.get('cells', []))
434
+                max_cols = max(max_cols, row_cols)
435
+            
436
+            cols = max_cols if max_cols > 0 else 0
431 437
             
432 438
             index = type_counters['table'] * 100  # 稀疏 index
433 439
             type_counters['table'] += 1
@@ -708,8 +714,13 @@ def _extract_rich_text(para) -> str | list:
708 714
     return segments if segments else text
709 715
 
710 716
 
711
-def _extract_table(table) -> dict:
712
-    """提取表格内容(包含合并单元格和尺寸信息)- 完整修复版"""
717
+def _extract_table(table, doc=None) -> dict:
718
+    """提取表格内容(包含合并单元格和尺寸信息)- 完整修复版
719
+    
720
+    Args:
721
+        table: python-docx 表格对象
722
+        doc: python-docx 文档对象(用于获取样式名称)
723
+    """
713 724
     rows_data = []
714 725
     
715 726
     # 提取表格列宽(从 tblGrid)
@@ -723,6 +734,12 @@ def _extract_table(table) -> dict:
723 734
                 # twips 转 pt (1 pt = 20 twips)
724 735
                 col_widths.append(int(width) / 20)
725 736
     
737
+    # 创建 XML 元素到 python-docx 单元格对象的映射
738
+    cell_map = {}
739
+    for row in table.rows:
740
+        for cell in row.cells:
741
+            cell_map[id(cell._element)] = cell
742
+    
726 743
     # 第一遍:从 XML 直接读取,建立列索引到行合并信息的映射
727 744
     # col_index -> [{start_row, end_row}, ...]  # 可能有多个合并区间
728 745
     vmerge_map = {}
@@ -929,16 +946,33 @@ def _extract_table(table) -> dict:
929 946
                 cell_style = {}
930 947
                 cell_word_style = None
931 948
                 
932
-                if paras:
949
+                # 尝试使用 python-docx API 获取样式
950
+                cell_obj = cell_map.get(id(tc))
951
+                if cell_obj and cell_obj.paragraphs:
952
+                    first_para = cell_obj.paragraphs[0]
953
+                    if first_para.style:
954
+                        cell_word_style = first_para.style.name
955
+                
956
+                # 如果没有通过 API 获取到,尝试从 XML 获取
957
+                if not cell_word_style and paras:
933 958
                     first_p = paras[0]
934 959
                     pPr = first_p.find(qn('w:pPr'))
935 960
                     
936 961
                     if pPr is not None:
937
-                        # 段落样式名称
962
+                        # 段落样式名称 - 从 XML 获取样式 ID
938 963
                         pStyle = pPr.find(qn('w:pStyle'))
939 964
                         if pStyle is not None:
940
-                            cell_word_style = pStyle.get(qn('w:val'))
941
-                        
965
+                            style_id = pStyle.get(qn('w:val'))
966
+                            # 尝试从已知的样式映射中查找
967
+                            # 注意:这里只能使用样式 ID 作为备选
968
+                            cell_word_style = style_id
969
+                
970
+                # 提取其他样式属性
971
+                if paras:
972
+                    first_p = paras[0]
973
+                    pPr = first_p.find(qn('w:pPr'))
974
+                    
975
+                    if pPr is not None:
942 976
                         # 对齐方式
943 977
                         jc = pPr.find(qn('w:jc'))
944 978
                         if jc is not None:

BIN
tmp/default.docx