文件格式排版

SKILL效率
效率 未标注权限 许可证不明确
安装这个技能

【世界药师节·AI药学技能大赛参赛作品】文件格式排版技能,由药师基于AI辅助开发,用于提升药学服务效率与质量。

一句话选型结论

按资料字段整理

文件格式排版是效率类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类效率 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_ac76f862/psm2026b-0009
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

效率
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_ac76f862仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验未标注上游最后核验时间 · 数据更新 1791622175
正文体量28.6 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
整理待办与日程

把零散任务收进清单,排出优先级。

批量处理重复任务

把固定动作做成模板,重复执行。

把流程写成清单

把口头流程整理成可复用的步骤。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在效率场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
文件格式排版当前 未标注权限 未标注 未标注 未标注 有
Nano PDF 文档 联网访问 ClawHub 市场 424.9 万 OpenClaw 有
自动更新程序 浏览器控制 ClawHub 市场 296.4 万 OpenClaw 有
Gemini 助手 联网访问 ClawHub 市场 254.7 万 OpenClaw 有
word DOCX 文档 联网访问 ClawHub 市场 239.6 万 OpenClaw 有
Excel XLSX 前端 联网访问 ClawHub 市场 227.0 万 OpenClaw 有

SKILL.md 全文

28.6 KB
模型实际读到的说明书原文,来自技能仓库

医院文档一键排版 Skill — 完整包

> 功能:将医院体系内「制度 / 职责 / 规程 / SOP」类文档草稿,一键套版为符合公文版式的 .docx 文件。
> 核心原则:只改格式,绝不修改任何业务文字。
> 输出:带页眉 LOGO、起草审核表、文件日志、四号仿宋正文、固定行距、四级编号体系的 Word 文档。
> 环境依赖:Python 3.8+、pip install python-docx pyyaml。


目 录

  1. 排版规范速查
  2. 安装说明
  3. 使用方式
  4. 输入格式说明
  5. 完整 Python 脚本
  6. YAML 输入模板
  7. 常见问题

一、排版规范速查

以下为医院公文排版标准,脚本已全部内置实现:

1.1 页面

项 取值
纸张 A4(21 × 29.7 cm)
上/下边距 2.54 cm
左右边距 3.18 cm
页眉距边 1.5 cm
页脚距边 1.75 cm

1.2 页眉(三栏)

左 中 右
医院 LOGO 编写部门/科室 文件编号
  • 字体:方正小标宋简体 / Times New Roman
  • 字号:小五(9 pt)
  • 下方一根黑色横线

1.3 页脚

第 X 页 共 X 页
  • 字体:仿宋_GB2312 / Times New Roman
  • 字号:小五(9 pt),居中
  • 使用 Word PAGE / NUMPAGES 字段(首次打开按 Ctrl+A → F9 刷新)

1.4 文件主标题

项 值
字体 方正小标宋简体
字号 小二(18 pt)
字重 加粗
对齐 居中
行距 固定 36 磅

1.5 起草审核表(表头表)

起草/修订人 起草/修订时间 审核人 审核日期 批准人 批准日期 发布日期 生效日期
  • 字体:仿宋_GB2312,四号(14 pt),单倍行距
  • 表头加粗居中,内容居中

1.6 文件日志表

文件编码 名称 生效日期 起草/修订内容 起草/修订人
  • 表头:四号(14 pt)加粗
  • 内容:小四(12 pt)
  • 行距:单倍

1.7 正文格式

项 值
字体 仿宋_GB2312 / Times New Roman
字号 四号(14 pt)
行距 固定 28 磅
对齐 两端对齐
首行缩进 2 字符(≈ 28 pt)

1.8 编号层级

层级 写法 加粗
第一级(章) 一、二、三、… 加粗
第二级(节) (一)(二)(三)… 加粗
第三级(款) 1. 2. 3. … 常规
第四级(项) 1.1 1.2 1.3 … 常规

二、安装说明

在终端中执行以下命令安装依赖:

pip install python-docx pyyaml

将本包中的 format_document.py 保存到本地任意目录,即可使用。

目录结构

hospital-doc-formatter/
├── format_document.py          ← 主脚本(下面第5节有完整代码)
└── sample-input.yaml           ← 输入模板(下面第6节)

三、使用方式

方式 A:命令行(推荐)

# 1) 先写好 sample-input.yaml(按第6节模板填写)
# 2) 运行
python format_document.py -i sample-input.yaml -o 输出.docx

# 可选:指定 LOGO 图片
python format_document.py -i sample-input.yaml -o 输出.docx --logo logo.png

方式 B:Markdown 输入

直接用 Markdown 写文档内容(# 标题 → H1,## → 章节,正文段落自动识别编号层级):

python format_document.py -i my-doc.md -o 输出.docx

> Markdown 模式下,文件标题取 H1 第一行。meta(起草人、日期等)需在生成后手动补充或在代码中传入。

方式 C:纯文本文档

直接传 .txt:

python format_document.py -i my-doc.txt -o 输出.docx

> 纯文本模式会自动把第一行当标题,把"一、""(一)""1.""1.1"识别为不同层级的章节标题。

方式 D:Python 调用

from format_document import format_document

meta = {
    "title": "临床药师查房制度",
    "doc_no": "YKY-Y-2026-001",
    "department": "药剂科",
    "drafter": "张三",
    "draft_date": "2026-08-01",
    "reviewer": "李四",
    "review_date": "2026-08-02",
    "approver": "王五",
    "approve_date": "2026-08-05",
    "publish_date": "2026-08-06",
    "effective_date": "2026-08-10",
    "revision_note": "首次制订",
}

blocks = [
    {"type": "heading", "level": 1, "text": "一、目的"},
    {"type": "para", "text": "为规范临床药师参与查房工作…"},
    {"type": "heading", "level": 1, "text": "二、适用范围"},
    {"type": "para", "text": "本制度适用于…"},
    # …更多段落…
]

format_document(meta, blocks, "输出.docx")

方式 E:配合 WorkBuddy 对话

把文档正文粘贴给 WorkBuddy,它会按照本 skill 规范读取 → 解析 → 生成 YAML → 调脚本 → 输出 docx。


四、输入格式说明

4.1 meta 字段

YAML key 说明 必填
title 文件主标题 是
doc_no 文件编号 是
department 编写部门 是
drafter 起草/修订人 推荐
draft_date 起草/修订时间 (YYYY-MM-DD) 推荐
reviewer 审核人 推荐
review_date 审核日期 推荐
approver 批准人 推荐
approve_date 批准日期 推荐
publish_date 发布日期 推荐
effective_date 生效日期 推荐
revision_note 修订说明(默认"首次制订") 选填
log_extra_rows 历史修订记录(5列 list) 选填

4.2 blocks 类型

type 说明 level
heading 章节标题 1=一、 2=(一) 3=1. 4=1.1
para 普通正文段落 —
title 主标题(一般由 meta.title 决定,不需要额外写) —
blank 空行 —

4.3 编号自动识别

脚本会自动从行首检测以下模式:

模式 → level
一、 二、 … 1
(一) (二) … 2
1. 2. 3. … 3
1.1 1.2 … 4

五、完整 Python 脚本

将以下代码保存为 format_document.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Hospital Document Formatter — 医院文档排版工具
================================================
根据医院的排版规范,将结构化内容(YAML/JSON/Markdown)格式化为符合规范的 .docx 文件。

规范要点:
  - 页眉:[LOGO] | 部门 | 文档号  方正小标宋简体 / Times New Roman / 小五
  - 页脚:第 X 页 共 X 页          仿宋_GB2312 / Times New Roman / 小五
  - 标题:方正小标宋简体 / 小二 / 加粗 / 居中 / 行距36磅
  - 正文:仿宋_GB2312 / 四号 / 行距28磅 / 首行缩进2字符
  - 第一级编号:一、二、三...(加粗)  第二级加粗:(一)(二)(三)
  - 第三级:1.2.3.                    第四级:1.1 1.2 1.3

用法:
  # 命令行
  python format_document.py --input input.yaml --output output.docx [--logo logo.png]

  # Python 调用
  from format_document import format_document
  format_document(meta=..., blocks=..., output_path=...)

依赖:pip install python-docx pyyaml
"""

import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any

from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.shared import Cm, Pt


# ============================================================
# Format specifications (常量)
# ============================================================
FONT_TITLE_CN  = "方正小标宋简体"
FONT_BODY_CN   = "仿宋_GB2312"
FONT_HEADER_CN = "方正小标宋简体"
FONT_LATIN     = "Times New Roman"

SIZE_TITLE      = Pt(18)   # 小二
SIZE_BODY       = Pt(14)   # 四号
SIZE_BODY_SMALL = Pt(12)   # 小四
SIZE_HEADER     = Pt(9)    # 小五

LINE_SPACING_TITLE  = 36
LINE_SPACING_BODY   = 28
LINE_SPACING_LOG    = 1.0

INDENT_FIRST_LINE   = Pt(28)  # ≈ 2字符(四号下)


# ============================================================
# 低层辅助:字体设置、段落行距、表格边框、页码字段
# ============================================================
def set_run_font(run, cn_font: str, latin_font: str, size: Pt,
                 bold: bool = False) -> None:
    run.font.name = latin_font
    run.font.size = size
    run.bold = bold
    rPr = run._element.get_or_add_rPr()
    rFonts = rPr.find(qn("w:rFonts"))
    if rFonts is None:
        rFonts = OxmlElement("w:rFonts")
        rPr.append(rFonts)
    rFonts.set(qn("w:eastAsia"), cn_font)
    rFonts.set(qn("w:ascii"), latin_font)
    rFonts.set(qn("w:hAnsi"), latin_font)


def set_paragraph_line_spacing(paragraph, pt_value: float) -> None:
    pf = paragraph.paragraph_format
    pf.line_spacing = Pt(pt_value)
    pf.line_spacing_rule = WD_LINE_SPACING.EXACTLY


def remove_cell_borders(cell) -> None:
    tcPr = cell._tc.get_or_add_tcPr()
    tcBorders = OxmlElement("w:tcBorders")
    for side in ["top","left","bottom","right","insideH","insideV"]:
        e = OxmlElement(f"w:{side}")
        e.set(qn("w:val"), "none")
        e.set(qn("w:sz"), "0")
        tcBorders.append(e)
    tcPr.append(tcBorders)


def _add_field(paragraph, code: str) -> None:
    fld_run = paragraph.add_run()
    set_run_font(fld_run, FONT_BODY_CN, FONT_LATIN, SIZE_HEADER)
    begin = OxmlElement("w:fldChar")
    begin.set(qn("w:fldCharType"), "begin")
    instr = OxmlElement("w:instrText")
    instr.set(qn("xml:space"), "preserve")
    instr.text = code
    end = OxmlElement("w:fldChar")
    end.set(qn("w:fldCharType"), "end")
    fld_run._r.append(begin)
    fld_run._r.append(instr)
    fld_run._r.append(end)


# ============================================================
# 页面
# ============================================================
def setup_page(doc: Document) -> None:
    sec = doc.sections[0]
    sec.page_height  = Cm(29.7)
    sec.page_width   = Cm(21.0)
    sec.top_margin    = Cm(2.54)
    sec.bottom_margin = Cm(2.54)
    sec.left_margin   = Cm(3.18)
    sec.right_margin  = Cm(3.18)
    sec.header_distance = Cm(1.5)
    sec.footer_distance = Cm(1.75)

    style = doc.styles["Normal"]
    style.font.name = FONT_LATIN
    style.font.size = SIZE_BODY
    rPr = style.element.get_or_add_rPr()
    rFonts = rPr.find(qn("w:rFonts"))
    if rFonts is None:
        rFonts = OxmlElement("w:rFonts")
        rPr.append(rFonts)
    rFonts.set(qn("w:eastAsia"), FONT_BODY_CN)
    rFonts.set(qn("w:ascii"),    FONT_LATIN)
    rFonts.set(qn("w:hAnsi"),    FONT_LATIN)


# ============================================================
# 页眉 / 页脚
# ============================================================
def setup_header(doc: Document, department: str, doc_no: str,
                 logo_path: str | None = None) -> None:
    hdr = doc.sections[0].header
    if hdr.paragraphs:
        hdr.paragraphs[0].clear()

    table = hdr.add_table(rows=1, cols=3, width=Cm(14.64))
    table.autofit = False
    for row in table.rows:
        for cell in row.cells:
            remove_cell_borders(cell)

    # 左 LOGO
    c0 = table.cell(0, 0); c0.width = Cm(3)
    p = c0.paragraphs[0]
    p.alignment = WD_ALIGN_PARAGRAPH.LEFT
    set_paragraph_line_spacing(p, 12)
    if logo_path and Path(logo_path).exists():
        r = p.add_run()
        r.add_picture(str(logo_path), height=Cm(1.0))
    else:
        r = p.add_run("【医院 LOGO】")
        set_run_font(r, FONT_HEADER_CN, FONT_LATIN, SIZE_HEADER)

    # 中 部门
    c1 = table.cell(0, 1); c1.width = Cm(8.64)
    p = c1.paragraphs[0]
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    set_paragraph_line_spacing(p, 12)
    r = p.add_run(department or "")
    set_run_font(r, FONT_HEADER_CN, FONT_LATIN, SIZE_HEADER)

    # 右 文件编号
    c2 = table.cell(0, 2); c2.width = Cm(3)
    p = c2.paragraphs[0]
    p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
    set_paragraph_line_spacing(p, 12)
    r = p.add_run(doc_no or "")
    set_run_font(r, FONT_HEADER_CN, FONT_LATIN, SIZE_HEADER)

    # 横线
    p_line = hdr.add_paragraph()
    pPr = p_line._p.get_or_add_pPr()
    pBdr = OxmlElement("w:pBdr")
    bot = OxmlElement("w:bottom")
    bot.set(qn("w:val"), "single")
    bot.set(qn("w:sz"), "6")
    bot.set(qn("w:space"), "1")
    bot.set(qn("w:color"), "000000")
    pBdr.append(bot)
    pPr.append(pBdr)


def setup_footer(doc: Document) -> None:
    ftr = doc.sections[0].footer
    if ftr.paragraphs:
        p = ftr.paragraphs[0]; p.clear()
    else:
        p = ftr.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    set_paragraph_line_spacing(p, 12)

    r = p.add_run("第 "); set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_HEADER)
    _add_field(p, "PAGE \\* MERGEFORMAT")
    r = p.add_run(" 页 共 "); set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_HEADER)
    _add_field(p, "NUMPAGES \\* MERGEFORMAT")
    r = p.add_run(" 页"); set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_HEADER)


# ============================================================
# 内容部件
# ============================================================
def add_title(doc: Document, title: str) -> None:
    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    set_paragraph_line_spacing(p, LINE_SPACING_TITLE)
    r = p.add_run(title or "")
    set_run_font(r, FONT_TITLE_CN, FONT_LATIN, SIZE_TITLE, bold=True)


def add_metadata_table(doc: Document, meta: dict) -> None:
    hdrs = ["起草/修订人","起草/修订时间","审核人","审核日期",
            "批准人","批准日期","发布日期","生效日期"]
    vals = [meta.get(k, "") for k in
            ("drafter","draft_date","reviewer","review_date",
             "approver","approve_date","publish_date","effective_date")]

    table = doc.add_table(rows=2, cols=8)
    table.style = "Table Grid"
    table.alignment = WD_ALIGN_PARAGRAPH.CENTER

    for i, h in enumerate(hdrs):
        c = table.cell(0, i); c.text = ""
        p = c.paragraphs[0]
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        set_paragraph_line_spacing(p, 12)
        r = p.add_run(h)
        set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY, bold=True)

    for i, v in enumerate(vals):
        c = table.cell(1, i); c.text = ""
        p = c.paragraphs[0]
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        set_paragraph_line_spacing(p, 12)
        r = p.add_run(str(v))
        set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY)

    p = doc.add_paragraph(); set_paragraph_line_spacing(p, 12)


def add_log_table(doc: Document, meta: dict,
                  extra_rows: list[list[str]] | None = None) -> None:
    hdrs = ["文件编码","名称","生效日期","起草/修订内容","起草/修订人"]

    cur = [meta.get("doc_no",""), meta.get("title",""),
           meta.get("effective_date",""),
           meta.get("revision_note","") or "首次制订",
           meta.get("drafter","")]
    rows = [cur]
    if extra_rows:
        rows.extend(extra_rows)

    table = doc.add_table(rows=len(rows)+1, cols=5)
    table.style = "Table Grid"

    for i, h in enumerate(hdrs):
        c = table.cell(0, i); c.text = ""
        p = c.paragraphs[0]; p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        set_paragraph_line_spacing(p, 12)
        r = p.add_run(h)
        set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY, bold=True)

    for ri, row in enumerate(rows, 1):
        for ci, val in enumerate(row):
            c = table.cell(ri, ci); c.text = ""
            p = c.paragraphs[0]; p.alignment = WD_ALIGN_PARAGRAPH.LEFT
            p.paragraph_format.line_spacing = LINE_SPACING_LOG
            r = p.add_run(str(val))
            set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY_SMALL)

    p = doc.add_paragraph(); set_paragraph_line_spacing(p, 12)


def add_section_heading(doc: Document, text: str, level: int) -> None:
    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.LEFT
    set_paragraph_line_spacing(p, LINE_SPACING_BODY)
    if level >= 2:
        p.paragraph_format.first_line_indent = INDENT_FIRST_LINE
    bold = level in (1, 2)
    r = p.add_run(text or "")
    set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY, bold=bold)


def add_body_paragraph(doc: Document, text: str, indent: bool = True) -> None:
    if not text or not text.strip():
        return
    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
    set_paragraph_line_spacing(p, LINE_SPACING_BODY)
    if indent:
        p.paragraph_format.first_line_indent = INDENT_FIRST_LINE
    r = p.add_run(text)
    set_run_font(r, FONT_BODY_CN, FONT_LATIN, SIZE_BODY)


# ============================================================
# 编号检测
# ============================================================
LV1_RE = re.compile(r"^([一二三四五六七八九十]+)[、.]")
LV2_RE = re.compile(r"^[((]([一二三四五六七八九十]+)[))]")
LV4_RE = re.compile(r"^(\d+\.\d+)[\s\..、]")
LV3_RE = re.compile(r"^(\d+)[..、]")


def detect_numbering(text: str) -> tuple[int, str, str] | None:
    t = text.strip()
    m = LV4_RE.match(t)
    if m:
        return (4, m.group(1), t[m.end():].strip())
    m = LV2_RE.match(t)
    if m:
        return (2, m.group(0), t[m.end():].strip())
    m = LV1_RE.match(t)
    if m:
        cn = m.group(1)
        return (1, cn + "、", t[m.end():].strip())
    m = LV3_RE.match(t)
    if m:
        return (3, m.group(1)+".", t[m.end()-1:].strip() if m.end()>=1 else t)
    return None


# ============================================================
# 输入解析
# ============================================================
def load_input_file(path: str | Path) -> dict | list:
    p = Path(path)
    text = p.read_text(encoding="utf-8")
    if p.suffix.lower() in (".yaml", ".yml"):
        try:
            import yaml
            return yaml.safe_load(text) or {}
        except ImportError:
            return _lite_yaml_load(text)
    if p.suffix.lower() == ".json":
        return json.loads(text)
    if p.suffix.lower() in (".md", ".markdown"):
        return parse_markdown(text)
    return parse_plain_text(text)


def _lite_yaml_load(text: str) -> dict:
    """极简 YAML 解析,仅在没有 PyYAML 时备用。"""
    out: dict = {}
    cur_key = None
    cur_list: list | None = None
    cur_sub: dict | None = None

    for raw in text.splitlines():
        if not raw.strip() or raw.lstrip().startswith("#"):
            continue
        if raw.startswith("  ") and cur_list is not None and cur_sub is not None:
            line = raw.strip()
            if ":" in line:
                k, v = line.split(":", 1)
                cur_sub[k.strip()] = v.strip().strip('"').strip("'")
            continue
        if raw.startswith(" ") and cur_list is not None:
            line = raw.strip()
            if line.startswith("- "):
                line = line[2:]
            if cur_sub is not None and ":" in line:
                k, v = line.split(":", 1)
                cur_sub[k.strip()] = v.strip().strip('"').strip("'")
            continue
        if ":" in raw:
            k, _, v = raw.partition(":")
            k, v = k.strip(), v.strip()
            if not v:
                cur_key = k; cur_list = []; cur_sub = None
                out[cur_key] = cur_list
            elif v in ("|", ">"):
                out[k] = ""
            else:
                if (v.startswith('"') and v.endswith('"')) or \
                   (v.startswith("'") and v.endswith("'")):
                    v = v[1:-1]
                out[k] = v
                cur_key = None; cur_list = None; cur_sub = None
    return out


def parse_markdown(text: str) -> dict:
    meta: dict = {}
    blocks: list[dict] = []
    title_set = False

    for raw in text.splitlines():
        line = raw.rstrip()
        if not line.strip():
            blocks.append({"type": "blank"})
            continue
        if line.startswith("# ") and not title_set:
            meta["title"] = line[2:].strip()
            title_set = True
            continue
        m = re.match(r"^(#{1,6})\s+(.*)$", line)
        if m:
            hashes, body = m.groups()
            level = len(hashes)
            numbering = detect_numbering(body)
            if numbering:
                lvl, _, _ = numbering
                level = lvl
            blocks.append({"type":"heading","level":level,"text":body})
            continue
        numbering = detect_numbering(line.strip())
        if numbering:
            lvl, prefix, body = numbering
            blocks.append({"type":"heading","level":lvl,
                           "text":f"{prefix} {body}".strip()})
            continue
        blocks.append({"type":"para","text":line.strip()})

    return {"meta": meta, "blocks": blocks}


def parse_plain_text(text: str) -> dict:
    blocks: list[dict] = []
    title_set = False
    for raw in text.splitlines():
        line = raw.rstrip()
        if not line.strip():
            blocks.append({"type": "blank"})
            continue
        if not title_set:
            blocks.append({"type":"title","text":line.strip()})
            title_set = True
            continue
        numbering = detect_numbering(line.strip())
        if numbering:
            lvl, prefix, body = numbering
            blocks.append({"type":"heading","level":lvl,
                           "text":f"{prefix}{body}"})
            continue
        blocks.append({"type":"para","text":line.strip()})
    return blocks


# ============================================================
# 主入口
# ============================================================
def format_document(meta: dict, blocks: list[dict],
                    output_path: str | Path,
                    logo_path: str | None = None) -> Path:
    output_path = Path(output_path)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    doc = Document()
    setup_page(doc)
    setup_header(doc,
                 meta.get("department",""), meta.get("doc_no",""), logo_path)
    setup_footer(doc)

    title = meta.get("title","")
    if title:
        add_title(doc, title)

    add_metadata_table(doc, meta)
    add_log_table(doc, meta, extra_rows=meta.get("log_extra_rows"))

    for block in blocks or []:
        btype = block.get("type","para")
        text  = block.get("text","")

        if btype == "title":
            if text and text != title:
                add_title(doc, text)
        elif btype == "heading":
            add_section_heading(doc, text, int(block.get("level",1)))
        elif btype == "para":
            add_body_paragraph(doc, text)
        elif btype == "blank":
            doc.add_paragraph()
        else:
            add_body_paragraph(doc, str(block))

    doc.save(str(output_path))
    return output_path


def main(argv: list[str] | None = None) -> int:
    p = argparse.ArgumentParser(
        description="医院文档一键排版 — 生成符合规范的 .docx")
    p.add_argument("--input","-i",
                   help="输入文件 (.yaml/.json/.md/.txt)")
    p.add_argument("--output","-o", required=True,
                   help="输出 .docx 路径")
    p.add_argument("--logo", help="可选:LOGO 图片路径")
    args = p.parse_args(argv)

    if args.input and args.input != "-":
        data = load_input_file(args.input)
        if isinstance(data, list):
            data = {"meta":{}, "blocks":data}
    else:
        text = sys.stdin.read()
        if text.lstrip().startswith("#"):
            data = parse_markdown(text)
        else:
            data = {"meta":{}, "blocks":parse_plain_text(text)}
        if "meta" not in data:
            data["meta"] = {}

    meta   = data.get("meta") or {}
    blocks = data.get("blocks") or []

    out = format_document(meta, blocks, args.output, args.logo)
    print(f"OK: {out}")
    return 0


if __name__ == "__main__":
    sys.exit(main())

六、YAML 输入模板

将以下内容保存为 sample-input.yaml,按实际修改 meta 字段和正文 blocks:

# ============================================================
# 医院文档一键排版 — 输入示例(YAML 格式)
# 用法:python format_document.py -i sample-input.yaml -o 输出.docx
# ============================================================

meta:
  title: 临床药师查房与用药干预制度
  doc_no: YKY-Y-2026-001
  department: 药剂科
  drafter: 张三
  draft_date: 2026-08-01
  reviewer: 李四
  review_date: 2026-08-02
  approver: 王五
  approve_date: 2026-08-05
  publish_date: 2026-08-06
  effective_date: 2026-08-10
  revision_note: 首次制订
  # 历史修订记录(不需要可删除整段)
  log_extra_rows:
    - ["YKY-Y-2024-015", "临床药师查房制度(v1)", "2024-09-01", "首次制订", "王二"]

# ============================================================
# 正文(blocks 列表)
# type: heading(level=1/2/3/4) | para | title | blank
# ============================================================

blocks:

  - type: heading
    level: 1
    text: 一、目的

  - type: para
    text: 为规范临床药师参与查房与用药干预工作,保障患者用药安全、有效、经济,依据《医疗机构药事管理规定》《处方管理办法》等文件,结合本院实际,制定本制度。

  - type: heading
    level: 1
    text: 二、适用范围

  - type: para
    text: 本制度适用于本院临床药学科全体临床药师及相关药学技术人员。

  - type: heading
    level: 1
    text: 三、职责

  - type: heading
    level: 2
    text: (一)临床药师的职责

  - type: para
    text: 临床药师应主动参与临床查房,了解患者用药史、过敏史、肝肾功能等情况。

  - type: heading
    level: 2
    text: (二)临床药学科的职责

  - type: para
    text: 临床药学科负责制订年度计划、组织疑难病例讨论、定期发布合理用药报告。

  - type: heading
    level: 1
    text: 四、工作内容

  - type: heading
    level: 2
    text: (一)日常查房

  - type: para
    text: 临床药师每周参与临床科室查房不少于 2 次。

  - type: heading
    level: 2
    text: (二)ADR 监测与上报

  - type: para
    text: 遇疑似 ADR 时,在规定时限内通过国家 ADR 监测系统上报。

  - type: heading
    level: 1
    text: 五、附件

  - type: para
    text: 附件 1 临床药师查房记录表。

  - type: heading
    level: 1
    text: 六、参考文献

  - type: para
    text: "[1] 国家卫生健康委员会. 医疗机构药事管理规定[Z]. 2011."

  - type: para
    text: "[2] 国家药品监督管理局. 药品不良反应报告和监测管理办法[Z]. 2011."

七、常见问题

Q1:方正小标宋简体字体找不到?

如果电脑未安装「方正小标宋简体」,Word 会自动用默认字体替代。建议安装医院标配字体,或在代码中把 FONT_TITLE_CN 改为你电脑上可用的宋体/黑体变体。

Q2:YAML 报错 expected <block end>

文本里出现 [1]、{xxx} 等被 YAML 当成序列/字典。解决方法:把这行用引号包起来——text: "[1] 国家卫健委. …"

Q3:页脚页码不显示?

输出的 docx 使用了 Word 域字段(PAGE / NUMPAGES),首次打开需按 Ctrl+A → F9 刷新。或者打印预览一次即可。

Q4:怎么批量处理多份文档?

当前脚本每次输出 1 个 docx。批量可以:
- 写一个 shell 循环调用
- 或在 WorkBuddy 中依次/并行提交多个排版任务

Q5:附件里包含真实表格怎么办?

当前脚本把附件作为正文段落处理。如需表格,可在 Word 里手动插入,或后续扩展支持。

Q6:能生成 pdf 吗?

脚本输出 .docx,如需 PDF,在 Word 里「另存为 → PDF」即可,或使用 libreoffice --headless --convert-to pdf 命令行转换。


快速开始(3 步)

# 1. 安装依赖
pip install python-docx pyyaml

# 2. 把上面的 YAML 模板复制到 sample-input.yaml,改好内容

# 3. 运行
python format_document.py -i sample-input.yaml -o 我的制度.docx

打开 我的制度.docx,按 Ctrl+A → F9 刷新页码,完成!


> 核心承诺:排版只动格式,不改任何一个业务文字。你可以放心把草稿丢进去,出来的就是排版规范的正式文档。

常见问题

5 问
答案由本站依据条目字段整理
文件格式排版怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_ac76f862/psm2026b-0009。安装方式为未标注。装完刷新技能列表或重开客户端。

文件格式排版支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

文件格式排版权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

文件格式排版收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

文件格式排版用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 未标注。