feat: add Chinese office productivity plugins

This commit is contained in:
CJYSEX
2026-07-23 12:15:04 +08:00
parent 9a24a2125f
commit 1b674e31da
24 changed files with 1001 additions and 2 deletions

View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""Small, original DOCX helper for structural inspection and simple edits."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Cm, Pt
def ensure_distinct(source: Path, output: Path) -> None:
if source.resolve() == output.resolve():
raise SystemExit("Refusing to overwrite the input file; choose a different output path.")
def inspect_docx(path: Path) -> None:
doc = Document(path)
headings = []
nonempty = 0
for index, paragraph in enumerate(doc.paragraphs, start=1):
text = paragraph.text.strip()
if text:
nonempty += 1
if text and paragraph.style and paragraph.style.name.lower().startswith("heading"):
headings.append({"paragraph": index, "style": paragraph.style.name, "text": text[:160]})
tables = []
for index, table in enumerate(doc.tables, start=1):
tables.append(
{
"table": index,
"rows": len(table.rows),
"columns": max((len(row.cells) for row in table.rows), default=0),
}
)
relationships = doc.part.rels.values()
images = sum(1 for rel in relationships if "image" in rel.reltype)
report = {
"file": str(path.resolve()),
"paragraphs": len(doc.paragraphs),
"nonempty_paragraphs": nonempty,
"tables": tables,
"sections": len(doc.sections),
"images": images,
"headings": headings,
}
print(json.dumps(report, ensure_ascii=False, indent=2))
def add_content(doc: Document, text: str) -> None:
for raw in text.splitlines():
line = raw.rstrip()
if not line:
doc.add_paragraph()
elif line.startswith("### "):
doc.add_heading(line[4:].strip(), level=3)
elif line.startswith("## "):
doc.add_heading(line[3:].strip(), level=2)
elif line.startswith("# "):
doc.add_heading(line[2:].strip(), level=1)
elif line.startswith(("- ", "* ")):
doc.add_paragraph(line[2:].strip(), style="List Bullet")
elif len(line) > 3 and line[:3].rstrip(".").isdigit() and line[1:3] in {". ", ""}:
doc.add_paragraph(line[3:].strip(), style="List Number")
else:
doc.add_paragraph(line)
def create_docx(input_path: Path, output: Path, title: str | None) -> None:
doc = Document()
section = doc.sections[0]
section.top_margin = Cm(2.3)
section.bottom_margin = Cm(2.3)
section.left_margin = Cm(2.5)
section.right_margin = Cm(2.5)
normal = doc.styles["Normal"]
normal.font.name = "Microsoft YaHei"
normal.font.size = Pt(10.5)
normal.paragraph_format.space_after = Pt(6)
normal.paragraph_format.line_spacing = 1.35
if title:
paragraph = doc.add_paragraph()
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = paragraph.add_run(title)
run.bold = True
run.font.name = "Microsoft YaHei"
run.font.size = Pt(22)
paragraph.paragraph_format.space_after = Pt(18)
add_content(doc, input_path.read_text(encoding="utf-8"))
output.parent.mkdir(parents=True, exist_ok=True)
doc.save(output)
inspect_docx(output)
def replace_text(source: Path, output: Path, old: str, new: str) -> None:
ensure_distinct(source, output)
doc = Document(source)
replacements = 0
for paragraph in doc.paragraphs:
if old in paragraph.text:
original = paragraph.text
for run in paragraph.runs:
run.text = ""
paragraph.add_run(original.replace(old, new))
replacements += original.count(old)
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
if old in paragraph.text:
original = paragraph.text
for run in paragraph.runs:
run.text = ""
paragraph.add_run(original.replace(old, new))
replacements += original.count(old)
output.parent.mkdir(parents=True, exist_ok=True)
doc.save(output)
print(json.dumps({"output": str(output.resolve()), "replacements": replacements}, ensure_ascii=False))
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
sub = parser.add_subparsers(dest="command", required=True)
inspect_cmd = sub.add_parser("inspect")
inspect_cmd.add_argument("input", type=Path)
create_cmd = sub.add_parser("create")
create_cmd.add_argument("--input", required=True, type=Path)
create_cmd.add_argument("--output", required=True, type=Path)
create_cmd.add_argument("--title")
replace_cmd = sub.add_parser("replace")
replace_cmd.add_argument("input", type=Path)
replace_cmd.add_argument("--output", required=True, type=Path)
replace_cmd.add_argument("--old", required=True)
replace_cmd.add_argument("--new", required=True)
return parser
def main() -> None:
args = build_parser().parse_args()
if args.command == "inspect":
inspect_docx(args.input)
elif args.command == "create":
create_docx(args.input, args.output, args.title)
elif args.command == "replace":
replace_text(args.input, args.output, args.old, args.new)
if __name__ == "__main__":
main()