mcpField
O
officecli-word-form

officecli-word-form

github.com/iofficeai文档处理32.7k95 次安装今天更新
代码仓库

关于 officecli-word-form

officecli-word-form 是用于创建结构化可填写 Word 表单和合并模板的 OfficeCLI 技能。它侧重表单控件、字段保护和数据验证,而非普通文档排版。

主要功能

  1. 01结构化内容控件
  2. 02下拉列表与日期字段
  3. 03旧式复选框字段
  4. 04邮件合并字段
  5. 05表单保护与质检

适用场景

  1. 01创建受保护的信息收集表
  2. 02制作含下拉列表和日期字段的模板
  3. 03准备邮件合并文档

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/iofficeai/officecli --skill officecli-word-form

安装后在 Claude Code 中输入/officecli-word-form确认已加载。

详细介绍

OfficeCLI Word 表单 Skill

本 Skill 独立于 docx,不是 docx 的场景层。 表单的载荷(<w:sdt> 内容控件、<w:ffData> 旧式表单域、<w:fldChar> 邮件合并、documentProtection)与 docx 的段落/标题/样式基本元素属于不同的元素类别。其质量检查也不同:docx 的交付检查关注视觉版式和实时 PAGE 域,本 Skill 的检查关注数据链路(保护是否生效 / 别名+标签 / 选项是否注入 / 名称 ≤ 20 字符 / 无下划线反模式)。反向转交: 如果用户文档没有可填写字段(报告、信函、备忘录、论文、提案),请转交 officecli-docx 或某个 docx 场景 Skill,不要使用本 Skill。

开始之前(关键)

如果未安装 officecli:

macOS / Linux

if ! command -v officecli >/dev/null 2>&1; then
    curl -fsSL https://d.officecli.ai/install.sh | bash
fi

Windows (PowerShell)

if (-not (Get-Command officecli -ErrorAction SilentlyContinue)) {
    irm https://d.officecli.ai/install.ps1 | iex
}

验证:officecli --version

如果首次安装后仍找不到 officecli,请打开新的终端并重新运行验证命令。

如果上述安装命令失败(例如被安全策略拦截、无网络访问或权限不足),请手动安装:从 https://github.com/iOfficeAI/OfficeCLI/releases 下载适用于你所用平台的二进制文件,然后重新运行验证命令。

先查帮助规则

本 Skill 讲解真实表单需要什么,而不是每一个命令行参数。当某个属性 / 别名 / 枚举值不确定时,请先查阅帮助再猜测:officecli help docx [element] [--json](例如 sdt、formfield、field)。帮助信息与已安装的 CLI 版本绑定,具有权威性——如果本 Skill 与帮助内容不一致,以帮助为准(sdt 的属性集合随时间不断增加,请以 help docx sdt 为准,而不是硬编码的列表)。

心智模型与继承关系

Word 表单本质上是一个 .docx,外加四层普通 docx Skill 不涉及的 OpenXML 载荷:<w:sdt> 内容控件(类型:文本 / 富文本 / 下拉列表 / 组合框 / 日期 / 图片 / 组)、<w:ffData> 旧式 FormField(仍是获得真正复选框的唯一方式——SDT 的 type=checkbox 尚未实现)、<w:fldChar> 复杂域(MERGEFIELD、REF、PAGEREF、SEQ、IF——属于模板阶段,而非用户填写阶段),以及 documentProtection(使非域文本在 Word 中只读的锁定机制;在 CLI 中,protection=forms 会锁定非域内容的编辑(需要 --force 或 raw-set),但仍允许编辑表单域(SDT),这正是表单保护的意义所在)。

不继承 docx v2 的规则。 docx 的交付检查(封面填充率、实时 PAGE 检查)不适用于表单——表单质量检查使用 view forms + query sdt alias+tag + protectionEnforced。

反向转交至 docx。 对于报告 / 信函 / 备忘录 / 论文 / 演示提案 / 任何没有可编辑字段的文档,请转交回 officecli-docx。只有当文档的用途是数据采集或模板合并时,才使用本 Skill。

Shell 与执行规范

一次只执行一条命令,读取输出后再执行下一条。 OfficeCLI 是增量式的,每次 add / set / remove 都会立即修改文件。以下所有示例均使用 shell 变量 FILE=form.docx。

三层 shell 转义规则:

  1. 含 [N] 的路径必须加引号 —— zsh/bash 会对方括号进行通配符展开。officecli get "$FILE" /body/sdt[1] 会报错 no matches found。正确写法:officecli get "$FILE" '/body/sdt[1]'。
  2. 含 $ 的属性值必须用单引号 —— "Total: $50,000" 在 $50 被变量展开后会变成 "Total: ,000"。正确写法:'Total: $50,000'。
  3. --after find:<text> 外层使用单引号,不要用内层双引号 —— --after find:"Client Signature:" 会使引号成为搜索字符串的一部分,导致匹配失败。正确写法:--after 'find:Client Signature:'。

WARNING: UNSUPPORTED(退出码 2)表示该元素被静默写错。 CLI 会在不包含被拒绝属性的情况下创建该元素(退出码 2 表示带注意事项运行;只有当所有属性都被拒绝时,--json 才会报告 success: false)。构建日志中出现任何 UNSUPPORTED,都说明当前 CLI 不接受该元素上的某个属性名——应立即停止,通过 help docx <element> 查询正确的属性名(大多数 SDT 属性如 items/format/lock 现已支持;maxlength 不支持),修正命令后重新执行。不要在此基础上继续操作。

protection=forms 必须是最后一个结构性命令。 设置后,protection=forms 会锁定非字段内容(这些编辑需要 --force 或 raw-set),但仍允许编辑表单字段(SDT)——这正是表单保护的意义所在。编辑非字段内容(例如 set /body/p[N] --prop text=)会被拒绝(ERROR: Document is protected … use --force);编辑表单字段(例如 set /body/sdt[N] --prop text= / --prop alias=)则会成功(退出码 0)。可使用 Query("editable") 查找可填写的字段。因此应先完成所有非字段(静态版式)编辑,再加锁;如果之后必须编辑静态内容,需添加 --force(或临时解除保护、编辑后重新加锁)。

--after find: 操作要点

--after find:<text> 匹配的是第一个出现的位置。锚点不准确会导致插入位置错误,且排查成本高。遵循以下三条规则:

  1. 锚点必须在全文中唯一。 在中英双语合同中,"甲方签字"会同时匹配双方——应使用唯一短语,例如 "甲方签字(Service Provider)" 或完整的英文标题。
  2. 插入之后,/body/p[last()] 不可靠——find 插入会改变 <w:body> 的子元素顺序。要继续操作新段落,应读取其真实的 paraId:officecli query "$FILE" paragraph --json | jq -r '.data.results[-1].format.paraId'。
  3. 中文及全角括号 () 在 find 中按字面匹配,但若不确定,应先执行 officecli view "$FILE" text | grep -n "锚点",确认文件中的确切字节。
# Trap: first-match hits 甲方 only, 乙方 missed
officecli add "$FILE" /body --type sdt --after 'find:签字'

# Fix: two signatories, two unique anchors
officecli add "$FILE" /body --type sdt --prop alias=Party_A_Name --prop tag=party_a \
  --after 'find:甲方签字(Service Provider)'
PID_A=$(officecli query "$FILE" paragraph --json | jq -r '.data.results[-1].format.paraId')
officecli add "$FILE" "/body/p[@paraId='$PID_A']" --type sdt --prop alias=Party_A_Title --prop tag=party_a_title

通过 --after find: 插入的内联 SDT 会作为匹配段落的子元素添加,而不是新建段落——当标签与 SDT 必须位于同一行时使用此方法。

什么才算真正的表单(标识)

真正可填写的表单需要结构化字段 + 文档保护。

方案Word 用户看到的内容命令行可读真正的表单?
SDT 控件 + protection=forms灰色边框字段;其余部分锁定query sdt / view forms是
FormField 复选框 + protection=forms真正可点击的复选框;其余部分锁定query formfield / view forms是(仅复选框)
MERGEFIELD 占位符由下游引擎合并后显示为 «CustomerName»query field是(模板阶段)
下划线 ___ / 空行仅视觉效果;整个文档可编辑否,没有结构化字段否

不要用下划线模拟字段。 姓名:_______________ 不会产生任何结构化数据,并且会绕过所有验证。始终使用 --type sdt 或 --type formfield。

复选框使用 formfield,而不是 SDT。 --type sdt --prop type=checkbox 会以退出码 1 失败(SDT type 'checkbox' is not implemented)。所有方案中的复选框都使用 --type formfield --prop type=checkbox。

MERGEFIELD 是独立的一条路线。 view forms 只列出 SDT 和 formfield;query field 只列出复杂字段。两者是互不相交的两套清单,可以同时存在于同一文件中。

输出要求(硬性底线)

每个表单都必须满足以下要求;交付检查(Delivery Gate)会将每一项作为可执行检查来验证。

  1. 已强制启用 protection=forms(get $FILE / → protectionEnforced=True)。
  2. 每个 SDT 同时具有 alias 和 tag。
  3. 每个下拉列表/组合框在 view forms 中都有非空的 items=...。
  4. 每个日期 SDT 都显示预期的 format=...。
  5. 每个锁定的 SDT 按预期显示 lock=sdtLocked / contentLocked / sdtContentLocked。
  6. 构建日志中不得出现 WARNING: UNSUPPORTED。
  7. 任何 SDT 上都不得出现 type=checkbox。
  8. 每个 formfield 的 name 不超过 20 个字符。
  9. 不得存在下划线线条或空行占位符。
  10. 字段类型与用户意图一致(短文本 / 段落 / 固定列表 / 列表+自定义 / 日期 / 布尔值)。

三种路径(核心决策)

SDT 属性是 add 命令的一等属性——请使用 officecli help docx sdt 确认当前支持的属性集。目前包括 type, tag, alias, text, items, format, lock, placeholder/placeholderText, date.*,以及 SDT 类型 text / richtext / dropdown / combobox / date / group / picture。因此大多数表单只需纯 add,无需 raw-set,也无需 Word 模板。仅剩两种路径适用于确实无法通过上述方式实现的情况。在编写任何命令之前先选定路径。

路径 A——纯命令行(几乎所有情况的默认选择)

适用于:任何 text / richtext / dropdown / combobox / date / picture / group SDT,包括其选项、日期格式和锁定设置。直接在 add 上传递这些属性;使用 get '/body/sdt[N]' --json 逐一验证其是否已保存。

# dropdown WITH its options and a non-default date format, in one add each — no raw-set:
officecli add "$FILE" /body --type sdt \
  --prop type=dropdown --prop alias="Department" --prop tag=dept \
  --prop items="Engineering,Finance,HR"
officecli add "$FILE" /body --type sdt \
  --prop type=date --prop alias="Start Date" --prop tag=start \
  --prop format="yyyy年MM月dd日"
# lock works on add AND set:
officecli add "$FILE" /body --type sdt --prop type=text --prop tag=full_name \
  --prop alias="Full Name" --prop text="Enter full name" --prop lock=sdtLocked
# protection comes last, once all fields exist:
# officecli set "$FILE" / --prop protection=forms
路径 B——命令行 + raw-set 桥接(仅适用于帮助信息未暴露的属性)

适用于:需要某个 help docx sdt 未列出的 SDT 属性时(例如 w:value 必须与显示文本不同的 <w:listItem>,或没有对应 --prop 的 sdtPr 子元素)。raw-set 是 OfficeCLI 通用的 OpenXML 回退命令,officecli --help 将其列为顶级命令。普通下拉列表应使用 --prop items=(路径 A);此处的 raw-set 是例外,而非常规做法。

# Display text ≠ stored value — not expressible via items=, so inject the listItems directly:
officecli add "$FILE" /body --type sdt --prop type=dropdown --prop alias="Department" --prop tag=dept
officecli raw-set "$FILE" /document \
  --xpath "//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList" \
  --action append \
  --xml '<w:listItem xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" w:displayText="Engineering" w:value="ENG"/><w:listItem xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" w:displayText="Finance" w:value="FIN"/>'
路径 C——Word 模板(仅用于其他任何 API 都无法实现的情况)

适用于:真正的 SDT 复选框(type=checkbox 仍会以退出码 1 失败,请改用旧式 FormField,参见 §旧式 FormField)、placeholderDocPart 提示文本部件,或超出 --prop 能力范围的自定义 richtext 外观 / 跨部件嵌套。图片和分组 SDT 已不再属于此路径,可通过路径 A 正常添加。

# One-time in Word: Developer tab → Insert Content Control → Save as template.docx
cp templates/onboarding_with_signature.docx "$FILE"
officecli open "$FILE"
officecli view "$FILE" forms                 # inspect embedded controls + paths
officecli set "$FILE" '/body/sdt[@sdtId=3]' --prop text="Jane Smith"
officecli set "$FILE" / --prop protection=forms
决策表
需求路径说明
带默认字符串的文本 / 富文本 SDTA--prop type/alias/tag/text
必须锁定的文本 SDTA--prop lock=sdtLocked 可用于 add(以及 set)
带选项的下拉列表 / 组合框A--prop items="A,B,C"
使用非默认格式的日期 SDTA--prop format="yyyy年MM月dd日"
签名图片 SDT、分组 SDTA直接使用 --prop type=picture / type=group 添加
存储值与显示文本不同的下拉列表B通过 raw-set 追加 <w:listItem w:value=…>
真正的复选框FormField--type formfield --prop type=checkbox(参见 §Legacy FormField)
邮件合并占位符MERGEFIELD--type field --prop fieldType=mergefield(参见 §MERGEFIELD)
真正的 SDT 复选框 / 占位符部件 / 自定义外观C先在 Word 中构建骨架,再通过 CLI 填充

快速开始 — 路径 A + FormField(最小化填写表单)

两个 SDT 文本字段、一个复选框、保护。可直接粘贴并按需调整;这是值得交付的最小表单。

FILE=intake.docx
officecli close "$FILE" 2>/dev/null; rm -f "$FILE"   # preflight: clear stale resident / prior file (cold-start after CLI upgrade commonly leaks a resident)
officecli create "$FILE"
officecli open "$FILE"

officecli set "$FILE" / --prop title="Employee Onboarding Intake" \
  --prop docDefaults.font="Calibri" --prop docDefaults.fontSize="12pt"

officecli add "$FILE" /body --type paragraph \
  --prop text="Employee Onboarding Intake" --prop style=Heading1 \
  --prop size=20 --prop bold=true --prop spaceAfter=18pt

officecli add "$FILE" /body --type paragraph \
  --prop text="Full Name:" --prop size=11 --prop bold=true --prop spaceAfter=4pt
officecli add "$FILE" /body --type sdt --prop type=text \
  --prop alias="Full Name" --prop tag=full_name --prop text="Enter full name"

officecli add "$FILE" /body --type paragraph \
  --prop text="Start Date:" --prop size=11 --prop bold=true --prop spaceAfter=4pt
officecli add "$FILE" /body --type sdt --prop type=date \
  --prop alias="Start Date" --prop tag=start_date

officecli add "$FILE" /body --type paragraph \
  --prop text="Read and agree to employee handbook" --prop size=11 --prop spaceAfter=4pt
officecli add "$FILE" /body --type formfield \
  --prop type=checkbox --prop name=agree_handbook --prop checked=false

officecli set "$FILE" '/body/sdt[1]' --prop lock=sdtlocked
officecli set "$FILE" '/body/sdt[2]' --prop lock=sdtlocked
officecli set "$FILE" / --prop protection=forms
officecli close "$FILE"
officecli view "$FILE" forms

路径 B — raw-set 方案

三个方案几乎可以覆盖 SDT 表单中所有复杂属性需求。

B1 — 下拉列表选项(追加)
# Skeleton (Path A)
officecli add "$FILE" /body --type sdt --prop type=dropdown \
  --prop alias="Department" --prop tag=dept

# Inject items
officecli raw-set "$FILE" /document \
  --xpath "//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList" \
  --action append \
  --xml '<w:listItem xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" w:displayText="Engineering" w:value="Engineering"/><w:listItem xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" w:displayText="Finance" w:value="Finance"/><w:listItem xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" w:displayText="HR" w:value="HR"/>'

# Verify
officecli get "$FILE" '/body/sdt[1]'   # expect: type=dropdown items=Engineering,Finance,HR

模板。 仅需替换 <TAG> / <LABEL> / <VALUE>。每个根级 <w:listItem> 都必须包含 xmlns:w=...,因为 raw-set 不会继承命名空间前缀。可在一次调用中串联多个 <w:listItem>,选项顺序会被保留。

B2 — 组合框选项(与 B1 相同,XPath 结尾不同)
officecli add "$FILE" /body --type sdt --prop type=combobox \
  --prop alias="Current Medication" --prop tag=current_med

officecli raw-set "$FILE" /document \
  --xpath "//w:sdt[w:sdtPr/w:tag/@w:val='current_med']/w:sdtPr/w:comboBox" \
  --action append \
  --xml '<w:listItem xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" w:displayText="Antihypertensives" w:value="Antihypertensives"/><w:listItem xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" w:displayText="Insulin" w:value="Insulin"/><w:listItem xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" w:displayText="Other (specify)" w:value="Other"/>'

与 B1 的唯一区别:XPath 结尾使用 w:comboBox 而非 w:dropDownList。组合框允许用户输入自定义内容;下拉列表则不允许。

B3 — 日期格式(setattr)
officecli add "$FILE" /body --type sdt --prop type=date \
  --prop alias="Contract Start Date" --prop tag=contract_start

# Chinese: yyyy年MM月dd日
officecli raw-set "$FILE" /document \
  --xpath "//w:sdt[w:sdtPr/w:tag/@w:val='contract_start']/w:sdtPr/w:date/w:dateFormat" \
  --action setattr \
  --xml "w:val=yyyy年MM月dd日"

# US:    w:val=MM/dd/yyyy
# ISO:   w:val=yyyy-MM-dd  (already the default)
# Long:  w:val="MMMM d, yyyy"

officecli get "$FILE" '/body/sdt[N]'   # expect: type=date format=yyyy年MM月dd日

setattr 用于替换单个属性——不要在 --xml 中为值加引号。只会修改 w:val,<w:dateFormat> 外层元素会被保留。

raw-set 操作与错误
--action表单用途
append在目标末尾插入新的子元素(B1、B2 — listItem)
setattr修改一个属性;--xml "key=value"(B3 — dateFormat/@val)
replace替换整个目标(较少使用 — 重置完整的 <w:date> 外层元素)
remove删除目标(重新填充前清空选项)
现象修复方法
raw-set: 0 element(s) affectedXPath 未匹配。检查 tag 值以及 SDT 是块级还是行内级。可回退到 officecli raw $FILE /document 读取实际 XML。
Error: prefix 'w' is not defined片段缺少 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"——--xml 中的每个根元素都需要该声明。
追加后读回的选项为空<w:dropDownList/> 必须已存在(路径 A 的 type=dropdown 会确保这一点)。若不存在,追加操作将无处插入。
成功信息同一行出现 VALIDATION: N new error(s) introduced你的追加操作引入了不符合 schema 的子元素。即使 raw-set 退出码为 0,也应将其视为需要立即停止并修复的问题。

路径 C — Word 模板工作流

对于 CLI 无法表达的字段(签名 picture SDT、真正的 SDT 复选框、placeholderDocPart 提示文本、分组 SDT、自定义富文本样式),先在 Word 中一次性构建骨架,再通过 CLI 填充。

在 Word 中的一次性操作: 文件 → 选项 → 自定义功能区 → 开发工具。开发工具选项卡 → 插入图片 / 复选框 / 分组内容控件 → 右键 → 属性 → 设置标题(alias)和标记(Tag)。另存为 template.docx。

通过 CLI 填充:

cp templates/onboarding_with_signature.docx "$FILE"
officecli open "$FILE"
officecli view "$FILE" forms                                    # see /body/... paths + sdtId values
officecli set "$FILE" '/body/sdt[@sdtId=3]' --prop text="Jane Smith"
officecli set "$FILE" / --prop protection=forms
officecli close "$FILE"

MERGEFIELD(数据驱动轨道)

help docx field 声明了一个包含约 30 个值的 fieldType 枚举,其中包括 mergefield、ref、pageref、seq、if——所有这些都可以通过 CLI 结合其带类型的属性来表达。MERGEFIELD 可与 SDT 共存于同一文件中,但只会被 query field 报告;view forms 不会列出 MERGEFIELD(它们不是用户可填写的字段)。

标准 MERGEFIELD:

officecli add "$FILE" /body --type paragraph --prop text="Dear "
officecli add "$FILE" '/body/p[1]' --type field --prop fieldType=mergefield --prop name=CustomerName
officecli add "$FILE" '/body/p[1]' --type run --prop text=", "
officecli add "$FILE" '/body/p[1]' --type field --prop fieldType=mergefield --prop name=CompanyName
# Readback: "Dear «CustomerName», «CompanyName»"

元素类型简写(等效写法):officecli add "$FILE" '/body/p[1]' --type mergefield --prop name=CustomerName。

常见字段模式
模式调用形式
邮件合并占位符--type field --prop fieldType=mergefield --prop name=<FieldName>
带数字格式的邮件合并(金额、百分比)--type field --prop fieldType=mergefield --prop name=Amount --prop instr='MERGEFIELD Amount \# "#,##0.00"'。对 mergefield 而言,类型化的 format 属性会被忽略(并输出警告);请使用 instr(别名 instruction)嵌入完整的域代码。验证:query "$FILE" field --json | jq '.data.results[].format.instruction' 的输出必须包含 \# 及格式图片。
带日期格式的邮件合并--type field --prop fieldType=mergefield --prop name=StartDate --prop instr='MERGEFIELD StartDate \@ "yyyy-MM-dd"'
书签文本的交叉引用--type field --prop fieldType=ref --prop name=<BookmarkName>
书签所在页码的交叉引用--type field --prop fieldType=pageref --prop name=<BookmarkName>
自动编号(图 1 / 2 / 3)--type field --prop fieldType=seq --prop identifier=Figure
页脚中的页码--type field --prop fieldType=page
"第 X 页,共 Y 页"两个域:fieldType=page + fieldType=numpages
条件文本--type field --prop fieldType=if --prop expression='{ MERGEFIELD Gender } = "Male"' --prop trueText="Mr." --prop falseText="Ms."
IF 条件域(可通过命令行表达)
officecli add "$FILE" /body --type paragraph --prop text=""
officecli add "$FILE" '/body/p[last()]' --type field --prop fieldType=if \
  --prop expression='{ MERGEFIELD Gender } = "Male"' \
  --prop trueText="Mr." --prop falseText="Ms."
officecli add "$FILE" '/body/p[last()]' --type run --prop text=" "
officecli add "$FILE" '/body/p[last()]' --type field --prop fieldType=mergefield --prop name=LastName
# Merge-time result: "Mr. «LastName»" or "Ms. «LastName»"

像 { IF { MERGEFIELD X } = "Y" { REF bm } "fallback" } 这样的嵌套结构无法通过 --prop 链式组合表达。此时需要直接设置手工编写的 <w:fldChar> / <w:instrText> 片段,或在 Word 模板中一次性构建(路径 C)。

回读。 query $FILE field 会列出 /field[N]、指令及 fieldType。view $FILE forms 不会列出 MERGEFIELD(只列出 SDT 和 formfield)——它们是模板阶段的内容,并非终端用户可填写的字段。get $FILE '/body/p[1]' 会渲染带有书名号包裹的字段名。

旧版 FormField

当需要真正的复选框时使用 FormField。对于文本和下拉框,优先使用 SDT。

help docx formfield:type(text/checkbox/check/dropdown)、name(必填,≤ 20 个字符——OpenXML 架构的 MaxLength 限制;add 允许更长的名称,但 validate 会拒绝)、text(仅文本,别名 value)、checked(仅复选框)。

# CHECKBOX — the only real checkbox (SDT type=checkbox is not implemented)
officecli add "$FILE" /body --type formfield --prop type=checkbox \
  --prop name=agree_terms --prop checked=false

# TEXT formfield
officecli add "$FILE" /body --type formfield --prop type=text \
  --prop name=emp_name --prop text="Enter name"

# DROPDOWN formfield — items NOT settable via CLI; use Word template or SDT Path B
officecli add "$FILE" /body --type formfield --prop type=dropdown --prop name=dept_select

# Read / modify by name (stable) or 1-based index
officecli get "$FILE" '/formfield[agree_terms]'
officecli set "$FILE" '/formfield[agree_terms]' --prop checked=true
officecli set "$FILE" '/formfield[emp_name]' --prop text="Jane Smith"
officecli set "$FILE" '/formfield[dept_select]' --prop text="Engineering"

FormField 路径(/formfield[N] 或 /formfield[<name>])与 SDT 路径(/body/sdt[N])相互独立。两者可以共存;protection=forms 同时覆盖两者。

规模。 已在单个文档中测试过 50 个以上的复选框——formfield 数量没有实际上限;构建和 validate 均保持通过。name ≤ 20 个字符(K13)是唯一的硬性约束。

渲染器说明——formfield 复选框 [RENDERER-BUG]。 LibreOffice 导出 PDF 时偶尔会将 formfield 复选框渲染为 ☐☐(双框)。Word 和 WPS 则渲染为单个可点击的方框(切换为 ☑)。这是 LibreOffice 渲染器的特性,不是 Skill 或文档质量问题——参见 K19。不要在表单中尝试变通处理;如果评审人员对 LibreOffice 生成的 PDF 截图并看到 ☐☐,应归因于 [RENDERER-BUG]。

文档保护与锁定

启用表单保护
officecli set "$FILE" / --prop protection=forms
officecli get "$FILE" /                                  # look for: protectionEnforced=True
保护模式
模式Word 用户可执行的操作CLI 行为
forms仅填写 SDT 与表单字段表单字段(SDT)编辑可用(退出码 0);非字段内容编辑需要 --force 或 raw-set
readOnly仅可阅读非字段编辑需要 --force;raw-set 可绕过
comments仅可添加批注非字段编辑需要 --force;raw-set 可绕过
trackedChanges仅可进行修订追踪编辑非字段编辑需要 --force;raw-set 可绕过
none完全编辑所有操作均可执行

关键提示: 文档保护同时限制 Word 用户与 CLI。在 protection=forms 模式下,表单字段(SDT)编辑(set /body/sdt[N] --prop text= / --prop alias=)会成功执行(退出码 0),这正是表单保护的用途。仅非字段内容编辑(例如 set /body/p[N] --prop text=)会被拒绝,并返回 ERROR: Document is protected (mode: forms). … use --force to override;若要强制编辑静态内容,需传入 --force(或使用 raw-set,这是唯一能完全绕过保护的命令)。使用 Query("editable") 可查找 Word 用户仍可填写的字段。

锁定值(可在 add 和 set 上设置)
officecli set "$FILE" '/body/sdt[1]' --prop lock=sdtlocked           # content editable; control cannot be deleted
officecli set "$FILE" '/body/sdt[1]' --prop lock=contentlocked       # content read-only; control can be deleted
officecli set "$FILE" '/body/sdt[1]' --prop lock=sdtcontentlocked    # both locked
# Omit lock entirely → unlocked (default)

--prop lock=... 既可用于 add,也可用于 set,建议在创建控件时直接内联设置。无论输入的大小写如何,回读时都会规范为驼峰式(sdtLocked),两种写法均可接受。

lock 与 protection=forms 的交互
lock 值已启用 protection=formsWord 用户能否编辑?Word 用户能否删除控件?
(无)是能能
sdtlocked是能不能
contentlocked是不能能
sdtcontentlocked是不能不能
块级 SDT 包裹并设为 contentlocked任意不能(无论保护与否,被包裹段落均为只读)不能
任意readOnly 模式不能不能
块级锁定(包裹段落的 SDT)

protection=forms 是文档级别的设置,管理员一旦取消保护,每个静态段落(免责声明、法律声明、合同条款)都会重新变得可编辑。主模板需要纵深防御:将关键段落包裹在设置了 lock=contentLocked 的块级 <w:sdt> 中,这样即使保护被移除,内容仍保持只读。

officecli add "$FILE" /body --type paragraph \
  --prop text="I authorize the above and acknowledge all clauses." --prop size=11 --prop spaceAfter=12pt
PID=$(officecli query "$FILE" paragraph --json | jq -r '.data.results[-1].format.paraId')

# raw-set actions: append | prepend | insertbefore | insertafter | replace | remove | setattr
# No `wrap` action — two-step instead: (1) insertbefore an empty <w:sdt><w:sdtContent/></w:sdt>,
# (2) move the original <w:p> inside by `replace` on the sdtContent with a copy of the paragraph XML.
# Simpler alternative: read the paragraph XML via `officecli raw`, then `replace` the whole <w:p> with <w:sdt>...<w:sdtContent>[original w:p]</w:sdtContent></w:sdt>:
# NOTE: this needs an XML-aware extraction, NOT awk. `raw /document` emits the whole document on ONE line,
# so an awk range like `/paraId=.../,/<\/w:p>/` grabs the entire <w:document> (schema-invalid). Parse the XML
# and pull the single <w:p> by paraId instead:
PARA_XML=$(officecli raw "$FILE" /document | python3 -c '
import sys, xml.etree.ElementTree as ET
xml = sys.stdin.read(); pid = sys.argv[1]
ns = {"w":"http://schemas.openxmlformats.org/wordprocessingml/2006/main","w14":"http://schemas.microsoft.com/office/word/2010/wordml"}
for p, u in ns.items(): ET.register_namespace(p, u)
root = ET.fromstring(xml); key = "{%s}paraId" % ns["w14"]
for p in root.iter("{%s}p" % ns["w"]):
    if p.attrib.get(key) == pid:
        sys.stdout.write(ET.tostring(p, encoding="unicode")); break
' "$PID")
officecli raw-set "$FILE" /document \
  --xpath "//w:p[@w14:paraId='$PID']" \
  --action replace \
  --xml "<w:sdt xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:w14=\"http://schemas.microsoft.com/office/word/2010/wordml\"><w:sdtPr><w:alias w:val=\"Authorization\"/><w:tag w:val=\"auth_para\"/><w:lock w:val=\"contentLocked\"/></w:sdtPr><w:sdtContent>${PARA_XML}</w:sdtContent></w:sdt>"

可使用 query sdt --json | jq '.data.results[] | select(.format.lock == "contentLocked" and .format.type == "block")' 进行验证。此方法仅适用于法律声明、合规免责声明、保密条款;常规录入字段无需使用。

角色限定字段(多角色表单)

当同一表单由两个角色填写(患者与医生;甲方与乙方)时,对另一角色不得修改的字段使用 lock=contentLocked。在 protection=forms 模式下,contentLocked 的 SDT 在 Word 中显示为只读;由目标角色取消保护(或管理员替换为各角色专用副本)后,才能填写另一部分。

# Patient section — editable (no lock, or sdtlocked to prevent accidental deletion only)
officecli set "$FILE" '/body/sdt[1]' --prop lock=sdtlocked      # patient_name
officecli set "$FILE" '/body/sdt[2]' --prop lock=sdtlocked      # patient_dob

# Physician section — locked against patient edits
officecli set "$FILE" '/body/sdt[14]' --prop lock=contentLocked # physician_diagnosis
officecli set "$FILE" '/body/sdt[15]' --prop lock=contentLocked # physician_signature

这是医疗问诊表、双方合同以及顺序审批表单的核心模式。

配方 — 含 MERGEFIELD 与签名的合同 / SOW 模板

三个子配方的行映射如下:SDT[1]=project_name,SDT[2]=contract_start,SDT[3]=payment_schedule,SDT[4]=signatory_name(内联)。请在同一 $FILE 上按 (sow-a) → (sow-b) → (sow-c) 顺序执行;每个子配方不超过 20 行,以免某个 shell 转义错误扩散到其他代码块。

配方 (sow-a) 样板、封面与各方信息

创建文件,设置 docDefaults,写入标题与引言,并插入两个 MERGEFIELD 占位符(CustomerName、ContractNo),供后续邮件合并填充。

FILE=sow.docx
officecli create "$FILE"
officecli open "$FILE"
officecli set "$FILE" / --prop title="Statement of Work" \
  --prop docDefaults.font="Calibri" --prop docDefaults.fontSize="12pt"

officecli add "$FILE" /body --type paragraph --prop text="Statement of Work" \
  --prop style=Heading1 --prop size=20 --prop bold=true --prop spaceAfter=12pt
officecli add "$FILE" /body --type paragraph \
  --prop text="This Statement of Work ('SOW') is entered into between the parties identified below and governs the delivery of professional services." \
  --prop size=11 --prop spaceAfter=12pt

officecli add "$FILE" /body --type paragraph --prop text="Customer: "
officecli add "$FILE" '/body/p[last()]' --type field \
  --prop fieldType=mergefield --prop name=CustomerName
officecli add "$FILE" /body --type paragraph --prop text="Contract #: "
officecli add "$FILE" '/body/p[last()]' --type field \
  --prop fieldType=mergefield --prop name=ContractNo
配方 (sow-b) SDT 字段 — 全部通过路径 A 完成

添加三个块级 SDT(项目 / 日期 / 下拉列表),并通过 --after 'find:Client Signature:' 在锚点处插入内联签名 SDT。日期格式与下拉选项直接在 add 时设置,无需 raw-set。

officecli add "$FILE" /body --type sdt --prop type=text \
  --prop alias="Project Name" --prop tag=project_name --prop text="Enter project name"
officecli add "$FILE" /body --type sdt --prop type=date \
  --prop alias="Contract Start Date" --prop tag=contract_start --prop format="MM/dd/yyyy"
officecli add "$FILE" /body --type sdt --prop type=dropdown \
  --prop alias="Payment Schedule" --prop tag=payment_schedule \
  --prop items="Full Prepayment,Net 30 Upon Delivery"
officecli add "$FILE" /body --type paragraph --prop text="Client Signature:" \
  --prop bold=true --prop spaceBefore=18pt --prop spaceAfter=4pt
officecli add "$FILE" /body --type sdt --prop type=text \
  --prop alias="Signatory Name" --prop tag=signatory_name --prop text="Authorized Signatory" \
  --after 'find:Client Signature:'
# (Only reach for raw-set if a listItem's stored value must differ from its display text — see Path B.)
配方(sow-c)水印 + 锁定 + 文档保护

添加 CONFIDENTIAL 水印(父级为 /,绝不能是 /body),锁定三个块级 SDT,说明如何锁定行内 signatory_name SDT(路径需先通过 view forms 获取),最后以 protection=forms 作为最后一条命令封存文档。

officecli add "$FILE" / --type watermark \
  --prop text="CONFIDENTIAL" --prop color=FF0000 --prop rotation=315

officecli set "$FILE" '/body/sdt[1]' --prop lock=sdtlocked
officecli set "$FILE" '/body/sdt[2]' --prop lock=sdtlocked
officecli set "$FILE" '/body/sdt[3]' --prop lock=sdtlocked
officecli view "$FILE" forms   # copy signatory_name path, then: set '/body/p[@paraId=...]/sdt[1]' --prop lock=sdtlocked

officecli set "$FILE" / --prop protection=forms
officecli close "$FILE"
officecli query "$FILE" field     # expect 2 MERGEFIELDs: CustomerName, ContractNo

表单设计原则

控件类型决策树:

Date → type=date | Fixed list → type=dropdown | List + custom → type=combobox
Short text → type=text | Long text → type=richtext | Boolean → formfield checkbox

排版比例。 间距单位陷阱:spaceBefore / spaceAfter / spaceLine 默认以 twips(1/20 pt)为单位,请始终写成 spaceBefore=18pt。

元素字号样式间距
表单标题(H1)20pt粗体spaceBefore=0pt,spaceAfter=12pt
章节标题(H2)14pt粗体spaceBefore=18pt,spaceAfter=8pt
字段标签11pt粗体spaceAfter=4pt
说明 / 备注11pt斜体,color=666666spaceAfter=18pt

无障碍字号提升。 对于医疗 / 老年 / 注重无障碍的表单,将字段标签和说明提升至 12pt(11pt 默认值对老年用户而言偏小);章节标题保持 14pt。

中日韩表单: 设置 docDefaults.font="Microsoft YaHei",因为 Calibri 不包含中文字形。

字段顺序。 (1) 个人 / 身份信息,(2) 角色 / 分类,(3) 日期,(4) 补充性自由文本,(5) 确认 / 签名。

是/否 + 条件性追加说明(常见于合规 / 医疗问诊表):先放一个 formfield 复选框,随后紧跟一个 richtext SDT,其 alias 承载提示语,例如 --type formfield --prop type=checkbox --prop name=has_cond,然后 --type sdt --prop type=richtext --prop alias="If yes, explain" --prop tag=cond_detail --prop text="If yes, explain here"。

签名区块顺序。 标签单独占一段,SDT 放在下一段(标签设置 spaceBefore=18pt,SDT 设置 spaceAfter=4pt)。切勿使用 Label: SDT 的行内写法,Word 会将两段文本渲染为紧贴在一起,视觉上粘连。

构建顺序。 create+open → 元数据 → 结构(标题、标签段落)→ SDT/formfield 骨架(路径 A 的 4 个属性)→ 路径 B 注入 → 逐字段锁定 → 最后执行 protection=forms → close。

页眉 / 页脚说明。 页眉/页脚在创建节时即已预定义(default/first/even 各 3 个)。首次修改必须对已存在的部件使用 set,而不是 add;add $FILE /header ... 会返回 already exists 或静默无效。应先执行 officecli query "$FILE" header --json 读取 type 值,再执行 officecli set "$FILE" '/header[@type=default]' --prop text=...。仅当需要为额外节创建独立的页眉/页脚时才使用 add。

批处理模式(简述)

对于控件较多的表单,批处理可减少开销。路径 A 与路径 B 可在同一批次中并存。

cat <<'EOF' | officecli batch "$FILE"
[
  {"command":"add","parent":"/body","type":"sdt","props":{"type":"text","alias":"Full Name","tag":"full_name","text":"Enter name"}},
  {"command":"add","parent":"/body","type":"sdt","props":{"type":"dropdown","alias":"Department","tag":"dept"}},
  {"command":"raw-set","part":"/document","xpath":"//w:sdt[w:sdtPr/w:tag/@w:val='dept']/w:sdtPr/w:dropDownList","action":"append","xml":"<w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Engineering\" w:value=\"Engineering\"/><w:listItem xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" w:displayText=\"Finance\" w:value=\"Finance\"/>"},
  {"command":"set","path":"/body/sdt[1]","props":{"lock":"sdtlocked"}},
  {"command":"set","path":"/body/sdt[2]","props":{"lock":"sdtlocked"}}
]
EOF
officecli set "$FILE" / --prop protection=forms
  • 在 xml 中需转义内部的 ",写作 \"。使用单引号包裹的 heredoc <<'EOF',以免 $var 被展开。
  • P0 批处理陷阱: 批处理中真正不受支持的属性会被静默丢弃,不会输出 WARNING(交互式 add 会输出 WARNING: UNSUPPORTED 并以退出码 2 结束)。防护方法:只发送 help docx sdt 所列出的属性(type/tag/alias/text/items/format/lock/... 可用于 add;maxlength 不可用),并在批处理后通过回读进行验证。
  • batch 支持 add、set、get、query、remove、validate、raw-set。

交付门禁(可执行)

每张表单完成后都要运行以下全部门禁。每个门禁都必须输出其 OK 行。任何 REJECT 均表示不得交付。

# Assumes FILE=<your-form.docx>, document has been closed with officecli close "$FILE"

# Gate 1 — Validate (must be clean; protection=forms no longer produces a schema error)
VAL_OUT=$(officecli validate "$FILE" 2>&1)
VAL_ERRS=$(echo "$VAL_OUT" | grep -c '\[Schema\]')
if [ "$VAL_ERRS" -eq 0 ]; then echo "Gate 1 OK (validate clean)"
else echo "REJECT Gate 1: $VAL_ERRS schema errors"; echo "$VAL_OUT"; exit 1
fi

# Gate 2 — Token / placeholder leak (labels used as visual underscore substitutes)
LEAK=$(officecli view "$FILE" text | grep -niE '_{3,}|TBD|\(fill in\)|\{\{|xxxx|lorem|placeholder')
[ -z "$LEAK" ] && echo "Gate 2 OK (no underscore / placeholder leak)" || { echo "REJECT Gate 2:"; echo "$LEAK"; exit 1; }

# Gate 3 — At least one structured field exists
SDT_N=$(officecli query "$FILE" sdt --json | jq '.data.results | length')
FF_N=$(officecli query "$FILE" formfield --json | jq '.data.results | length')
FLD_N=$(officecli query "$FILE" field --json | jq '.data.results | length')
TOTAL=$((SDT_N + FF_N + FLD_N))
[ "$TOTAL" -gt 0 ] && echo "Gate 3 OK ($SDT_N sdt + $FF_N formfield + $FLD_N field)" || { echo "REJECT Gate 3: 0 structured fields — this is not a form"; exit 1; }

# Gate 4 — Every SDT has alias + tag (skill-imposed H2)
# NOTE: `query`/`get --json` wrap prop fields under `.data.results[N].format.{prop}` — use `.data.results[0].format.alias` / `.format.tag`, never bare `.alias` or `.data.format`.
SDT_MISSING=$(officecli query "$FILE" sdt --json | jq '[.data.results[] | select(.format.alias == null or .format.alias == "" or .format.tag == null or .format.tag == "")] | length')
[ "$SDT_MISSING" -eq 0 ] && echo "Gate 4 OK (every SDT has alias+tag)" || { echo "REJECT Gate 4: $SDT_MISSING SDT(s) missing alias or tag"; exit 1; }

# Gate 5 — Protection enforced + per-field lock inventory
PROT=$(officecli get "$FILE" / --json | jq -r '.data.results[0].format.protection // "none"')
[ "$PROT" = "forms" ] && echo "Gate 5 OK (protection=forms enforced)" || { echo "REJECT Gate 5: protection is '$PROT', expected 'forms'"; exit 1; }
officecli view "$FILE" forms | head -40   # visual spot-check: every dropdown shows items=; every date shows format=; every locked SDT shows lock=

# Gate 6 — No type=checkbox leaked onto any SDT
BAD_CB=$(officecli query "$FILE" sdt --json | jq '[.data.results[] | select(.format.type == "checkbox")] | length')
[ "$BAD_CB" -eq 0 ] && echo "Gate 6 OK (no SDT checkbox — formfield only)" || { echo "REJECT Gate 6: $BAD_CB SDT with type=checkbox"; exit 1; }

为何 view issues 不作为门禁。 它只运行偏重文字排版的检查(首行缩进、标题字号),并会将每个表单标签都标记为 Body paragraph missing first-line indent,在表单上造成大量误报。本技能中应忽略它。请改用 validate(结构完整性)和 view forms(字段清单)。

已知问题

#问题行为变通方法
K1SDT type=checkbox 未实现add ... --type sdt --prop type=checkbox → Error: SDT type 'checkbox' is not implemented,退出码 1(错误信息现已列出支持的类型,包括 group/picture)使用 --type formfield --prop type=checkbox
K3add 时 SDT maxlength 不受支持WARNING: UNSUPPORTED: maxlength,退出码 2;元素仍会被创建。(items / format / lock / placeholderText 现在已支持用于 add,仅 maxlength 被拒绝;name 可被接受,但对 SDT 无效果,请改用 alias/tag)在下游校验长度;使用 text 设置初始内容
K4创建后无法设置 SDT items / format / typeset --prop items=... → UNSUPPORTED props (use raw-set instead)。(这些属性可在 add 时设置,请在创建时内联设置)在 add 时设置;如需稍后修改,使用路径 B raw-set,或 remove 后重新 add
K5FormField maxlength 不受支持WARNING: UNSUPPORTED: maxlength;formfield 仍被创建在下游校验中限制长度
K6FormField 下拉框 items 不受支持创建的下拉 formfield 选项列表为空改用带 --prop items= 的 SDT 下拉框
K7水印 width / height 无法设置水印创建时不带这些属性;opacity 可在 add 时设置并可读回(实际尺寸会自动计算)如需精确尺寸,请打开 Word 手动调整形状(第二阶段)
K9批处理模式会静默丢弃确实不受支持的属性没有 WARNING 行;即使某属性(例如 maxlength)被丢弃,批处理仍报告“N succeeded”批处理中的 SDT 条目仅使用 help docx sdt 列出的属性;批处理后通过回读进行验证
K13FormField name 超过 20 个字符add 返回退出码 0,且没有任何警告;之后 validate 会在 /w:ffData/w:name 上报告 [Schema] ... MaxLength=20将 name 保持在 20 个字符以内(OpenXML 架构限制)。SDT 的 alias / tag 没有此限制
K14段落上的 shd.fill 会生成架构无效的 <w:pPr>/<w:shd>validate 每个实例报告 2 个架构错误(unexpected child element、required attribute 'val' missing);Word 仍会照常渲染改为在文本块(run)上应用高亮(shading=HEX,扁平规范形式),或将 <w:shd w:val="clear" w:fill="HEX"/> 通过 raw-set 写入文本块的 <w:rPr>
K15view forms 不会列出 MERGEFIELD输出中仅包含 SDT 和 formfield;MERGEFIELD 是模板阶段的内容,不可由最终用户填写将 query field 和 view forms 视为两个互不重叠的清单。每个方案都要同时验证两者
K16页眉 / 页脚在创建节时已预定义(default/first/even,各 3 个)add $FILE /header ... 首次调用时返回 already exists 或静默无操作首次修改应使用 set 作用于已有部件:先运行 officecli query $FILE header --json 读取 type,然后执行 set '/header[@type=default]' --prop text=...。仅在为全新节添加页眉/页脚时才使用 add
K17注入页眉的水印会生成架构无效的 <w:noProof> 子元素validate 会在 /header[N]/w:sdt/.../w:noProof 处额外报告一个 [Schema] 错误——这是需要修复的真实架构错误,而非无害问题执行 add $FILE / --type watermark 后,对每个页眉部件运行一次:officecli raw-set $FILE /word/header1.xml --xpath "//w:noProof" --action remove(若存在 header2.xml、header3.xml,同样重复执行)
K18query/get --json 会将属性字段包裹在 .data.results[N].format.{prop} 下对裸 .alias / .tag / .protection 或 .data.format.* 编写 jq,会返回 null/0 匹配,导致 Gate 4/5 误报 "missing=N"使用 .data.results[].format.alias / .format.tag;对于 get /,使用 .data.results[0].format.protection(而非 .data.format.protection)。.format.type / .format.paraId 同理
K19LibreOffice 在导出 PDF 时将 formfield 复选框渲染为 ☐☐(双框)仅为外观问题——Word / WPS 渲染为单个方框,可点击切换为 ☑。这是 LibreOffice 渲染器的特性,标记为 [RENDERER-BUG]不要尝试在本技能中“修复”。若评估者从 LibreOffice 生成的 PDF 截图中看到 ☐☐,应归因于 [RENDERER-BUG],而非表单质量缺陷

第二阶段:在 Word 中增强

部分优化超出了 CLI 的范围。以下内容需交由人工在 Word 中完成;这些都不是生成有效表单所必需的。

需求为何要打开 Word
具有特定锁定设置的真实 SDT 复选框type=checkbox 会以退出码 1 结束;请使用“开发工具” → “复选框内容控件”(或通过 CLI 添加旧版 FormField 复选框)
提示文本(“单击此处输入日期”)需要在 /word/glossary/document.xml 中设置 placeholderDocPart
自定义富文本默认外观在 Word 的样式窗格中调整所引用的样式
水印尺寸调整无法设置 width / height;请拖动形状控制点

(picture 和 group 类型的 SDT 可直接通过 --type sdt --prop type=picture / type=group 添加,不再属于第二阶段的事项。)

前四项请先按路径 C 构建一次骨架,然后重复使用。

帮助指引

如有疑问,请运行:officecli help docx、officecli help docx <element>、officecli help docx <element> --json。帮助信息是权威的结构定义;本技能则是在其之上构建真实可填写 Word 表单的决策指南。

评论

支持 Markdown