模板在线编辑与导出链路重构

This commit is contained in:
zwt13703
2026-07-05 23:35:00 +08:00
parent 1369d87afb
commit c84ec6aa71
15 changed files with 1434 additions and 166 deletions
+92 -34
View File
@@ -1,4 +1,5 @@
import json
import re
from collections.abc import Iterator
from dataclasses import dataclass
@@ -22,6 +23,12 @@ class ParsedParagraph:
is_table: bool
table_json: str
write_mode: str
block_type: str = "text"
placeholder_key: str = ""
variable_key: str = ""
default_value: str = ""
edit_mode: str = "manual"
output_format: str = "text"
def _iter_block_items(document: DocumentObject) -> Iterator[Paragraph | Table]:
@@ -159,11 +166,35 @@ def _extract_table_data(table: Table) -> dict:
}
PLACEHOLDER_PATTERN = re.compile(r"^\{\{\s*([a-zA-Z0-9_\-\.]+)\s*\}\}$")
def _build_block_title(text: str, fallback: str) -> str:
normalized = " ".join((text or "").split())
if not normalized:
return fallback
return normalized[:24] + ("..." if len(normalized) > 24 else "")
def _classify_placeholder(text: str) -> tuple[str, str, str]:
matched = PLACEHOLDER_PATTERN.match(text.strip())
if not matched:
return "text", "", ""
key = matched.group(1)
lowered = key.lower()
if any(token in lowered for token in ("summary", "opening", "section", "content", "analysis")):
return "ai_slot", key, ""
return "variable", "", key
def parse_template(file_path: str) -> list[ParsedParagraph]:
document = Document(file_path)
parsed: list[ParsedParagraph] = []
current_item: ParsedParagraph | None = None
current_heading: str | None = None
current_heading_style_json = "{}"
loose_table_count = 0
body_block_count = 0
preface_count = 0
for block in _iter_block_items(document):
if isinstance(block, Paragraph):
@@ -173,52 +204,79 @@ def parse_template(file_path: str) -> list[ParsedParagraph]:
level = _heading_level(block.style.name if block.style is not None else "")
if level is not None:
current_item = ParsedParagraph(
current_heading = text
current_heading_style_json = json.dumps(_capture_paragraph_style(block, level), ensure_ascii=False)
body_block_count = 0
parsed.append(ParsedParagraph(
sort_index=len(parsed) + 1,
anchor_title=text,
title=text,
content="",
style_json=json.dumps(_capture_paragraph_style(block, level), ensure_ascii=False),
style_json=current_heading_style_json,
is_table=False,
table_json="{}",
write_mode="replace_section",
)
parsed.append(current_item)
write_mode="replace_heading_only",
block_type="heading",
edit_mode="manual",
output_format="text",
))
continue
if current_item is None:
current_item = ParsedParagraph(
sort_index=len(parsed) + 1,
anchor_title="未命名段落",
title="未命名段落",
content=text,
style_json=json.dumps(_capture_paragraph_style(block, 0), ensure_ascii=False),
is_table=False,
table_json="{}",
write_mode="replace_section",
)
parsed.append(current_item)
block_type, placeholder_key, variable_key = _classify_placeholder(text)
if current_heading is None:
preface_count += 1
anchor_title = f"文档起始_{preface_count}"
title = _build_block_title(text, anchor_title)
write_mode = "replace_section"
else:
current_item.content = "\n".join(filter(None, [current_item.content, text]))
body_block_count += 1
anchor_title = current_heading
title = _build_block_title(text, f"{current_heading}-正文{body_block_count}")
write_mode = "append_after_heading"
parsed.append(ParsedParagraph(
sort_index=len(parsed) + 1,
anchor_title=anchor_title,
title=title,
content=text,
style_json=json.dumps(_capture_paragraph_style(block, 0), ensure_ascii=False),
is_table=False,
table_json="{}",
write_mode=write_mode,
block_type=block_type,
placeholder_key=placeholder_key,
variable_key=variable_key,
default_value="" if variable_key else text,
edit_mode="ai" if block_type == "ai_slot" else "manual",
output_format="text",
))
else:
table_data = _extract_table_data(block)
table_text = f"[表格] {table_data['rows']}{table_data['cols']}"
if current_item is None:
if current_heading is None:
loose_table_count += 1
current_item = ParsedParagraph(
sort_index=len(parsed) + 1,
anchor_title=f"表格_{loose_table_count}",
title=f"表格_{loose_table_count}",
content=table_text,
style_json="{}",
is_table=True,
table_json=json.dumps(table_data, ensure_ascii=False),
write_mode="replace_section",
)
parsed.append(current_item)
anchor_title = f"表格_{loose_table_count}"
title = anchor_title
write_mode = "replace_section"
else:
current_item.is_table = True
current_item.table_json = json.dumps(table_data, ensure_ascii=False)
current_item.content = "\n".join(filter(None, [current_item.content, table_text]))
body_block_count += 1
anchor_title = current_heading
title = f"{current_heading}-表格{body_block_count}"
write_mode = "append_after_heading"
parsed.append(ParsedParagraph(
sort_index=len(parsed) + 1,
anchor_title=anchor_title,
title=title,
content=table_text,
style_json=current_heading_style_json if current_heading else "{}",
is_table=True,
table_json=json.dumps(table_data, ensure_ascii=False),
write_mode=write_mode,
block_type="table",
default_value=table_text,
edit_mode="manual",
output_format="table",
))
return parsed