91 lines
4.1 KiB
Python
91 lines
4.1 KiB
Python
"""Map patent content to the shared, validated DOCX creation pipeline."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from _document_builder import expect_list, expect_object
|
|
|
|
|
|
def _text(value: Any, label: str) -> str:
|
|
if not isinstance(value, str) or not value.strip():
|
|
raise ValueError(f"{label} 必须是非空文本")
|
|
return value
|
|
|
|
|
|
def build_patent_spec(data: dict[str, Any]) -> dict[str, Any]:
|
|
unknown = set(data) - {"claims", "specification", "abstract", "properties"}
|
|
if unknown:
|
|
raise ValueError(f"专利说明包含未知字段:{sorted(unknown)}")
|
|
blocks: list[dict[str, Any]] = []
|
|
sections: list[dict[str, Any]] = []
|
|
margin = 2.5 / 2.54
|
|
page = {"size": "A4", "margins": dict.fromkeys(("top", "bottom", "left", "right"), margin)}
|
|
|
|
def start_section(title: str) -> None:
|
|
if sections:
|
|
blocks.append({"type": "section_break", **page})
|
|
sections.append({
|
|
"index": len(sections),
|
|
"header": {"text": title, "alignment": "center"},
|
|
"footer": {"text": "", "alignment": "center", "page_number": True,
|
|
"page_number_prefix": "第 ", "page_number_suffix": " 页"},
|
|
"page_number_start": 1,
|
|
})
|
|
blocks.append({"type": "heading", "level": 1, "text": title, "alignment": "center"})
|
|
|
|
claims = expect_list(data.get("claims", []), "claims")
|
|
if claims:
|
|
start_section("权利要求书")
|
|
items = []
|
|
for index, raw in enumerate(claims, 1):
|
|
claim = expect_object(raw, "claims[]")
|
|
if set(claim) - {"number", "text", "dependent"}:
|
|
raise ValueError("claims[] 仅支持 number、text、dependent")
|
|
if claim.get("number") != index:
|
|
raise ValueError("claims[].number 必须从 1 开始连续编号")
|
|
items.append({"text": _text(claim.get("text"), "claims[].text"),
|
|
"alignment": "justify", "keep_together": True})
|
|
blocks.append({"type": "numbered_list", "items": items})
|
|
|
|
specification = expect_object(data.get("specification", {}), "specification")
|
|
titles = [("field", "技术领域"), ("background", "背景技术"), ("summary", "发明内容"),
|
|
("drawings", "附图说明"), ("detailed", "具体实施方式")]
|
|
unknown = set(specification) - {key for key, _ in titles}
|
|
if unknown:
|
|
raise ValueError(f"specification 包含未知字段:{sorted(unknown)}")
|
|
if specification:
|
|
start_section("说明书")
|
|
for key, title in titles:
|
|
if key not in specification:
|
|
continue
|
|
value = specification[key]
|
|
paragraphs = value if isinstance(value, list) else [value]
|
|
if not paragraphs:
|
|
raise ValueError(f"specification.{key} 不能为空")
|
|
blocks.append({"type": "heading", "level": 2, "text": title})
|
|
blocks.extend({"type": "paragraph", "text": _text(text, f"specification.{key}"),
|
|
"alignment": "justify", "first_line_indent": 24 / 72}
|
|
for text in paragraphs)
|
|
if "abstract" in data:
|
|
abstract = _text(data["abstract"], "abstract")
|
|
start_section("摘要")
|
|
blocks.append({"type": "paragraph", "text": abstract, "alignment": "justify",
|
|
"first_line_indent": 24 / 72})
|
|
if not blocks:
|
|
raise ValueError("专利说明至少需要非空 claims、specification 或 abstract")
|
|
return {
|
|
"properties": expect_object(data.get("properties", {}), "properties"),
|
|
"page": page,
|
|
"default_font": {"name": "Liberation Serif", "east_asia": "Noto Serif CJK SC",
|
|
"size": 12, "line_spacing": 1.5},
|
|
"styles": {
|
|
"Heading 1": {"font": "Liberation Sans", "east_asia_font": "Noto Sans CJK SC",
|
|
"size": 16, "bold": True, "color": "000000"},
|
|
"Heading 2": {"font": "Liberation Sans", "east_asia_font": "Noto Sans CJK SC",
|
|
"size": 14, "bold": True, "color": "000000"},
|
|
},
|
|
"blocks": blocks,
|
|
"sections": sections,
|
|
}
|