模板内置技能与 hook:预装 9 个基础 skill、加 auto-wiki Stop hook、删除 index.html
This commit is contained in:
parent
c8d219670c
commit
28a7a2e2af
43
.claude/hooks/wiki-ingest-reminder.sh
Executable file
43
.claude/hooks/wiki-ingest-reminder.sh
Executable file
@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
# wiki-ingest-reminder.sh
|
||||
# Stop hook:当对话中产生了实质性研究产出时,提醒用户将知识 ingest 进 wiki。
|
||||
# 判断逻辑:检查 transcript 中是否包含研究/分析类关键词,且对话轮次 >= 5。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path // empty')
|
||||
|
||||
# 无 transcript 则静默退出
|
||||
if [ -z "$TRANSCRIPT_PATH" ] || [ ! -f "$TRANSCRIPT_PATH" ]; then
|
||||
echo '{"continue": true}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 统计对话轮次(assistant 消息数)
|
||||
TURN_COUNT=$(grep -c '"role":"assistant"' "$TRANSCRIPT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
# 轮次不足则不提醒(避免简单问答也弹提醒)
|
||||
if [ "$TURN_COUNT" -lt 5 ]; then
|
||||
echo '{"continue": true}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 检查是否包含研究/分析类产出的信号词
|
||||
RESEARCH_SIGNALS="分析|研究|报告|对比|总结|结论|规律|启示|发现|梳理|整理|数据|指标|回测|归因|板块|行业|估值|财务|研报"
|
||||
SIGNAL_COUNT=$(grep -cE "$RESEARCH_SIGNALS" "$TRANSCRIPT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
# 检查是否已经触发过 ingest(避免重复提醒)
|
||||
ALREADY_INGESTED=$(grep -c "auto-wiki.*ingest\|ingest.*wiki\|已 ingest" "$TRANSCRIPT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$SIGNAL_COUNT" -ge 10 ] && [ "$ALREADY_INGESTED" -eq 0 ]; then
|
||||
jq -n '{
|
||||
"continue": true,
|
||||
"systemMessage": "[auto-wiki] 本次对话产生了较多研究分析内容,建议在结束前将有价值的知识 ingest 进 wiki 以便跨会话积累。可以说「把这次的研究整理进 wiki」触发。"
|
||||
}'
|
||||
else
|
||||
echo '{"continue": true}'
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@ -12,5 +12,19 @@
|
||||
"Edit(//etc/**)",
|
||||
"Write(//etc/**)"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/home/core/dev/__USERNAME__/.claude/hooks/wiki-ingest-reminder.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
351
.claude/skills/auto-wiki-cn/SKILL.md
Normal file
351
.claude/skills/auto-wiki-cn/SKILL.md
Normal file
@ -0,0 +1,351 @@
|
||||
---
|
||||
name: auto-wiki
|
||||
version: 0.2.0
|
||||
description: |
|
||||
知识编译器:教 Agent 把源文件增量编译进持久化 wiki,实现跨会话知识积累。
|
||||
运行时依赖:Python 3.8+(标准库 + pydantic)。可选增强:WebSearch(主动搜索)、外部 MCP 校验器(逻辑校验)。
|
||||
|
||||
五个模式,根据用户意图自动路由:
|
||||
|
||||
recall → 用户想基于已有知识回答问题。
|
||||
触发词:recall、知识模式、打开 wiki、带着知识回答、根据 wiki、
|
||||
基于积累、查一下 wiki、wiki 里有没有、之前研究过、上次整理的。
|
||||
|
||||
ingest → 用户提供了新材料,想编译进 wiki。
|
||||
触发词:ingest、编译、整理这篇、消化这篇、学习这个、归档、
|
||||
把这篇加进去、积累、研究一下、帮我整理、加入知识库。
|
||||
|
||||
query → 用户问了一个具体问题,想从 wiki 中找答案(单次)。
|
||||
触发词:query、根据 wiki 回答、wiki 里怎么说、查查看。
|
||||
|
||||
lint → 用户想检查 wiki 健康度。
|
||||
触发词:lint、检查 wiki、wiki 健康、清理一下、有没有矛盾。
|
||||
|
||||
deep-dive → 用户想让 Agent 自动找出知识缺口并补全。
|
||||
触发词:deep-dive、深度研究、补全知识、查漏补缺、上强度、
|
||||
自动补全、知识补全、全面补充。
|
||||
注意:deep-dive 不是独立模式,是 lint(Coverage) + ingest(搜索填充) 的组合管道。
|
||||
|
||||
路由规则:如果用户没有提供新材料但提到了 wiki 或领域知识 → recall。
|
||||
如果用户提供了文件或大段文本 → ingest。
|
||||
如果用户说"deep-dive"或"上强度" → 执行 deep-dive 管道。
|
||||
如果不确定 → 问用户。
|
||||
---
|
||||
|
||||
# 知识编译器
|
||||
|
||||
> Agent 做研究、拉数据、写报告——wiki 把这些产出串起来。Agent 越用越懂你的领域。
|
||||
|
||||
## 运行时依赖与权限声明
|
||||
|
||||
| 依赖 | 必需? | 说明 |
|
||||
|------|--------|------|
|
||||
| **Python 3.8+** | ✅ 必需 | `schema.py`(frontmatter 校验)、`store.py`(SQLite 数据管理)、`build_index.py`(FTS5 索引)均为 Python 脚本。仅用标准库(`sqlite3`、`json`、`pathlib`)+ `pydantic` |
|
||||
| **pydantic** | ✅ 必需 | `schema.py` 的 frontmatter 校验依赖。`pip install pydantic` |
|
||||
| **文件系统写入** | ✅ 必需 | 在 `.wiki/{topic}/` 下创建和编辑 Markdown、SQLite、`.obsidian/` 配置。**首次创建 `.wiki/` 时会向用户确认位置** |
|
||||
| **WebSearch / WebFetch** | ❌ 可选 | 主动模式(Agent 自主搜索材料)需要。被动模式(用户提供文件)不需要 |
|
||||
| **外部校验器(MCP)** | ❌ 可选 | 仅当 wiki 声明了 validator 时 lint 会尝试调用。不可达时静默跳过,零影响。**不需要用户提供任何凭证**——`Mcp-Session-Id` 是标准 MCP 协议的会话握手,由 Agent 自动完成 |
|
||||
| **搜索类 MCP** | ❌ 可选 | deep-dive 和主动 ingest 可用域数据 MCP 增强搜索质量。没有时退化为 WebSearch |
|
||||
|
||||
> **核心承诺**:被动模式(用户提供文件 → Agent 编译)只需要 Python 3 + 文件读写,零网络依赖。所有网络调用都是可选增强,且会在首次使用时通过环境检查告知用户。
|
||||
|
||||
## Quick Start
|
||||
|
||||
```
|
||||
用户: /auto-wiki recall personal-pension
|
||||
Agent: [扫描 .wiki/personal-pension/ → 读 index.md → 加载 data.db 摘要]
|
||||
Agent: 已进入 recall 模式。当前 wiki:22 页 / 8 数据点 / 2 处 contested。
|
||||
接下来的问题我会先查 wiki 再回答。
|
||||
|
||||
用户: 参与率低的原因是什么?
|
||||
Agent: [读 wiki 中 enrollment-friction、tax-incentive-effect 等页面]
|
||||
Agent: 根据 wiki 积累的 6 篇来源...(引用具体页面和数据)
|
||||
⚠️ 注意:税优激励效果存在矛盾(77.8% vs 25%),详见 [[participation-willingness]]
|
||||
```
|
||||
|
||||
## 核心理念
|
||||
|
||||
Agent 每天帮你做研究、写报告、拉数据——但做完就忘。下次问同样领域的问题,又从零开始。
|
||||
|
||||
这个 Skill 解决一件事:**给 Agent 一个可以持续积累的知识库。**
|
||||
|
||||
不是 RAG(每次从文档堆里临时检索),是编译——Agent 读完源文件后,把关键信息写进 wiki 已有页面,和旧知识比较、合并、标注冲突。下次执行任何任务前,先读 wiki,从积累的基础上工作。
|
||||
|
||||
## 四个模式
|
||||
|
||||
| 模式 | 触发 | Agent 做什么 |
|
||||
|------|------|-------------|
|
||||
| **recall** | `recall` / `recall {topic}` | 加载 wiki 上下文,后续所有问题先查 wiki 再回答 |
|
||||
| **ingest** | 用户提供源文件或文本 | 读源文件 → 搜索已有 wiki → 比较新旧 → 更新/创建页面 → 更新索引 |
|
||||
| **query** | 用户提问(单次) | 读 index → 找相关页面 → 综合回答 → 有价值的分析可归档 |
|
||||
| **lint** | 用户说"检查 wiki" | 扫描全部页面 → 合并重复 → 归档过时 → 报告矛盾和健康度 |
|
||||
| **deep-dive** | `deep-dive` / "上强度" | 运行 Coverage lint → 展示缺口报告 → 用户确认 → 搜索 + ingest 填补缺口 |
|
||||
|
||||
> deep-dive 不是第五个独立模式——它是 lint(Coverage)和 ingest(带搜索工具)的组合管道。需要搜索工具(主动模式)。
|
||||
|
||||
recall 模式 vs query 的区别:query 是单次操作(问一个问题,查一次 wiki)。recall 模式是持续状态——进入后,这轮对话里的每个问题都先过 wiki。
|
||||
|
||||
---
|
||||
|
||||
## recall 模式
|
||||
|
||||
### 进入
|
||||
|
||||
用户说 `/auto-wiki recall` 或 `/auto-wiki recall {topic}` 时触发。
|
||||
|
||||
Agent 执行:
|
||||
|
||||
1. **扫描 `.wiki/` 目录**,列出可用的 wiki 主题
|
||||
2. 如果用户指定了主题 → 加载该 wiki;如果没指定 → 列出可选主题让用户选
|
||||
3. **读 index.md** → 获取全部页面列表和结构
|
||||
4. **读 data.db 摘要** → `python references/store.py dump .wiki/{topic}/`,获取数据点数、关系数、contested 数
|
||||
5. **向用户报告**:
|
||||
```
|
||||
已进入recall 模式:{主题}
|
||||
- 页面:{N}(sources: X, entities: Y, concepts: Z)
|
||||
- 数据点:{N} | 关系:{N} | Contested:{N}
|
||||
接下来的问题我会先查 wiki 再回答。说"退出recall 模式"恢复正常。
|
||||
```
|
||||
|
||||
### 回答流程
|
||||
|
||||
进入recall 模式后,每次收到用户问题:
|
||||
|
||||
1. **从问题中提取关键词**(实体名、概念名、指标名)
|
||||
2. **在 index.md 中匹配**相关页面(标题 + 描述)
|
||||
3. **在 data.db 中查询**相关数据点:
|
||||
```sql
|
||||
SELECT * FROM data_points WHERE field LIKE '%关键词%' OR page_slug LIKE '%关键词%'
|
||||
```
|
||||
4. **读取匹配的 wiki 页面**(通常 2-5 个),沿 wikilink 展开一层
|
||||
5. **综合回答**,必须:
|
||||
- 引用具体页面:`[[slug]]`
|
||||
- 引用具体数据:值 + 单位 + 时段 + 来源
|
||||
- 如果涉及 contested 信息,主动标注
|
||||
- 如果 wiki 中信息不足,明确说"wiki 中没有这方面的积累,建议 ingest XX"
|
||||
6. **不编造 wiki 中没有的信息**。宁可说"不知道"也不要假装 wiki 里有
|
||||
|
||||
### 退出
|
||||
|
||||
用户说 `exit recall`、切换到其他操作(ingest/lint)、或开始新话题时退出。
|
||||
|
||||
---
|
||||
|
||||
## 执行流程
|
||||
|
||||
### Phase 0: 识别研究主题与本体类型
|
||||
|
||||
收到用户输入后,判断三件事:**操作类型**、**目标 wiki**、**本体类型**。
|
||||
|
||||
| 用户输入 | 操作 | 目标 wiki | 本体类型 |
|
||||
|---------|------|-----------|---------|
|
||||
| "帮我整理这篇研报" + 文件 | ingest | 从内容推断,或问用户 | domain |
|
||||
| "ingest 到个人养老金" + 文件 | ingest | personal-pension | domain |
|
||||
| "研究一下 Charlie Munger" + 材料 | ingest | charlie-munger | cognitive |
|
||||
| "个人养老金参与率怎么样" | query | 从问题推断,或问用户 | — |
|
||||
| "检查一下养老金 wiki" | lint | personal-pension | — |
|
||||
|
||||
**本体类型**决定 wiki 的页面结构和采集策略:
|
||||
|
||||
| 本体类型 | 研究对象 | 页面侧重 | 参考 |
|
||||
|---------|---------|---------|------|
|
||||
| **cognitive** | 人(思维模型、决策方式) | MentalModel, Heuristic, Value, StylePattern | `references/ontology-types/cognitive.md` |
|
||||
| **domain** | 领域(机构、制度、指标) | Entity, Concept, Metric | `references/ontology-types/domain.md` |
|
||||
| **general** | 以上都不是 | 默认 entity/concept 结构 | — |
|
||||
|
||||
**一个 wiki 只有一种类型。** 如果研究横跨人和领域(如"Munger 的投资框架在企业年金中的应用"),分属两个 wiki,用跨 wiki query 综合回答。不要在一个 wiki 里混用 cognitive 和 domain 页面结构。
|
||||
|
||||
**如果 wiki 目录不存在**,先向用户确认创建位置(默认 `.wiki/{topic}/`,在当前仓库根目录下),然后按 `references/storage-spec.md` 创建初始结构(含 meta.yaml、index.md 模板、log.md 模板)。建议用户将 `.wiki/` 加入 `.gitignore`(如尚未添加)。
|
||||
|
||||
**领域种子(seed)**:如果目标领域有对应的种子文件(`seeds/{name}.md`),在 meta.yaml 中声明 `seed: {name}`。种子提供标准术语词表、关系模板和禁混规则,让 wiki 从规范化的起点开始生长。没有种子的领域,wiki 自由生长——两种路径都能跑。种子是社区可贡献的插件,任何人可以为自己的垂直领域写一个 markdown 文件。详见 `references/seed-ontologies.md`。
|
||||
|
||||
**首次使用时**,执行环境检查(见 `references/source-validation.md`),告知用户当前可用的能力(被动模式 vs 主动模式)。
|
||||
|
||||
### Reference 加载策略
|
||||
|
||||
不要一次读完所有 reference。按操作类型按需加载:
|
||||
|
||||
| 操作 | 必读 | 首次时读 | 有工具时读 |
|
||||
|------|------|---------|-----------|
|
||||
| **ingest** | `ingest-protocol.md`, `wiki-format.md`, `schema.py` | `storage-spec.md`(wiki 不存在时), `seed-ontologies.md` + `seeds/{name}.md`(meta.yaml 声明了 seed 时) | `fact-check.md`, `source-validation.md` |
|
||||
| **query** | `query-protocol.md` | — | — |
|
||||
| **lint** | `lint-protocol.md`, `schema.py` | — | `validators/{name}.md`(seed 声明了 validator 时) |
|
||||
| **deep-dive** | `lint-protocol.md`, `ingest-protocol.md`, `source-validation.md`, `wiki-format.md`, `schema.py` | `storage-spec.md`(wiki 不存在时) | `fact-check.md` |
|
||||
|
||||
**不需要读的**:`scaling.md` 仅当页面数 > 500 时才相关;`ontology-types/` 仅当新建 wiki 需判断类型时。
|
||||
|
||||
### Phase 1: Ingest(知识编译)
|
||||
|
||||
**这是核心操作。** 详细协议见 `references/ingest-protocol.md`。
|
||||
|
||||
简要流程:
|
||||
|
||||
1. **读取源文件**,提取关键信息
|
||||
2. **校验关键数据**(如有可用工具)— 详见 `references/fact-check.md`
|
||||
3. **写 source 摘要页**(`sources/{date}-{slug}.md`)
|
||||
4. **搜索 wiki 中已有的相关页面**(读 index.md,grep 关键实体名)
|
||||
5. **逐页比较新旧信息**:
|
||||
- 新信息**支持**已有结论 → 加引用,提升 confidence
|
||||
- 新信息**推翻**已有结论 → 数值写入 data.db(旧值自动进 history 表),改写正文分析
|
||||
- 新信息**矛盾**且无法判断 → 并列两种说法,confidence → `contested`
|
||||
6. **创建新页面**(仅当涉及 wiki 中没有的实体/概念)
|
||||
7. **更新 index.md + 追加 log.md**
|
||||
8. **Schema 校验**——对本次创建/修改的所有页面运行 `python references/schema.py {page.md}`,确保 frontmatter 符合规范。不通过则立即修复再继续
|
||||
|
||||
Ingest 完成后向用户报告:
|
||||
```
|
||||
已 ingest 到 {主题} wiki:
|
||||
- 新建:{N} 页(列出)
|
||||
- 更新:{N} 页(列出 + 简述变更原因)
|
||||
- 冲突:{N} 处(列出矛盾点)
|
||||
- 校验:{N} 页全部通过 / {M} 页有问题(列出)
|
||||
```
|
||||
|
||||
### Phase 2: Query(知识查询)
|
||||
|
||||
**详细协议见 `references/query-protocol.md`。**
|
||||
|
||||
1. 读 index.md,识别与问题相关的页面
|
||||
2. 读取匹配页面 + 沿 wikilink 展开一层关联页面
|
||||
3. 基于页面内容综合回答,**引用来源页面**:
|
||||
```
|
||||
根据 wiki 中 5 篇源文件的积累:
|
||||
... 分析内容 ...
|
||||
来源:[[alpha-corp]]、[[2026-policy-doc]]
|
||||
```
|
||||
4. 如果涉及 contested 信息,明确标注矛盾
|
||||
5. 如果回答中包含有价值的新分析,提示用户归档
|
||||
|
||||
**如果 wiki 中信息不足以回答**,明确说明缺口:
|
||||
```
|
||||
wiki 中关于 XX 的信息不足,目前只有 2 篇相关源文件。
|
||||
建议 ingest 更多关于 XX 的材料。
|
||||
```
|
||||
|
||||
### Phase 3: Lint(知识治理)
|
||||
|
||||
**详细协议见 `references/lint-protocol.md`(7 项检查 + 健康报告格式)。**
|
||||
|
||||
Lint 分两档:
|
||||
|
||||
| 档位 | 触发 | 检查项 | 代价 |
|
||||
|------|------|--------|------|
|
||||
| **结构档**(默认) | `lint` / `检查 wiki` | Validation, Orphan, Broken Link, Staleness | 全量扫描,确定性 |
|
||||
| **语义档**(按需) | `深度 lint` / `检查矛盾` | Contradiction, Duplication, Coverage | Agent 语义理解,按范围控制 |
|
||||
|
||||
1. **结构档**:自动扫描全部页面,修复格式、断链、孤页、过时标注
|
||||
2. **语义档**(用户触发时):检测矛盾、重复、覆盖度缺口。wiki < 50 页全量扫描,50-200 页只扫最近 30 天 ingest 触及的页面,> 200 页须用户指定范围
|
||||
3. **报告健康度**:
|
||||
```
|
||||
Wiki 健康报告:{主题}
|
||||
- 页面总数:42(entities: 15, concepts: 10, sources: 12, analyses: 5)
|
||||
- 健康度:良好
|
||||
- 结构修复:修复 1 个断链,归档 1 个过时页面
|
||||
- [语义] 待人工确认:2 处矛盾(列出)
|
||||
- 建议:XX 领域源文件较少(仅 1 篇),建议补充
|
||||
```
|
||||
|
||||
### Phase 4: Deep-Dive(知识补全管道)
|
||||
|
||||
**deep-dive = lint(Coverage) + ingest(搜索填充)**。不是独立模式,是组合管道。
|
||||
|
||||
**前提条件**:需要搜索工具(主动模式)。无搜索工具时,只输出缺口报告,不执行自动填充。提示用户手动 ingest。
|
||||
|
||||
**流程**:
|
||||
|
||||
```
|
||||
1. 运行 lint Coverage 检查(5 类缺口检测)
|
||||
→ 输出结构化 Gap Report(见 lint-protocol.md)
|
||||
|
||||
2. 展示 Gap Report,请用户确认
|
||||
→ 用户可以:全部接受 / 选择子集 / 限定范围 / 取消
|
||||
→ 这一步不可跳过——防止无监督的批量写入
|
||||
|
||||
3. 对确认的每个缺口,执行 from-lint ingest 流程
|
||||
→ 搜索 → 用户确认来源 → 标准 ingest
|
||||
→ 详见 ingest-protocol.md 的 From-Lint 章节
|
||||
|
||||
4. 输出补全报告:已补全 / 未能补全 / 建议
|
||||
```
|
||||
|
||||
**触发词**:`deep-dive`、`深度研究`、`补全知识`、`查漏补缺`、`上强度`
|
||||
|
||||
**示例**:
|
||||
```
|
||||
用户: deep-dive treasury-futures
|
||||
Agent: [运行 Coverage lint...]
|
||||
Agent: 发现 6 个知识缺口:
|
||||
1. [high] page_missing: stock-bond-correlation(被 4 个页面引用)
|
||||
2. [high] concept_missing: 基差(在 5 个实体页中提到)
|
||||
3. [medium] single_source: treasury-futures-basics(仅 1 个来源)
|
||||
...
|
||||
要补全哪些?(all / 选序号 / cancel)
|
||||
|
||||
用户: 1, 2
|
||||
|
||||
Agent: [搜索"股债联动 国债期货"...]
|
||||
Agent: 找到 2 个候选来源:
|
||||
- [二手·权威] 中金固收报告《股债联动分析》 ← 推荐
|
||||
- [二手] 某公众号文章 ← 跳过(黑名单渠道)
|
||||
确认使用中金报告?
|
||||
|
||||
用户: 确认
|
||||
|
||||
Agent: [执行标准 ingest → 新建 concepts/stock-bond-correlation.md]
|
||||
Agent: 补全完成。新建 2 页,更新 0 页,1 个缺口未能补全(建议手动提供材料)。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Wiki 页面格式
|
||||
|
||||
详见 `references/wiki-format.md`。简要:
|
||||
|
||||
- 每个页面是带 frontmatter 的 markdown(title, type, created, updated, sources, confidence)
|
||||
- 5 种页面类型:source / entity / concept / analysis / mental-model
|
||||
- 用 `[[slug]]` 做页面间链接
|
||||
- index.md 是目录,log.md 是操作日志
|
||||
|
||||
## 本体类型参考
|
||||
|
||||
当研究对象是**人**时,参见 `references/ontology-types/cognitive.md` 的采集策略——页面类型侧重心智模型、启发式、价值体系、表达风格。
|
||||
|
||||
当研究对象是**领域**时,参见 `references/ontology-types/domain.md` 的采集策略——页面类型侧重机构实体、制度概念、量化指标。
|
||||
|
||||
两者共用同一套 wiki 基础设施(ingest/query/lint),区别仅在页面分类和采集侧重。
|
||||
|
||||
## 垂直领域适配
|
||||
|
||||
Skill 核心是领域无关的编译引擎。垂直领域的专业性通过两层插件注入:
|
||||
|
||||
| 层 | 载体 | 作用 | 必须? |
|
||||
|----|------|------|--------|
|
||||
| **种子(seed)** | `seeds/{name}.md` | 冷启动词表:标准术语、关系模板、禁混规则 | 可选 |
|
||||
| **校验器(validator)** | `validators/{name}.md` | 运行时逻辑校验:关系合法性、必要关系完整性 | 可选 |
|
||||
|
||||
没有插件,wiki 自由生长,适合探索性研究。有了插件,wiki 从行业标准起步,概念命名规范、关系结构清晰、逻辑缺口可检测。
|
||||
|
||||
**社区可贡献**:为你的垂直领域写一个 seed 文件(markdown),声明 20-50 个核心术语和禁混规则,就能让该领域的 wiki 从规范化起点生长。
|
||||
|
||||
当前可用:
|
||||
- `seeds/fibo-pensions.md` — 企业年金/养老金(基于 FIBO 标准)
|
||||
- `validators/fibo-mcp.md` — FIBO SPARQL 逻辑校验(627K 推理三元组)
|
||||
|
||||
## 不做什么
|
||||
|
||||
- **不做向量检索**。小规模靠 index + grep,大规模靠 SQLite FTS5 + BM25(见 `references/scaling.md`)。向量检索留给平台级工具。
|
||||
- **不做多用户协作**。wiki 目录是本地文件,一个用户一个 wiki。
|
||||
- **不替代专业数据工具**。领域数据获取用对应的 MCP/工具,本 Skill 只接住它们的产出并编译进 wiki。
|
||||
|
||||
## 与其他工具的关系
|
||||
|
||||
本 Skill 不替代任何专业工具,它**串联**它们:
|
||||
|
||||
```
|
||||
任意研究工具产出分析 → ingest 进对应 wiki
|
||||
任意数据工具拉数据 → ingest 进对应 wiki
|
||||
领域种子提供起跑线 → 标准术语 + 禁混规则
|
||||
外部校验器纠逻辑 → lint 时检查知识结构完整性
|
||||
|
||||
下次执行任务时,Agent 先读相关 wiki → 带着积累的知识工作
|
||||
```
|
||||
137
.claude/skills/auto-wiki-cn/references/fact-check.md
Normal file
137
.claude/skills/auto-wiki-cn/references/fact-check.md
Normal file
@ -0,0 +1,137 @@
|
||||
# 数据校验协议
|
||||
|
||||
> 防止错误数据污染 wiki。可验证的数据必须验证,不可验证的必须标注。
|
||||
|
||||
## 核心原则
|
||||
|
||||
**Wiki 是知识资产,不是垃圾桶。** 错误数据进入 wiki 后会被后续 query 引用、被其他 ingest 当作"已有结论"比较,错误会 compound。必须在入口把关。
|
||||
|
||||
## 校验时机
|
||||
|
||||
在 ingest 流程中,**读取源文件之后、写入 wiki 之前**插入校验步骤:
|
||||
|
||||
```
|
||||
读源文件 → 提取关键声明
|
||||
↓
|
||||
分类:可验证 vs 不可验证
|
||||
↓
|
||||
可验证 → 用工具交叉验证 → 通过/不通过/无法验证
|
||||
↓
|
||||
写入 wiki(带校验标注)
|
||||
```
|
||||
|
||||
## 可验证数据的识别
|
||||
|
||||
以下类型的声明应该尝试验证。**具体用什么工具取决于用户环境中可用的 MCP/工具**——下表是常见映射,不是硬性要求:
|
||||
|
||||
| 数据类型 | 示例 | 常见验证工具(视环境而定) |
|
||||
|---------|------|--------------------------|
|
||||
| 上市公司财务数据 | 营收、净利、ROE | 金融数据 MCP(如 tushare、ifind、wind) |
|
||||
| 基金/年金规模 | 管理规模、份额 | 金融数据 MCP |
|
||||
| 宏观经济指标 | GDP、CPI、PMI | 金融数据 MCP |
|
||||
| 监管文件编号 | 人社部发〔2026〕XX 号 | WebSearch |
|
||||
| 公司基本信息 | 成立时间、注册资本、法人 | 金融数据 MCP / WebSearch |
|
||||
| 行业统计数据 | 市场规模、增速、份额 | WebSearch / 行业数据 MCP |
|
||||
| 日期和事件 | "2026年3月发布" | WebSearch |
|
||||
|
||||
**工具发现**:Agent 在首次 ingest 时通过环境检查(见 `source-validation.md`)确认可用工具。没有对应工具时,该类数据标注 `verified: false`,不阻塞 ingest。
|
||||
|
||||
**不可验证的声明**:观点、分析、预测、推断、主观评价。这些跳过校验,按 source-validation.md 的来源分级标注。
|
||||
|
||||
## 校验流程
|
||||
|
||||
### Step 1: 提取可验证声明
|
||||
|
||||
从源文件中识别包含具体数字、日期、文号的事实性声明:
|
||||
|
||||
```
|
||||
源文件原文:
|
||||
"截至2024年底,中国企业年金基金累计规模达到3.2万亿元,覆盖企业12.8万家"
|
||||
|
||||
提取出 2 个可验证声明:
|
||||
- 声明 A:企业年金累计规模 3.2 万亿(截至 2024 年底)
|
||||
- 声明 B:覆盖企业 12.8 万家(截至 2024 年底)
|
||||
```
|
||||
|
||||
### Step 2: 尝试验证
|
||||
|
||||
按优先级使用可用工具:
|
||||
|
||||
```
|
||||
1. 专业数据 MCP(如金融、医疗、法律等领域数据接口):有接口 → 直接查
|
||||
2. WebSearch:搜索官方发布的同期数据
|
||||
3. Wiki 内交叉:看 wiki 中其他页面是否有相关数据
|
||||
4. 无可用工具:跳过,标注 unverified
|
||||
```
|
||||
|
||||
### Step 3: 判定结果
|
||||
|
||||
| 结果 | 处理 | frontmatter 标注 |
|
||||
|------|------|-----------------|
|
||||
| **verified** — 工具返回数据一致(误差 < 5%) | 正常 ingest | `verified: true` |
|
||||
| **disputed** — 工具返回数据不一致 | **暂停**,展示差异,让用户决定 | 用户确认后标注 `verified: user-confirmed` |
|
||||
| **unverifiable** — 没有工具或数据源不覆盖 | 正常 ingest | `verified: false` |
|
||||
| **partial** — 部分声明验证通过,部分未验证 | 逐条标注 | 混合标注 |
|
||||
|
||||
### disputed 时的用户交互
|
||||
|
||||
以下示例以金融领域为例,实际执行时替换为用户的目标领域和工具:
|
||||
|
||||
```
|
||||
⚠️ 数据校验发现差异:
|
||||
|
||||
声明:"XX 基金累计规模达到 3.2 万亿元(2024 年底)"
|
||||
数据工具查询结果:3.58 万亿元(2024 年底)
|
||||
差异:-10.6%
|
||||
|
||||
可能原因:
|
||||
- 源文件数据有误
|
||||
- 统计口径不同
|
||||
- 数据工具更新延迟
|
||||
|
||||
请选择:
|
||||
1. 使用工具验证数据(3.58 万亿)→ 替换源文件数据后 ingest
|
||||
2. 使用源文件数据(3.2 万亿)→ 标注 user-confirmed 后 ingest
|
||||
3. 两者都记录 → 以 contested 方式 ingest
|
||||
4. 放弃本条 → 跳过此声明
|
||||
```
|
||||
|
||||
## 页面中的校验标注
|
||||
|
||||
在实体/概念页面中标注验证状态:
|
||||
|
||||
```markdown
|
||||
## 基金规模
|
||||
|
||||
截至 2024 年底,XX 基金累计规模达 3.58 万亿元。
|
||||
✅ verified(数据工具 2025-01-15 查询)
|
||||
(来源:[[2026-04-06-source-doc]],原文数据 3.2 万亿经工具校正)
|
||||
|
||||
## 历史
|
||||
|
||||
- ~~累计规模 3.2 万亿元~~(来源:[[2026-04-06-source-doc]] 原文,经工具校正为 3.58 万亿)
|
||||
```
|
||||
|
||||
## 工具可用性
|
||||
|
||||
| 场景 | 有工具时 | 无工具时 |
|
||||
|------|---------|---------|
|
||||
| 专业领域数据 | 对应 MCP 自动验证 | 标注 unverified,建议用户手动确认 |
|
||||
| 公开信息 | WebSearch 交叉验证 | 标注 unverified |
|
||||
| 内部数据(口述/会议) | 无法验证 | 标注 source_type: 口述,confidence: medium |
|
||||
|
||||
**Skill 在被动模式(无搜索工具)下**:所有数据标注为 `verified: false`,依赖 source-validation.md 的来源分级作为可信度参考。不会阻断 ingest,但会在每个数字旁标注"未验证"。
|
||||
|
||||
## 与 ingest-protocol 的关系
|
||||
|
||||
校验插入在 ingest-protocol.md 的 Step 1 和 Step 2 之间:
|
||||
|
||||
```
|
||||
Step 1: 读取源文件,提取关键信息
|
||||
Step 1.5: 【数据校验】提取可验证声明 → 工具交叉验证 → 判定结果
|
||||
Step 2: 搜索已有 wiki
|
||||
Step 3: 逐页比较新旧
|
||||
...
|
||||
```
|
||||
|
||||
如果 Step 1.5 发现 disputed 数据并暂停,用户确认后才继续 Step 2。
|
||||
273
.claude/skills/auto-wiki-cn/references/ingest-protocol.md
Normal file
273
.claude/skills/auto-wiki-cn/references/ingest-protocol.md
Normal file
@ -0,0 +1,273 @@
|
||||
# Ingest 协议
|
||||
|
||||
> Ingest 不是追加文件,是编译——读旧、比新、改旧。
|
||||
|
||||
## 流程
|
||||
|
||||
```
|
||||
1. 读取源文件
|
||||
├─ 提取关键信息:实体、概念、数据、结论、时间
|
||||
└─ 生成 source 摘要页(sources/{date}-{slug}.md)
|
||||
|
||||
2. 搜索已有 wiki
|
||||
├─ 读 index.md 获取全部页面列表
|
||||
├─ 识别与新信息相关的已有页面
|
||||
└─ 读取这些页面的当前内容
|
||||
|
||||
3. 逐页比较新旧(核心步骤)——三种结果,必须选一个
|
||||
│
|
||||
├─ A) 强化:新信息与已有结论一致,提供额外佐证
|
||||
│ → 在页面 sources 列表中加入新 source slug
|
||||
│ → 如果 confidence 是 medium/low → 升为 high
|
||||
│ → 正文不改或仅补充细节
|
||||
│ → log: "reinforced: {page}"
|
||||
│
|
||||
├─ B) 更新:新信息明确推翻或修正已有结论(有更新数据/更权威来源)
|
||||
│ → store.upsert_data() 写入新值(旧值自动进 history 表)
|
||||
│ → 改写 Markdown 正文为新结论
|
||||
│ → 更新 frontmatter 的 updated 日期和 sources 列表
|
||||
│ → log: "updated: {page}, reason: {简述}"
|
||||
│
|
||||
└─ C) 冲突:新旧信息矛盾,但无法判断谁对(数据口径不同/来源同级)
|
||||
→ 不改写,在页面中并列两种说法,标注各自来源
|
||||
→ confidence → contested
|
||||
→ log: "conflict: {page}, {说法A} vs {说法B}"
|
||||
|
||||
判定规则:新信息有更新日期或更权威来源 → B(更新);
|
||||
两者同级、无法分优劣 → C(冲突);其余 → A(强化)。
|
||||
|
||||
4. 结构化数据写入 data.db
|
||||
├─ 数值数据 → store.upsert_data()(自动处理 history)
|
||||
├─ 关系 → store.add_relation()
|
||||
└─ 页面元数据 → store.upsert_page()
|
||||
|
||||
5. 创建/更新 Markdown 页面
|
||||
├─ 新实体/概念 → 按 wiki-format.md 创建页面(frontmatter 只存元数据)
|
||||
├─ 正文写叙事分析,引述数据结论但不重复具体数值
|
||||
├─ 添加 wikilinks 到已有相关页面
|
||||
└─ 在已有相关页面中也加上指向新页面的 wikilink
|
||||
|
||||
6. 更新 index.md
|
||||
├─ 新页面加入对应分组
|
||||
├─ 更新页面计数和 Last updated 日期
|
||||
└─ 不修改已有条目的描述(除非页面标题变了)
|
||||
|
||||
7. 追加 log.md
|
||||
├─ 记录本次 ingest 的所有操作
|
||||
└─ 格式见 wiki-format.md
|
||||
```
|
||||
|
||||
## 关键原则
|
||||
|
||||
**1. 改旧优先于建新**
|
||||
|
||||
搜索 wiki 后发现已有 `entities/alpha-corp.md`,新研报也提到该机构 → 更新已有页面,不要新建 `entities/alpha-corp-2.md`。
|
||||
|
||||
**2. 不删除,只归档**
|
||||
|
||||
过时的结论不删除,移入 "## 历史" 段落。这样 wiki 保留了知识的演化痕迹。
|
||||
|
||||
```markdown
|
||||
## 管理规模
|
||||
|
||||
截至 2025 年底,某机构管理规模达 XXX 亿元。(来源:[[2026-04-06-policy-doc]])
|
||||
|
||||
## 历史
|
||||
|
||||
- ~~管理规模约 XXX 亿元~~(来源:[[2024-12-annual-report]],已被更新数据替代)
|
||||
```
|
||||
|
||||
**3. 冲突不调和**
|
||||
|
||||
两个来源说法矛盾时,不要编一个折中解释。并列呈现,标注来源,让用户或后续证据判断。
|
||||
|
||||
```markdown
|
||||
## 市场份额
|
||||
|
||||
> ⚠️ contested — 两个来源数据不一致
|
||||
|
||||
- 据 [[2026-04-06-policy-doc]]:某机构市场份额约 15%
|
||||
- 据 [[2025-annual-industry-report]]:某机构市场份额约 12%
|
||||
|
||||
差异可能来自统计口径不同(含/不含职业年金)。
|
||||
```
|
||||
|
||||
**4. 一次 ingest 触及多个页面是正常的**
|
||||
|
||||
一篇研报可能涉及 5-10 个实体和概念。一次 ingest 更新 8 个页面是正常的。在 log 中完整记录。
|
||||
|
||||
**5. Source 页面是不可变的**
|
||||
|
||||
`sources/` 目录下的摘要页在创建后不再修改(除非发现摘要有错误)。它是原始材料的忠实记录。其他页面通过 `sources` frontmatter 字段引用它。
|
||||
|
||||
---
|
||||
|
||||
## Worked Example: 完整 Ingest 流程
|
||||
|
||||
> 以企业年金领域为例。实际执行时替换为用户的目标领域。
|
||||
|
||||
**场景**:用户 ingest 一篇政策文件到某领域 wiki。wiki 中已有相关实体页面。
|
||||
|
||||
### Step 1 — 读取源文件,生成 source 摘要页
|
||||
|
||||
新建 `sources/2026-04-06-hrss-policy.md`:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 人社部2025年度企业年金基金统计报告
|
||||
type: source
|
||||
created: 2026-04-06
|
||||
updated: 2026-04-06
|
||||
sources: []
|
||||
confidence: high
|
||||
source_type: 一手
|
||||
source_origin: 人社部官网
|
||||
source_date: 2025-12-31
|
||||
---
|
||||
```
|
||||
|
||||
正文写原文关键信息的忠实摘要。
|
||||
|
||||
### Step 2 — 搜索已有 wiki
|
||||
|
||||
读 index.md,发现 `entities/alpha-corp.md` 与新文件相关。查询 data.db 中该页面的当前数据:
|
||||
|
||||
```python
|
||||
store.query_data(page_slug="alpha-corp")
|
||||
# → [{ field: "管理规模", value: 800, unit: "亿元", period: "2024-12", source_slug: "2024-12-annual-report" }]
|
||||
```
|
||||
|
||||
### Step 3 — 比较新旧,判定结果
|
||||
|
||||
新文件说:"截至 2025 年底,某机构管理规模达 1200 亿元。"
|
||||
|
||||
判定:新数据时点更新(2025 vs 2024)→ 选 **B) 更新**。
|
||||
|
||||
Agent 执行两步操作:
|
||||
|
||||
**a) 结构化数据写入 data.db**(自动记录 history):
|
||||
|
||||
```python
|
||||
old = store.upsert_data("alpha-corp", "管理规模", 1200, "亿元", "2025-12", "2026-04-06-policy-doc")
|
||||
# old = { value: 800, unit: "亿元", source_slug: "2024-12-annual-report" }
|
||||
# → history 表自动写入旧值
|
||||
store.add_relation("alpha-corp", "受托人市场格局", "part_of")
|
||||
```
|
||||
|
||||
**b) 更新 Markdown 页面**(frontmatter 只留元数据):
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Alpha Corp 养老金业务
|
||||
type: entity
|
||||
created: 2026-04-01
|
||||
updated: 2026-04-06
|
||||
sources: [2024-12-annual-report, 2026-04-06-policy-doc]
|
||||
confidence: high
|
||||
relations:
|
||||
- target: 受托人市场格局
|
||||
type: part_of
|
||||
---
|
||||
```
|
||||
|
||||
正文更新分析内容(引述数据结论,不写具体数值——数值在 data.db 中)。
|
||||
|
||||
### Step 4-7 — 新建页面、写 DB、更新 index、追加 log
|
||||
|
||||
新文件还提到"可携带企业年金"概念(wiki 中不存在)→ 新建 `concepts/portable-annuity.md`:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 可携带企业年金
|
||||
type: concept
|
||||
created: 2026-04-06
|
||||
updated: 2026-04-06
|
||||
sources: [2026-04-06-hrss-policy]
|
||||
confidence: medium # 仅单一来源首次提及
|
||||
relations:
|
||||
- target: 企业年金制度
|
||||
type: part_of
|
||||
---
|
||||
```
|
||||
|
||||
log.md 追加:
|
||||
```
|
||||
## 2026-04-06 14:30 — ingest
|
||||
- Source: 2026-04-06-policy-doc
|
||||
- Updated: entities/alpha-corp (data.管理规模 800→1200亿)
|
||||
- Created: concepts/portable-annuity
|
||||
- Conflicts: none
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## From-Lint 流程(deep-dive 管道的 ingest 阶段)
|
||||
|
||||
当 ingest 由 deep-dive 管道触发时,输入不是用户提供的源文件,而是 lint Coverage 输出的缺口报告(Gap Report)。
|
||||
|
||||
### 与标准 ingest 的区别
|
||||
|
||||
| 方面 | 标准 ingest | from-lint ingest |
|
||||
|------|------------|------------------|
|
||||
| 输入 | 用户提供的源文件 | Gap Report 中的缺口条目 |
|
||||
| 来源获取 | 用户已提供 | Agent 通过搜索工具获取 |
|
||||
| 批量操作 | 通常 1 个源文件 | 可能 N 个缺口,逐个处理 |
|
||||
| 用户确认 | 不需要(用户已主动提供) | 需要(搜索前确认范围,搜索后确认质量) |
|
||||
|
||||
### 流程
|
||||
|
||||
```
|
||||
输入:Gap Report(来自 lint Coverage)
|
||||
|
||||
For each confirmed gap:
|
||||
|
||||
1. 制定搜索计划
|
||||
├─ page_missing → 搜索该实体/概念的基本信息
|
||||
├─ concept_missing → 搜索该术语的定义和解释
|
||||
├─ data_missing → 搜索该指标的最新数据
|
||||
├─ single_source → 搜索额外来源以交叉验证
|
||||
└─ outdated → 搜索该指标/实体的最新信息
|
||||
|
||||
2. 执行搜索(需要搜索工具——主动模式)
|
||||
├─ 使用 WebSearch / 搜索类 MCP 获取候选来源
|
||||
├─ 搜索工具优先级:领域专业工具 > 通用搜索
|
||||
├─ 按 source-validation.md 分级筛选
|
||||
└─ 排除黑名单渠道,取 top 1-3 个可信来源
|
||||
|
||||
3. 展示搜索结果,请用户确认
|
||||
├─ 展示每个来源的标题、URL、可信度分级
|
||||
├─ 用户选择:接受 / 跳过 / 替换
|
||||
└─ 搜索结果质量不够 → 标注"未能补全",跳过
|
||||
|
||||
4. 对确认的每个来源,执行标准 ingest 流程
|
||||
├─ Step 1-7 与普通 ingest 完全一致
|
||||
└─ source 摘要页额外记录 deep-dive 元数据(见 source-validation.md)
|
||||
```
|
||||
|
||||
### 防扩散机制
|
||||
|
||||
- 每个 gap 最多搜索 3 次(换关键词)。3 次搜不到可信来源 → 标注"未能补全"
|
||||
- 单次 deep-dive 最多处理 10 个 gap(可通过 `--max-gaps` 调整)
|
||||
- 搜索到的来源如果引入了 wiki 中完全不存在的新实体,**不自动创建页面**——只填补已知缺口,不主动扩展 wiki 范围
|
||||
- 所有搜索来源的 confidence 上限为 medium(除非来源是一手/权威二手)
|
||||
|
||||
### 补全报告
|
||||
|
||||
```
|
||||
## Deep-Dive 补全报告:{topic}
|
||||
执行时间:{date}
|
||||
|
||||
### 已补全:{N} / {total} 个缺口
|
||||
| # | Gap | Action | Source | Confidence |
|
||||
|---|-----|--------|--------|------------|
|
||||
| 1 | page_missing: portable-annuity | 新建页面 | [二手·权威] 财新报道 | medium |
|
||||
| 2 | single_source: alpha-corp | 增加 1 个来源 | [二手] 行业研报 | medium |
|
||||
|
||||
### 未能补全:{M} 个缺口
|
||||
| # | Gap | Reason |
|
||||
|---|-----|--------|
|
||||
| 3 | data_missing: beta-corp/市场份额 | 搜索 3 次未找到可信来源 |
|
||||
|
||||
### 建议
|
||||
- beta-corp 市场份额数据建议用户手动提供行业报告
|
||||
```
|
||||
230
.claude/skills/auto-wiki-cn/references/lint-protocol.md
Normal file
230
.claude/skills/auto-wiki-cn/references/lint-protocol.md
Normal file
@ -0,0 +1,230 @@
|
||||
# Lint 协议
|
||||
|
||||
> Lint 不只是检查问题,更重要的是**治理**——合并、归档、修复。防止 wiki 变成信息坟场。
|
||||
|
||||
## 流程
|
||||
|
||||
```
|
||||
1. 扫描全部页面
|
||||
├─ 读 meta.yaml 获取 wiki 基本信息
|
||||
├─ 读 index.md 获取页面列表
|
||||
├─ 遍历 sources/ entities/ concepts/ analyses/ 目录
|
||||
└─ 对比 index 与实际文件(发现 index 遗漏或多余的条目)
|
||||
|
||||
2. 逐项检查(7 项)
|
||||
├─ 2.1 Validation — 页面格式是否合规
|
||||
├─ 2.2 Contradiction — 不同页面之间是否有矛盾
|
||||
├─ 2.3 Duplication — 是否有重复页面
|
||||
├─ 2.4 Orphan — 是否有孤立页面
|
||||
├─ 2.5 Broken Link — 是否有断链
|
||||
├─ 2.6 Staleness — 是否有过时内容
|
||||
└─ 2.7 Coverage — 知识覆盖是否有明显缺口
|
||||
|
||||
3. 执行修复(自动 + 需确认)
|
||||
├─ 自动修复:index 同步、断链修复、格式补全
|
||||
└─ 需确认:合并重复、归档过时、矛盾标注
|
||||
|
||||
4. 输出健康报告
|
||||
5. 更新 meta.yaml 统计
|
||||
6. 追加 log.md
|
||||
```
|
||||
|
||||
## 两档执行
|
||||
|
||||
Lint 分两档。结构化检查可以自动跑完全量页面,语义检查需要 Agent 逐页理解内容,代价随页面数线性增长。
|
||||
|
||||
| 档位 | 包含检查项 | 执行方式 | 代价 |
|
||||
|------|-----------|---------|------|
|
||||
| **结构档**(必跑) | Validation, Orphan, Broken Link, Staleness | 全量扫描,确定性输出 | O(N) 文件读取 |
|
||||
| **语义档**(按需) | Contradiction, Duplication, Coverage | Agent 抽样或用户指定范围 | O(N²) 语义比较 |
|
||||
|
||||
**默认行为**:`lint` 只跑结构档。用户说"深度 lint"或"检查矛盾"时跑语义档。
|
||||
|
||||
**语义档的范围控制**:
|
||||
- wiki < 50 页:全量扫描
|
||||
- wiki 50-200 页:只扫最近 30 天内 ingest 触及的页面 + 其关联页面
|
||||
- wiki > 200 页:用户必须指定范围(如"检查 entities/ 下的矛盾")
|
||||
|
||||
---
|
||||
|
||||
## 7 项检查细则
|
||||
|
||||
### 2.1 Validation(格式校验)— 结构档
|
||||
|
||||
按 wiki-format.md 的 Validation Rules 检查每个页面:
|
||||
|
||||
| 规则 | 自动修复? |
|
||||
|------|-----------|
|
||||
| frontmatter 缺字段 | 是 — 补充默认值(type=entity, confidence=medium) |
|
||||
| type 值非法 | 否 — 报告,等用户确认 |
|
||||
| sources 为空(非 source 类型) | 否 — 报告,建议关联 |
|
||||
| slug 与文件名不一致 | 否 — 报告 |
|
||||
| 日期格式错误 | 是 — 尝试自动修正 |
|
||||
|
||||
### 2.2 Contradiction(矛盾检测)— 语义档
|
||||
|
||||
扫描实体页和概念页,检查:
|
||||
- 同一事实在不同页面有不同数值/结论
|
||||
- 同一实体页内有 `contested` 标注——检查是否有新 source 可以解决
|
||||
|
||||
```
|
||||
发现矛盾:
|
||||
- alpha-corp.md 说管理规模 1200 亿(来源:2026-policy-doc)
|
||||
- industry-overview.md 说某机构市场份额对应约 1000 亿(来源:industry-report-2025)
|
||||
→ 标注两个页面 confidence → contested
|
||||
```
|
||||
|
||||
### 2.3 Duplication(重复检测)— 语义档
|
||||
|
||||
检查是否有两个页面描述同一个实体/概念:
|
||||
- 文件名相似(如 `alpha-corp.md` 和 `alpha-annuity.md`)
|
||||
- 标题相似
|
||||
- 正文内容重叠度高
|
||||
|
||||
**处理**:合并为一个页面,保留更完整的版本,将另一个的独有信息合并进来。需用户确认。
|
||||
|
||||
### 2.4 Orphan(孤页检测)— 结构档
|
||||
|
||||
页面没有任何入链(没有别的页面通过 `[[slug]]` 引用它)。
|
||||
|
||||
**处理**:
|
||||
1. 检查是否有应该引用它的页面 → 是 → 添加 wikilink
|
||||
2. 仍然无关联 → 建议归档或删除
|
||||
|
||||
### 2.5 Broken Link(断链检测)— 结构档
|
||||
|
||||
页面中有 `[[slug]]` 但对应文件不存在。
|
||||
|
||||
**处理**:
|
||||
1. 如果能推断应该是哪个页面(slug 拼写接近)→ 修正 wikilink
|
||||
2. 否则 → 创建 stub 页面(只有 frontmatter + "待补充"),或移除断链
|
||||
|
||||
### 2.6 Staleness(过时检测)— 结构档
|
||||
|
||||
| 条件 | 判定 |
|
||||
|------|------|
|
||||
| 页面 `updated` 距今 > 6 个月,且 confidence ≤ medium | 过时候选 |
|
||||
| 页面的所有 sources 都 > 12 个月 | 过时候选 |
|
||||
| 页面 confidence 为 low 且从未被 ingest 强化过 | 过时候选 |
|
||||
|
||||
**处理**:标注"待验证"或建议归档。不自动删除。
|
||||
|
||||
### 2.7 Coverage(覆盖度评估)— 语义档
|
||||
|
||||
不是找错误,而是找缺口。分 5 类检测:
|
||||
|
||||
#### Gap-1: Page Missing(页面缺失)
|
||||
|
||||
被其他页面通过 `[[slug]]` 引用但实际文件不存在。与 Broken Link 不同——Broken Link 是链接拼写错误,Page Missing 是知识本身缺失。
|
||||
|
||||
- 检测:遍历所有 wikilink,找到指向不存在页面的引用
|
||||
- 判定:3+ 个页面引用同一个不存在的 slug → 不是拼写错误,是知识缺口
|
||||
- 输出:`{ gap_type: "page_missing", slug: "xxx", referenced_by: [...] }`
|
||||
|
||||
#### Gap-2: Concept Missing(概念缺失)
|
||||
|
||||
多个实体页面反复提到同一术语/概念,但没有独立的概念页解释它。
|
||||
|
||||
- 检测:在 entities/ 正文中提取高频术语,检查 concepts/ 中是否有对应页面
|
||||
- 阈值:某术语在 3+ 个不同页面出现但无独立概念页 → 缺口
|
||||
- 输出:`{ gap_type: "concept_missing", term: "xxx", mentioned_in: [...] }`
|
||||
|
||||
#### Gap-3: Data Missing(数据缺失)
|
||||
|
||||
实体页面提到某个指标但 data.db 中没有对应数值,或数值缺少关键维度(没有 period、没有来源)。
|
||||
|
||||
- 检测:扫描页面正文中的指标名称,交叉检查 data.db
|
||||
- 输出:`{ gap_type: "data_missing", page: "xxx", field: "xxx" }`
|
||||
|
||||
#### Gap-4: Single Source(来源单一)
|
||||
|
||||
页面 confidence 依赖唯一来源,且该来源不是一手/权威来源。
|
||||
|
||||
- 检测:sources 列表长度 = 1 且 source_type 不是"一手"或"二手·权威"
|
||||
- 输出:`{ gap_type: "single_source", page: "xxx", current_source: "xxx" }`
|
||||
|
||||
#### Gap-5: Outdated(过时待更新)
|
||||
|
||||
与 Staleness 不同——Staleness 标注过时候选,Outdated 侧重数据已有更新时点但 wiki 未跟上。
|
||||
|
||||
- 检测:data.db 中数据的 period 距今 > 12 个月,且该领域通常有年度更新
|
||||
- 输出:`{ gap_type: "outdated", page: "xxx", field: "xxx", last_period: "xxx" }`
|
||||
|
||||
#### Gap-6: Validator Gap(校验器缺口)— 仅当 wiki 声明了 validator 时
|
||||
|
||||
如果 meta.yaml 声明了 `seed` 且对应种子文件指向了 `validator`,Coverage 额外运行校验器检查:
|
||||
|
||||
- 检测:调用校验器(如 FIBO SPARQL)查询实体类型的必要关系(`someValuesFrom` 约束),对比 wiki 中已建立的关系
|
||||
- 示例:FIBO 说 PensionFund 必须有 Trustee 关系,wiki 中该实体页缺了这条关系 → 缺口
|
||||
- 输出:`{ gap_type: "validator_gap", page: "xxx", missing_relation: "hasTrustee", standard: "FIBO" }`
|
||||
- 降级:校验器不可达时静默跳过,在报告中注明"外部校验器不可达,已跳过"
|
||||
|
||||
这一类缺口检测的不是"信息缺失",而是"逻辑不完整"——你说这是一个 PensionFund,但按行业标准定义,它至少还需要管理人、托管人、监管方。
|
||||
|
||||
#### 覆盖度启发式(补充)
|
||||
|
||||
以上 6 类之外,保留原有启发式检查:
|
||||
- 某个实体被 5 个其他页面引用,但自身内容很薄(< 100 字)→ 建议深化
|
||||
- 某个类型(如 concepts/)页面很少,但 entities/ 页面很多 → 建议提炼概念
|
||||
- source 页面多但 analysis 页面少 → 建议做综合分析
|
||||
|
||||
## 健康报告格式
|
||||
|
||||
```
|
||||
## Wiki 健康报告:{主题名}
|
||||
生成时间:{日期}
|
||||
|
||||
### 概况
|
||||
- 页面总数:42(sources: 12, entities: 15, concepts: 10, analyses: 5)
|
||||
- 健康度:良好 / 需要关注 / 需要干预
|
||||
- confidence 分布:high 30 / medium 8 / low 2 / contested 2
|
||||
|
||||
### 本次修复
|
||||
- [自动] index.md 同步(新增 2 个遗漏条目)
|
||||
- [自动] 修复 1 个断链(portable-annuity → portable-annuity-scheme)
|
||||
- [需确认] 合并 alpha-corp.md 和 alpha-annuity.md(疑似重复)
|
||||
|
||||
### 待处理问题
|
||||
- 矛盾:2 处(列出具体页面和矛盾点)
|
||||
- 过时:1 页建议归档(portfolio-category,6 个月未更新)
|
||||
|
||||
### 覆盖度建议
|
||||
- entities/beta-corp.md 内容较薄(仅 50 字),被 4 个页面引用,建议 ingest 更多材料
|
||||
- concepts/ 只有 10 页,而 entities/ 有 15 页,建议提炼更多概念页
|
||||
|
||||
### 统计
|
||||
- 最活跃 source:hrss-2026-policy(被 8 个页面引用)
|
||||
- 最孤立 entity:portfolio-category(0 个入链)
|
||||
- 最近 ingest:2026-04-06(3 天前)
|
||||
```
|
||||
|
||||
## 缺口报告格式(供 deep-dive 使用)
|
||||
|
||||
当 Coverage 检查由 deep-dive 触发时,除健康报告外还输出结构化缺口报告:
|
||||
|
||||
```
|
||||
## Gap Report: {topic}
|
||||
Generated: {date}
|
||||
Scope: {全量 | 指定范围}
|
||||
|
||||
### Gaps Found: {N}
|
||||
|
||||
| # | Category | Target | Detail | Priority | Search Direction |
|
||||
|---|----------|--------|--------|----------|------------------|
|
||||
| 1 | page_missing | portable-annuity | 被 4 个页面引用 | high | 搜索"可携带企业年金 政策" |
|
||||
| 2 | concept_missing | 受托人资格 | 在 5 个实体页中提到 | high | 搜索"企业年金受托人资格 要求" |
|
||||
| 3 | single_source | alpha-corp | 仅有来源: industry-report-2025 | medium | 搜索"Alpha Corp 养老金 年报" |
|
||||
|
||||
### Priority Rules
|
||||
- high: page_missing(3+ 引用)、concept_missing(5+ 提及)
|
||||
- medium: single_source、data_missing
|
||||
- low: outdated(数据年龄 12-24 个月)
|
||||
```
|
||||
|
||||
### 范围控制(deep-dive 场景)
|
||||
|
||||
deep-dive 触发时,Coverage 接受可选范围参数:
|
||||
- `deep-dive {topic}` → 限定在指定 wiki
|
||||
- `deep-dive {topic} entities/` → 限定在子目录
|
||||
- `deep-dive {topic} --max-gaps N` → 限制最大缺口数(默认 10)
|
||||
- 无范围指定时,按语义档的范围控制规则执行(<50 页全量,50-200 页近 30 天,>200 页要求指定)
|
||||
@ -0,0 +1,150 @@
|
||||
# 认知本体:人物研究的采集与合成策略
|
||||
|
||||
> 当研究对象是一个**人**(其思维方式、决策模式、表达风格),使用本策略。
|
||||
> 改编自认知画像方法论,仅覆盖 wiki 积累阶段,不涉及最终人格结晶。
|
||||
|
||||
---
|
||||
|
||||
## 一、6 维采集框架
|
||||
|
||||
每个维度对应一次 ingest,产出一个 source 页面。
|
||||
|
||||
| 维度 | 搜索什么 | 提取什么 | source 页面产出 |
|
||||
|------|---------|---------|----------------|
|
||||
| **著作** | 书、长文、论文、newsletter | 反复出现的核心论点(>=3 次=真信念);自创术语;推荐书单(智识谱系) | `sources/{date}-writings.md` |
|
||||
| **对话** | 播客、长视频、AMA、深度访谈 | 被追问时的回答方式;即兴类比;改变立场的瞬间;拒绝回答的问题 | `sources/{date}-conversations.md` |
|
||||
| **表达 DNA** | Twitter/X、微博、即刻、短文 | 高频用词句式;争议立场;幽默方式;公开辩论 | `sources/{date}-expression-dna.md` |
|
||||
| **他者视角** | 他人分析、书评、批评、传记 | 外部观察到的模式;批评与争议;与同行对比 | `sources/{date}-external-views.md` |
|
||||
| **决策记录** | 重大决策、转折点、争议行为 | 决策背景与逻辑;事后反思;言行一致/不一致案例 | `sources/{date}-decisions.md` |
|
||||
| **时间线** | 完整履历 + 最近 12 个月动态 | 关键里程碑;思想转折点;最新状态(防过时) | `sources/{date}-timeline.md` |
|
||||
|
||||
**信息分级**:每条提取必须标注来源类型——一手(本人原文)> 二手(他人转述)> 推测。
|
||||
|
||||
**信息源优先级**:本人著作/长访谈/实际决策 > 社交媒体/他人评价 > 二手转述。
|
||||
信息源黑名单:知乎、微信公众号、百度百科。中文人物优先用 B 站原始视频、小宇宙播客、权威媒体(36 氪/晚点/财新)。
|
||||
|
||||
---
|
||||
|
||||
## 二、合成规则
|
||||
|
||||
6 维 source 页面齐备后,执行合成,产出 entity 和 concept 页面。
|
||||
|
||||
### 2.1 三重验证(心智模型 vs 决策启发式)
|
||||
|
||||
从全部 source 中列出候选论点(通常 15-30 个),逐一验证:
|
||||
|
||||
| 验证维度 | 判定方法 | 通过标志 |
|
||||
|---------|---------|---------|
|
||||
| **跨域复现** | 同一思维框架出现在 >=2 个不同领域/话题中 | 著作 + 决策都印证 |
|
||||
| **生成力** | 用此模型能推断此人对未表态问题的立场 | 能产生合理预测 |
|
||||
| **排他性** | 不是所有聪明人都这样想,体现此人独特视角 | 有区分度 |
|
||||
|
||||
- 三重通过 → 心智模型(写入 entity 页面)
|
||||
- 仅 1-2 重 → 降级为决策启发式(写入 concept 页面)
|
||||
- 0 重 → 不纳入 wiki
|
||||
|
||||
### 2.2 表达 DNA 量化
|
||||
|
||||
从此人长文/演讲中抽取 20 段,统计:
|
||||
|
||||
- **句式指纹**:平均句长、疑问句比例、类比密度(/千字)、第一人称频率、确定性语气比例
|
||||
- **风格标签**(7 轴打分):正式-口语、抽象-具体、谨慎-断言、学术-通俗、长句-短句、铺垫型-结论先行、数据驱动-叙事驱动
|
||||
- **禁忌词与口癖**:从不用的词 + 高频表达
|
||||
|
||||
产出一个 concept 页面:`concepts/expression-dna.md`。
|
||||
|
||||
### 2.3 矛盾处理
|
||||
|
||||
矛盾是人格特征,不是需要修复的 bug。分三类处理:
|
||||
|
||||
| 矛盾类型 | 含义 | wiki 中的处理 |
|
||||
|---------|------|--------------|
|
||||
| **时间性矛盾** | 早期说 A,后来说 B(观点演化) | 在页面中记录演化轨迹,标注时期;confidence 保持 `high` |
|
||||
| **领域性矛盾** | 工作中主张 X,生活中主张 Y | 分领域记录,不强求统一;这是深度的来源 |
|
||||
| **本质性张力** | 价值观内在冲突(如既追求自由又重视纪律) | 创建独立 concept 页面 `concepts/tension-{name}.md`,标注为核心张力 |
|
||||
|
||||
绝不做:选一边忽略另一边、编调和解释、假装矛盾不存在。
|
||||
|
||||
---
|
||||
|
||||
## 三、Wiki 页面类型
|
||||
|
||||
### Entity 页面
|
||||
|
||||
**每个心智模型一个页面**,不是每个人一个页面。
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: 逆向思维
|
||||
type: mental-model
|
||||
created: 2026-04-06
|
||||
updated: 2026-04-06
|
||||
sources: [2026-04-06-writings, 2026-04-06-decisions]
|
||||
confidence: high
|
||||
verification:
|
||||
cross_domain: true
|
||||
generative: true
|
||||
exclusive: false
|
||||
domains: [投资决策, 风险管理, 产品设计]
|
||||
relations:
|
||||
- target: 误判心理学
|
||||
type: derived_from
|
||||
---
|
||||
|
||||
## 模型描述
|
||||
面对"如何成功",先想"如何确保失败"。
|
||||
|
||||
## 来源证据
|
||||
- 著作:《穷查理宝典》中 X 处提及([[2026-04-06-writings]])
|
||||
- 决策:在 Y 事件中实际应用([[2026-04-06-decisions]])
|
||||
|
||||
## 应用方式
|
||||
给定任意目标 G,先列出导致 ~G 的所有路径,逐一规避。
|
||||
|
||||
## 局限性
|
||||
倾向于保守,可能错过需要正面进攻的机会。
|
||||
```
|
||||
|
||||
人物本身也有一个总览 entity 页面(`entities/{person-slug}.md`),链接到所有心智模型和概念页。
|
||||
|
||||
### Concept 页面
|
||||
|
||||
用于以下内容:
|
||||
|
||||
| 内容 | 页面示例 |
|
||||
|------|---------|
|
||||
| 决策启发式(未通过三重验证的规则) | `concepts/heuristic-{name}.md` |
|
||||
| 价值观与反模式 | `concepts/values.md`、`concepts/anti-patterns.md` |
|
||||
| 表达 DNA | `concepts/expression-dna.md` |
|
||||
| 核心张力 | `concepts/tension-{name}.md` |
|
||||
| 智识谱系(影响/被影响关系) | `concepts/intellectual-lineage.md` |
|
||||
| 诚实边界(此框架做不到什么) | `concepts/honest-boundaries.md` |
|
||||
|
||||
### Source 页面
|
||||
|
||||
每个采集维度一个 source 页面(共 6 个),是原始素材的忠实摘要,创建后不再修改。
|
||||
|
||||
---
|
||||
|
||||
## 四、Ingest 顺序建议
|
||||
|
||||
不要求严格顺序,但推荐:
|
||||
|
||||
1. **著作 + 时间线**先行——建立基本框架和时间脉络
|
||||
2. **对话 + 决策**补充——捕捉即兴思维和真实行为
|
||||
3. **表达 DNA + 他者视角**收尾——量化风格、引入外部校准
|
||||
|
||||
每次 ingest 后检查:已有页面是否需要更新 confidence、是否出现新矛盾。
|
||||
|
||||
---
|
||||
|
||||
## 五、质量标准
|
||||
|
||||
| 检查项 | 通过标准 |
|
||||
|--------|---------|
|
||||
| 心智模型数量 | 3-7 个,每个有 >=2 个不同领域的证据 |
|
||||
| 每个模型的局限性 | 明确写出失效条件 |
|
||||
| 表达 DNA | 统计维度齐全(句式+风格+禁忌词) |
|
||||
| 一手来源占比 | > 50% |
|
||||
| 矛盾记录 | >=2 对张力,不回避不调和 |
|
||||
| 诚实边界 | >=3 条具体局限,标注信息不足的维度 |
|
||||
@ -0,0 +1,83 @@
|
||||
# 领域/行业研究(Domain)采集策略
|
||||
|
||||
> 适用于研究一个行业或垂直领域。典型产出:实体页、概念页、关系网络。
|
||||
> 本文档仅覆盖 **wiki 积累阶段**,不涉及本体结晶。
|
||||
|
||||
## 采集维度
|
||||
|
||||
### 1. 政策法规文件
|
||||
- **找**:部委规章、行业标准、监管通知、行政处罚案例
|
||||
- **提取**:生效日期、发文机关、核心规则、影响范围、废止/替代关系
|
||||
- **产出**:source 页 + concept 页(制度/规则)+ entity 页(监管机构)
|
||||
|
||||
### 2. 行业数据与统计
|
||||
- **找**:官方统计公报、行业协会年报、监管机构披露数据
|
||||
- **提取**:关键指标及口径、时间序列、同比变化、统计范围说明
|
||||
- **产出**:source 页 + concept 页(指标定义)+ entity 页(统计主体)
|
||||
|
||||
### 3. 研究报告与分析
|
||||
- **找**:券商研报、智库报告、学术论文、行业白皮书
|
||||
- **提取**:核心结论、论据和数据来源、市场预测、与其他来源的一致/矛盾
|
||||
- **产出**:source 页 + 更新已有实体/概念页
|
||||
|
||||
### 4. 案例与实例
|
||||
- **找**:典型企业案例、产品方案、事件复盘、司法判例
|
||||
- **提取**:参与方、时间线、关键决策及结果、可推广的模式
|
||||
- **产出**:source 页 + entity 页(涉及的机构/产品)
|
||||
|
||||
### 5. 市场参与者档案
|
||||
- **找**:机构官网、年报、业务资质、市场份额、组织架构
|
||||
- **提取**:机构名称/类型/角色、业务范围、管理规模、持牌资质、核心人物
|
||||
- **产出**:entity 页(机构)+ 如需区分机构与其角色,分别建页
|
||||
|
||||
### 6. 历史沿革与时间线
|
||||
- **找**:制度演变史、行业大事记、政策迭代脉络
|
||||
- **提取**:关键节点(含日期)、制度分期、变革驱动因素
|
||||
- **产出**:concept 页(制度/阶段)+ 更新 entity 页的"历史"段落
|
||||
|
||||
## 实体识别规则
|
||||
|
||||
**Entity**:可唯一标识的客观存在(机构、产品、基金、计划、自然人)。判据:有专名、多 source 反复出现、可被"查询"。
|
||||
|
||||
**Concept**:无法唯一实例化的制度、方法、指标、分类。判据:需定义才能理解、容易与相近概念混淆。
|
||||
|
||||
### 禁混规则(Anti-patterns)
|
||||
|
||||
以企业年金为例,类似变体存在于任何行业:
|
||||
|
||||
| 容易混淆 | 正确做法 | 判断依据 |
|
||||
|----------|---------|---------|
|
||||
| 机构 vs 机构角色 | 分开建页 | 同一机构可持多个角色(同一银行既是托管人又是账管人) |
|
||||
| 计划 vs 产品 | 分开建页 | 一个计划可选配多个产品 |
|
||||
| 指标 vs 观测值 | 分开建页 | "投资收益率"是指标,"2025年收益率5.2%"是观测值 |
|
||||
| 分类体系 vs 实例 | 分开建页 | "DB型/DC型"是分类,"XX公司年金计划"是实例 |
|
||||
|
||||
**新建 vs 更新**:先搜 index.md,已有页面就更新,不要新建同义页面。
|
||||
|
||||
## 关系标注
|
||||
|
||||
在正文中用自然语言 + wikilink 表达关系,不需要专门的关系表:
|
||||
|
||||
```markdown
|
||||
[[某银行]] 作为受托人管理 [[XX企业年金计划]],该计划投资于 [[某养老金产品-稳健型]]。
|
||||
```
|
||||
|
||||
常见动词短语:`受托管理`、`监管`、`隶属于`、`投资于`、`由…演变而来`。
|
||||
|
||||
**关系双写**:frontmatter 的 `relations` 字段(Obsidian 可见)+ data.db 的 `relations` 表(可查询)。正文中用 `[[wikilink]]` 自然引用。三者保持一致。
|
||||
|
||||
## 页面结构模板
|
||||
|
||||
### Entity 页
|
||||
|
||||
Sections:**概况**(一句话定位)→ **关键数据**(规模/份额/资质,注明口径和时点)→ **关系**(wikilink 到关联实体/概念)→ **历史**(时间线,过时数据移到此处)
|
||||
|
||||
### Concept 页
|
||||
|
||||
Sections:**定义**(一段话,注明来源)→ **适用范围**(哪些主体/场景)→ **易混辨析**(与 [[相近概念]] 的区别)
|
||||
|
||||
### Source 页
|
||||
|
||||
Sections:**基本信息**(发布机构/日期/文号)→ **核心内容**(3-5 条要点)→ **涉及实体与概念**(wikilink 列表,便于 ingest 定位更新目标)
|
||||
|
||||
所有页面的 frontmatter 格式遵循 wiki-format.md(title / type / created / updated / sources / confidence)。
|
||||
97
.claude/skills/auto-wiki-cn/references/query-protocol.md
Normal file
97
.claude/skills/auto-wiki-cn/references/query-protocol.md
Normal file
@ -0,0 +1,97 @@
|
||||
# Query 协议
|
||||
|
||||
> 基于 wiki 积累回答问题。不是 RAG——不搜原始文档,搜编译后的 wiki 页面。
|
||||
|
||||
## 流程
|
||||
|
||||
```
|
||||
1. 理解问题
|
||||
├─ 识别目标 wiki(从问题内容推断,或用户指定)
|
||||
├─ 提取关键词(实体名、概念名、时间范围)
|
||||
└─ 判断问题类型(事实查询 / 比较分析 / 趋势判断 / 开放探索)
|
||||
|
||||
2. 搜索 wiki
|
||||
├─ 读 index.md,按关键词匹配页面标题和描述
|
||||
├─ 读取匹配的页面(通常 3-8 个)
|
||||
├─ 如果页面中有 [[wikilink]],沿链接读取关联页面(一层展开)
|
||||
└─ 注意 confidence 字段:contested 页面需要特别标注
|
||||
|
||||
3. 综合回答
|
||||
├─ 基于读取的页面内容综合分析
|
||||
├─ 每个关键论点标注来源页面:[[page-slug]]
|
||||
├─ 如果涉及 contested 信息,明确说明:
|
||||
│ "关于 XX,wiki 中存在矛盾:来源 A 说...,来源 B 说..."
|
||||
└─ 如果信息不足,明确说明缺口并建议 ingest 方向
|
||||
|
||||
4. 可选归档
|
||||
├─ 如果回答中包含有价值的新分析(不是简单复述页面内容)
|
||||
├─ 提示用户:"这个分析要归档到 wiki 吗?"
|
||||
└─ 用户同意 → 写入 analyses/{slug}.md,更新 index
|
||||
```
|
||||
|
||||
## 回答格式
|
||||
|
||||
### 事实查询
|
||||
|
||||
```
|
||||
根据 wiki 中 {N} 篇源文件的积累:
|
||||
|
||||
{直接回答}
|
||||
|
||||
来源:[[page-1]]、[[page-2]]
|
||||
```
|
||||
|
||||
### 比较分析
|
||||
|
||||
```
|
||||
基于 wiki 中的记录,{A} 和 {B} 的对比:
|
||||
|
||||
| 维度 | {A} | {B} |
|
||||
|------|-----|-----|
|
||||
| ... | ... | ... |
|
||||
|
||||
来源:[[page-1]]、[[page-2]]、[[page-3]]
|
||||
|
||||
注:关于 XX 维度,wiki 中数据有限(仅 1 篇来源),建议补充。
|
||||
```
|
||||
|
||||
### 信息不足
|
||||
|
||||
```
|
||||
wiki 中关于 {主题} 的信息不足:
|
||||
- 相关页面:{N} 个
|
||||
- 源文件:{N} 篇
|
||||
- 缺口:{具体缺什么}
|
||||
|
||||
建议 ingest:
|
||||
- {建议补充的材料类型 1}
|
||||
- {建议补充的材料类型 2}
|
||||
```
|
||||
|
||||
## 跨 Wiki 查询
|
||||
|
||||
当问题涉及多个 wiki 时:
|
||||
|
||||
1. 列出 `.wiki/` 下所有 wiki 目录
|
||||
2. 读取每个可能相关的 wiki 的 index.md
|
||||
3. 分别搜索
|
||||
4. 综合回答时标注来源属于哪个 wiki:
|
||||
|
||||
```
|
||||
综合 enterprise-annuity wiki 和 charlie-munger wiki 的内容:
|
||||
|
||||
{分析}
|
||||
|
||||
来源:
|
||||
- enterprise-annuity: [[alpha-corp]]、[[fiduciary-responsibility]]
|
||||
- charlie-munger: [[circle-of-competence]]、[[margin-of-safety]]
|
||||
```
|
||||
|
||||
## 查询性能
|
||||
|
||||
| Wiki 规模 | 查询策略 | 预期延迟 |
|
||||
|-----------|---------|---------|
|
||||
| < 30 页 | 读 index + 直接读匹配页面 | 快(< 5s) |
|
||||
| 30-150 页 | 读 index + grep 关键词 + 读 top 匹配 | 中(5-15s) |
|
||||
| 150-500 页 | 读 index + grep + 分批读取 | 慢(15-30s) |
|
||||
| > 500 页 | 超出 Skill 设计范围 | 建议迁移到平台 |
|
||||
317
.claude/skills/auto-wiki-cn/references/scaling.md
Normal file
317
.claude/skills/auto-wiki-cn/references/scaling.md
Normal file
@ -0,0 +1,317 @@
|
||||
# Wiki 扩容方案
|
||||
|
||||
> 默认模式(grep + index.md)适用于 < 500 页。超过后启用 SQLite 索引层。
|
||||
> 零额外依赖——Python 3 自带 sqlite3 + FTS5。
|
||||
|
||||
## 三档检索策略
|
||||
|
||||
| 档位 | 页面数 | 检索方式 | 触发条件 |
|
||||
|------|--------|---------|---------|
|
||||
| L0 | < 50 | 读 index.md + 直接读页面 | 默认 |
|
||||
| L1 | 50-500 | 分层 index + grep 关键词 | 页面数 > 50 时自动切换 |
|
||||
| L2 | 500+ | SQLite FTS5 + BM25 排序 | 页面数 > 500 或用户手动启用 |
|
||||
|
||||
---
|
||||
|
||||
## L1: 分层索引(50-500 页)
|
||||
|
||||
当页面数超过 50,index.md 拆分为分层结构:
|
||||
|
||||
```
|
||||
.wiki/{主题}/
|
||||
├── index.md # 顶层索引(只有分类摘要 + 链接到子索引)
|
||||
├── entities/
|
||||
│ └── _index.md # 实体子索引(该目录下所有页面的标题+一句话)
|
||||
├── concepts/
|
||||
│ └── _index.md # 概念子索引
|
||||
├── sources/
|
||||
│ └── _index.md # 源文件子索引
|
||||
└── analyses/
|
||||
└── _index.md # 分析子索引
|
||||
```
|
||||
|
||||
**顶层 index.md 变为导航页**:
|
||||
|
||||
```markdown
|
||||
# {主题} Wiki Index
|
||||
|
||||
> 287 pages | Last updated: 2026-04-06
|
||||
|
||||
## Overview
|
||||
- Sources: 45 篇 → [sources/_index.md]
|
||||
- Entities: 120 个 → [entities/_index.md]
|
||||
- Concepts: 87 个 → [concepts/_index.md]
|
||||
- Analyses: 35 篇 → [analyses/_index.md]
|
||||
|
||||
## 最近 ingest(最近 10 条)
|
||||
- 2026-04-06: hrss-policy → 更新 8 页
|
||||
- 2026-04-05: annual-report → 更新 5 页
|
||||
...
|
||||
|
||||
## 高频实体 Top 10(被引用最多)
|
||||
- [[alpha-corp]] (引用 23 次)
|
||||
- [[national-council-ssf]] (引用 18 次)
|
||||
...
|
||||
```
|
||||
|
||||
Agent 查询时先读顶层 index 定位分类,再读对应子索引定位具体页面,避免一次加载全部。
|
||||
|
||||
---
|
||||
|
||||
## L2: SQLite FTS5 索引(500+ 页)
|
||||
|
||||
### 原理
|
||||
|
||||
Wiki 页面(markdown)仍然是数据源头。SQLite 只是索引——丢了可以从页面重建。
|
||||
|
||||
```
|
||||
.wiki/{主题}/
|
||||
├── search.db # SQLite 索引文件(自动生成,可重建)
|
||||
├── index.md # 保留(给人浏览用)
|
||||
├── meta.yaml
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Schema
|
||||
|
||||
```sql
|
||||
-- 页面表
|
||||
CREATE TABLE pages (
|
||||
slug TEXT PRIMARY KEY, -- 文件名(去 .md)
|
||||
type TEXT NOT NULL, -- source/entity/concept/analysis
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL, -- 正文全文
|
||||
confidence TEXT DEFAULT 'high',
|
||||
created TEXT,
|
||||
updated TEXT,
|
||||
sources TEXT -- JSON array of source slugs
|
||||
);
|
||||
|
||||
-- FTS5 全文索引(内置 BM25 排名)
|
||||
CREATE VIRTUAL TABLE pages_fts USING fts5(
|
||||
title,
|
||||
content,
|
||||
content='pages',
|
||||
content_rowid='rowid',
|
||||
tokenize='unicode61' -- 支持中文分词
|
||||
);
|
||||
|
||||
-- Wikilink 关系表
|
||||
CREATE TABLE links (
|
||||
from_slug TEXT NOT NULL,
|
||||
to_slug TEXT NOT NULL,
|
||||
PRIMARY KEY (from_slug, to_slug)
|
||||
);
|
||||
|
||||
-- 触发器:pages 变化时自动更新 FTS 索引
|
||||
CREATE TRIGGER pages_ai AFTER INSERT ON pages BEGIN
|
||||
INSERT INTO pages_fts(rowid, title, content)
|
||||
VALUES (new.rowid, new.title, new.content);
|
||||
END;
|
||||
```
|
||||
|
||||
### 建索引脚本
|
||||
|
||||
Agent 在 ingest 完成后自动执行(如果 search.db 存在):
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""从 wiki markdown 文件重建 SQLite FTS5 索引。"""
|
||||
import sqlite3, os, re, json, yaml
|
||||
from pathlib import Path
|
||||
|
||||
def parse_page(path):
|
||||
"""解析 markdown 页面,提取 frontmatter 和正文。"""
|
||||
text = path.read_text(encoding='utf-8')
|
||||
if text.startswith('---'):
|
||||
_, fm, body = text.split('---', 2)
|
||||
meta = yaml.safe_load(fm)
|
||||
return meta, body.strip()
|
||||
return {}, text
|
||||
|
||||
def extract_links(content):
|
||||
"""提取 [[wikilink]]。"""
|
||||
return re.findall(r'\[\[([^\]]+)\]\]', content)
|
||||
|
||||
def build_index(wiki_dir):
|
||||
db_path = wiki_dir / 'search.db'
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
c = conn.cursor()
|
||||
|
||||
# 建表(如果不存在)
|
||||
c.executescript('''
|
||||
CREATE TABLE IF NOT EXISTS pages (
|
||||
slug TEXT PRIMARY KEY, type TEXT, title TEXT,
|
||||
content TEXT, confidence TEXT, created TEXT,
|
||||
updated TEXT, sources TEXT
|
||||
);
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS pages_fts USING fts5(
|
||||
title, content, content='pages', content_rowid='rowid',
|
||||
tokenize='unicode61'
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS links (
|
||||
from_slug TEXT, to_slug TEXT,
|
||||
PRIMARY KEY (from_slug, to_slug)
|
||||
);
|
||||
''')
|
||||
|
||||
# 清空重建
|
||||
c.execute('DELETE FROM pages')
|
||||
c.execute('DELETE FROM links')
|
||||
c.execute("INSERT INTO pages_fts(pages_fts) VALUES('delete-all')")
|
||||
|
||||
# 遍历所有 md 文件
|
||||
for subdir in ['sources', 'entities', 'concepts', 'analyses']:
|
||||
dir_path = wiki_dir / subdir
|
||||
if not dir_path.exists():
|
||||
continue
|
||||
for f in dir_path.glob('*.md'):
|
||||
if f.name.startswith('_'):
|
||||
continue
|
||||
slug = f.stem
|
||||
meta, body = parse_page(f)
|
||||
c.execute(
|
||||
'INSERT OR REPLACE INTO pages VALUES (?,?,?,?,?,?,?,?)',
|
||||
(slug, meta.get('type',''), meta.get('title',''),
|
||||
body, meta.get('confidence','high'),
|
||||
meta.get('created',''), meta.get('updated',''),
|
||||
json.dumps(meta.get('sources',[])))
|
||||
)
|
||||
for link in extract_links(body):
|
||||
c.execute('INSERT OR IGNORE INTO links VALUES (?,?)', (slug, link))
|
||||
|
||||
# 重建 FTS
|
||||
c.execute("INSERT INTO pages_fts(pages_fts) VALUES('rebuild')")
|
||||
|
||||
conn.commit()
|
||||
count = c.execute('SELECT COUNT(*) FROM pages').fetchone()[0]
|
||||
conn.close()
|
||||
return count
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
wiki_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path('.')
|
||||
n = build_index(wiki_dir)
|
||||
print(f'Indexed {n} pages → {wiki_dir}/search.db')
|
||||
```
|
||||
|
||||
### 查询方式
|
||||
|
||||
Agent 在 query 操作中,用 Python 查询 SQLite:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""BM25 搜索 wiki 页面。"""
|
||||
import sqlite3, sys, json
|
||||
|
||||
def search(db_path, query, limit=10):
|
||||
conn = sqlite3.connect(db_path)
|
||||
c = conn.cursor()
|
||||
# BM25 排序:FTS5 内置,rank 越小越相关
|
||||
results = c.execute('''
|
||||
SELECT p.slug, p.type, p.title, p.confidence,
|
||||
snippet(pages_fts, 1, '>>>', '<<<', '...', 30) as snippet,
|
||||
rank
|
||||
FROM pages_fts
|
||||
JOIN pages p ON pages_fts.rowid = p.rowid
|
||||
WHERE pages_fts MATCH ?
|
||||
ORDER BY rank
|
||||
LIMIT ?
|
||||
''', (query, limit)).fetchall()
|
||||
conn.close()
|
||||
return results
|
||||
|
||||
if __name__ == '__main__':
|
||||
db = sys.argv[1]
|
||||
q = sys.argv[2]
|
||||
for slug, type_, title, conf, snippet, rank in search(db, q):
|
||||
print(f'[{type_}] {title} ({slug}) confidence={conf} rank={rank:.2f}')
|
||||
print(f' {snippet}')
|
||||
print()
|
||||
```
|
||||
|
||||
**使用示例**:
|
||||
|
||||
```bash
|
||||
# 建索引
|
||||
python3 build_index.py .wiki/enterprise-annuity/
|
||||
|
||||
# BM25 搜索
|
||||
python3 search.py .wiki/enterprise-annuity/search.db "受托人 市场份额"
|
||||
|
||||
# 输出
|
||||
# [entity] Alpha Corp 养老金业务 (alpha-corp) confidence=high rank=-3.42
|
||||
# ...受托人>>>市场份额<<<约 15%...
|
||||
# [analysis] 受托人市场格局对比 (trustee-market-comparison) confidence=high rank=-2.87
|
||||
# ...各>>>受托人<<<的>>>市场份额<<<变化...
|
||||
```
|
||||
|
||||
### 反向链接查询
|
||||
|
||||
```sql
|
||||
-- 谁引用了 alpha-corp?
|
||||
SELECT from_slug FROM links WHERE to_slug = 'alpha-corp';
|
||||
|
||||
-- alpha-corp 引用了谁?
|
||||
SELECT to_slug FROM links WHERE from_slug = 'alpha-corp';
|
||||
|
||||
-- 最孤立的页面(入链最少)
|
||||
SELECT p.slug, p.title, COUNT(l.from_slug) as inlinks
|
||||
FROM pages p
|
||||
LEFT JOIN links l ON l.to_slug = p.slug
|
||||
GROUP BY p.slug
|
||||
ORDER BY inlinks ASC
|
||||
LIMIT 10;
|
||||
|
||||
-- 最核心的页面(被引用最多)
|
||||
SELECT p.slug, p.title, COUNT(l.from_slug) as inlinks
|
||||
FROM pages p
|
||||
LEFT JOIN links l ON l.to_slug = p.slug
|
||||
GROUP BY p.slug
|
||||
ORDER BY inlinks DESC
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
### lint 增强
|
||||
|
||||
L2 模式下,lint 可以用 SQL 高效执行:
|
||||
|
||||
```sql
|
||||
-- 找 contested 页面
|
||||
SELECT slug, title FROM pages WHERE confidence = 'contested';
|
||||
|
||||
-- 找孤页(无入链 + 非 source 类型)
|
||||
SELECT p.slug, p.title FROM pages p
|
||||
LEFT JOIN links l ON l.to_slug = p.slug
|
||||
WHERE l.from_slug IS NULL AND p.type != 'source';
|
||||
|
||||
-- 找断链(wikilink 目标不存在)
|
||||
SELECT l.from_slug, l.to_slug FROM links l
|
||||
LEFT JOIN pages p ON p.slug = l.to_slug
|
||||
WHERE p.slug IS NULL;
|
||||
|
||||
-- 找过时页面(6 个月未更新 + 低置信度)
|
||||
SELECT slug, title, updated, confidence FROM pages
|
||||
WHERE updated < date('now', '-6 months')
|
||||
AND confidence IN ('low', 'medium');
|
||||
|
||||
-- 统计 coverage
|
||||
SELECT type, COUNT(*) as count,
|
||||
SUM(CASE WHEN confidence = 'contested' THEN 1 ELSE 0 END) as contested
|
||||
FROM pages GROUP BY type;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 何时升级
|
||||
|
||||
| 信号 | 建议 |
|
||||
|------|------|
|
||||
| index.md 超过 200 行 | 启用 L1 分层索引 |
|
||||
| grep 搜索 > 5 秒 | 启用 L2 SQLite 索引 |
|
||||
| 页面数 > 500 | 必须启用 L2 |
|
||||
| 需要反向链接查询 | 启用 L2(links 表) |
|
||||
| 需要 BM25 排序 | 启用 L2(FTS5) |
|
||||
| 多用户协作 / 向量检索 | 超出 Skill 范围 → 迁移到外部平台 |
|
||||
|
||||
**升级是非破坏性的**——wiki 页面(markdown)不变,只是旁边多了一个 search.db。删掉 search.db,wiki 完整可用,只是退回 grep 检索。
|
||||
758
.claude/skills/auto-wiki-cn/references/schema.py
Normal file
758
.claude/skills/auto-wiki-cn/references/schema.py
Normal file
@ -0,0 +1,758 @@
|
||||
"""Wiki 页面 frontmatter 的 Pydantic 校验模型。
|
||||
|
||||
用法:
|
||||
python schema.py .wiki/my-research/
|
||||
python schema.py .wiki/my-research/entities/alpha-corp.md
|
||||
|
||||
Agent 在 ingest 完成后应自动运行校验。lint 操作也会调用。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import date
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
import yaml
|
||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
|
||||
|
||||
# ── 枚举 ──────────────────────────────────────────────────
|
||||
|
||||
class PageType(str, Enum):
|
||||
source = "source"
|
||||
entity = "entity"
|
||||
concept = "concept"
|
||||
analysis = "analysis"
|
||||
mental_model = "mental-model"
|
||||
|
||||
|
||||
class Confidence(str, Enum):
|
||||
high = "high"
|
||||
medium = "medium"
|
||||
low = "low"
|
||||
contested = "contested"
|
||||
|
||||
|
||||
class SourceType(str, Enum):
|
||||
primary = "一手"
|
||||
authoritative_secondary = "二手·权威"
|
||||
secondary = "二手"
|
||||
hearsay = "转述"
|
||||
inference = "推断"
|
||||
oral = "口述"
|
||||
|
||||
|
||||
# ── 关系 ──────────────────────────────────────────────────
|
||||
|
||||
class Relation(BaseModel):
|
||||
"""页面间的类型化关系。"""
|
||||
target: str # 目标页面 slug
|
||||
type: str # 关系类型
|
||||
|
||||
@field_validator("type")
|
||||
@classmethod
|
||||
def known_type(cls, v: str) -> str:
|
||||
"""建议使用标准关系类型,但不强制。"""
|
||||
standard = {
|
||||
"part_of", "manages", "regulated_by", "competes_with",
|
||||
"implements", "derived_from", "contradicts", "influenced_by",
|
||||
"applies_to", "supplies", "subsidiary_of", "contrasted_with",
|
||||
}
|
||||
if v not in standard:
|
||||
import warnings
|
||||
warnings.warn(
|
||||
f"非标准关系类型 '{v}'(允许使用,但建议对齐标准类型:{', '.join(sorted(standard))})",
|
||||
stacklevel=2,
|
||||
)
|
||||
return v
|
||||
|
||||
|
||||
# ── 三重验证(cognitive 类型专用)──────────────────────────
|
||||
|
||||
class MentalModelVerification(BaseModel):
|
||||
"""心智模型的三重验证结果。"""
|
||||
cross_domain: bool = False # 跨域复现
|
||||
generative: bool = False # 有生成力
|
||||
exclusive: bool = False # 有排他性
|
||||
domains: list[str] = Field(default_factory=list) # 出现过的领域
|
||||
|
||||
|
||||
# ── 页面基类 ──────────────────────────────────────────────
|
||||
|
||||
class BasePage(BaseModel):
|
||||
"""所有页面的公共字段。"""
|
||||
title: str
|
||||
type: PageType
|
||||
created: Union[str, date] # YYYY-MM-DD(YAML 可能解析为 date 对象)
|
||||
updated: Union[str, date] # YYYY-MM-DD
|
||||
sources: list[str] = Field(default_factory=list)
|
||||
confidence: Confidence = Confidence.high
|
||||
|
||||
@field_validator("created", "updated", mode="before")
|
||||
@classmethod
|
||||
def coerce_date(cls, v: Any) -> str:
|
||||
"""YAML 会把 2026-04-07 自动解析为 datetime.date,统一转为 str。"""
|
||||
if isinstance(v, date):
|
||||
return v.isoformat()
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
date.fromisoformat(v)
|
||||
except ValueError:
|
||||
raise ValueError(f"日期必须为 YYYY-MM-DD 格式: {v}")
|
||||
return v
|
||||
raise ValueError(f"日期类型错误: {type(v)}")
|
||||
|
||||
|
||||
class SourcePage(BasePage):
|
||||
"""source 类型页面。"""
|
||||
type: PageType = PageType.source
|
||||
source_type: SourceType
|
||||
source_origin: str # 来源出处
|
||||
source_date: Union[str, date] # 原始材料日期
|
||||
source_url: str = "" # 来源 URL
|
||||
|
||||
@field_validator("source_date", mode="before")
|
||||
@classmethod
|
||||
def coerce_source_date(cls, v: Any) -> str:
|
||||
if isinstance(v, date):
|
||||
return v.isoformat()
|
||||
return v
|
||||
|
||||
@model_validator(mode="after")
|
||||
def sources_should_be_empty(self) -> "SourcePage":
|
||||
if self.sources:
|
||||
raise ValueError("source 类型页面的 sources 应为空列表")
|
||||
return self
|
||||
|
||||
|
||||
class DataPage(BasePage):
|
||||
"""entity / concept / analysis 页面。
|
||||
结构化数据(data/history)存在 data.db 中,不在 frontmatter。
|
||||
relations 保留在 frontmatter(Obsidian wikilink 渲染),同时写入 data.db。
|
||||
"""
|
||||
relations: list[Relation] = Field(default_factory=list)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def sources_not_empty(self) -> "DataPage":
|
||||
if self.type != PageType.source and not self.sources:
|
||||
raise ValueError(f"{self.type.value} 类型页面的 sources 不能为空")
|
||||
return self
|
||||
|
||||
|
||||
class MentalModelPage(BasePage):
|
||||
"""mental-model 类型页面(cognitive wiki 专用)。"""
|
||||
type: PageType = PageType.mental_model
|
||||
verification: Optional[MentalModelVerification] = None
|
||||
relations: list[Relation] = Field(default_factory=list)
|
||||
|
||||
|
||||
# ── 解析与校验 ────────────────────────────────────────────
|
||||
|
||||
def parse_frontmatter(path: Path) -> dict[str, Any]:
|
||||
"""从 markdown 文件中提取 YAML frontmatter。"""
|
||||
text = path.read_text(encoding="utf-8")
|
||||
if not text.startswith("---"):
|
||||
raise ValueError(f"页面缺少 YAML frontmatter: {path}")
|
||||
parts = text.split("---", 2)
|
||||
if len(parts) < 3:
|
||||
raise ValueError(f"YAML frontmatter 格式错误: {path}")
|
||||
return yaml.safe_load(parts[1]) or {}
|
||||
|
||||
|
||||
def validate_page(path: Path) -> tuple[bool, str]:
|
||||
"""校验单个页面。返回 (通过?, 消息)。"""
|
||||
try:
|
||||
fm = parse_frontmatter(path)
|
||||
except Exception as e:
|
||||
return False, f"PARSE ERROR: {e}"
|
||||
|
||||
page_type = fm.get("type", "")
|
||||
warnings_list: list[str] = []
|
||||
|
||||
# data/history 应在 data.db 中,不在 frontmatter
|
||||
if "data" in fm and fm["data"]:
|
||||
warnings_list.append("MIGRATE: frontmatter 中有 data 字段,应迁移到 data.db(python store.py init)")
|
||||
if "history" in fm and fm["history"]:
|
||||
warnings_list.append("MIGRATE: frontmatter 中有 history 字段,应迁移到 data.db")
|
||||
|
||||
# 从 fm 中移除 data/history 避免 Pydantic 报错(模型已不包含这些字段)
|
||||
fm_clean = {k: v for k, v in fm.items() if k not in ("data", "history")}
|
||||
|
||||
try:
|
||||
if page_type == "source":
|
||||
SourcePage(**fm_clean)
|
||||
elif page_type == "mental-model":
|
||||
MentalModelPage(**fm_clean)
|
||||
elif page_type in ("entity", "concept", "analysis"):
|
||||
DataPage(**fm_clean)
|
||||
else:
|
||||
return False, f"UNKNOWN TYPE: {page_type}"
|
||||
if warnings_list:
|
||||
return True, "OK (⚠️ " + "; ".join(warnings_list) + ")"
|
||||
return True, "OK"
|
||||
except Exception as e:
|
||||
return False, f"VALIDATION ERROR: {e}"
|
||||
|
||||
|
||||
def validate_wiki(wiki_dir: Path) -> list[tuple[str, bool, str]]:
|
||||
"""校验整个 wiki 目录。返回 [(文件名, 通过?, 消息)]。"""
|
||||
results = []
|
||||
for subdir in ["sources", "entities", "concepts", "analyses", "mental-models"]:
|
||||
d = wiki_dir / subdir
|
||||
if not d.exists():
|
||||
continue
|
||||
for f in sorted(d.glob("*.md")):
|
||||
if f.name.startswith("_"):
|
||||
continue
|
||||
ok, msg = validate_page(f)
|
||||
rel = f.relative_to(wiki_dir)
|
||||
results.append((str(rel), ok, msg))
|
||||
return results
|
||||
|
||||
|
||||
# ── Report 数据提取 ──────────────────────────────────────
|
||||
|
||||
import json
|
||||
import re
|
||||
from collections import Counter
|
||||
|
||||
def _page_body(path: Path) -> str:
|
||||
"""读取 markdown 正文(frontmatter 之后的部分)。"""
|
||||
text = path.read_text(encoding="utf-8")
|
||||
parts = text.split("---", 2)
|
||||
return parts[2] if len(parts) >= 3 else ""
|
||||
|
||||
def _extract_wikilinks(body: str) -> list[str]:
|
||||
"""提取正文中的 [[slug]] 或 [[slug|display]] 链接。"""
|
||||
return re.findall(r"\[\[([^\]|]+)(?:\|[^\]]+)?\]\]", body)
|
||||
|
||||
def collect_report_data(wiki_dir: Path) -> dict[str, Any]:
|
||||
"""扫描 wiki 目录,提取完整的报告数据。"""
|
||||
meta_path = wiki_dir / "meta.yaml"
|
||||
meta = {}
|
||||
if meta_path.exists():
|
||||
meta = yaml.safe_load(meta_path.read_text(encoding="utf-8")) or {}
|
||||
|
||||
nodes: list[dict] = []
|
||||
edges: list[dict] = []
|
||||
data_rows: list[dict] = []
|
||||
type_counts: Counter = Counter()
|
||||
confidence_counts: Counter = Counter()
|
||||
contested_pages: list[str] = []
|
||||
inlink_counts: Counter = Counter()
|
||||
page_slugs: set[str] = set()
|
||||
freshness: list[dict] = []
|
||||
|
||||
subdirs = ["sources", "entities", "concepts", "analyses", "mental-models"]
|
||||
for subdir in subdirs:
|
||||
d = wiki_dir / subdir
|
||||
if not d.exists():
|
||||
continue
|
||||
for f in sorted(d.glob("*.md")):
|
||||
if f.name.startswith("_"):
|
||||
continue
|
||||
slug = f.stem
|
||||
page_slugs.add(slug)
|
||||
rel_path = str(f.relative_to(wiki_dir))
|
||||
|
||||
try:
|
||||
fm = parse_frontmatter(f)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
page_type = fm.get("type", "unknown")
|
||||
confidence = fm.get("confidence", "unknown")
|
||||
title = fm.get("title", slug)
|
||||
updated = str(fm.get("updated", ""))
|
||||
|
||||
type_counts[page_type] += 1
|
||||
confidence_counts[confidence] += 1
|
||||
|
||||
if confidence == "contested":
|
||||
contested_pages.append(rel_path)
|
||||
|
||||
# 节点颜色按类型
|
||||
color_map = {
|
||||
"entity": "#4A90D9",
|
||||
"concept": "#7B68EE",
|
||||
"source": "#50C878",
|
||||
"analysis": "#FF8C00",
|
||||
"mental-model": "#E91E63",
|
||||
}
|
||||
nodes.append({
|
||||
"id": slug,
|
||||
"label": title,
|
||||
"type": page_type,
|
||||
"color": color_map.get(page_type, "#999"),
|
||||
"confidence": confidence,
|
||||
"updated": updated,
|
||||
"path": rel_path,
|
||||
})
|
||||
|
||||
freshness.append({"slug": slug, "updated": updated, "type": page_type})
|
||||
|
||||
# 关系 → 边
|
||||
for rel in fm.get("relations", []):
|
||||
target = rel.get("target", "")
|
||||
rel_type = rel.get("type", "")
|
||||
if target:
|
||||
edges.append({
|
||||
"from": slug,
|
||||
"to": target,
|
||||
"label": rel_type,
|
||||
})
|
||||
|
||||
# 正文 wikilinks → 入链统计
|
||||
body = _page_body(f)
|
||||
for linked_slug in _extract_wikilinks(body):
|
||||
inlink_counts[linked_slug] += 1
|
||||
|
||||
# 从 data.db 读取结构化数据(唯一数据源)
|
||||
db_path = wiki_dir / "data.db"
|
||||
if db_path.exists():
|
||||
import sqlite3 as _sqlite3
|
||||
_conn = _sqlite3.connect(str(db_path))
|
||||
_conn.row_factory = _sqlite3.Row
|
||||
for r in _conn.execute("SELECT * FROM data_points ORDER BY page_slug, field").fetchall():
|
||||
page_title = r["page_slug"]
|
||||
pg = _conn.execute("SELECT title FROM pages WHERE slug=?", (r["page_slug"],)).fetchone()
|
||||
if pg:
|
||||
page_title = pg["title"]
|
||||
data_rows.append({
|
||||
"page": page_title, "slug": r["page_slug"], "field": r["field"],
|
||||
"value": r["value"], "unit": r["unit"], "period": r["period"],
|
||||
"source": r["source_slug"], "verified": r["verified"],
|
||||
"confidence": r["confidence"] or "high",
|
||||
})
|
||||
# DB relations 补充 frontmatter edges
|
||||
edge_set = {(e["from"], e["to"], e["label"]) for e in edges}
|
||||
for r in _conn.execute("SELECT * FROM relations").fetchall():
|
||||
key = (r["from_slug"], r["to_slug"], r["type"])
|
||||
if key not in edge_set:
|
||||
edges.append({"from": r["from_slug"], "to": r["to_slug"], "label": r["type"]})
|
||||
# DB contested 补充
|
||||
contested_set = set(contested_pages)
|
||||
for r in _conn.execute("SELECT DISTINCT page_slug FROM data_points WHERE confidence='contested'").fetchall():
|
||||
for n in nodes:
|
||||
if n["id"] == r["page_slug"] and n["path"] not in contested_set:
|
||||
contested_pages.append(n["path"])
|
||||
contested_set.add(n["path"])
|
||||
_conn.close()
|
||||
|
||||
# frontmatter relations 也算入链
|
||||
for edge in edges:
|
||||
inlink_counts[edge["to"]] += 1
|
||||
|
||||
# 覆盖度分析
|
||||
coverage_gaps: list[dict] = []
|
||||
for slug in page_slugs:
|
||||
inlinks = inlink_counts.get(slug, 0)
|
||||
node = next((n for n in nodes if n["id"] == slug), None)
|
||||
if not node:
|
||||
continue
|
||||
if inlinks == 0 and node["type"] != "source":
|
||||
coverage_gaps.append({
|
||||
"slug": slug,
|
||||
"issue": "orphan",
|
||||
"detail": f"零入链(无其他页面引用)",
|
||||
})
|
||||
|
||||
# 被大量引用但内容可能薄的页面(通过 wikilink 被引用但不在 page_slugs 中)
|
||||
for linked, count in inlink_counts.most_common():
|
||||
if linked not in page_slugs and count >= 2:
|
||||
coverage_gaps.append({
|
||||
"slug": linked,
|
||||
"issue": "missing",
|
||||
"detail": f"被引用 {count} 次但页面不存在",
|
||||
})
|
||||
|
||||
return {
|
||||
"name": meta.get("name", wiki_dir.name),
|
||||
"ontology_type": meta.get("ontology_type", "unknown"),
|
||||
"description": meta.get("description", ""),
|
||||
"seed": meta.get("seed", ""),
|
||||
"total_pages": len(nodes),
|
||||
"type_counts": dict(type_counts),
|
||||
"confidence_counts": dict(confidence_counts),
|
||||
"contested_pages": contested_pages,
|
||||
"nodes": nodes,
|
||||
"edges": edges,
|
||||
"data_rows": data_rows,
|
||||
"freshness": freshness,
|
||||
"coverage_gaps": coverage_gaps,
|
||||
}
|
||||
|
||||
|
||||
REPORT_HTML_TEMPLATE = r"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Wiki Report: {{WIKI_NAME}}</title>
|
||||
<script src="https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js"></script>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, "Noto Sans SC", sans-serif; background: #f0f2f5; color: #1a1a1a; height: 100vh; overflow: hidden; }
|
||||
|
||||
.layout { display: flex; height: 100vh; }
|
||||
|
||||
/* ── Left: header + cards + graph ── */
|
||||
.left { flex: 0 0 55%; display: flex; flex-direction: column; height: 100vh; border-right: 1px solid #e0e0e0; }
|
||||
.left-header { padding: 16px 20px 0; flex-shrink: 0; }
|
||||
h1 { font-size: 18px; font-weight: 600; color: #111; }
|
||||
.subtitle { color: #999; font-size: 12px; margin-top: 2px; }
|
||||
.cards { display: flex; gap: 8px; padding: 12px 20px 0; flex-shrink: 0; }
|
||||
.card { background: #fff; border-radius: 6px; padding: 8px 14px; border: 1px solid #e0e0e0; flex: 1; min-width: 0; }
|
||||
.card .num { font-size: 20px; font-weight: 700; color: #111; }
|
||||
.card .label { font-size: 10px; color: #aaa; margin-top: 1px; text-transform: uppercase; }
|
||||
.legend { display: flex; gap: 12px; flex-wrap: wrap; padding: 10px 20px 6px; flex-shrink: 0; }
|
||||
.legend-item { display: flex; align-items: center; gap: 4px; font-size: 11px; color: #888; }
|
||||
.legend-dot { width: 8px; height: 8px; border-radius: 50%; }
|
||||
.graph-container { flex: 1; min-height: 0; margin: 0 12px 12px; background: #fff; border-radius: 8px; border: 1px solid #e0e0e0; }
|
||||
|
||||
/* ── Right: node detail + tables ── */
|
||||
.right { flex: 0 0 45%; display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
|
||||
.right-scroll { flex: 1; overflow-y: auto; padding: 16px 20px; display: flex; flex-direction: column; gap: 12px; }
|
||||
.right-scroll::-webkit-scrollbar { width: 4px; }
|
||||
.right-scroll::-webkit-scrollbar-thumb { background: #d0d0d0; border-radius: 2px; }
|
||||
|
||||
/* ── Node detail ── */
|
||||
.node-detail { background: #fff; border-radius: 8px; border: 1px solid #e0e0e0; overflow: hidden; flex-shrink: 0; display: none; }
|
||||
.node-detail.active { display: block; }
|
||||
.nd-header { padding: 10px 14px; background: #fafafa; border-bottom: 1px solid #eee; display: flex; align-items: center; justify-content: space-between; }
|
||||
.nd-header h3 { font-size: 14px; font-weight: 600; }
|
||||
.nd-close { cursor: pointer; color: #aaa; font-size: 16px; padding: 0 4px; }
|
||||
.nd-close:hover { color: #333; }
|
||||
.nd-body { padding: 12px 14px; font-size: 12px; color: #555; line-height: 1.7; }
|
||||
.nd-body b { color: #333; }
|
||||
.nd-section { margin-top: 8px; }
|
||||
.nd-row { display: flex; justify-content: space-between; padding: 3px 0; border-bottom: 1px solid #f5f5f5; }
|
||||
.nd-row:last-child { border-bottom: none; }
|
||||
.nd-key { color: #888; }
|
||||
.nd-val { font-weight: 600; color: #111; }
|
||||
.nd-rel { padding: 2px 0; }
|
||||
.nd-rel-arrow { color: #bbb; margin: 0 4px; }
|
||||
|
||||
/* ── Panel ── */
|
||||
.panel { background: #fff; border-radius: 8px; border: 1px solid #e0e0e0; overflow: hidden; flex-shrink: 0; }
|
||||
.panel-header { padding: 9px 14px; font-size: 11px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.5px; background: #fafafa; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
|
||||
.panel-count { background: #eee; color: #666; padding: 1px 6px; border-radius: 3px; font-size: 10px; }
|
||||
|
||||
/* ── Table ── */
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th { text-align: left; padding: 7px 12px; font-size: 10px; color: #aaa; font-weight: 600; text-transform: uppercase; }
|
||||
td { padding: 6px 12px; font-size: 12px; border-top: 1px solid #f3f3f3; }
|
||||
.r { text-align: right; }
|
||||
tr:hover td { background: #f8f9fb; }
|
||||
|
||||
.badge { display: inline-block; padding: 1px 7px; border-radius: 3px; font-size: 10px; font-weight: 600; }
|
||||
.badge-high { background: #dcfce7; color: #166534; }
|
||||
.badge-medium { background: #fef9c3; color: #854d0e; }
|
||||
.badge-low { background: #fee2e2; color: #991b1b; }
|
||||
.badge-contested { background: #f3e8ff; color: #7c3aed; }
|
||||
.badge-entity { background: #dbeafe; color: #1e40af; }
|
||||
.badge-concept { background: #ede9fe; color: #5b21b6; }
|
||||
.badge-source { background: #dcfce7; color: #166534; }
|
||||
.badge-analysis { background: #ffedd5; color: #9a3412; }
|
||||
.badge-mental-model { background: #fce7f3; color: #be185d; }
|
||||
.badge-orphan { background: #fee2e2; color: #991b1b; }
|
||||
.badge-missing { background: #fef9c3; color: #854d0e; }
|
||||
.empty { color: #ccc; font-style: italic; padding: 14px; text-align: center; font-size: 12px; }
|
||||
|
||||
/* ── Hint overlay on graph ── */
|
||||
.graph-hint { position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); font-size: 11px; color: #bbb; pointer-events: none; transition: opacity 0.3s; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
|
||||
<!-- ── LEFT ── -->
|
||||
<div class="left">
|
||||
<div class="left-header">
|
||||
<h1>{{WIKI_NAME}}</h1>
|
||||
<div class="subtitle">{{WIKI_DESC}} · {{ONTOLOGY_TYPE}} · {{TOTAL_PAGES}} pages</div>
|
||||
</div>
|
||||
<div class="cards" id="cards"></div>
|
||||
<div class="legend" id="legend"></div>
|
||||
<div class="graph-container" id="graph" style="position:relative;">
|
||||
<div class="graph-hint" id="graph-hint">click a node to inspect</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── RIGHT ── -->
|
||||
<div class="right">
|
||||
<div class="right-scroll">
|
||||
<div class="node-detail" id="node-detail">
|
||||
<div class="nd-header">
|
||||
<h3 id="nd-title"></h3>
|
||||
<span class="nd-close" id="nd-close">×</span>
|
||||
</div>
|
||||
<div class="nd-body" id="nd-body"></div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-header">Data Points <span class="panel-count" id="dp-count"></span></div>
|
||||
<div id="data-table"></div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-header">Pages <span class="panel-count" id="pg-count"></span></div>
|
||||
<div id="freshness-table"></div>
|
||||
</div>
|
||||
<div class="panel" id="contested-panel" style="display:none">
|
||||
<div class="panel-header">Contested <span class="panel-count" id="ct-count"></span></div>
|
||||
<div id="contested-list"></div>
|
||||
</div>
|
||||
<div class="panel" id="coverage-panel" style="display:none">
|
||||
<div class="panel-header">Coverage Gaps <span class="panel-count" id="cg-count"></span></div>
|
||||
<div id="coverage-table"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const DATA = {{JSON_DATA}};
|
||||
|
||||
// ── Cards ──
|
||||
const cardsEl = document.getElementById('cards');
|
||||
const cc = [
|
||||
{ num: DATA.total_pages, label: 'Pages' },
|
||||
...Object.entries(DATA.type_counts).map(([k,v]) => ({ num: v, label: k })),
|
||||
{ num: DATA.contested_pages.length, label: 'Contested' },
|
||||
{ num: DATA.edges.length, label: 'Relations' },
|
||||
];
|
||||
cardsEl.innerHTML = cc.map(c => `<div class="card"><div class="num">${c.num}</div><div class="label">${c.label}</div></div>`).join('');
|
||||
|
||||
// ── Legend ──
|
||||
const cm = { entity:'#4A90D9', concept:'#7B68EE', source:'#50C878', analysis:'#FF8C00', 'mental-model':'#E91E63' };
|
||||
document.getElementById('legend').innerHTML = Object.entries(cm).map(([t,c]) =>
|
||||
`<div class="legend-item"><div class="legend-dot" style="background:${c}"></div>${t}</div>`).join('');
|
||||
|
||||
// ── Graph ──
|
||||
const hint = document.getElementById('graph-hint');
|
||||
let network = null;
|
||||
if (DATA.nodes.length > 0) {
|
||||
// count connections per node for sizing
|
||||
const connCount = {};
|
||||
DATA.edges.forEach(e => { connCount[e.from] = (connCount[e.from]||0)+1; connCount[e.to] = (connCount[e.to]||0)+1; });
|
||||
const maxConn = Math.max(1, ...Object.values(connCount));
|
||||
|
||||
const graphNodes = DATA.nodes.filter(n => n.type !== 'source').map(n => {
|
||||
const c = connCount[n.id] || 0;
|
||||
const sz = 14 + (c / maxConn) * 22;
|
||||
return {
|
||||
id: n.id, label: n.label,
|
||||
color: { background: n.color, border: n.color, highlight: { background: '#fff', border: n.color } },
|
||||
font: { color: '#333', size: Math.max(12, sz * 0.65), face: '-apple-system, "Noto Sans SC", sans-serif' },
|
||||
shape: 'dot', size: sz,
|
||||
};
|
||||
});
|
||||
const nodeIds = new Set(graphNodes.map(n => n.id));
|
||||
DATA.edges.forEach(e => {
|
||||
[e.to, e.from].forEach(id => {
|
||||
if (!nodeIds.has(id)) {
|
||||
graphNodes.push({ id, label: id, color:{ background:'#e0e0e0', border:'#ccc' }, font:{ color:'#999', size:11 }, shape:'dot', size:10 });
|
||||
nodeIds.add(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// dedupe edges: same from→to pair, merge labels
|
||||
const edgeMap = {};
|
||||
DATA.edges.forEach(e => {
|
||||
const key = e.from + '→' + e.to;
|
||||
if (edgeMap[key]) { edgeMap[key].label += '\n' + e.label; }
|
||||
else { edgeMap[key] = { ...e }; }
|
||||
});
|
||||
const graphEdges = Object.values(edgeMap).map((e,i) => ({
|
||||
id: i, from: e.from, to: e.to, label: e.label,
|
||||
color: { color:'#d0d0d0', highlight:'#888' },
|
||||
font: { color:'#aaa', size:10, strokeWidth:3, strokeColor:'#fff', multi:'md', face:'monospace' },
|
||||
arrows: { to:{ scaleFactor:0.5 } },
|
||||
smooth: { type:'curvedCW', roundness: 0.18 },
|
||||
}));
|
||||
|
||||
network = new vis.Network(document.getElementById('graph'), { nodes: graphNodes, edges: graphEdges }, {
|
||||
physics: {
|
||||
solver: 'forceAtlas2Based',
|
||||
forceAtlas2Based: { gravitationalConstant: -80, springLength: 200, springConstant: 0.04, damping: 0.6 },
|
||||
stabilization: { iterations: 150 },
|
||||
},
|
||||
interaction: { hover: true, tooltipDelay: 150, zoomView: true, navigationButtons: false },
|
||||
layout: { improvedLayout: true },
|
||||
});
|
||||
|
||||
// fit after stabilize
|
||||
network.on('stabilized', () => { network.fit({ animation: { duration: 300 } }); });
|
||||
|
||||
// ── Click node → right panel detail ──
|
||||
const ndEl = document.getElementById('node-detail');
|
||||
const ndTitle = document.getElementById('nd-title');
|
||||
const ndBody = document.getElementById('nd-body');
|
||||
document.getElementById('nd-close').onclick = () => { ndEl.classList.remove('active'); network.unselectAll(); };
|
||||
|
||||
network.on('click', function(p) {
|
||||
if (!p.nodes.length) { ndEl.classList.remove('active'); return; }
|
||||
hint.style.opacity = '0';
|
||||
const nid = p.nodes[0];
|
||||
const node = DATA.nodes.find(n => n.id === nid);
|
||||
if (!node) return;
|
||||
ndTitle.textContent = node.label;
|
||||
|
||||
let html = `<div><span class="badge badge-${node.type}">${node.type}</span> <span class="badge badge-${node.confidence}">${node.confidence}</span> ${node.updated}</div>`;
|
||||
|
||||
// data points
|
||||
const dp = DATA.data_rows.filter(r => r.slug === nid);
|
||||
if (dp.length) {
|
||||
html += '<div class="nd-section"><b>Data</b></div>';
|
||||
dp.forEach(d => {
|
||||
html += `<div class="nd-row"><span class="nd-key">${d.field}</span><span class="nd-val">${d.value} ${d.unit} <span style="color:#aaa;font-weight:400">${d.period}</span></span></div>`;
|
||||
});
|
||||
}
|
||||
|
||||
// relations
|
||||
const rels = DATA.edges.filter(e => e.from === nid || e.to === nid);
|
||||
if (rels.length) {
|
||||
html += '<div class="nd-section"><b>Relations</b></div>';
|
||||
rels.forEach(r => {
|
||||
if (r.from === nid)
|
||||
html += `<div class="nd-rel">${node.label} <span class="nd-rel-arrow">→</span> <span style="color:#7c3aed">${r.label}</span> <span class="nd-rel-arrow">→</span> ${r.to}</div>`;
|
||||
else
|
||||
html += `<div class="nd-rel">${r.from} <span class="nd-rel-arrow">→</span> <span style="color:#7c3aed">${r.label}</span> <span class="nd-rel-arrow">→</span> ${node.label}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
ndBody.innerHTML = html;
|
||||
ndEl.classList.add('active');
|
||||
ndEl.scrollIntoView({ behavior:'smooth', block:'start' });
|
||||
});
|
||||
} else {
|
||||
document.getElementById('graph').innerHTML = '<div class="empty">No nodes</div>';
|
||||
}
|
||||
|
||||
// ── Data Table ──
|
||||
document.getElementById('dp-count').textContent = DATA.data_rows.length;
|
||||
const dtEl = document.getElementById('data-table');
|
||||
if (DATA.data_rows.length) {
|
||||
dtEl.innerHTML = `<table><thead><tr><th>Page</th><th>Field</th><th class="r">Value</th><th>Unit</th><th>Period</th><th>Conf.</th></tr></thead><tbody>`
|
||||
+ DATA.data_rows.map(r =>
|
||||
`<tr><td>${r.page}</td><td>${r.field}</td><td class="r"><b>${r.value}</b></td><td>${r.unit}</td><td>${r.period}</td><td><span class="badge badge-${r.confidence}">${r.confidence}</span></td></tr>`
|
||||
).join('') + '</tbody></table>';
|
||||
} else { dtEl.innerHTML = '<div class="empty">No data points</div>'; }
|
||||
|
||||
// ── Pages ──
|
||||
document.getElementById('pg-count').textContent = DATA.freshness.length;
|
||||
const ftEl = document.getElementById('freshness-table');
|
||||
if (DATA.freshness.length) {
|
||||
const s = [...DATA.freshness].sort((a,b) => b.updated.localeCompare(a.updated));
|
||||
ftEl.innerHTML = `<table><thead><tr><th>Page</th><th>Type</th><th class="r">Updated</th></tr></thead><tbody>`
|
||||
+ s.map(r => `<tr><td>${r.slug}</td><td><span class="badge badge-${r.type}">${r.type}</span></td><td class="r">${r.updated}</td></tr>`).join('')
|
||||
+ '</tbody></table>';
|
||||
} else { ftEl.innerHTML = '<div class="empty">No pages</div>'; }
|
||||
|
||||
// ── Contested ──
|
||||
if (DATA.contested_pages.length) {
|
||||
document.getElementById('contested-panel').style.display = '';
|
||||
document.getElementById('ct-count').textContent = DATA.contested_pages.length;
|
||||
document.getElementById('contested-list').innerHTML = `<table><tbody>`
|
||||
+ DATA.contested_pages.map(p => `<tr><td><span class="badge badge-contested">contested</span> ${p}</td></tr>`).join('') + '</tbody></table>';
|
||||
}
|
||||
|
||||
// ── Coverage ──
|
||||
if (DATA.coverage_gaps.length) {
|
||||
document.getElementById('coverage-panel').style.display = '';
|
||||
document.getElementById('cg-count').textContent = DATA.coverage_gaps.length;
|
||||
document.getElementById('coverage-table').innerHTML = `<table><thead><tr><th>Page</th><th>Issue</th><th>Detail</th></tr></thead><tbody>`
|
||||
+ DATA.coverage_gaps.map(r => `<tr><td>${r.slug}</td><td><span class="badge badge-${r.issue}">${r.issue}</span></td><td>${r.detail}</td></tr>`).join('')
|
||||
+ '</tbody></table>';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
def generate_report(wiki_dir: Path) -> Path:
|
||||
"""生成 wiki 可视化报告 HTML。返回输出文件路径。"""
|
||||
data = collect_report_data(wiki_dir)
|
||||
html = REPORT_HTML_TEMPLATE
|
||||
html = html.replace("{{WIKI_NAME}}", data["name"])
|
||||
html = html.replace("{{WIKI_DESC}}", data.get("description", ""))
|
||||
html = html.replace("{{ONTOLOGY_TYPE}}", data.get("ontology_type", ""))
|
||||
html = html.replace("{{TOTAL_PAGES}}", str(data["total_pages"]))
|
||||
html = html.replace("{{JSON_DATA}}", json.dumps(data, ensure_ascii=False, default=str))
|
||||
|
||||
out_path = wiki_dir / "_report.html"
|
||||
out_path.write_text(html, encoding="utf-8")
|
||||
return out_path
|
||||
|
||||
|
||||
# ── CLI ───────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage:")
|
||||
print(" python schema.py <wiki_dir> — validate all pages")
|
||||
print(" python schema.py <page.md> — validate one page")
|
||||
print(" python schema.py --report <wiki_dir> — generate visual report")
|
||||
sys.exit(1)
|
||||
|
||||
# --report 模式
|
||||
if sys.argv[1] == "--report":
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: python schema.py --report <wiki_dir>")
|
||||
sys.exit(1)
|
||||
target = Path(sys.argv[2])
|
||||
if not target.is_dir():
|
||||
print(f"Not a directory: {target}")
|
||||
sys.exit(1)
|
||||
out = generate_report(target)
|
||||
print(f"Report generated: {out}")
|
||||
sys.exit(0)
|
||||
|
||||
target = Path(sys.argv[1])
|
||||
|
||||
if target.is_file():
|
||||
ok, msg = validate_page(target)
|
||||
status = "✅" if ok else "❌"
|
||||
print(f"{status} {target.name}: {msg}")
|
||||
sys.exit(0 if ok else 1)
|
||||
|
||||
if target.is_dir():
|
||||
results = validate_wiki(target)
|
||||
if not results:
|
||||
print("No pages found.")
|
||||
sys.exit(0)
|
||||
|
||||
passed = sum(1 for _, ok, _ in results if ok)
|
||||
failed = sum(1 for _, ok, _ in results if not ok)
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Wiki Validation: {target.name}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
for name, ok, msg in results:
|
||||
status = "✅" if ok else "❌"
|
||||
print(f" {status} {name}")
|
||||
if not ok:
|
||||
# 缩进显示错误详情
|
||||
for line in str(msg).split("\n"):
|
||||
print(f" {line}")
|
||||
|
||||
print(f"\n{'─'*60}")
|
||||
print(f" Total: {len(results)} | Passed: {passed} | Failed: {failed}")
|
||||
if failed == 0:
|
||||
print(" Result: ALL PASSED ✅")
|
||||
else:
|
||||
print(f" Result: {failed} FAILED ❌")
|
||||
print(f"{'─'*60}\n")
|
||||
|
||||
sys.exit(0 if failed == 0 else 1)
|
||||
|
||||
print(f"Not a file or directory: {target}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
137
.claude/skills/auto-wiki-cn/references/seed-ontologies.md
Normal file
137
.claude/skills/auto-wiki-cn/references/seed-ontologies.md
Normal file
@ -0,0 +1,137 @@
|
||||
# 种子本体:用标准词表引导 wiki 结构
|
||||
|
||||
> 种子是可选的冷启动参考,不是强制依赖。
|
||||
> 没有种子的领域,wiki 自由生长——种子只是让起步更规范。
|
||||
|
||||
## 机制
|
||||
|
||||
### 什么是种子
|
||||
|
||||
种子(seed)是一份领域词表配置,包含:
|
||||
|
||||
| 内容 | 作用 |
|
||||
|------|------|
|
||||
| **标准术语** | 给 wiki 页面的 slug 和标题提供命名参考 |
|
||||
| **分类体系** | 提示 Agent 该领域通常有哪些维度需要关注 |
|
||||
| **关系模板** | 标准的实体间关系类型(manages, regulates, invests_in 等) |
|
||||
| **禁混规则** | 标注常见的概念混淆,防止 Agent 搞混 |
|
||||
|
||||
种子文件存放在 `seeds/` 目录,每个领域一个文件。
|
||||
|
||||
### 如何引用
|
||||
|
||||
新建 wiki 时,在 `meta.yaml` 中声明使用哪个种子:
|
||||
|
||||
```yaml
|
||||
name: my-research-topic
|
||||
ontology_type: domain
|
||||
seed: fibo-pensions # 引用 seeds/fibo-pensions.md
|
||||
```
|
||||
|
||||
Agent 在首次 ingest 前读取对应的种子文件。如果 `seed` 字段为空或未设置,跳过种子,wiki 自由生长。
|
||||
|
||||
### 不做什么
|
||||
|
||||
- 不做 OWL/RDF 导入——wiki 是 markdown,不是语义网
|
||||
- 不强制使用标准术语——如果领域实际用语不同,以实际为准,但标注映射关系
|
||||
- 不覆盖用户自定义——种子只是起步参考,wiki 演化后会超出种子范围
|
||||
|
||||
---
|
||||
|
||||
## 可用种子
|
||||
|
||||
| 种子文件 | 覆盖领域 | 基于标准 |
|
||||
|---------|---------|---------|
|
||||
| `seeds/fibo-pensions.md` | 企业年金、养老金管理 | FIBO (EDM Council) |
|
||||
| *(待扩展)* | | |
|
||||
|
||||
### 可参考的行业标准本体
|
||||
|
||||
写新种子时可以参考这些标准:
|
||||
|
||||
| 标准 | 覆盖领域 | 适用场景 | 参考链接 |
|
||||
|------|---------|---------|---------|
|
||||
| **FIBO** | 金融全行业 | 银行、保险、基金、养老金 | spec.edmcouncil.org/fibo |
|
||||
| **XBRL Taxonomy** | 财务报告 | 上市公司财务数据分析 | xbrl.org |
|
||||
| **Schema.org** | 通用实体 | 人物、组织、事件、地点 | schema.org |
|
||||
| **SKOS** | 知识组织 | 分类体系、概念层级 | w3.org/2004/02/skos |
|
||||
| **Dublin Core** | 文档元数据 | source 页面的 frontmatter | dublincore.org |
|
||||
| **FOAF** | 人物与社交 | 人物研究(cognitive 类型) | xmlns.com/foaf |
|
||||
|
||||
| 研究类型 | 推荐种子/标准 |
|
||||
|---------|-------------|
|
||||
| 企业年金 / 养老金 | `fibo-pensions` |
|
||||
| 公募基金 | FIBO-SEC (Fund),可基于此写新种子 |
|
||||
| 上市公司分析 | FIBO-BP + XBRL |
|
||||
| 宏观经济 | 无标准种子(自由生长) |
|
||||
| 人物认知 | FOAF + 自定义心智模型类型 |
|
||||
| 通用主题 | Schema.org |
|
||||
|
||||
---
|
||||
|
||||
## Agent 如何使用种子
|
||||
|
||||
### 在 ingest 时
|
||||
|
||||
```
|
||||
1. 读取源文件,提取关键实体
|
||||
2. 如果 meta.yaml 声明了 seed → 读取种子文件,对照词表:
|
||||
- 该实体是否有标准名称?→ 使用标准名称作为页面 slug
|
||||
- 该实体属于哪个标准类别?→ 放入对应的 entities/ 或 concepts/
|
||||
- 是否触碰禁混规则?→ 在页面中明确区分
|
||||
3. 正常执行 ingest 后续步骤
|
||||
```
|
||||
|
||||
**示例**(以金融领域为例):
|
||||
```
|
||||
源文件提到某银行的养老金业务
|
||||
→ 对照种子词表:这是一个 Organization,担任 Trustee 角色
|
||||
→ 创建 entities/bank-x.md(机构页面)
|
||||
→ 在关系中标注 bank-x 担任 trustee 角色
|
||||
→ 不创建 entities/bank-x-trustee.md(机构 ≠ 角色,遵守禁混规则)
|
||||
```
|
||||
|
||||
### 在 lint 时
|
||||
|
||||
```
|
||||
1. 如果 meta.yaml 声明了 seed → 读取种子文件
|
||||
2. 检查页面命名是否与种子词表对齐
|
||||
3. 检查是否违反禁混规则
|
||||
4. 检查是否有种子中的关键维度未被覆盖
|
||||
5. 在健康报告中输出对齐度评分
|
||||
```
|
||||
|
||||
### 在 query 时
|
||||
|
||||
```
|
||||
用户提问某个领域术语
|
||||
→ Agent 从种子词表知道该术语的标准位置和关联概念
|
||||
→ 搜索时扩展到关联概念
|
||||
→ 回答更全面
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 外部校验器
|
||||
|
||||
种子文件可以声明关联的外部校验器(frontmatter 中的 `validator` 字段)。
|
||||
校验器提供运行时逻辑校验,超越种子的静态词表——校验关系的 domain/range 合法性、实体的必要关系是否缺失等。
|
||||
|
||||
校验器配置存放在 `validators/` 目录。详见具体校验器文档。
|
||||
|
||||
当前可用校验器:
|
||||
|
||||
| 校验器 | 说明 | 对应种子 |
|
||||
|--------|------|---------|
|
||||
| `validators/fibo-mcp.md` | FIBO SPARQL 逻辑校验(627K 推理三元组) | `fibo-pensions` |
|
||||
|
||||
**校验器是可选增强**。不可达时 lint 退化为 schema.py 格式校验 + 种子静态规则,不影响核心流程。
|
||||
|
||||
---
|
||||
|
||||
## 局限
|
||||
|
||||
1. **种子只是起点,不是终点**。Wiki 积累后会产生种子中没有的概念
|
||||
2. **标准术语可能与行业实际用语不同**。以行业用语为准,但在页面中标注标准术语映射
|
||||
3. **不是所有领域都有成熟的标准本体**。没有合适种子的领域,wiki 自由生长即可
|
||||
4. **种子本身也在演化**。标准更新时,wiki 不需要同步更新——种子只在冷启动时参考
|
||||
141
.claude/skills/auto-wiki-cn/references/source-validation.md
Normal file
141
.claude/skills/auto-wiki-cn/references/source-validation.md
Normal file
@ -0,0 +1,141 @@
|
||||
# 信息来源校验
|
||||
|
||||
## 来源可信度分级
|
||||
|
||||
每条信息标注来源类型,不同类型对应不同 confidence 默认值:
|
||||
|
||||
| 来源类型 | 标记 | 默认 confidence | 说明 |
|
||||
|---------|------|----------------|------|
|
||||
| 一手来源 | `[一手]` | high | 本人著作、官方文件、原始数据、亲自参与的会议 |
|
||||
| 权威二手 | `[二手·权威]` | high | 权威媒体报道、学术论文、监管机构发布 |
|
||||
| 普通二手 | `[二手]` | medium | 行业研报、分析文章、第三方整理 |
|
||||
| 转述 | `[转述]` | low | 二次加工内容、社交媒体讨论、未标来源的信息 |
|
||||
| 推断 | `[推断]` | low | Agent 或用户基于已有信息推导的结论 |
|
||||
| 口述 | `[口述]` | medium | 用户口述、会议纪要、未录音的对话 |
|
||||
|
||||
### 在 source 摘要页中标注
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: 人社部企业年金新规
|
||||
type: source
|
||||
source_type: 一手 # 来源类型
|
||||
source_origin: 人社部官网 # 来源出处
|
||||
source_date: 2026-04-01 # 原始材料日期
|
||||
source_url: "" # 来源 URL(如有)
|
||||
---
|
||||
|
||||
## 关键信息
|
||||
|
||||
- [一手] 企业年金可携带转移新规发布...
|
||||
- [一手] 受托人准入门槛提升至...
|
||||
```
|
||||
|
||||
### 在实体/概念页中标注
|
||||
|
||||
当引用不同可信度的来源时,在正文中标注:
|
||||
|
||||
```markdown
|
||||
## 管理规模
|
||||
|
||||
截至 2025 年底,管理规模达 1200 亿元。[一手](来源:[[2026-04-06-hrss-policy]])
|
||||
|
||||
市场排名约第 3 位。[二手](来源:[[2025-industry-report]],非官方数据)
|
||||
```
|
||||
|
||||
## 来源黑名单
|
||||
|
||||
以下渠道不作为独立来源使用(可作为线索但不引用):
|
||||
|
||||
| 渠道 | 原因 |
|
||||
|------|------|
|
||||
| 知乎 | 洗稿严重,信息失真率高 |
|
||||
| 微信公众号 | 封闭生态,无法验证,大量二手转述 |
|
||||
| 百度百科/百度知道 | 信息陈旧且不可靠 |
|
||||
| 未标来源的聚合内容 | 无法追溯,不可审计 |
|
||||
|
||||
**中文可接受渠道**:36氪、极客公园、晚点 LatePost、财新、第一财经、虎嗅、少数派、机器之心、监管机构官网、上市公司公告。
|
||||
|
||||
## 工具依赖检查
|
||||
|
||||
### 首次使用时的环境检查
|
||||
|
||||
Agent 在第一次执行 ingest 时,检查用户环境中的可用工具:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 知识编译器 · 环境检查 │
|
||||
│ │
|
||||
│ ✅ 文件读写 — 可以创建和编辑 wiki 页面 │
|
||||
│ ✅ 本地搜索 — 可以 grep wiki 内容 │
|
||||
│ │
|
||||
│ ⚠️ 以下能力取决于你的配置: │
|
||||
│ {?} 网络搜索 — 需要 WebSearch 工具或搜索类 MCP │
|
||||
│ {?} 网页读取 — 需要 WebFetch 工具 │
|
||||
│ {?} PDF 读取 — 需要 Agent 支持 PDF(Claude Code 支持) │
|
||||
│ {?} 领域数据 — 需要对应领域的数据 MCP │
|
||||
│ │
|
||||
│ 📝 当前模式: │
|
||||
│ • 用户提供源文件 → ✅ 随时可用 │
|
||||
│ • Agent 自主搜索 → 需要搜索工具 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 两种工作模式
|
||||
|
||||
| 模式 | 需要的工具 | 说明 |
|
||||
|------|-----------|------|
|
||||
| **被动模式** | 只需文件读写 | 用户提供源文件,Agent 编译进 wiki。零依赖 |
|
||||
| **主动模式** | 搜索 + 网页读取 | Agent 自主搜索信息,再 ingest。需要 MCP/WebSearch |
|
||||
|
||||
**Skill 默认为被动模式**——用户丢文件进来,Agent 编译。不假设用户有任何搜索工具。
|
||||
|
||||
**当用户说"帮我研究 XX"但没提供源文件时**:
|
||||
|
||||
```
|
||||
Agent: 你希望我怎么获取材料?
|
||||
|
||||
1. 你提供文件/文本 → 我直接编译(推荐)
|
||||
2. 我自主搜索 → 需要确认你有以下工具:
|
||||
- WebSearch 或搜索类 MCP 工具
|
||||
- WebFetch(读取网页内容)
|
||||
如果没有,我无法自主获取材料。
|
||||
```
|
||||
|
||||
### 搜索工具适配
|
||||
|
||||
如果用户有搜索工具,Agent 的 ingest 流程扩展为:
|
||||
|
||||
```
|
||||
1. 根据研究主题制定搜索计划(搜什么、去哪搜)
|
||||
2. 执行搜索,获取候选来源列表
|
||||
3. 按来源可信度分级,优先读取一手/权威来源
|
||||
4. 对每个有价值的来源执行标准 ingest 流程
|
||||
5. 在 source 摘要页中记录搜索关键词和筛选过程
|
||||
```
|
||||
|
||||
**关键原则:搜索是获取源文件的手段,不改变 ingest 的核心逻辑(读旧比新改旧)。**
|
||||
|
||||
### Deep-Dive 搜索来源的标注
|
||||
|
||||
当来源由 deep-dive 管道的自动搜索获取时,source 摘要页需要额外记录 deep-dive 元数据:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: Alpha Corp 2025 年报摘要
|
||||
type: source
|
||||
source_type: 二手 # 按实际判定,不因搜索方式改变
|
||||
source_origin: 财新网
|
||||
source_date: 2025-06-15
|
||||
source_url: "https://..."
|
||||
deep_dive_meta: # deep-dive 特有字段
|
||||
search_query: "Alpha Corp 企业年金 年报 2025"
|
||||
gap_filled: "single_source:alpha-corp"
|
||||
search_date: 2026-04-09
|
||||
---
|
||||
```
|
||||
|
||||
**Confidence 上限规则**:
|
||||
- 搜索获取的来源,confidence 上限为 medium,除非来源满足"一手"或"二手·权威"标准
|
||||
- 多个搜索来源佐证同一结论时,confidence 可提升至 high
|
||||
- 来源的 source_type 仍按标准分级判定,不因获取方式(搜索 vs 用户提供)而改变
|
||||
205
.claude/skills/auto-wiki-cn/references/storage-spec.md
Normal file
205
.claude/skills/auto-wiki-cn/references/storage-spec.md
Normal file
@ -0,0 +1,205 @@
|
||||
# 存储规格
|
||||
|
||||
## Wiki 根目录
|
||||
|
||||
所有 wiki 存储在工作目录下的 `.wiki/` 目录中:
|
||||
|
||||
```
|
||||
{项目根目录}/
|
||||
└── .wiki/
|
||||
├── enterprise-annuity/ # 一个研究主题一个目录
|
||||
├── charlie-munger/
|
||||
└── public-fund/
|
||||
```
|
||||
|
||||
**位置选择逻辑**(按优先级):
|
||||
1. 如果当前目录有 `.wiki/` → 使用它
|
||||
2. 如果当前目录有 `.claude/` → 在同级创建 `.wiki/`
|
||||
3. 如果当前目录是 git repo 根 → 在根目录创建 `.wiki/`
|
||||
4. 否则 → 在当前目录创建 `.wiki/`
|
||||
|
||||
**与 .gitignore 的关系**:建议将 `.wiki/` 加入 `.gitignore`(wiki 是个人知识库,不应随项目代码提交)。如果用户希望版本管理 wiki,可以在 `.wiki/` 内单独 `git init`。
|
||||
|
||||
### Obsidian 兼容
|
||||
|
||||
`.wiki/` 目录可直接作为 Obsidian vault 打开(`Open folder as vault`):
|
||||
|
||||
- `[[slug]]` / `[[slug|display]]` → Obsidian Graph View 自动渲染拓扑
|
||||
- YAML frontmatter → Obsidian Properties 面板,可按 confidence、type 等字段过滤
|
||||
- `sources/`、`entities/`、`concepts/` 目录 → Obsidian 文件夹视图
|
||||
|
||||
**首次创建 `.wiki/` 时**,Agent 应初始化 `.obsidian/` 配置目录,启用图谱着色:
|
||||
|
||||
```
|
||||
.wiki/.obsidian/
|
||||
├── graph.json # 图谱配色方案(按 path 和 tag 分组着色)
|
||||
├── app.json # {}
|
||||
├── appearance.json # {}
|
||||
└── core-plugins.json # 启用 graph、backlink、properties、tag-pane
|
||||
```
|
||||
|
||||
`graph.json` 预设 5 个颜色分组:
|
||||
|
||||
| 分组规则 | 颜色 | 说明 |
|
||||
|---------|------|------|
|
||||
| `path:sources/` | 蓝灰 | 源文件 |
|
||||
| `path:entities/` | 青绿 | 实体 |
|
||||
| `path:concepts/` | 翠绿 | 概念 |
|
||||
| `path:analyses/` | 紫色 | 分析 |
|
||||
| `[confidence:contested]` | 红色 | 有争议的节点(Obsidian Properties 语法匹配 frontmatter) |
|
||||
|
||||
同时设置:
|
||||
- `showTags: false`(不在图谱中显示 tag 节点——着色靠 `path:` 和 Properties 查询,tag 仅用于搜索过滤)
|
||||
- `showArrow: true`(显示关系方向)
|
||||
- `textFadeMultiplier: -1.5`(默认显示节点标签)
|
||||
- `search: "-path:index -path:log"`(排除 index 和 log 元文件)
|
||||
|
||||
如果需要排除 `_report.html` 等生成文件,在 Obsidian Settings → Files & Links → Excluded files 中添加 `_*`。
|
||||
|
||||
### 可视化报告
|
||||
|
||||
运行 `python schema.py --report .wiki/{主题}/` 生成 `_report.html`,浏览器打开即可查看:
|
||||
|
||||
- 统计面板:页面数、类型分布、contested 数
|
||||
- 交互式关系图:vis-network.js 渲染,可拖拽、缩放
|
||||
- 数据表:所有结构化数据点(value + unit + period + confidence)
|
||||
- Freshness:按更新日期排序
|
||||
- Coverage Gaps:孤页、缺失页面
|
||||
|
||||
## 单个 Wiki 的目录结构
|
||||
|
||||
```
|
||||
.wiki/{主题名}/
|
||||
├── data.db # 结构化数据(SQLite,store.py 管理)
|
||||
├── meta.yaml # Wiki 元数据(本体类型、创建时间、描述)
|
||||
├── index.md # 页面目录(Agent 自动维护)
|
||||
├── log.md # 操作日志(append-only)
|
||||
├── _report.html # 可视化报告(schema.py --report 生成)
|
||||
├── sources/ # 源文件摘要页(不可变)
|
||||
│ ├── 2026-04-06-policy-doc.md
|
||||
│ └── 2026-04-03-annual-report.md
|
||||
├── entities/ # 实体页(叙事分析)
|
||||
│ ├── alpha-corp.md
|
||||
│ └── regulatory-agency.md
|
||||
├── concepts/ # 概念页
|
||||
│ ├── fiduciary-responsibility.md
|
||||
│ └── portable-annuity.md
|
||||
└── analyses/ # 分析归档页(query 产出)
|
||||
└── market-comparison.md
|
||||
```
|
||||
|
||||
### 数据分层原则
|
||||
|
||||
| 层 | 载体 | 存什么 | 为什么 |
|
||||
|----|------|--------|--------|
|
||||
| **叙事层** | Markdown 页面 | 分析、上下文、wikilink | 人类阅读、Obsidian 浏览 |
|
||||
| **数据层** | data.db (SQLite) | 数值、时序、关系、history | 聚合查询、跨页面对比、时间线 |
|
||||
| **元数据层** | YAML frontmatter | title, type, created, updated, sources, confidence | 页面身份标识 |
|
||||
|
||||
**Frontmatter 不再存储 `data` 和 `history` 字段。** 所有结构化数据写入 `data.db`。
|
||||
Frontmatter 只保留:`title`, `type`, `created`, `updated`, `sources`, `confidence`。
|
||||
`relations` 在 frontmatter 中保留(Obsidian wikilink 渲染需要),同时写入 `data.db`(查询需要)。
|
||||
|
||||
### data.db 初始化
|
||||
|
||||
Wiki 创建时,Agent 运行 `python store.py init .wiki/{主题}/` 初始化数据库。
|
||||
表结构见 `store.py`,包含:`pages`, `data_points`, `history`, `relations`。
|
||||
|
||||
## meta.yaml
|
||||
|
||||
每个 wiki 的元信息,创建时写入,后续 lint 时更新统计:
|
||||
|
||||
```yaml
|
||||
name: my-research-topic
|
||||
ontology_type: domain # domain | cognitive | general
|
||||
description: 研究主题描述
|
||||
seed: fibo-pensions # 可选,引用 seeds/ 下的种子文件名
|
||||
created: 2026-04-06
|
||||
last_ingest: 2026-04-06
|
||||
stats:
|
||||
sources: 3
|
||||
entities: 8
|
||||
concepts: 5
|
||||
analyses: 1
|
||||
total_pages: 17
|
||||
contested_count: 1
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| name | 是 | wiki 目录名 |
|
||||
| ontology_type | 是 | domain / cognitive / general |
|
||||
| description | 是 | 一句话描述研究主题 |
|
||||
| seed | 否 | 种子配置名,对应 `seeds/{name}.md`。不设则无种子,wiki 自由生长 |
|
||||
| created | 是 | 创建日期 |
|
||||
| last_ingest | 是 | 最近 ingest 日期(Agent 更新) |
|
||||
| stats | 是 | 页面统计(Agent / lint 更新) |
|
||||
|
||||
## 初始化模板
|
||||
|
||||
### 新建 Wiki 时创建的 index.md
|
||||
|
||||
```markdown
|
||||
# {主题名} Wiki Index
|
||||
|
||||
> 0 pages | Created: {日期} | Type: {ontology_type}
|
||||
|
||||
## Sources (0)
|
||||
|
||||
## Entities (0)
|
||||
|
||||
## Concepts (0)
|
||||
|
||||
## Analyses (0)
|
||||
```
|
||||
|
||||
### 新建 Wiki 时创建的 log.md
|
||||
|
||||
```markdown
|
||||
# {主题名} Wiki Log
|
||||
|
||||
## {日期} — init
|
||||
- Created wiki: {主题名}
|
||||
- Ontology type: {ontology_type}
|
||||
- Description: {描述}
|
||||
```
|
||||
|
||||
## 文件大小预期
|
||||
|
||||
| Wiki 规模 | 源文件数 | 总页面数 | 磁盘占用 | 适用检索方式 |
|
||||
|-----------|---------|---------|---------|------------|
|
||||
| 小 | 1-10 | 5-30 | < 1 MB | grep + 读 index |
|
||||
| 中 | 10-50 | 30-150 | 1-10 MB | grep + 读 index |
|
||||
| 大 | 50-200 | 150-500 | 10-50 MB | 需要升级到索引检索(超出 Skill 范围) |
|
||||
|
||||
**默认模式上限:~500 页。** 超过后启用 SQLite FTS5 索引(零依赖,Python 自带),支持 BM25 排序和反向链接查询。详见 `scaling.md`。
|
||||
|
||||
> 再往上(需要向量检索 / 多用户协作),迁移到外部平台。
|
||||
|
||||
## 跨 Wiki 操作
|
||||
|
||||
`query` 默认在单个 wiki 内搜索。如果用户问的问题跨越多个 wiki:
|
||||
|
||||
```
|
||||
用户:Munger 的投资框架和企业年金领域有什么交叉?
|
||||
|
||||
Agent:
|
||||
1. 读 .wiki/ 下的目录列表,识别相关 wiki
|
||||
2. 分别读 charlie-munger/index.md 和 enterprise-annuity/index.md
|
||||
3. 在两个 wiki 中搜索相关页面
|
||||
4. 综合回答,标注来源属于哪个 wiki
|
||||
```
|
||||
|
||||
## 源文件处理
|
||||
|
||||
不同格式的源文件,ingest 时的处理方式:
|
||||
|
||||
| 格式 | 处理方式 | 说明 |
|
||||
|------|---------|------|
|
||||
| 文本 / Markdown | 直接读取 | 最理想的输入格式 |
|
||||
| PDF | 读取文本内容(Agent 能力范围内) | 复杂排版可能丢失结构 |
|
||||
| Excel / CSV | 读取数据,提取关键指标 | 数值数据写入实体页的"关键数据"段落 |
|
||||
| 用户口述 / 对话文本 | 作为文本 ingest | 标注来源为"口述",confidence 默认 medium |
|
||||
| URL / 网页 | 用 WebFetch 获取内容后 ingest | 标注来源 URL |
|
||||
|
||||
**Agent 不存储源文件原件**——只存储 source 摘要页。原件由用户自行管理。
|
||||
307
.claude/skills/auto-wiki-cn/references/store.py
Normal file
307
.claude/skills/auto-wiki-cn/references/store.py
Normal file
@ -0,0 +1,307 @@
|
||||
"""Wiki 结构化数据存储层。
|
||||
|
||||
每个 wiki 目录下维护一个 data.db(SQLite),存储所有结构化数据。
|
||||
Markdown 页面只负责叙事分析,不在 frontmatter 中存储 data/history。
|
||||
|
||||
用法:
|
||||
from store import WikiStore
|
||||
|
||||
store = WikiStore(".wiki/my-research/")
|
||||
store.upsert_data("alpha-corp", "管理规模", 1200, "亿元", "2025-12", "2026-04-policy-doc")
|
||||
store.add_relation("alpha-corp", "受托人市场格局", "part_of")
|
||||
|
||||
# 查询
|
||||
rows = store.query_data(page_slug="alpha-corp")
|
||||
timeline = store.query_timeline(field="管理规模")
|
||||
|
||||
CLI:
|
||||
python store.py init .wiki/my-research/
|
||||
python store.py dump .wiki/my-research/
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlite3
|
||||
import sys
|
||||
from datetime import date, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
SCHEMA_SQL = """
|
||||
CREATE TABLE IF NOT EXISTS pages (
|
||||
slug TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK(type IN ('source','entity','concept','analysis','mental-model')),
|
||||
confidence TEXT NOT NULL DEFAULT 'medium' CHECK(confidence IN ('high','medium','low','contested')),
|
||||
created TEXT NOT NULL,
|
||||
updated TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS data_points (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
page_slug TEXT NOT NULL REFERENCES pages(slug),
|
||||
field TEXT NOT NULL,
|
||||
value REAL NOT NULL,
|
||||
unit TEXT NOT NULL,
|
||||
period TEXT NOT NULL,
|
||||
source_slug TEXT NOT NULL,
|
||||
scope TEXT,
|
||||
verified INTEGER, -- NULL=unknown, 0=false, 1=true
|
||||
confidence TEXT DEFAULT 'high' CHECK(confidence IN ('high','medium','low','contested')),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE(page_slug, field, period) -- 同一页面同一字段同一时段只留一条(upsert 覆盖)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
page_slug TEXT NOT NULL REFERENCES pages(slug),
|
||||
field TEXT NOT NULL,
|
||||
old_value REAL NOT NULL,
|
||||
old_unit TEXT NOT NULL,
|
||||
old_source TEXT NOT NULL,
|
||||
new_source TEXT,
|
||||
reason TEXT NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS relations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
from_slug TEXT NOT NULL,
|
||||
to_slug TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE(from_slug, to_slug, type)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_dp_page ON data_points(page_slug);
|
||||
CREATE INDEX IF NOT EXISTS idx_dp_field ON data_points(field);
|
||||
CREATE INDEX IF NOT EXISTS idx_dp_period ON data_points(period);
|
||||
CREATE INDEX IF NOT EXISTS idx_rel_from ON relations(from_slug);
|
||||
CREATE INDEX IF NOT EXISTS idx_rel_to ON relations(to_slug);
|
||||
CREATE INDEX IF NOT EXISTS idx_hist_page ON history(page_slug);
|
||||
"""
|
||||
|
||||
|
||||
class WikiStore:
|
||||
"""单个 wiki 的 SQLite 存储接口。"""
|
||||
|
||||
def __init__(self, wiki_dir: str | Path):
|
||||
self.wiki_dir = Path(wiki_dir)
|
||||
self.db_path = self.wiki_dir / "data.db"
|
||||
self._conn: Optional[sqlite3.Connection] = None
|
||||
|
||||
@property
|
||||
def conn(self) -> sqlite3.Connection:
|
||||
if self._conn is None:
|
||||
self._conn = sqlite3.connect(str(self.db_path))
|
||||
self._conn.row_factory = sqlite3.Row
|
||||
self._conn.execute("PRAGMA journal_mode=WAL")
|
||||
self._conn.execute("PRAGMA foreign_keys=ON")
|
||||
return self._conn
|
||||
|
||||
def init_db(self) -> None:
|
||||
"""创建表结构(幂等)。"""
|
||||
self.conn.executescript(SCHEMA_SQL)
|
||||
self.conn.commit()
|
||||
|
||||
def close(self) -> None:
|
||||
if self._conn:
|
||||
self._conn.close()
|
||||
self._conn = None
|
||||
|
||||
# ── Pages ──
|
||||
|
||||
def upsert_page(self, slug: str, title: str, page_type: str,
|
||||
confidence: str = "medium",
|
||||
created: str = "", updated: str = "") -> None:
|
||||
today = date.today().isoformat()
|
||||
self.conn.execute("""
|
||||
INSERT INTO pages (slug, title, type, confidence, created, updated)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(slug) DO UPDATE SET
|
||||
title=excluded.title, type=excluded.type,
|
||||
confidence=excluded.confidence, updated=excluded.updated
|
||||
""", (slug, title, page_type, confidence, created or today, updated or today))
|
||||
self.conn.commit()
|
||||
|
||||
# ── Data Points ──
|
||||
|
||||
def upsert_data(self, page_slug: str, field: str, value: float,
|
||||
unit: str, period: str, source_slug: str,
|
||||
scope: str = None, verified: bool = None,
|
||||
confidence: str = "high") -> Optional[dict]:
|
||||
"""写入数据点。如果同字段同时段已有旧值,自动写入 history 并返回旧记录。"""
|
||||
# 查旧值
|
||||
old = self.conn.execute(
|
||||
"SELECT value, unit, source_slug FROM data_points WHERE page_slug=? AND field=? AND period=?",
|
||||
(page_slug, field, period)
|
||||
).fetchone()
|
||||
|
||||
old_record = None
|
||||
if old and old["value"] != value:
|
||||
old_record = dict(old)
|
||||
# 写 history
|
||||
self.conn.execute("""
|
||||
INSERT INTO history (page_slug, field, old_value, old_unit, old_source, new_source, reason, date)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", (page_slug, field, old["value"], old["unit"], old["source_slug"],
|
||||
source_slug, f"{field}: {old['value']} → {value}", date.today().isoformat()))
|
||||
|
||||
# upsert data point
|
||||
v_int = None if verified is None else (1 if verified else 0)
|
||||
self.conn.execute("""
|
||||
INSERT INTO data_points (page_slug, field, value, unit, period, source_slug, scope, verified, confidence)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(page_slug, field, period) DO UPDATE SET
|
||||
value=excluded.value, unit=excluded.unit,
|
||||
source_slug=excluded.source_slug, scope=excluded.scope,
|
||||
verified=excluded.verified, confidence=excluded.confidence
|
||||
""", (page_slug, field, value, unit, period, source_slug, scope, v_int, confidence))
|
||||
self.conn.commit()
|
||||
return old_record
|
||||
|
||||
# ── Relations ──
|
||||
|
||||
def add_relation(self, from_slug: str, to_slug: str, rel_type: str) -> None:
|
||||
self.conn.execute("""
|
||||
INSERT OR IGNORE INTO relations (from_slug, to_slug, type)
|
||||
VALUES (?, ?, ?)
|
||||
""", (from_slug, to_slug, rel_type))
|
||||
self.conn.commit()
|
||||
|
||||
# ── Queries ──
|
||||
|
||||
def query_data(self, page_slug: str = None, field: str = None) -> list[dict]:
|
||||
"""查询数据点。可按页面或字段过滤。"""
|
||||
sql = "SELECT * FROM data_points WHERE 1=1"
|
||||
params: list = []
|
||||
if page_slug:
|
||||
sql += " AND page_slug=?"
|
||||
params.append(page_slug)
|
||||
if field:
|
||||
sql += " AND field=?"
|
||||
params.append(field)
|
||||
sql += " ORDER BY period DESC"
|
||||
return [dict(r) for r in self.conn.execute(sql, params).fetchall()]
|
||||
|
||||
def query_timeline(self, field: str, page_slug: str = None) -> list[dict]:
|
||||
"""查询某字段的时间线(含历史值)。"""
|
||||
# 当前值
|
||||
sql = "SELECT page_slug, field, value, unit, period, source_slug, 'current' as status FROM data_points WHERE field=?"
|
||||
params: list = [field]
|
||||
if page_slug:
|
||||
sql += " AND page_slug=?"
|
||||
params.append(page_slug)
|
||||
|
||||
# 历史值
|
||||
sql2 = "SELECT page_slug, field, old_value as value, old_unit as unit, date as period, old_source as source_slug, 'superseded' as status FROM history WHERE field=?"
|
||||
params2: list = [field]
|
||||
if page_slug:
|
||||
sql2 += " AND page_slug=?"
|
||||
params2.append(page_slug)
|
||||
|
||||
rows = [dict(r) for r in self.conn.execute(sql, params).fetchall()]
|
||||
rows += [dict(r) for r in self.conn.execute(sql2, params2).fetchall()]
|
||||
rows.sort(key=lambda r: r.get("period", ""), reverse=True)
|
||||
return rows
|
||||
|
||||
def query_relations(self, slug: str = None, rel_type: str = None) -> list[dict]:
|
||||
sql = "SELECT * FROM relations WHERE 1=1"
|
||||
params: list = []
|
||||
if slug:
|
||||
sql += " AND (from_slug=? OR to_slug=?)"
|
||||
params += [slug, slug]
|
||||
if rel_type:
|
||||
sql += " AND type=?"
|
||||
params.append(rel_type)
|
||||
return [dict(r) for r in self.conn.execute(sql, params).fetchall()]
|
||||
|
||||
def get_page(self, slug: str) -> Optional[dict]:
|
||||
row = self.conn.execute("SELECT * FROM pages WHERE slug=?", (slug,)).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
def list_pages(self, page_type: str = None) -> list[dict]:
|
||||
sql = "SELECT * FROM pages"
|
||||
params: list = []
|
||||
if page_type:
|
||||
sql += " WHERE type=?"
|
||||
params.append(page_type)
|
||||
sql += " ORDER BY updated DESC"
|
||||
return [dict(r) for r in self.conn.execute(sql, params).fetchall()]
|
||||
|
||||
def stats(self) -> dict:
|
||||
"""返回 wiki 数据库统计。"""
|
||||
s: dict[str, Any] = {}
|
||||
s["pages"] = self.conn.execute("SELECT COUNT(*) FROM pages").fetchone()[0]
|
||||
s["data_points"] = self.conn.execute("SELECT COUNT(*) FROM data_points").fetchone()[0]
|
||||
s["relations"] = self.conn.execute("SELECT COUNT(*) FROM relations").fetchone()[0]
|
||||
s["contested"] = self.conn.execute("SELECT COUNT(*) FROM data_points WHERE confidence='contested'").fetchone()[0]
|
||||
for row in self.conn.execute("SELECT type, COUNT(*) as cnt FROM pages GROUP BY type").fetchall():
|
||||
s[f"pages_{row['type']}"] = row["cnt"]
|
||||
return s
|
||||
|
||||
def dump(self) -> str:
|
||||
"""输出人类可读的数据库摘要。"""
|
||||
st = self.stats()
|
||||
lines = [
|
||||
f"Wiki Store: {self.wiki_dir.name}",
|
||||
f"{'='*50}",
|
||||
f"Pages: {st['pages']} | Data Points: {st['data_points']} | Relations: {st['relations']} | Contested: {st['contested']}",
|
||||
"",
|
||||
]
|
||||
# pages by type
|
||||
for pt in ["entity", "concept", "source", "analysis", "mental-model"]:
|
||||
key = f"pages_{pt}"
|
||||
if st.get(key):
|
||||
lines.append(f" {pt}: {st[key]}")
|
||||
|
||||
# recent data points
|
||||
recent = self.conn.execute(
|
||||
"SELECT page_slug, field, value, unit, period FROM data_points ORDER BY created_at DESC LIMIT 10"
|
||||
).fetchall()
|
||||
if recent:
|
||||
lines += ["", "Recent Data Points:"]
|
||||
for r in recent:
|
||||
lines.append(f" {r['page_slug']}.{r['field']} = {r['value']} {r['unit']} ({r['period']})")
|
||||
|
||||
# relations
|
||||
rels = self.conn.execute("SELECT * FROM relations ORDER BY created_at DESC LIMIT 10").fetchall()
|
||||
if rels:
|
||||
lines += ["", "Recent Relations:"]
|
||||
for r in rels:
|
||||
lines.append(f" {r['from_slug']} --{r['type']}--> {r['to_slug']}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ── CLI ──
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage:")
|
||||
print(" python store.py init <wiki_dir> — initialize data.db")
|
||||
print(" python store.py dump <wiki_dir> — dump database summary")
|
||||
sys.exit(1)
|
||||
|
||||
cmd, target = sys.argv[1], Path(sys.argv[2])
|
||||
store = WikiStore(target)
|
||||
|
||||
if cmd == "init":
|
||||
store.init_db()
|
||||
print(f"Initialized: {store.db_path}")
|
||||
elif cmd == "dump":
|
||||
if not store.db_path.exists():
|
||||
print(f"No data.db found in {target}")
|
||||
sys.exit(1)
|
||||
print(store.dump())
|
||||
else:
|
||||
print(f"Unknown command: {cmd}")
|
||||
sys.exit(1)
|
||||
|
||||
store.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
308
.claude/skills/auto-wiki-cn/references/wiki-format.md
Normal file
308
.claude/skills/auto-wiki-cn/references/wiki-format.md
Normal file
@ -0,0 +1,308 @@
|
||||
# Wiki 页面格式
|
||||
|
||||
> **所有 frontmatter 结构由 `schema.py` 中的 Pydantic 模型定义和校验。**
|
||||
> 本文档是人类可读的规范说明,`schema.py` 是机器可执行的校验工具。两者必须一致。
|
||||
> 校验命令:`python references/schema.py .wiki/{主题}/`
|
||||
|
||||
## 目录结构
|
||||
|
||||
每个研究主题一个目录:
|
||||
|
||||
```
|
||||
{主题名}/
|
||||
├── meta.yaml # Wiki 元数据(见 storage-spec.md)
|
||||
├── index.md # 页面目录(Agent 维护,按类型分组)
|
||||
├── log.md # 操作日志(append-only,人类可读)
|
||||
├── sources/ # 源文件摘要
|
||||
├── entities/ # 实体页(机构、人物、产品)
|
||||
├── concepts/ # 概念页(制度、方法、指标)
|
||||
└── analyses/ # 分析归档(query 产出的有价值分析)
|
||||
```
|
||||
|
||||
cognitive 类型 wiki 的目录变体:
|
||||
```
|
||||
{人物名}/
|
||||
├── mental-models/ # 替代 entities/——每个心智模型一个页面
|
||||
├── concepts/ # 启发式、价值观、表达风格、矛盾
|
||||
├── sources/ # 采集来源
|
||||
└── analyses/ # 分析归档
|
||||
```
|
||||
|
||||
## 页面格式
|
||||
|
||||
每个页面分为两部分:**YAML frontmatter(结构化数据)** + **Markdown 正文(叙事分析)**。
|
||||
|
||||
**核心原则:数据放 YAML,分析放正文。** 正文不写数据表格。
|
||||
|
||||
---
|
||||
|
||||
## Frontmatter Schema
|
||||
|
||||
### 基础字段(所有页面必填)
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 页面标题
|
||||
type: entity # entity | concept | source | analysis | mental-model
|
||||
created: 2026-04-06
|
||||
updated: 2026-04-06
|
||||
sources: [source-slug-1, source-slug-2]
|
||||
confidence: high # high | medium | low | contested
|
||||
tags: [entity] # 必填:页面类型 + 可选状态标签
|
||||
aliases: [] # 可选:页面别名
|
||||
---
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| title | 是 | 页面标题 |
|
||||
| type | 是 | 页面类型 |
|
||||
| created | 是 | 创建日期 YYYY-MM-DD |
|
||||
| updated | 是 | 最后更新日期(每次修改必须更新) |
|
||||
| sources | 是 | 引用的 source 页面 slug 列表(source 类型页面填 `[]`) |
|
||||
| confidence | 是 | 置信度:`high` / `medium` / `low` / `contested` |
|
||||
| tags | 是 | Obsidian 标签列表,用于搜索过滤和分类浏览 |
|
||||
| aliases | 否 | 页面别名列表,用于 Obsidian 搜索和链接补全 |
|
||||
|
||||
### tags 规则(Obsidian 搜索过滤必需)
|
||||
|
||||
`tags` 必须包含页面类型(`source` / `entity` / `concept` / `analysis` / `mental-model`),可追加状态标签:
|
||||
|
||||
```yaml
|
||||
tags:
|
||||
- concept # 必填:页面类型
|
||||
- contested # 可选:confidence=contested 时加
|
||||
- low-confidence # 可选:confidence=low 时加
|
||||
```
|
||||
|
||||
source 类型页面额外加来源等级标签:
|
||||
|
||||
```yaml
|
||||
tags:
|
||||
- source
|
||||
- primary-source # 一手来源
|
||||
# 或 authoritative-secondary / secondary / hearsay / inference
|
||||
```
|
||||
|
||||
这些标签用于 Obsidian 搜索过滤(如在搜索栏输入 `tag:#contested` 快速定位有争议的页面)。图谱着色不依赖 tags——靠 `path:` 规则区分页面类型,靠 `[confidence:contested]` Properties 查询高亮风险节点。
|
||||
|
||||
### aliases 规则
|
||||
|
||||
标题含括号说明时,拆出短名和括号内容作为别名:
|
||||
|
||||
```yaml
|
||||
title: EET 税收模式(个人养老金税优机制)
|
||||
aliases:
|
||||
- EET 税收模式
|
||||
- 个人养老金税优机制
|
||||
```
|
||||
|
||||
### 结构化数据 → data.db
|
||||
|
||||
**所有可量化、可查证、可对比的数据写入 `data.db`(SQLite),不放在 frontmatter 中。**
|
||||
|
||||
Agent 在 ingest 时调用 `store.py` 的 `WikiStore` 接口:
|
||||
|
||||
```python
|
||||
store.upsert_data("alpha-corp", "管理规模", 1350, "亿元", "2025-Q1", "2026-04-policy-doc", scope="含职业年金")
|
||||
store.upsert_data("alpha-corp", "市场份额", 12, "%", "2025-Q1", "2026-04-policy-doc", confidence="contested")
|
||||
```
|
||||
|
||||
如果同字段同时段已有旧值,`upsert_data` 自动将旧值写入 `history` 表并返回旧记录。
|
||||
|
||||
**数据字段规范**(由 `store.py: data_points` 表约束):
|
||||
|
||||
| 字段 | 必填 | 类型 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `page_slug` | **是** | TEXT | 所属页面的 slug |
|
||||
| `field` | **是** | TEXT | 数据维度名(如"管理规模") |
|
||||
| `value` | **是** | REAL | 数值 |
|
||||
| `unit` | **是** | TEXT | 单位(亿元、%、万人、家...) |
|
||||
| `period` | **是** | TEXT | 数据时点(如 "2023-12"、"2025-Q1") |
|
||||
| `source_slug` | **是** | TEXT | source 页面 slug |
|
||||
| `scope` | 否 | TEXT | 统计口径说明 |
|
||||
| `verified` | 否 | INTEGER | NULL=未知, 0=未验证, 1=已验证 |
|
||||
| `confidence` | 否 | TEXT | 该数据点的置信度 |
|
||||
|
||||
**每个数字都必须有出处。** `source_slug` 指向哪个 source 页面。
|
||||
|
||||
**查询示例**:
|
||||
|
||||
```python
|
||||
# 某机构的所有数据
|
||||
store.query_data(page_slug="alpha-corp")
|
||||
|
||||
# 某字段的时间线(含历史值)
|
||||
store.query_timeline(field="管理规模")
|
||||
|
||||
# 所有 contested 数据
|
||||
store.conn.execute("SELECT * FROM data_points WHERE confidence='contested'").fetchall()
|
||||
```
|
||||
|
||||
### relations 字段(结构化关系)
|
||||
|
||||
页面间的语义关系,补充正文中的 `[[wikilink]]`:
|
||||
|
||||
```yaml
|
||||
relations:
|
||||
- target: beta-corp
|
||||
type: competes_with
|
||||
- target: 受托人市场格局
|
||||
type: part_of
|
||||
- target: national-council-ssf
|
||||
type: regulated_by
|
||||
```
|
||||
|
||||
**常用关系类型**:
|
||||
|
||||
| type | 含义 | 示例 |
|
||||
|------|------|------|
|
||||
| `part_of` | 属于 | 某机构 part_of 受托人市场 |
|
||||
| `manages` | 管理 | 受托人 manages 年金基金 |
|
||||
| `regulated_by` | 受监管 | 机构 regulated_by 人社部 |
|
||||
| `competes_with` | 竞争 | 机构A competes_with 机构B |
|
||||
| `implements` | 实施 | 机构 implements 受托责任 |
|
||||
| `derived_from` | 来源于 | 概念 B derived_from 概念 A |
|
||||
| `contradicts` | 矛盾 | 数据 A contradicts 数据 B |
|
||||
| `influenced_by` | 受影响(cognitive 类型) | 心智模型 influenced_by 人物 |
|
||||
| `applies_to` | 适用于 | 心智模型 applies_to 领域 |
|
||||
|
||||
**relations 规范**:
|
||||
- `target` 填 slug(不加路径前缀)
|
||||
- `type` 从上表选取,或自定义(但保持项目内一致)
|
||||
- relations 是 frontmatter 中的结构化声明,正文中的 `[[wikilink]]` 是人类可读的引用——两者互补
|
||||
|
||||
### source 类型页面的额外字段
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 人社部2024年度企业年金基金统计报告
|
||||
type: source
|
||||
created: 2026-04-06
|
||||
updated: 2026-04-06
|
||||
sources: []
|
||||
confidence: high
|
||||
source_type: 一手 # 一手 | 二手·权威 | 二手 | 转述 | 推断 | 口述
|
||||
source_origin: 人社部官网
|
||||
source_date: 2024-12-31 # 原始材料的日期(不是 ingest 日期)
|
||||
source_url: "" # 来源 URL(如有)
|
||||
---
|
||||
```
|
||||
|
||||
### cognitive 类型(心智模型页)的额外字段
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 能力圈
|
||||
type: mental-model
|
||||
created: 2026-04-06
|
||||
updated: 2026-04-06
|
||||
sources: [poor-charlies-almanack]
|
||||
confidence: high
|
||||
verification:
|
||||
cross_domain: true # 跨域复现
|
||||
generative: true # 有生成力
|
||||
exclusive: true # 有排他性
|
||||
domains: [投资, 商业决策, 人生选择] # 出现过的领域
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 正文约定
|
||||
|
||||
**正文只写叙事分析和上下文解读,不写数据表格。**
|
||||
|
||||
以下为示例(以金融领域为例):
|
||||
|
||||
```markdown
|
||||
# 某机构业务概况
|
||||
|
||||
该机构是行业规模最大的参与者之一。2025年Q1管理规模增至 XXX 亿元,
|
||||
但市场份额数据因统计口径变更与上期报告不可直接比较。
|
||||
|
||||
[[competitor-a|竞争对手 A]]同期增速行业第一,正在缩小差距。
|
||||
详见 [[市场格局]]。
|
||||
```
|
||||
|
||||
**正文规则**:
|
||||
- 用 `[[slug]]` 或 `[[slug|显示名]]` 做页面链接
|
||||
- 提到数据时引述结论,不重复 frontmatter 中的具体数值(避免不一致)
|
||||
- 可以用 `> ⚠️` blockquote 标注重要警告(如口径差异)
|
||||
- 分析性内容是正文的核心价值——这是 YAML 无法承载的部分
|
||||
|
||||
---
|
||||
|
||||
## 文件命名
|
||||
|
||||
- slug 格式:小写字母 + 连字符,如 `alpha-corp.md`
|
||||
- 中文概念用中文 slug:`受托人市场格局.md`(Obsidian 友好)或拼音 slug
|
||||
- source 页面加日期前缀:`2026-04-06-hrss-report.md`(连字符分隔)
|
||||
|
||||
## index.md 格式
|
||||
|
||||
index 只做导航,不内联数据:
|
||||
|
||||
```markdown
|
||||
# {主题名} Wiki Index
|
||||
|
||||
> {N} pages | Last updated: {日期} | Type: {ontology_type}
|
||||
|
||||
## Entities ({N})
|
||||
- [[alpha-corp]] — 机构 A 养老金业务
|
||||
- [[beta-corp]] — 机构 B 养老保险
|
||||
|
||||
## Concepts ({N})
|
||||
- [[受托人市场格局]] — 受托人竞争格局与份额
|
||||
- [[企业年金制度]] — 政策法规与制度框架
|
||||
|
||||
## Sources ({N})
|
||||
- [[2026-04-06-hrss-report]] — 人社部2024年度报告
|
||||
- [[2026-04-06-q1-briefing]] — 2025年一季度市场简报
|
||||
|
||||
## Analyses ({N})
|
||||
- [[trustee-comparison]] — 受托人市场格局对比分析
|
||||
```
|
||||
|
||||
**index 规则**:每个条目一行,`[[slug]] — 一句话描述`。不放表格、不放统计数据。
|
||||
|
||||
## log.md 格式
|
||||
|
||||
```markdown
|
||||
# {主题名} Wiki Log
|
||||
|
||||
## 2026-04-06 14:30 — ingest
|
||||
- Source: 2026-04-06-hrss-report
|
||||
- Created: entities/alpha-corp, concepts/受托人市场格局
|
||||
- Updated: (无)
|
||||
- Conflicts: (无)
|
||||
|
||||
## 2026-04-06 15:00 — ingest
|
||||
- Source: 2026-04-06-q1-briefing
|
||||
- Updated: entities/alpha-corp (管理规模 1200→1350亿,份额 contested)
|
||||
- Created: entities/beta-corp
|
||||
- Conflicts: alpha-corp.市场份额 (15% vs 12%,口径不同)
|
||||
```
|
||||
|
||||
## Validation Rules
|
||||
|
||||
| 规则 | 要求 |
|
||||
|------|------|
|
||||
| frontmatter 完整 | `title`, `type`, `created`, `updated`, `sources`, `confidence` 六字段必须存在 |
|
||||
| type 值合法 | `source` / `entity` / `concept` / `analysis` / `mental-model` |
|
||||
| data 字段规范 | 每个数据点必须有 `value`, `unit`, `period`, `source` |
|
||||
| history 有 reason | 每条历史记录必须有 `reason` 字段 |
|
||||
| relations 有 type | 每条关系必须有 `target` 和 `type` |
|
||||
| sources 非空 | 除 source 类型外,`sources` 列表至少一个 slug |
|
||||
| 日期格式 | `YYYY-MM-DD` |
|
||||
| slug 与文件名一致 | 文件名(去 `.md`)= slug |
|
||||
|
||||
## 置信度更新规则
|
||||
|
||||
| 事件 | 置信度变化 |
|
||||
|------|-----------|
|
||||
| 新 source 印证已有数据(data 字段一致) | → `high` |
|
||||
| 新 source 更新已有数据(有更新时点/更权威来源) | 更新 data,旧值进 history |
|
||||
| 新 source 与已有数据矛盾且无法判断 | data 中该字段 confidence → `contested` |
|
||||
| lint 发现 data 中有无 source 的字段 | → `low` |
|
||||
| 页面 6 个月未被 ingest 触及 | lint 建议标注"待验证" |
|
||||
63
.claude/skills/auto-wiki-cn/seeds/README.md
Normal file
63
.claude/skills/auto-wiki-cn/seeds/README.md
Normal file
@ -0,0 +1,63 @@
|
||||
# 种子配置(Seeds)
|
||||
|
||||
种子文件为特定领域的 wiki 提供冷启动词表。存放在本目录,每个领域一个文件。
|
||||
|
||||
## 文件格式
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-seed-name # 唯一标识,meta.yaml 中引用此名
|
||||
display_name: 显示名称
|
||||
source: 基于的标准本体名称
|
||||
url: 标准本体的参考链接
|
||||
applies_to: 适用的研究领域描述
|
||||
validator: validators/xxx.md # 可选,关联的外部校验器
|
||||
---
|
||||
|
||||
# 种子标题
|
||||
|
||||
## 词表分类 1
|
||||
|
||||
| 标准概念 | 说明 | wiki 中的对应 |
|
||||
|---------|------|--------------|
|
||||
| ConceptA | ... | entities/ |
|
||||
| ConceptB | ... | concepts/ |
|
||||
|
||||
## 关系模板
|
||||
|
||||
```
|
||||
EntityA --relation_type--> EntityB
|
||||
```
|
||||
|
||||
## 禁混规则
|
||||
|
||||
| 容易混淆的概念对 | 区别 |
|
||||
|----------------|------|
|
||||
| A ≠ B | 说明 |
|
||||
```
|
||||
|
||||
## 如何引用
|
||||
|
||||
在 wiki 的 `meta.yaml` 中设置 `seed` 字段:
|
||||
|
||||
```yaml
|
||||
name: my-research-topic
|
||||
ontology_type: domain
|
||||
seed: my-seed-name # 对应种子文件 frontmatter 中的 name
|
||||
```
|
||||
|
||||
Agent 在首次 ingest 前读取对应的种子文件。
|
||||
|
||||
## 编写原则
|
||||
|
||||
1. **词表要精简**。只列该领域最核心的 20-50 个概念,不要试图覆盖整个标准
|
||||
2. **禁混规则是核心价值**。Agent 最容易搞混的概念对,写清楚区别
|
||||
3. **关系模板要具体**。不要只列关系类型名,要给出 `A --type--> B` 的完整示例
|
||||
4. **允许中文**。概念名用标准英文,但说明和禁混规则用中文(或目标语言)
|
||||
5. **声明校验器是可选的**。如果该领域有可用的外部校验器(如 FIBO MCP),在 frontmatter 的 `validator` 字段指向它
|
||||
|
||||
## 当前可用种子
|
||||
|
||||
| 文件 | 领域 | 概念数 |
|
||||
|------|------|--------|
|
||||
| `fibo-pensions.md` | 企业年金/养老金 | ~30 |
|
||||
82
.claude/skills/auto-wiki-cn/seeds/fibo-pensions.md
Normal file
82
.claude/skills/auto-wiki-cn/seeds/fibo-pensions.md
Normal file
@ -0,0 +1,82 @@
|
||||
---
|
||||
name: fibo-pensions
|
||||
display_name: FIBO 养老金模块
|
||||
source: EDM Council FIBO (Financial Industry Business Ontology)
|
||||
url: https://spec.edmcouncil.org/fibo/
|
||||
applies_to: 企业年金、职业年金、养老金管理
|
||||
validator: validators/fibo-mcp.md
|
||||
---
|
||||
|
||||
# FIBO 养老金种子词表
|
||||
|
||||
> 用于企业年金/养老金领域 wiki 的冷启动参考。
|
||||
> 在 `meta.yaml` 中设置 `seed: fibo-pensions` 引用本文件。
|
||||
|
||||
## 基础金融概念(FIBO-FND)
|
||||
|
||||
所有金融领域 wiki 都可参考:
|
||||
|
||||
| 标准概念 | 说明 | wiki 中通常对应 |
|
||||
|---------|------|----------------|
|
||||
| LegalEntity | 法人实体 | entities/ 下的机构页面 |
|
||||
| Contract | 合同/协议 | concepts/ 下的制度页面 |
|
||||
| FinancialInstrument | 金融工具 | entities/ 下的产品页面 |
|
||||
| RegulatoryAgency | 监管机构 | entities/ |
|
||||
| Jurisdiction | 管辖区域 | concepts/ |
|
||||
| DatePeriod | 时间段 | frontmatter 的时间字段 |
|
||||
|
||||
## 商业实体(FIBO-BP)
|
||||
|
||||
| 标准概念 | 说明 | 常见混淆 |
|
||||
|---------|------|---------|
|
||||
| Organization | 组织机构 | ≠ OrganizationalRole(机构 ≠ 机构角色) |
|
||||
| FunctionalEntity | 职能实体 | 如"受托人"是角色,不是机构本身 |
|
||||
| Person | 自然人 | |
|
||||
|
||||
## 证券(FIBO-SEC)
|
||||
|
||||
| 标准概念 | 说明 | 适用场景 |
|
||||
|---------|------|---------|
|
||||
| Fund | 基金 | 公募基金、企业年金基金 |
|
||||
| Portfolio | 投资组合 | ≠ Product(组合 ≠ 产品) |
|
||||
| Security | 证券 | |
|
||||
| Issuer | 发行人 | |
|
||||
|
||||
## 养老金专用(FIBO-Pensions)
|
||||
|
||||
| 标准概念 | 中文对应 | 禁混规则 |
|
||||
|---------|---------|---------|
|
||||
| PensionPlan | 企业年金计划 | ≠ PensionFund(计划 ≠ 基金) |
|
||||
| PensionFund | 企业年金基金 | ≠ PensionProduct(基金 ≠ 产品) |
|
||||
| PlanSponsor | 委托人(企业) | |
|
||||
| Trustee | 受托人 | 是角色,不是机构——同一机构可以同时是受托人和投管人 |
|
||||
| InvestmentManager | 投资管理人 | |
|
||||
| Custodian | 托管人 | |
|
||||
| AccountManager | 账户管理人 | |
|
||||
| Beneficiary | 受益人(职工) | |
|
||||
| VestingSchedule | 归属计划 | |
|
||||
| ContributionRate | 缴费比例 | |
|
||||
| DefinedBenefit | 确定给付型(DB) | ≠ DefinedContribution(DC),中国企业年金是 DC 型 |
|
||||
| DefinedContribution | 确定缴费型(DC) | |
|
||||
|
||||
## 关系模板
|
||||
|
||||
```
|
||||
PlanSponsor --establishes--> PensionPlan
|
||||
PensionPlan --managed_by--> Trustee (受托管理)
|
||||
Trustee --delegates_to--> InvestmentManager (投资管理)
|
||||
Trustee --delegates_to--> Custodian (托管)
|
||||
Trustee --delegates_to--> AccountManager (账户管理)
|
||||
PensionFund --invests_in--> Portfolio
|
||||
Beneficiary --participates_in--> PensionPlan
|
||||
```
|
||||
|
||||
## 禁混规则
|
||||
|
||||
| 容易混淆的概念对 | 区别 |
|
||||
|----------------|------|
|
||||
| PensionPlan ≠ PensionFund | 计划是制度安排,基金是钱 |
|
||||
| PensionFund ≠ PensionProduct | 基金是资金池,产品是投资工具 |
|
||||
| Organization ≠ FunctionalRole | 某银行是机构,受托人是角色;同一机构可以同时担任受托人和账管人 |
|
||||
| PlanType ≠ PortfolioCategory | 计划类型(单一/集合)≠ 投资组合类别(稳健/积极) |
|
||||
| ContributionRate ≠ InvestmentReturn | 缴费率 ≠ 投资回报率 |
|
||||
110
.claude/skills/auto-wiki-cn/validators/fibo-mcp.md
Normal file
110
.claude/skills/auto-wiki-cn/validators/fibo-mcp.md
Normal file
@ -0,0 +1,110 @@
|
||||
# FIBO MCP:运行时逻辑校验器
|
||||
|
||||
> 外部校验器,通过 SPARQL 查询 FIBO 本体(627K 推理三元组)校验 wiki 中知识的逻辑结构。
|
||||
> 可选增强——不可达时 lint 退化为 schema.py 格式校验 + 种子静态规则。
|
||||
>
|
||||
> 基于 [NeuroFusionAI/fibo-mcp](https://github.com/NeuroFusionAI/fibo-mcp)(MIT),将 FIBO 本体物化为可查询的 MCP SPARQL 端点。
|
||||
|
||||
## 服务信息
|
||||
|
||||
| 项 | 值 |
|
||||
|----|-----|
|
||||
| 端点 | `https://mcp.ablemind.cc/fibomcp/mcp` |
|
||||
| 协议 | MCP Streamable HTTP(需 `Mcp-Session-Id`),HTTPS via Cloudflare |
|
||||
| 工具 | 仅 `sparql`(无 search) |
|
||||
| 数据规模 | 627,712 triples(含 OWL-RL 推理物化) |
|
||||
|
||||
## 调用方式
|
||||
|
||||
通过 MCP 协议发送 `tools/call` 请求,tool name = `sparql`,参数为 SPARQL 查询字符串。
|
||||
需要先 `initialize` 获取 `Mcp-Session-Id`,后续请求带上该 header。
|
||||
|
||||
> **无需用户凭证**:`Mcp-Session-Id` 是 MCP Streamable HTTP 传输层的标准会话标识(类似 HTTP Session),由 Agent 调用 `initialize` 时自动获取,不需要用户配置 API key 或任何密钥。该端点为公开只读 SPARQL 查询服务。
|
||||
|
||||
## 校验的三个层次
|
||||
|
||||
schema.py 校验页面格式(frontmatter 字段有没有、类型对不对)。
|
||||
FIBO SPARQL 校验知识逻辑——三个层次:
|
||||
|
||||
### 1. 逻辑通路:关系的 domain/range 是否合法
|
||||
|
||||
Agent 写了一条关系,这条关系在标准本体中合法吗?
|
||||
|
||||
**查询模板**:给定一个属性名,查其 domain 和 range。
|
||||
|
||||
```sparql
|
||||
SELECT DISTINCT ?domainLabel ?rangeLabel WHERE {
|
||||
?prop rdfs:label ?propLabel .
|
||||
FILTER(CONTAINS(LCASE(STR(?propLabel)), "{property_name}"))
|
||||
?prop rdfs:domain ?domain . ?domain rdfs:label ?domainLabel .
|
||||
?prop rdfs:range ?range . ?range rdfs:label ?rangeLabel .
|
||||
}
|
||||
```
|
||||
|
||||
**示例**(以 `has trustee` 为例,2026-04-07 验证):
|
||||
|
||||
| domain | range |
|
||||
|--------|-------|
|
||||
| business entity | trustee |
|
||||
| trust | trustee |
|
||||
| trust | controlling party |
|
||||
|
||||
-> 如果 Agent 写 `PensionProduct --hasTrustee--> X`,逻辑通路不合法:PensionProduct 不在 domain 中。
|
||||
|
||||
### 2. 条件边:实体成立的必要关系
|
||||
|
||||
Agent 创建了一个实体页面,需要哪些必要关系?
|
||||
|
||||
**查询模板**:给定一个类的 URI,查其 OWL 约束。
|
||||
|
||||
```sparql
|
||||
SELECT DISTINCT ?onPropLabel ?restrictType ?valueLabel WHERE {
|
||||
<{class_uri}> rdfs:subClassOf ?r .
|
||||
{ ?r owl:onProperty ?p . ?p rdfs:label ?onPropLabel .
|
||||
?r owl:someValuesFrom ?v . ?v rdfs:label ?valueLabel .
|
||||
BIND("someValuesFrom" AS ?restrictType) }
|
||||
UNION
|
||||
{ ?r owl:onProperty ?p . ?p rdfs:label ?onPropLabel .
|
||||
?r owl:allValuesFrom ?v . ?v rdfs:label ?valueLabel .
|
||||
BIND("allValuesFrom" AS ?restrictType) }
|
||||
}
|
||||
```
|
||||
|
||||
`someValuesFrom` = 该类实体**必须**存在此关系(至少一个)。
|
||||
`allValuesFrom` = 该关系的值**只能**是指定类型。
|
||||
|
||||
### 3. 类型层级:实体归类是否正确
|
||||
|
||||
Agent 把实体标记为某个类型,标准本体中有没有?
|
||||
|
||||
**查询模板**:模糊搜索类名。
|
||||
|
||||
```sparql
|
||||
SELECT DISTINCT ?label ?def WHERE {
|
||||
?class rdfs:label ?label .
|
||||
FILTER(CONTAINS(LCASE(STR(?label)), "{keyword}"))
|
||||
OPTIONAL { ?class <http://www.w3.org/2004/02/skos/core#definition> ?def }
|
||||
}
|
||||
```
|
||||
|
||||
如果搜不到,校验应提示:"该类型不在标准本体中,请确认命名"。
|
||||
|
||||
## 集成方式
|
||||
|
||||
不改 Skill 核心流程,作为 lint 的可选增强层:
|
||||
|
||||
```
|
||||
lint → schema.py 格式校验
|
||||
→ 外部校验器(如果 meta.yaml 声明了 validator 且可达)
|
||||
├─ 逻辑通路:relation type 的 domain/range 是否匹配
|
||||
├─ 条件边:必要关系(someValuesFrom)是否缺失
|
||||
└─ 类型层级:实体类型是否在标准本体中
|
||||
→ 健康报告
|
||||
```
|
||||
|
||||
## 原则
|
||||
|
||||
- 不把 FIBO 约束硬编码进 schema.py——外部参考,不是内部规则
|
||||
- 不要求 wiki 页面完全满足所有 OWL 约束——报告缺失即可,Agent 判断是否补充
|
||||
- 不在 ingest 时阻塞——逻辑校验只在 lint 时运行,ingest 优先保证速度
|
||||
- 服务不可达时静默跳过——在健康报告中注明"外部校验器不可达,已跳过"
|
||||
30
.claude/skills/docx/LICENSE.txt
Normal file
30
.claude/skills/docx/LICENSE.txt
Normal file
@ -0,0 +1,30 @@
|
||||
© 2025 Anthropic, PBC. All rights reserved.
|
||||
|
||||
LICENSE: Use of these materials (including all code, prompts, assets, files,
|
||||
and other components of this Skill) is governed by your agreement with
|
||||
Anthropic regarding use of Anthropic's services. If no separate agreement
|
||||
exists, use is governed by Anthropic's Consumer Terms of Service or
|
||||
Commercial Terms of Service, as applicable:
|
||||
https://www.anthropic.com/legal/consumer-terms
|
||||
https://www.anthropic.com/legal/commercial-terms
|
||||
Your applicable agreement is referred to as the "Agreement." "Services" are
|
||||
as defined in the Agreement.
|
||||
|
||||
ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
|
||||
contrary, users may not:
|
||||
|
||||
- Extract these materials from the Services or retain copies of these
|
||||
materials outside the Services
|
||||
- Reproduce or copy these materials, except for temporary copies created
|
||||
automatically during authorized use of the Services
|
||||
- Create derivative works based on these materials
|
||||
- Distribute, sublicense, or transfer these materials to any third party
|
||||
- Make, offer to sell, sell, or import any inventions embodied in these
|
||||
materials
|
||||
- Reverse engineer, decompile, or disassemble these materials
|
||||
|
||||
The receipt, viewing, or possession of these materials does not convey or
|
||||
imply any license or right beyond those expressly granted above.
|
||||
|
||||
Anthropic retains all right, title, and interest in these materials,
|
||||
including all copyrights, patents, and other intellectual property rights.
|
||||
198
.claude/skills/docx/SKILL.md
Normal file
198
.claude/skills/docx/SKILL.md
Normal file
@ -0,0 +1,198 @@
|
||||
---
|
||||
name: docx
|
||||
description: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks"
|
||||
license: Proprietary. LICENSE.txt has complete terms
|
||||
---
|
||||
|
||||
# DOCX creation, editing, and analysis
|
||||
|
||||
## Overview
|
||||
|
||||
A user may ask you to create, edit, or analyze the contents of a .docx file. A .docx file is essentially a ZIP archive containing XML files and other resources that you can read or edit. You have different tools and workflows available for different tasks.
|
||||
|
||||
## Workflow Decision Tree
|
||||
|
||||
### Reading/Analyzing Content
|
||||
Use "Text extraction" or "Raw XML access" sections below
|
||||
|
||||
### Creating New Document
|
||||
Use "Creating a new Word document" workflow
|
||||
|
||||
### Editing Existing Document
|
||||
- **Your own document + simple changes**
|
||||
Use "Basic OOXML editing" workflow
|
||||
|
||||
- **Someone else's document**
|
||||
Use **"Redlining workflow"** (recommended default)
|
||||
|
||||
- **Legal, academic, business, or government docs**
|
||||
Use **"Redlining workflow"** (required)
|
||||
|
||||
## Reading and analyzing content
|
||||
|
||||
### Text extraction
|
||||
If you just need to read the text contents of a document, you should convert the document to markdown using pandoc. Pandoc provides excellent support for preserving document structure and can show tracked changes:
|
||||
|
||||
```bash
|
||||
# Convert document to markdown with tracked changes
|
||||
pandoc --track-changes=all path-to-file.docx -o output.md
|
||||
# Options: --track-changes=accept/reject/all
|
||||
```
|
||||
|
||||
### Raw XML access
|
||||
You need raw XML access for: comments, complex formatting, document structure, embedded media, and metadata. For any of these features, you'll need to unpack a document and read its raw XML contents.
|
||||
|
||||
#### Unpacking a file
|
||||
`python ooxml/scripts/unpack.py <office_file> <output_directory>`
|
||||
|
||||
#### Key file structures
|
||||
* `word/document.xml` - Main document contents
|
||||
* `word/comments.xml` - Comments referenced in document.xml
|
||||
* `word/media/` - Embedded images and media files
|
||||
* Tracked changes use `<w:ins>` (insertions) and `<w:del>` (deletions) tags
|
||||
|
||||
## Creating a new Word document
|
||||
|
||||
When creating a new Word document from scratch, use **docx-js**, which allows you to create Word documents using JavaScript/TypeScript.
|
||||
|
||||
### Workflow
|
||||
1. **MANDATORY - READ ENTIRE FILE**: Read [`docx-js.md`](docx-js.md) (~500 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Read the full file content for detailed syntax, critical formatting rules, and best practices before proceeding with document creation.
|
||||
2. Create a JavaScript/TypeScript file using Document, Paragraph, TextRun components (You can assume all dependencies are installed, but if not, refer to the dependencies section below)
|
||||
3. Export as .docx using Packer.toBuffer()
|
||||
|
||||
## Editing an existing Word document
|
||||
|
||||
When editing an existing Word document, use the **Document library** (a Python library for OOXML manipulation). The library automatically handles infrastructure setup and provides methods for document manipulation. For complex scenarios, you can access the underlying DOM directly through the library.
|
||||
|
||||
### Workflow
|
||||
1. **MANDATORY - READ ENTIRE FILE**: Read [`ooxml.md`](ooxml.md) (~600 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Read the full file content for the Document library API and XML patterns for directly editing document files.
|
||||
2. Unpack the document: `python ooxml/scripts/unpack.py <office_file> <output_directory>`
|
||||
3. Create and run a Python script using the Document library (see "Document Library" section in ooxml.md)
|
||||
4. Pack the final document: `python ooxml/scripts/pack.py <input_directory> <office_file>`
|
||||
|
||||
The Document library provides both high-level methods for common operations and direct DOM access for complex scenarios.
|
||||
|
||||
## Redlining workflow for document review
|
||||
|
||||
This workflow allows you to plan comprehensive tracked changes using markdown before implementing them in OOXML. **CRITICAL**: For complete tracked changes, you must implement ALL changes systematically.
|
||||
|
||||
**Batching Strategy**: Group related changes into batches of 3-10 changes. This makes debugging manageable while maintaining efficiency. Test each batch before moving to the next.
|
||||
|
||||
**Principle: Minimal, Precise Edits**
|
||||
When implementing tracked changes, only mark text that actually changes. Repeating unchanged text makes edits harder to review and appears unprofessional. Break replacements into: [unchanged text] + [deletion] + [insertion] + [unchanged text]. Preserve the original run's RSID for unchanged text by extracting the `<w:r>` element from the original and reusing it.
|
||||
|
||||
Example - Changing "30 days" to "60 days" in a sentence:
|
||||
```python
|
||||
# BAD - Replaces entire sentence
|
||||
'<w:del><w:r><w:delText>The term is 30 days.</w:delText></w:r></w:del><w:ins><w:r><w:t>The term is 60 days.</w:t></w:r></w:ins>'
|
||||
|
||||
# GOOD - Only marks what changed, preserves original <w:r> for unchanged text
|
||||
'<w:r w:rsidR="00AB12CD"><w:t>The term is </w:t></w:r><w:del><w:r><w:delText>30</w:delText></w:r></w:del><w:ins><w:r><w:t>60</w:t></w:r></w:ins><w:r w:rsidR="00AB12CD"><w:t> days.</w:t></w:r>'
|
||||
```
|
||||
|
||||
### Tracked changes workflow
|
||||
|
||||
1. **Get markdown representation**: Convert document to markdown with tracked changes preserved:
|
||||
```bash
|
||||
pandoc --track-changes=all path-to-file.docx -o current.md
|
||||
```
|
||||
|
||||
2. **Identify and group changes**: Review the document and identify ALL changes needed, organizing them into logical batches:
|
||||
|
||||
**Location methods** (for finding changes in XML):
|
||||
- Section/heading numbers (e.g., "Section 3.2", "Article IV")
|
||||
- Paragraph identifiers if numbered
|
||||
- Grep patterns with unique surrounding text
|
||||
- Document structure (e.g., "first paragraph", "signature block")
|
||||
- **DO NOT use markdown line numbers** - they don't map to XML structure
|
||||
|
||||
**Batch organization** (group 3-10 related changes per batch):
|
||||
- By section: "Batch 1: Section 2 amendments", "Batch 2: Section 5 updates"
|
||||
- By type: "Batch 1: Date corrections", "Batch 2: Party name changes"
|
||||
- By complexity: Start with simple text replacements, then tackle complex structural changes
|
||||
- Sequential: "Batch 1: Pages 1-3", "Batch 2: Pages 4-6"
|
||||
|
||||
3. **Read documentation and unpack**:
|
||||
- **MANDATORY - READ ENTIRE FILE**: Read [`ooxml.md`](ooxml.md) (~600 lines) completely from start to finish. **NEVER set any range limits when reading this file.** Pay special attention to the "Document Library" and "Tracked Change Patterns" sections.
|
||||
- **Unpack the document**: `python ooxml/scripts/unpack.py <file.docx> <dir>`
|
||||
- **Note the suggested RSID**: The unpack script will suggest an RSID to use for your tracked changes. Copy this RSID for use in step 4b.
|
||||
|
||||
4. **Implement changes in batches**: Group changes logically (by section, by type, or by proximity) and implement them together in a single script. This approach:
|
||||
- Makes debugging easier (smaller batch = easier to isolate errors)
|
||||
- Allows incremental progress
|
||||
- Maintains efficiency (batch size of 3-10 changes works well)
|
||||
|
||||
**Suggested batch groupings:**
|
||||
- By document section (e.g., "Section 3 changes", "Definitions", "Termination clause")
|
||||
- By change type (e.g., "Date changes", "Party name updates", "Legal term replacements")
|
||||
- By proximity (e.g., "Changes on pages 1-3", "Changes in first half of document")
|
||||
|
||||
For each batch of related changes:
|
||||
|
||||
**a. Map text to XML**: Grep for text in `word/document.xml` to verify how text is split across `<w:r>` elements.
|
||||
|
||||
**b. Create and run script**: Use `get_node` to find nodes, implement changes, then `doc.save()`. See **"Document Library"** section in ooxml.md for patterns.
|
||||
|
||||
**Note**: Always grep `word/document.xml` immediately before writing a script to get current line numbers and verify text content. Line numbers change after each script run.
|
||||
|
||||
5. **Pack the document**: After all batches are complete, convert the unpacked directory back to .docx:
|
||||
```bash
|
||||
python ooxml/scripts/pack.py unpacked reviewed-document.docx
|
||||
```
|
||||
|
||||
6. **Final verification**: Do a comprehensive check of the complete document:
|
||||
- Convert final document to markdown:
|
||||
```bash
|
||||
pandoc --track-changes=all reviewed-document.docx -o verification.md
|
||||
```
|
||||
- Verify ALL changes were applied correctly:
|
||||
```bash
|
||||
grep "original phrase" verification.md # Should NOT find it
|
||||
grep "replacement phrase" verification.md # Should find it
|
||||
```
|
||||
- Check that no unintended changes were introduced
|
||||
|
||||
|
||||
## Converting Documents to Images
|
||||
|
||||
To visually analyze Word documents, convert them to images using a two-step process:
|
||||
|
||||
1. **Convert DOCX to PDF**:
|
||||
```bash
|
||||
soffice --headless --convert-to pdf document.docx
|
||||
```
|
||||
|
||||
2. **Convert PDF pages to JPEG images**:
|
||||
```bash
|
||||
pdftoppm -jpeg -r 150 document.pdf page
|
||||
```
|
||||
This creates files like `page-1.jpg`, `page-2.jpg`, etc.
|
||||
|
||||
Options:
|
||||
- `-r 150`: Sets resolution to 150 DPI (adjust for quality/size balance)
|
||||
- `-jpeg`: Output JPEG format (use `-png` for PNG if preferred)
|
||||
- `-f N`: First page to convert (e.g., `-f 2` starts from page 2)
|
||||
- `-l N`: Last page to convert (e.g., `-l 5` stops at page 5)
|
||||
- `page`: Prefix for output files
|
||||
|
||||
Example for specific range:
|
||||
```bash
|
||||
pdftoppm -jpeg -r 150 -f 2 -l 5 document.pdf page # Converts only pages 2-5
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
**IMPORTANT**: When generating code for DOCX operations:
|
||||
- Write concise code
|
||||
- Avoid verbose variable names and redundant operations
|
||||
- Avoid unnecessary print statements
|
||||
- **跨平台路径**: 始终用 `path.join()` (JS) 或 `os.path.join()` (Python) 拼接路径,不要硬编码 `/` 或 `\\`。详见 docx-js.md「Cross-Platform 路径处理」章节
|
||||
|
||||
## Dependencies
|
||||
|
||||
Required dependencies (install if not available):
|
||||
|
||||
- **pandoc**: `sudo apt-get install pandoc` (for text extraction)
|
||||
- **docx**: `npm install -g docx` (for creating new documents)
|
||||
- **LibreOffice**: `sudo apt-get install libreoffice` (for PDF conversion)
|
||||
- **Poppler**: `sudo apt-get install poppler-utils` (for pdftoppm to convert PDF to images)
|
||||
- **defusedxml**: `pip install defusedxml` (for secure XML parsing)
|
||||
499
.claude/skills/docx/docx-js.md
Normal file
499
.claude/skills/docx/docx-js.md
Normal file
@ -0,0 +1,499 @@
|
||||
# DOCX Library Tutorial
|
||||
|
||||
Generate .docx files with JavaScript/TypeScript.
|
||||
|
||||
**Important: Read this entire document before starting.** Critical formatting rules and common pitfalls are covered throughout - skipping sections may result in corrupted files or rendering issues.
|
||||
|
||||
## Setup
|
||||
Assumes docx is already installed globally
|
||||
If not installed: `npm install -g docx`
|
||||
|
||||
```javascript
|
||||
const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun, Media,
|
||||
Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
|
||||
InternalHyperlink, TableOfContents, HeadingLevel, BorderStyle, WidthType, TabStopType,
|
||||
TabStopPosition, UnderlineType, ShadingType, VerticalAlign, SymbolRun, PageNumber,
|
||||
FootnoteReferenceRun, Footnote, PageBreak } = require('docx');
|
||||
|
||||
// Create & Save
|
||||
const doc = new Document({ sections: [{ children: [/* content */] }] });
|
||||
Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer)); // Node.js
|
||||
Packer.toBlob(doc).then(blob => { /* download logic */ }); // Browser
|
||||
```
|
||||
|
||||
## Text & Formatting
|
||||
```javascript
|
||||
// IMPORTANT: Never use \n for line breaks - always use separate Paragraph elements
|
||||
// ❌ WRONG: new TextRun("Line 1\nLine 2")
|
||||
// ✅ CORRECT: new Paragraph({ children: [new TextRun("Line 1")] }), new Paragraph({ children: [new TextRun("Line 2")] })
|
||||
|
||||
// Basic text with all formatting options (公文配置:仿宋 14pt 默认)
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.JUSTIFIED, // 公文两端对齐
|
||||
spacing: { before: 200, after: 200 },
|
||||
indent: { firstLine: 560 }, // 公文首行缩进 2em
|
||||
children: [
|
||||
new TextRun({ text: "加粗", bold: true }),
|
||||
new TextRun({ text: "斜体", italics: true }),
|
||||
new TextRun({ text: "下划线", underline: { type: UnderlineType.SINGLE, color: "000000" } }), // 公文用黑色
|
||||
new TextRun({ text: "指定字号", size: 28, font: "STFangsong" }), // 仿宋 14pt
|
||||
new TextRun({ text: "高亮", highlight: "yellow" }),
|
||||
new TextRun({ text: "删除线", strike: true }),
|
||||
new TextRun({ text: "x2", superScript: true }),
|
||||
new TextRun({ text: "H2O", subScript: true }),
|
||||
new SymbolRun({ char: "2022", font: "Symbol" }), // Bullet •
|
||||
new SymbolRun({ char: "00A9", font: "STFangsong" }) // Copyright ©
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
## Styles — AbleMind 公文 UI 设计系统
|
||||
|
||||
### 字体体系
|
||||
|
||||
| 变量 | 字体栈 | 用途 |
|
||||
|------|--------|------|
|
||||
| `--gov-font-body` | STFangsong → FangSong → Fangsong SC → Noto Serif SC → serif | 正文(仿宋体) |
|
||||
| `--gov-font-heading` | Heiti SC → PingFang SC → SimHei → Noto Sans SC → sans-serif | 标题(黑体) |
|
||||
| `--gov-font-mono` | IBM Plex Mono → JetBrains Mono → monospace | UI 等宽 |
|
||||
| `--gov-font-code` | Courier New → monospace | 代码块 |
|
||||
|
||||
在 docx-js 中使用时,font 值按优先级取第一个系统可用字体即可(macOS 优先 STFangsong / Heiti SC)。
|
||||
|
||||
### 公文排版规范
|
||||
|
||||
| 元素 | 字体 | 字号 | 其他 |
|
||||
|------|------|------|------|
|
||||
| 正文 | 仿宋 (STFangsong) | 14pt (size: 28) | 行距 1.5,首行缩进 2em,两端对齐 |
|
||||
| h1 | 黑体 (Heiti SC) | 16pt (size: 32) | 居中,加粗 |
|
||||
| h2 | 黑体 (Heiti SC) | 15pt (size: 30) | 左对齐,加粗 |
|
||||
| h3–h6 | 黑体 (Heiti SC) | 14pt (size: 28) | 左对齐,加粗 |
|
||||
| 表格 | 仿宋 (STFangsong) | 小四 12pt (size: 24) | 全线框,表头灰底 |
|
||||
| 代码 | Courier New | 12pt (size: 24) | 灰底框线 |
|
||||
| 链接 | 同正文 | 同正文 | 黑色下划线(公文不用彩色链接) |
|
||||
|
||||
### 标准公文样式模板
|
||||
|
||||
```javascript
|
||||
// AbleMind 公文配置 — 默认样式
|
||||
const GOV_FONT_BODY = "STFangsong"; // 仿宋体(正文)
|
||||
const GOV_FONT_HEADING = "Heiti SC"; // 黑体(标题)
|
||||
const GOV_FONT_CODE = "Courier New"; // 代码块
|
||||
|
||||
const doc = new Document({
|
||||
styles: {
|
||||
default: {
|
||||
document: {
|
||||
run: { font: GOV_FONT_BODY, size: 28 }, // 仿宋 14pt
|
||||
paragraph: {
|
||||
spacing: { line: 360 }, // 行距 1.5 (240 * 1.5)
|
||||
alignment: AlignmentType.JUSTIFIED // 两端对齐
|
||||
}
|
||||
}
|
||||
},
|
||||
paragraphStyles: [
|
||||
// 公文标题 — 黑体 16pt 居中
|
||||
{ id: "Title", name: "Title", basedOn: "Normal",
|
||||
run: { size: 32, bold: true, color: "000000", font: GOV_FONT_HEADING },
|
||||
paragraph: { spacing: { before: 240, after: 120 }, alignment: AlignmentType.CENTER } },
|
||||
// h1 — 黑体 16pt 居中
|
||||
{ id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 32, bold: true, color: "000000", font: GOV_FONT_HEADING },
|
||||
paragraph: { spacing: { before: 240, after: 240, line: 360 }, alignment: AlignmentType.CENTER, outlineLevel: 0 } },
|
||||
// h2 — 黑体 15pt 左对齐
|
||||
{ id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 30, bold: true, color: "000000", font: GOV_FONT_HEADING },
|
||||
paragraph: { spacing: { before: 180, after: 180, line: 360 }, outlineLevel: 1 } },
|
||||
// h3–h6 — 黑体 14pt 左对齐
|
||||
{ id: "Heading3", name: "Heading 3", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 28, bold: true, color: "000000", font: GOV_FONT_HEADING },
|
||||
paragraph: { spacing: { before: 120, after: 120, line: 360 }, outlineLevel: 2 } },
|
||||
{ id: "Heading4", name: "Heading 4", basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { size: 28, bold: true, color: "000000", font: GOV_FONT_HEADING },
|
||||
paragraph: { spacing: { before: 120, after: 120, line: 360 }, outlineLevel: 3 } },
|
||||
// 自定义样式仍可添加
|
||||
{ id: "govNote", name: "Gov Note", basedOn: "Normal",
|
||||
run: { size: 24, color: "333333", font: GOV_FONT_BODY },
|
||||
paragraph: { spacing: { after: 60 } } }
|
||||
],
|
||||
characterStyles: [
|
||||
// 公文链接:黑色下划线,不用彩色
|
||||
{ id: "Hyperlink", name: "Hyperlink",
|
||||
run: { color: "000000", underline: { type: UnderlineType.SINGLE, color: "000000" } } },
|
||||
{ id: "govEmphasis", name: "Gov Emphasis",
|
||||
run: { bold: true, font: GOV_FONT_HEADING } }
|
||||
]
|
||||
},
|
||||
sections: [{
|
||||
properties: {
|
||||
page: {
|
||||
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }, // A4 标准页边距 1 英寸
|
||||
size: { width: 11906, height: 16838 } // A4 尺寸 (210mm × 297mm in DXA)
|
||||
}
|
||||
},
|
||||
children: [
|
||||
new Paragraph({ heading: HeadingLevel.TITLE, children: [new TextRun("公文标题")] }),
|
||||
new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("一级标题")] }),
|
||||
new Paragraph({ heading: HeadingLevel.HEADING_2, children: [new TextRun("二级标题")] }),
|
||||
// 正文段落 — 首行缩进 2em(仿宋14pt ≈ 560 DXA)
|
||||
new Paragraph({
|
||||
indent: { firstLine: 560 },
|
||||
children: [new TextRun("正文内容,仿宋14pt,行距1.5,首行缩进2em,两端对齐。")]
|
||||
})
|
||||
]
|
||||
}]
|
||||
});
|
||||
```
|
||||
|
||||
### 公文首行缩进说明
|
||||
- 首行缩进 2em = 2 × 字号对应的 DXA 值
|
||||
- 仿宋 14pt → `firstLine: 560` (14pt × 20 DXA/pt × 2)
|
||||
- 小四 12pt → `firstLine: 480` (12pt × 20 DXA/pt × 2)
|
||||
- 通过 `indent: { firstLine: 560 }` 设置在每个正文 Paragraph 上
|
||||
|
||||
### 跨平台字体回退
|
||||
- **macOS**: STFangsong / Heiti SC(系统自带)
|
||||
- **Windows**: FangSong / SimHei(系统自带)
|
||||
- **Linux/CI**: Noto Serif SC / Noto Sans SC(需安装 Google Noto CJK)
|
||||
- docx-js 的 `font` 属性只写一个字体名,Word 打开时自动使用系统可用字体
|
||||
|
||||
**Key Styling Principles:**
|
||||
- **Override built-in styles**: Use exact IDs like "Heading1", "Heading2", "Heading3" to override Word's built-in heading styles
|
||||
- **HeadingLevel constants**: `HeadingLevel.HEADING_1` uses "Heading1" style, `HeadingLevel.HEADING_2` uses "Heading2" style, etc.
|
||||
- **Include outlineLevel**: Set `outlineLevel: 0` for H1, `outlineLevel: 1` for H2, etc. to ensure TOC works correctly
|
||||
- **公文字体一致性**: 正文统一仿宋,标题统一黑体,不混用其他字体
|
||||
- **公文不用彩色**: 链接、标题全部黑色,不使用蓝色超链接或灰色标题
|
||||
- **A4 纸张**: 使用 `size: { width: 11906, height: 16838 }` 设置 A4 尺寸
|
||||
- **行距 1.5**: 在 default paragraph spacing 中设置 `line: 360`
|
||||
- **首行缩进**: 正文段落添加 `indent: { firstLine: 560 }`
|
||||
|
||||
|
||||
## Lists (ALWAYS USE PROPER LISTS - NEVER USE UNICODE BULLETS)
|
||||
```javascript
|
||||
// Bullets - ALWAYS use the numbering config, NOT unicode symbols
|
||||
// CRITICAL: Use LevelFormat.BULLET constant, NOT the string "bullet"
|
||||
const doc = new Document({
|
||||
numbering: {
|
||||
config: [
|
||||
{ reference: "bullet-list",
|
||||
levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
|
||||
{ reference: "first-numbered-list",
|
||||
levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
|
||||
{ reference: "second-numbered-list", // Different reference = restarts at 1
|
||||
levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
|
||||
style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }
|
||||
]
|
||||
},
|
||||
sections: [{
|
||||
children: [
|
||||
// Bullet list items
|
||||
new Paragraph({ numbering: { reference: "bullet-list", level: 0 },
|
||||
children: [new TextRun("First bullet point")] }),
|
||||
new Paragraph({ numbering: { reference: "bullet-list", level: 0 },
|
||||
children: [new TextRun("Second bullet point")] }),
|
||||
// Numbered list items
|
||||
new Paragraph({ numbering: { reference: "first-numbered-list", level: 0 },
|
||||
children: [new TextRun("First numbered item")] }),
|
||||
new Paragraph({ numbering: { reference: "first-numbered-list", level: 0 },
|
||||
children: [new TextRun("Second numbered item")] }),
|
||||
// ⚠️ CRITICAL: Different reference = INDEPENDENT list that restarts at 1
|
||||
// Same reference = CONTINUES previous numbering
|
||||
new Paragraph({ numbering: { reference: "second-numbered-list", level: 0 },
|
||||
children: [new TextRun("Starts at 1 again (because different reference)")] })
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
// ⚠️ CRITICAL NUMBERING RULE: Each reference creates an INDEPENDENT numbered list
|
||||
// - Same reference = continues numbering (1, 2, 3... then 4, 5, 6...)
|
||||
// - Different reference = restarts at 1 (1, 2, 3... then 1, 2, 3...)
|
||||
// Use unique reference names for each separate numbered section!
|
||||
|
||||
// ⚠️ CRITICAL: NEVER use unicode bullets - they create fake lists that don't work properly
|
||||
// new TextRun("• Item") // WRONG
|
||||
// new SymbolRun({ char: "2022" }) // WRONG
|
||||
// ✅ ALWAYS use numbering config with LevelFormat.BULLET for real Word lists
|
||||
```
|
||||
|
||||
## Tables — 公文表格规范
|
||||
```javascript
|
||||
// 公文表格:小四 12pt 仿宋,全线框,表头灰底居中加粗
|
||||
const GOV_FONT_BODY = "STFangsong";
|
||||
const tableBorder = { style: BorderStyle.SINGLE, size: 1, color: "000000" }; // 公文用黑色全线框
|
||||
const cellBorders = { top: tableBorder, bottom: tableBorder, left: tableBorder, right: tableBorder };
|
||||
|
||||
new Table({
|
||||
columnWidths: [4680, 4680], // ⚠️ CRITICAL: Set column widths at table level - values in DXA (twentieths of a point)
|
||||
margins: { top: 80, bottom: 80, left: 120, right: 120 }, // Set once for all cells
|
||||
rows: [
|
||||
// 表头行:灰底居中加粗
|
||||
new TableRow({
|
||||
tableHeader: true,
|
||||
children: [
|
||||
new TableCell({
|
||||
borders: cellBorders,
|
||||
width: { size: 4680, type: WidthType.DXA },
|
||||
// ⚠️ CRITICAL: Always use ShadingType.CLEAR to prevent black backgrounds in Word.
|
||||
shading: { fill: "D9D9D9", type: ShadingType.CLEAR }, // 浅灰底
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new TextRun({ text: "表头", bold: true, size: 24, font: GOV_FONT_BODY })] // 小四 12pt
|
||||
})]
|
||||
}),
|
||||
new TableCell({
|
||||
borders: cellBorders,
|
||||
width: { size: 4680, type: WidthType.DXA },
|
||||
shading: { fill: "D9D9D9", type: ShadingType.CLEAR },
|
||||
verticalAlign: VerticalAlign.CENTER,
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new TextRun({ text: "列标题", bold: true, size: 24, font: GOV_FONT_BODY })]
|
||||
})]
|
||||
})
|
||||
]
|
||||
}),
|
||||
// 数据行:小四仿宋,左对齐
|
||||
new TableRow({
|
||||
children: [
|
||||
new TableCell({
|
||||
borders: cellBorders,
|
||||
width: { size: 4680, type: WidthType.DXA },
|
||||
children: [new Paragraph({ children: [new TextRun({ text: "数据内容", size: 24, font: GOV_FONT_BODY })] })]
|
||||
}),
|
||||
new TableCell({
|
||||
borders: cellBorders,
|
||||
width: { size: 4680, type: WidthType.DXA },
|
||||
children: [
|
||||
new Paragraph({
|
||||
numbering: { reference: "bullet-list", level: 0 },
|
||||
children: [new TextRun({ text: "列表项一", size: 24, font: GOV_FONT_BODY })]
|
||||
}),
|
||||
new Paragraph({
|
||||
numbering: { reference: "bullet-list", level: 0 },
|
||||
children: [new TextRun({ text: "列表项二", size: 24, font: GOV_FONT_BODY })]
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
**IMPORTANT: Table Width & Borders**
|
||||
- Use BOTH `columnWidths: [width1, width2, ...]` array AND `width: { size: X, type: WidthType.DXA }` on each cell
|
||||
- Values in DXA (twentieths of a point): 1440 = 1 inch, Letter usable width = 9360 DXA (with 1" margins)
|
||||
- Apply borders to individual `TableCell` elements, NOT the `Table` itself
|
||||
|
||||
**Precomputed Column Widths (Letter size with 1" margins = 9360 DXA total):**
|
||||
- **2 columns:** `columnWidths: [4680, 4680]` (equal width)
|
||||
- **3 columns:** `columnWidths: [3120, 3120, 3120]` (equal width)
|
||||
|
||||
## Links & Navigation
|
||||
```javascript
|
||||
// TOC (requires headings) - CRITICAL: Use HeadingLevel only, NOT custom styles
|
||||
// ❌ WRONG: new Paragraph({ heading: HeadingLevel.HEADING_1, style: "customHeader", children: [new TextRun("Title")] })
|
||||
// ✅ CORRECT: new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Title")] })
|
||||
new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" }),
|
||||
|
||||
// External link
|
||||
new Paragraph({
|
||||
children: [new ExternalHyperlink({
|
||||
children: [new TextRun({ text: "Google", style: "Hyperlink" })],
|
||||
link: "https://www.google.com"
|
||||
})]
|
||||
}),
|
||||
|
||||
// Internal link & bookmark
|
||||
new Paragraph({
|
||||
children: [new InternalHyperlink({
|
||||
children: [new TextRun({ text: "Go to Section", style: "Hyperlink" })],
|
||||
anchor: "section1"
|
||||
})]
|
||||
}),
|
||||
new Paragraph({
|
||||
children: [new TextRun("Section Content")],
|
||||
bookmark: { id: "section1", name: "section1" }
|
||||
}),
|
||||
```
|
||||
|
||||
## Images & Media
|
||||
```javascript
|
||||
// Basic image with sizing & positioning
|
||||
// CRITICAL: Always specify 'type' parameter - it's REQUIRED for ImageRun
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new ImageRun({
|
||||
type: "png", // NEW REQUIREMENT: Must specify image type (png, jpg, jpeg, gif, bmp, svg)
|
||||
data: fs.readFileSync("image.png"),
|
||||
transformation: { width: 200, height: 150, rotation: 0 }, // rotation in degrees
|
||||
altText: { title: "Logo", description: "Company logo", name: "Name" } // IMPORTANT: All three fields are required
|
||||
})]
|
||||
})
|
||||
```
|
||||
|
||||
## Page Breaks
|
||||
```javascript
|
||||
// Manual page break
|
||||
new Paragraph({ children: [new PageBreak()] }),
|
||||
|
||||
// Page break before paragraph
|
||||
new Paragraph({
|
||||
pageBreakBefore: true,
|
||||
children: [new TextRun("This starts on a new page")]
|
||||
})
|
||||
|
||||
// ⚠️ CRITICAL: NEVER use PageBreak standalone - it will create invalid XML that Word cannot open
|
||||
// ❌ WRONG: new PageBreak()
|
||||
// ✅ CORRECT: new Paragraph({ children: [new PageBreak()] })
|
||||
```
|
||||
|
||||
## Headers/Footers & Page Setup
|
||||
```javascript
|
||||
const doc = new Document({
|
||||
sections: [{
|
||||
properties: {
|
||||
page: {
|
||||
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }, // 1440 = 1 inch
|
||||
size: { orientation: PageOrientation.LANDSCAPE },
|
||||
pageNumbers: { start: 1, formatType: "decimal" } // "upperRoman", "lowerRoman", "upperLetter", "lowerLetter"
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
default: new Header({ children: [new Paragraph({
|
||||
alignment: AlignmentType.RIGHT,
|
||||
children: [new TextRun("Header Text")]
|
||||
})] })
|
||||
},
|
||||
footers: {
|
||||
default: new Footer({ children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] }), new TextRun(" of "), new TextRun({ children: [PageNumber.TOTAL_PAGES] })]
|
||||
})] })
|
||||
},
|
||||
children: [/* content */]
|
||||
}]
|
||||
});
|
||||
```
|
||||
|
||||
## Tabs
|
||||
```javascript
|
||||
new Paragraph({
|
||||
tabStops: [
|
||||
{ type: TabStopType.LEFT, position: TabStopPosition.MAX / 4 },
|
||||
{ type: TabStopType.CENTER, position: TabStopPosition.MAX / 2 },
|
||||
{ type: TabStopType.RIGHT, position: TabStopPosition.MAX * 3 / 4 }
|
||||
],
|
||||
children: [new TextRun("Left\tCenter\tRight")]
|
||||
})
|
||||
```
|
||||
|
||||
## Constants & Quick Reference
|
||||
- **Underlines:** `SINGLE`, `DOUBLE`, `WAVY`, `DASH`
|
||||
- **Borders:** `SINGLE`, `DOUBLE`, `DASHED`, `DOTTED`
|
||||
- **Numbering:** `DECIMAL` (1,2,3), `UPPER_ROMAN` (I,II,III), `LOWER_LETTER` (a,b,c)
|
||||
- **Tabs:** `LEFT`, `CENTER`, `RIGHT`, `DECIMAL`
|
||||
- **Symbols:** `"2022"` (•), `"00A9"` (©), `"00AE"` (®), `"2122"` (™), `"00B0"` (°), `"F070"` (✓), `"F0FC"` (✗)
|
||||
|
||||
## Cross-Platform 路径处理(Windows / macOS / Linux)
|
||||
|
||||
**根本原因**: Windows 用 `\` 作路径分隔符,macOS/Linux 用 `/`。在 JS 字符串中 `\` 是转义符,直接写 `"C:\Users\file"` 会被解析为 `"C:Usersile"`。
|
||||
|
||||
### 必须遵守的规则
|
||||
|
||||
```javascript
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// ❌ 硬编码斜杠 — Windows 上可能失败
|
||||
const img = fs.readFileSync("images/logo.png");
|
||||
const out = "output/report.docx";
|
||||
|
||||
// ✅ 始终用 path.join() 拼接路径
|
||||
const img = fs.readFileSync(path.join("images", "logo.png"));
|
||||
const out = path.join("output", "report.docx");
|
||||
|
||||
// ❌ 模板字符串拼路径
|
||||
const file = `${dir}/report.docx`;
|
||||
|
||||
// ✅ path.join 拼接
|
||||
const file = path.join(dir, "report.docx");
|
||||
|
||||
// ❌ __dirname + 硬编码斜杠
|
||||
const tpl = __dirname + "/templates/header.xml";
|
||||
|
||||
// ✅ path.join(__dirname, ...)
|
||||
const tpl = path.join(__dirname, "templates", "header.xml");
|
||||
```
|
||||
|
||||
### 输出文件名注意事项
|
||||
|
||||
```javascript
|
||||
// ✅ 写文件前确保目录存在
|
||||
const outDir = path.join("output");
|
||||
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||||
Packer.toBuffer(doc).then(buf => fs.writeFileSync(path.join(outDir, "report.docx"), buf));
|
||||
```
|
||||
|
||||
### Python 脚本同样适用
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
# ❌ 硬编码斜杠
|
||||
doc_path = "word/document.xml"
|
||||
|
||||
# ✅ os.path.join
|
||||
doc_path = os.path.join("word", "document.xml")
|
||||
|
||||
# ✅ pathlib (Python 3.4+) 更优雅
|
||||
from pathlib import Path
|
||||
doc_path = Path("word") / "document.xml"
|
||||
```
|
||||
|
||||
### Shell 命令中的路径
|
||||
|
||||
```bash
|
||||
# ✅ 正斜杠在所有平台的 shell 中都能工作(包括 Windows PowerShell/cmd)
|
||||
python ooxml/scripts/unpack.py input.docx output_dir
|
||||
|
||||
# ⚠️ 但如果路径来自变量且含空格,务必加引号
|
||||
python "ooxml/scripts/unpack.py" "$INPUT_FILE" "$OUTPUT_DIR"
|
||||
```
|
||||
|
||||
### 快速检查清单
|
||||
|
||||
| 检查项 | 说明 |
|
||||
|--------|------|
|
||||
| 不出现 `"/"` 拼路径 | 用 `path.join()` / `os.path.join()` |
|
||||
| 不出现 `"\\"` 拼路径 | 同上 |
|
||||
| 不出现 `` `${x}/y` `` 拼路径 | 用 `path.join(x, "y")` |
|
||||
| `fs.mkdirSync` 带 `recursive` | 确保输出目录存在 |
|
||||
| 文件名不含 `: * ? " < > \|` | Windows 保留字符,会导致写入失败 |
|
||||
| 路径含空格时加引号 | shell 命令中 `"$PATH"` |
|
||||
|
||||
## Critical Issues & Common Mistakes
|
||||
- **CRITICAL: PageBreak must ALWAYS be inside a Paragraph** - standalone PageBreak creates invalid XML that Word cannot open
|
||||
- **ALWAYS use ShadingType.CLEAR for table cell shading** - Never use ShadingType.SOLID (causes black background).
|
||||
- Measurements in DXA (1440 = 1 inch) | Each table cell needs ≥1 Paragraph | TOC requires HeadingLevel styles only
|
||||
- **公文字体**: 正文用仿宋 (STFangsong),标题用黑体 (Heiti SC),表格用小四仿宋,代码用 Courier New
|
||||
- **公文默认字号**: 正文 14pt (size: 28),h1 16pt (size: 32),h2 15pt (size: 30),h3+ 14pt (size: 28),表格 12pt (size: 24)
|
||||
- **公文行距**: 在 default paragraph 中设置 `spacing: { line: 360 }` (1.5 倍行距)
|
||||
- **公文首行缩进**: 正文段落添加 `indent: { firstLine: 560 }`(14pt × 20 × 2)
|
||||
- **公文纸张**: A4 尺寸 `size: { width: 11906, height: 16838 }`
|
||||
- **公文链接**: 黑色下划线,覆盖 Hyperlink 字符样式为 `color: "000000"`
|
||||
- **公文表格**: 黑色全线框 `color: "000000"`,表头灰底 `fill: "D9D9D9"`
|
||||
- **ALWAYS use columnWidths array for tables** + individual cell widths for compatibility
|
||||
- **NEVER use unicode symbols for bullets** - always use proper numbering configuration with `LevelFormat.BULLET` constant (NOT the string "bullet")
|
||||
- **NEVER use \n for line breaks anywhere** - always use separate Paragraph elements for each line
|
||||
- **ALWAYS use TextRun objects within Paragraph children** - never use text property directly on Paragraph
|
||||
- **CRITICAL for images**: ImageRun REQUIRES `type` parameter - always specify "png", "jpg", "jpeg", "gif", "bmp", or "svg"
|
||||
- **CRITICAL for bullets**: Must use `LevelFormat.BULLET` constant, not string "bullet", and include `text: "•"` for the bullet character
|
||||
- **CRITICAL for numbering**: Each numbering reference creates an INDEPENDENT list. Same reference = continues numbering (1,2,3 then 4,5,6). Different reference = restarts at 1 (1,2,3 then 1,2,3). Use unique reference names for each separate numbered section!
|
||||
- **CRITICAL for TOC**: When using TableOfContents, headings must use HeadingLevel ONLY - do NOT add custom styles to heading paragraphs or TOC will break
|
||||
- **Tables**: Set `columnWidths` array + individual cell widths, apply borders to cells not table
|
||||
- **Set table margins at TABLE level** for consistent cell padding (avoids repetition per cell)
|
||||
- **跨平台路径**: 始终用 `path.join()` 拼接路径,不硬编码 `/` 或 `\\`。写文件前用 `fs.mkdirSync(dir, { recursive: true })` 确保目录存在
|
||||
610
.claude/skills/docx/ooxml.md
Normal file
610
.claude/skills/docx/ooxml.md
Normal file
@ -0,0 +1,610 @@
|
||||
# Office Open XML Technical Reference
|
||||
|
||||
**Important: Read this entire document before starting.** This document covers:
|
||||
- [Technical Guidelines](#technical-guidelines) - Schema compliance rules and validation requirements
|
||||
- [Document Content Patterns](#document-content-patterns) - XML patterns for headings, lists, tables, formatting, etc.
|
||||
- [Document Library (Python)](#document-library-python) - Recommended approach for OOXML manipulation with automatic infrastructure setup
|
||||
- [Tracked Changes (Redlining)](#tracked-changes-redlining) - XML patterns for implementing tracked changes
|
||||
|
||||
## Technical Guidelines
|
||||
|
||||
### Schema Compliance
|
||||
- **Element ordering in `<w:pPr>`**: `<w:pStyle>`, `<w:numPr>`, `<w:spacing>`, `<w:ind>`, `<w:jc>`
|
||||
- **Whitespace**: Add `xml:space='preserve'` to `<w:t>` elements with leading/trailing spaces
|
||||
- **Unicode**: Escape characters in ASCII content: `"` becomes `“`
|
||||
- **Character encoding reference**: Curly quotes `""` become `“”`, apostrophe `'` becomes `’`, em-dash `—` becomes `—`
|
||||
- **Tracked changes**: Use `<w:del>` and `<w:ins>` tags with `w:author="Claude"` outside `<w:r>` elements
|
||||
- **Critical**: `<w:ins>` closes with `</w:ins>`, `<w:del>` closes with `</w:del>` - never mix
|
||||
- **RSIDs must be 8-digit hex**: Use values like `00AB1234` (only 0-9, A-F characters)
|
||||
- **trackRevisions placement**: Add `<w:trackRevisions/>` after `<w:proofState>` in settings.xml
|
||||
- **Images**: Add to `word/media/`, reference in `document.xml`, set dimensions to prevent overflow
|
||||
|
||||
## Document Content Patterns
|
||||
|
||||
### Basic Structure
|
||||
```xml
|
||||
<w:p>
|
||||
<w:r><w:t>Text content</w:t></w:r>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
### Headings and Styles
|
||||
```xml
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Title"/>
|
||||
<w:jc w:val="center"/>
|
||||
</w:pPr>
|
||||
<w:r><w:t>Document Title</w:t></w:r>
|
||||
</w:p>
|
||||
|
||||
<w:p>
|
||||
<w:pPr><w:pStyle w:val="Heading2"/></w:pPr>
|
||||
<w:r><w:t>Section Heading</w:t></w:r>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
### Text Formatting
|
||||
```xml
|
||||
<!-- Bold -->
|
||||
<w:r><w:rPr><w:b/><w:bCs/></w:rPr><w:t>Bold</w:t></w:r>
|
||||
<!-- Italic -->
|
||||
<w:r><w:rPr><w:i/><w:iCs/></w:rPr><w:t>Italic</w:t></w:r>
|
||||
<!-- Underline -->
|
||||
<w:r><w:rPr><w:u w:val="single"/></w:rPr><w:t>Underlined</w:t></w:r>
|
||||
<!-- Highlight -->
|
||||
<w:r><w:rPr><w:highlight w:val="yellow"/></w:rPr><w:t>Highlighted</w:t></w:r>
|
||||
```
|
||||
|
||||
### Lists
|
||||
```xml
|
||||
<!-- Numbered list -->
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="ListParagraph"/>
|
||||
<w:numPr><w:ilvl w:val="0"/><w:numId w:val="1"/></w:numPr>
|
||||
<w:spacing w:before="240"/>
|
||||
</w:pPr>
|
||||
<w:r><w:t>First item</w:t></w:r>
|
||||
</w:p>
|
||||
|
||||
<!-- Restart numbered list at 1 - use different numId -->
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="ListParagraph"/>
|
||||
<w:numPr><w:ilvl w:val="0"/><w:numId w:val="2"/></w:numPr>
|
||||
<w:spacing w:before="240"/>
|
||||
</w:pPr>
|
||||
<w:r><w:t>New list item 1</w:t></w:r>
|
||||
</w:p>
|
||||
|
||||
<!-- Bullet list (level 2) -->
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="ListParagraph"/>
|
||||
<w:numPr><w:ilvl w:val="1"/><w:numId w:val="1"/></w:numPr>
|
||||
<w:spacing w:before="240"/>
|
||||
<w:ind w:left="900"/>
|
||||
</w:pPr>
|
||||
<w:r><w:t>Bullet item</w:t></w:r>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
### Tables
|
||||
```xml
|
||||
<w:tbl>
|
||||
<w:tblPr>
|
||||
<w:tblStyle w:val="TableGrid"/>
|
||||
<w:tblW w:w="0" w:type="auto"/>
|
||||
</w:tblPr>
|
||||
<w:tblGrid>
|
||||
<w:gridCol w:w="4675"/><w:gridCol w:w="4675"/>
|
||||
</w:tblGrid>
|
||||
<w:tr>
|
||||
<w:tc>
|
||||
<w:tcPr><w:tcW w:w="4675" w:type="dxa"/></w:tcPr>
|
||||
<w:p><w:r><w:t>Cell 1</w:t></w:r></w:p>
|
||||
</w:tc>
|
||||
<w:tc>
|
||||
<w:tcPr><w:tcW w:w="4675" w:type="dxa"/></w:tcPr>
|
||||
<w:p><w:r><w:t>Cell 2</w:t></w:r></w:p>
|
||||
</w:tc>
|
||||
</w:tr>
|
||||
</w:tbl>
|
||||
```
|
||||
|
||||
### Layout
|
||||
```xml
|
||||
<!-- Page break before new section (common pattern) -->
|
||||
<w:p>
|
||||
<w:r>
|
||||
<w:br w:type="page"/>
|
||||
</w:r>
|
||||
</w:p>
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Heading1"/>
|
||||
</w:pPr>
|
||||
<w:r>
|
||||
<w:t>New Section Title</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
|
||||
<!-- Centered paragraph -->
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:spacing w:before="240" w:after="0"/>
|
||||
<w:jc w:val="center"/>
|
||||
</w:pPr>
|
||||
<w:r><w:t>Centered text</w:t></w:r>
|
||||
</w:p>
|
||||
|
||||
<!-- Font change - paragraph level (applies to all runs) -->
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:rPr><w:rFonts w:ascii="Courier New" w:hAnsi="Courier New"/></w:rPr>
|
||||
</w:pPr>
|
||||
<w:r><w:t>Monospace text</w:t></w:r>
|
||||
</w:p>
|
||||
|
||||
<!-- Font change - run level (specific to this text) -->
|
||||
<w:p>
|
||||
<w:r>
|
||||
<w:rPr><w:rFonts w:ascii="Courier New" w:hAnsi="Courier New"/></w:rPr>
|
||||
<w:t>This text is Courier New</w:t>
|
||||
</w:r>
|
||||
<w:r><w:t> and this text uses default font</w:t></w:r>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
## File Updates
|
||||
|
||||
When adding content, update these files:
|
||||
|
||||
**`word/_rels/document.xml.rels`:**
|
||||
```xml
|
||||
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering" Target="numbering.xml"/>
|
||||
<Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png"/>
|
||||
```
|
||||
|
||||
**`[Content_Types].xml`:**
|
||||
```xml
|
||||
<Default Extension="png" ContentType="image/png"/>
|
||||
<Override PartName="/word/numbering.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"/>
|
||||
```
|
||||
|
||||
### Images
|
||||
**CRITICAL**: Calculate dimensions to prevent page overflow and maintain aspect ratio.
|
||||
|
||||
```xml
|
||||
<!-- Minimal required structure -->
|
||||
<w:p>
|
||||
<w:r>
|
||||
<w:drawing>
|
||||
<wp:inline>
|
||||
<wp:extent cx="2743200" cy="1828800"/>
|
||||
<wp:docPr id="1" name="Picture 1"/>
|
||||
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
||||
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
||||
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
||||
<pic:nvPicPr>
|
||||
<pic:cNvPr id="0" name="image1.png"/>
|
||||
<pic:cNvPicPr/>
|
||||
</pic:nvPicPr>
|
||||
<pic:blipFill>
|
||||
<a:blip r:embed="rId5"/>
|
||||
<!-- Add for stretch fill with aspect ratio preservation -->
|
||||
<a:stretch>
|
||||
<a:fillRect/>
|
||||
</a:stretch>
|
||||
</pic:blipFill>
|
||||
<pic:spPr>
|
||||
<a:xfrm>
|
||||
<a:ext cx="2743200" cy="1828800"/>
|
||||
</a:xfrm>
|
||||
<a:prstGeom prst="rect"/>
|
||||
</pic:spPr>
|
||||
</pic:pic>
|
||||
</a:graphicData>
|
||||
</a:graphic>
|
||||
</wp:inline>
|
||||
</w:drawing>
|
||||
</w:r>
|
||||
</w:p>
|
||||
```
|
||||
|
||||
### Links (Hyperlinks)
|
||||
|
||||
**IMPORTANT**: All hyperlinks (both internal and external) require the Hyperlink style to be defined in styles.xml. Without this style, links will look like regular text instead of blue underlined clickable links.
|
||||
|
||||
**External Links:**
|
||||
```xml
|
||||
<!-- In document.xml -->
|
||||
<w:hyperlink r:id="rId5">
|
||||
<w:r>
|
||||
<w:rPr><w:rStyle w:val="Hyperlink"/></w:rPr>
|
||||
<w:t>Link Text</w:t>
|
||||
</w:r>
|
||||
</w:hyperlink>
|
||||
|
||||
<!-- In word/_rels/document.xml.rels -->
|
||||
<Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
|
||||
Target="https://www.example.com/" TargetMode="External"/>
|
||||
```
|
||||
|
||||
**Internal Links:**
|
||||
|
||||
```xml
|
||||
<!-- Link to bookmark -->
|
||||
<w:hyperlink w:anchor="myBookmark">
|
||||
<w:r>
|
||||
<w:rPr><w:rStyle w:val="Hyperlink"/></w:rPr>
|
||||
<w:t>Link Text</w:t>
|
||||
</w:r>
|
||||
</w:hyperlink>
|
||||
|
||||
<!-- Bookmark target -->
|
||||
<w:bookmarkStart w:id="0" w:name="myBookmark"/>
|
||||
<w:r><w:t>Target content</w:t></w:r>
|
||||
<w:bookmarkEnd w:id="0"/>
|
||||
```
|
||||
|
||||
**Hyperlink Style (required in styles.xml):**
|
||||
```xml
|
||||
<w:style w:type="character" w:styleId="Hyperlink">
|
||||
<w:name w:val="Hyperlink"/>
|
||||
<w:basedOn w:val="DefaultParagraphFont"/>
|
||||
<w:uiPriority w:val="99"/>
|
||||
<w:unhideWhenUsed/>
|
||||
<w:rPr>
|
||||
<w:color w:val="467886" w:themeColor="hyperlink"/>
|
||||
<w:u w:val="single"/>
|
||||
</w:rPr>
|
||||
</w:style>
|
||||
```
|
||||
|
||||
## Document Library (Python)
|
||||
|
||||
Use the Document class from `scripts/document.py` for all tracked changes and comments. It automatically handles infrastructure setup (people.xml, RSIDs, settings.xml, comment files, relationships, content types). Only use direct XML manipulation for complex scenarios not supported by the library.
|
||||
|
||||
**Working with Unicode and Entities:**
|
||||
- **Searching**: Both entity notation and Unicode characters work - `contains="“Company"` and `contains="\u201cCompany"` find the same text
|
||||
- **Replacing**: Use either entities (`“`) or Unicode (`\u201c`) - both work and will be converted appropriately based on the file's encoding (ascii → entities, utf-8 → Unicode)
|
||||
|
||||
### Initialization
|
||||
|
||||
**Find the docx skill root** (directory containing `scripts/` and `ooxml/`):
|
||||
```bash
|
||||
# Search for document.py to locate the skill root
|
||||
# Note: /mnt/skills is used here as an example; check your context for the actual location
|
||||
find /mnt/skills -name "document.py" -path "*/docx/scripts/*" 2>/dev/null | head -1
|
||||
# Example output: /mnt/skills/docx/scripts/document.py
|
||||
# Skill root is: /mnt/skills/docx
|
||||
```
|
||||
|
||||
**Run your script with PYTHONPATH** set to the docx skill root:
|
||||
```bash
|
||||
PYTHONPATH=/mnt/skills/docx python your_script.py
|
||||
```
|
||||
|
||||
**In your script**, import from the skill root:
|
||||
```python
|
||||
from scripts.document import Document, DocxXMLEditor
|
||||
|
||||
# Basic initialization (automatically creates temp copy and sets up infrastructure)
|
||||
doc = Document('unpacked')
|
||||
|
||||
# Customize author and initials
|
||||
doc = Document('unpacked', author="John Doe", initials="JD")
|
||||
|
||||
# Enable track revisions mode
|
||||
doc = Document('unpacked', track_revisions=True)
|
||||
|
||||
# Specify custom RSID (auto-generated if not provided)
|
||||
doc = Document('unpacked', rsid="07DC5ECB")
|
||||
```
|
||||
|
||||
### Creating Tracked Changes
|
||||
|
||||
**CRITICAL**: Only mark text that actually changes. Keep ALL unchanged text outside `<w:del>`/`<w:ins>` tags. Marking unchanged text makes edits unprofessional and harder to review.
|
||||
|
||||
**Attribute Handling**: The Document class auto-injects attributes (w:id, w:date, w:rsidR, w:rsidDel, w16du:dateUtc, xml:space) into new elements. When preserving unchanged text from the original document, copy the original `<w:r>` element with its existing attributes to maintain document integrity.
|
||||
|
||||
**Method Selection Guide**:
|
||||
- **Adding your own changes to regular text**: Use `replace_node()` with `<w:del>`/`<w:ins>` tags, or `suggest_deletion()` for removing entire `<w:r>` or `<w:p>` elements
|
||||
- **Partially modifying another author's tracked change**: Use `replace_node()` to nest your changes inside their `<w:ins>`/`<w:del>`
|
||||
- **Completely rejecting another author's insertion**: Use `revert_insertion()` on the `<w:ins>` element (NOT `suggest_deletion()`)
|
||||
- **Completely rejecting another author's deletion**: Use `revert_deletion()` on the `<w:del>` element to restore deleted content using tracked changes
|
||||
|
||||
```python
|
||||
# Minimal edit - change one word: "The report is monthly" → "The report is quarterly"
|
||||
# Original: <w:r w:rsidR="00AB12CD"><w:rPr><w:rFonts w:ascii="Calibri"/></w:rPr><w:t>The report is monthly</w:t></w:r>
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="The report is monthly")
|
||||
rpr = tags[0].toxml() if (tags := node.getElementsByTagName("w:rPr")) else ""
|
||||
replacement = f'<w:r w:rsidR="00AB12CD">{rpr}<w:t>The report is </w:t></w:r><w:del><w:r>{rpr}<w:delText>monthly</w:delText></w:r></w:del><w:ins><w:r>{rpr}<w:t>quarterly</w:t></w:r></w:ins>'
|
||||
doc["word/document.xml"].replace_node(node, replacement)
|
||||
|
||||
# Minimal edit - change number: "within 30 days" → "within 45 days"
|
||||
# Original: <w:r w:rsidR="00XYZ789"><w:rPr><w:rFonts w:ascii="Calibri"/></w:rPr><w:t>within 30 days</w:t></w:r>
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="within 30 days")
|
||||
rpr = tags[0].toxml() if (tags := node.getElementsByTagName("w:rPr")) else ""
|
||||
replacement = f'<w:r w:rsidR="00XYZ789">{rpr}<w:t>within </w:t></w:r><w:del><w:r>{rpr}<w:delText>30</w:delText></w:r></w:del><w:ins><w:r>{rpr}<w:t>45</w:t></w:r></w:ins><w:r w:rsidR="00XYZ789">{rpr}<w:t> days</w:t></w:r>'
|
||||
doc["word/document.xml"].replace_node(node, replacement)
|
||||
|
||||
# Complete replacement - preserve formatting even when replacing all text
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="apple")
|
||||
rpr = tags[0].toxml() if (tags := node.getElementsByTagName("w:rPr")) else ""
|
||||
replacement = f'<w:del><w:r>{rpr}<w:delText>apple</w:delText></w:r></w:del><w:ins><w:r>{rpr}<w:t>banana orange</w:t></w:r></w:ins>'
|
||||
doc["word/document.xml"].replace_node(node, replacement)
|
||||
|
||||
# Insert new content (no attributes needed - auto-injected)
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="existing text")
|
||||
doc["word/document.xml"].insert_after(node, '<w:ins><w:r><w:t>new text</w:t></w:r></w:ins>')
|
||||
|
||||
# Partially delete another author's insertion
|
||||
# Original: <w:ins w:author="Jane Smith" w:date="..."><w:r><w:t>quarterly financial report</w:t></w:r></w:ins>
|
||||
# Goal: Delete only "financial" to make it "quarterly report"
|
||||
node = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "5"})
|
||||
# IMPORTANT: Preserve w:author="Jane Smith" on the outer <w:ins> to maintain authorship
|
||||
replacement = '''<w:ins w:author="Jane Smith" w:date="2025-01-15T10:00:00Z">
|
||||
<w:r><w:t>quarterly </w:t></w:r>
|
||||
<w:del><w:r><w:delText>financial </w:delText></w:r></w:del>
|
||||
<w:r><w:t>report</w:t></w:r>
|
||||
</w:ins>'''
|
||||
doc["word/document.xml"].replace_node(node, replacement)
|
||||
|
||||
# Change part of another author's insertion
|
||||
# Original: <w:ins w:author="Jane Smith"><w:r><w:t>in silence, safe and sound</w:t></w:r></w:ins>
|
||||
# Goal: Change "safe and sound" to "soft and unbound"
|
||||
node = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "8"})
|
||||
replacement = f'''<w:ins w:author="Jane Smith" w:date="2025-01-15T10:00:00Z">
|
||||
<w:r><w:t>in silence, </w:t></w:r>
|
||||
</w:ins>
|
||||
<w:ins>
|
||||
<w:r><w:t>soft and unbound</w:t></w:r>
|
||||
</w:ins>
|
||||
<w:ins w:author="Jane Smith" w:date="2025-01-15T10:00:00Z">
|
||||
<w:del><w:r><w:delText>safe and sound</w:delText></w:r></w:del>
|
||||
</w:ins>'''
|
||||
doc["word/document.xml"].replace_node(node, replacement)
|
||||
|
||||
# Delete entire run (use only when deleting all content; use replace_node for partial deletions)
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="text to delete")
|
||||
doc["word/document.xml"].suggest_deletion(node)
|
||||
|
||||
# Delete entire paragraph (in-place, handles both regular and numbered list paragraphs)
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph to delete")
|
||||
doc["word/document.xml"].suggest_deletion(para)
|
||||
|
||||
# Add new numbered list item
|
||||
target_para = doc["word/document.xml"].get_node(tag="w:p", contains="existing list item")
|
||||
pPr = tags[0].toxml() if (tags := target_para.getElementsByTagName("w:pPr")) else ""
|
||||
new_item = f'<w:p>{pPr}<w:r><w:t>New item</w:t></w:r></w:p>'
|
||||
tracked_para = DocxXMLEditor.suggest_paragraph(new_item)
|
||||
doc["word/document.xml"].insert_after(target_para, tracked_para)
|
||||
# Optional: add spacing paragraph before content for better visual separation
|
||||
# spacing = DocxXMLEditor.suggest_paragraph('<w:p><w:pPr><w:pStyle w:val="ListParagraph"/></w:pPr></w:p>')
|
||||
# doc["word/document.xml"].insert_after(target_para, spacing + tracked_para)
|
||||
```
|
||||
|
||||
### Adding Comments
|
||||
|
||||
```python
|
||||
# Add comment spanning two existing tracked changes
|
||||
# Note: w:id is auto-generated. Only search by w:id if you know it from XML inspection
|
||||
start_node = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "1"})
|
||||
end_node = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "2"})
|
||||
doc.add_comment(start=start_node, end=end_node, text="Explanation of this change")
|
||||
|
||||
# Add comment on a paragraph
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph text")
|
||||
doc.add_comment(start=para, end=para, text="Comment on this paragraph")
|
||||
|
||||
# Add comment on newly created tracked change
|
||||
# First create the tracked change
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="old")
|
||||
new_nodes = doc["word/document.xml"].replace_node(
|
||||
node,
|
||||
'<w:del><w:r><w:delText>old</w:delText></w:r></w:del><w:ins><w:r><w:t>new</w:t></w:r></w:ins>'
|
||||
)
|
||||
# Then add comment on the newly created elements
|
||||
# new_nodes[0] is the <w:del>, new_nodes[1] is the <w:ins>
|
||||
doc.add_comment(start=new_nodes[0], end=new_nodes[1], text="Changed old to new per requirements")
|
||||
|
||||
# Reply to existing comment
|
||||
doc.reply_to_comment(parent_comment_id=0, text="I agree with this change")
|
||||
```
|
||||
|
||||
### Rejecting Tracked Changes
|
||||
|
||||
**IMPORTANT**: Use `revert_insertion()` to reject insertions and `revert_deletion()` to restore deletions using tracked changes. Use `suggest_deletion()` only for regular unmarked content.
|
||||
|
||||
```python
|
||||
# Reject insertion (wraps it in deletion)
|
||||
# Use this when another author inserted text that you want to delete
|
||||
ins = doc["word/document.xml"].get_node(tag="w:ins", attrs={"w:id": "5"})
|
||||
nodes = doc["word/document.xml"].revert_insertion(ins) # Returns [ins]
|
||||
|
||||
# Reject deletion (creates insertion to restore deleted content)
|
||||
# Use this when another author deleted text that you want to restore
|
||||
del_elem = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "3"})
|
||||
nodes = doc["word/document.xml"].revert_deletion(del_elem) # Returns [del_elem, new_ins]
|
||||
|
||||
# Reject all insertions in a paragraph
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph text")
|
||||
nodes = doc["word/document.xml"].revert_insertion(para) # Returns [para]
|
||||
|
||||
# Reject all deletions in a paragraph
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", contains="paragraph text")
|
||||
nodes = doc["word/document.xml"].revert_deletion(para) # Returns [para]
|
||||
```
|
||||
|
||||
### Inserting Images
|
||||
|
||||
**CRITICAL**: The Document class works with a temporary copy at `doc.unpacked_path`. Always copy images to this temp directory, not the original unpacked folder.
|
||||
|
||||
```python
|
||||
from PIL import Image
|
||||
import shutil, os
|
||||
|
||||
# Initialize document first
|
||||
doc = Document('unpacked')
|
||||
|
||||
# Copy image and calculate full-width dimensions with aspect ratio
|
||||
media_dir = os.path.join(doc.unpacked_path, 'word/media')
|
||||
os.makedirs(media_dir, exist_ok=True)
|
||||
shutil.copy('image.png', os.path.join(media_dir, 'image1.png'))
|
||||
img = Image.open(os.path.join(media_dir, 'image1.png'))
|
||||
width_emus = int(6.5 * 914400) # 6.5" usable width, 914400 EMUs/inch
|
||||
height_emus = int(width_emus * img.size[1] / img.size[0])
|
||||
|
||||
# Add relationship and content type
|
||||
rels_editor = doc['word/_rels/document.xml.rels']
|
||||
next_rid = rels_editor.get_next_rid()
|
||||
rels_editor.append_to(rels_editor.dom.documentElement,
|
||||
f'<Relationship Id="{next_rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="media/image1.png"/>')
|
||||
doc['[Content_Types].xml'].append_to(doc['[Content_Types].xml'].dom.documentElement,
|
||||
'<Default Extension="png" ContentType="image/png"/>')
|
||||
|
||||
# Insert image
|
||||
node = doc["word/document.xml"].get_node(tag="w:p", line_number=100)
|
||||
doc["word/document.xml"].insert_after(node, f'''<w:p>
|
||||
<w:r>
|
||||
<w:drawing>
|
||||
<wp:inline distT="0" distB="0" distL="0" distR="0">
|
||||
<wp:extent cx="{width_emus}" cy="{height_emus}"/>
|
||||
<wp:docPr id="1" name="Picture 1"/>
|
||||
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
|
||||
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
||||
<pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
||||
<pic:nvPicPr><pic:cNvPr id="1" name="image1.png"/><pic:cNvPicPr/></pic:nvPicPr>
|
||||
<pic:blipFill><a:blip r:embed="{next_rid}"/><a:stretch><a:fillRect/></a:stretch></pic:blipFill>
|
||||
<pic:spPr><a:xfrm><a:ext cx="{width_emus}" cy="{height_emus}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom></pic:spPr>
|
||||
</pic:pic>
|
||||
</a:graphicData>
|
||||
</a:graphic>
|
||||
</wp:inline>
|
||||
</w:drawing>
|
||||
</w:r>
|
||||
</w:p>''')
|
||||
```
|
||||
|
||||
### Getting Nodes
|
||||
|
||||
```python
|
||||
# By text content
|
||||
node = doc["word/document.xml"].get_node(tag="w:p", contains="specific text")
|
||||
|
||||
# By line range
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", line_number=range(100, 150))
|
||||
|
||||
# By attributes
|
||||
node = doc["word/document.xml"].get_node(tag="w:del", attrs={"w:id": "1"})
|
||||
|
||||
# By exact line number (must be line number where tag opens)
|
||||
para = doc["word/document.xml"].get_node(tag="w:p", line_number=42)
|
||||
|
||||
# Combine filters
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", line_number=range(40, 60), contains="text")
|
||||
|
||||
# Disambiguate when text appears multiple times - add line_number range
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", contains="Section", line_number=range(2400, 2500))
|
||||
```
|
||||
|
||||
### Saving
|
||||
|
||||
```python
|
||||
# Save with automatic validation (copies back to original directory)
|
||||
doc.save() # Validates by default, raises error if validation fails
|
||||
|
||||
# Save to different location
|
||||
doc.save('modified-unpacked')
|
||||
|
||||
# Skip validation (debugging only - needing this in production indicates XML issues)
|
||||
doc.save(validate=False)
|
||||
```
|
||||
|
||||
### Direct DOM Manipulation
|
||||
|
||||
For complex scenarios not covered by the library:
|
||||
|
||||
```python
|
||||
# Access any XML file
|
||||
editor = doc["word/document.xml"]
|
||||
editor = doc["word/comments.xml"]
|
||||
|
||||
# Direct DOM access (defusedxml.minidom.Document)
|
||||
node = doc["word/document.xml"].get_node(tag="w:p", line_number=5)
|
||||
parent = node.parentNode
|
||||
parent.removeChild(node)
|
||||
parent.appendChild(node) # Move to end
|
||||
|
||||
# General document manipulation (without tracked changes)
|
||||
old_node = doc["word/document.xml"].get_node(tag="w:p", contains="original text")
|
||||
doc["word/document.xml"].replace_node(old_node, "<w:p><w:r><w:t>replacement text</w:t></w:r></w:p>")
|
||||
|
||||
# Multiple insertions - use return value to maintain order
|
||||
node = doc["word/document.xml"].get_node(tag="w:r", line_number=100)
|
||||
nodes = doc["word/document.xml"].insert_after(node, "<w:r><w:t>A</w:t></w:r>")
|
||||
nodes = doc["word/document.xml"].insert_after(nodes[-1], "<w:r><w:t>B</w:t></w:r>")
|
||||
nodes = doc["word/document.xml"].insert_after(nodes[-1], "<w:r><w:t>C</w:t></w:r>")
|
||||
# Results in: original_node, A, B, C
|
||||
```
|
||||
|
||||
## Tracked Changes (Redlining)
|
||||
|
||||
**Use the Document class above for all tracked changes.** The patterns below are for reference when constructing replacement XML strings.
|
||||
|
||||
### Validation Rules
|
||||
The validator checks that the document text matches the original after reverting Claude's changes. This means:
|
||||
- **NEVER modify text inside another author's `<w:ins>` or `<w:del>` tags**
|
||||
- **ALWAYS use nested deletions** to remove another author's insertions
|
||||
- **Every edit must be properly tracked** with `<w:ins>` or `<w:del>` tags
|
||||
|
||||
### Tracked Change Patterns
|
||||
|
||||
**CRITICAL RULES**:
|
||||
1. Never modify the content inside another author's tracked changes. Always use nested deletions.
|
||||
2. **XML Structure**: Always place `<w:del>` and `<w:ins>` at paragraph level containing complete `<w:r>` elements. Never nest inside `<w:r>` elements - this creates invalid XML that breaks document processing.
|
||||
|
||||
**Text Insertion:**
|
||||
```xml
|
||||
<w:ins w:id="1" w:author="Claude" w:date="2025-07-30T23:05:00Z" w16du:dateUtc="2025-07-31T06:05:00Z">
|
||||
<w:r w:rsidR="00792858">
|
||||
<w:t>inserted text</w:t>
|
||||
</w:r>
|
||||
</w:ins>
|
||||
```
|
||||
|
||||
**Text Deletion:**
|
||||
```xml
|
||||
<w:del w:id="2" w:author="Claude" w:date="2025-07-30T23:05:00Z" w16du:dateUtc="2025-07-31T06:05:00Z">
|
||||
<w:r w:rsidDel="00792858">
|
||||
<w:delText>deleted text</w:delText>
|
||||
</w:r>
|
||||
</w:del>
|
||||
```
|
||||
|
||||
**Deleting Another Author's Insertion (MUST use nested structure):**
|
||||
```xml
|
||||
<!-- Nest deletion inside the original insertion -->
|
||||
<w:ins w:author="Jane Smith" w:id="16">
|
||||
<w:del w:author="Claude" w:id="40">
|
||||
<w:r><w:delText>monthly</w:delText></w:r>
|
||||
</w:del>
|
||||
</w:ins>
|
||||
<w:ins w:author="Claude" w:id="41">
|
||||
<w:r><w:t>weekly</w:t></w:r>
|
||||
</w:ins>
|
||||
```
|
||||
|
||||
**Restoring Another Author's Deletion:**
|
||||
```xml
|
||||
<!-- Leave their deletion unchanged, add new insertion after it -->
|
||||
<w:del w:author="Jane Smith" w:id="50">
|
||||
<w:r><w:delText>within 30 days</w:delText></w:r>
|
||||
</w:del>
|
||||
<w:ins w:author="Claude" w:id="51">
|
||||
<w:r><w:t>within 30 days</w:t></w:r>
|
||||
</w:ins>
|
||||
```
|
||||
1499
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
Normal file
1499
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
|
||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/chartDrawing"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
schemaLocation="dml-main.xsd"/>
|
||||
<xsd:complexType name="CT_ShapeNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1" maxOccurs="1"
|
||||
/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Shape">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="textlink" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ConnectorNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvCxnSpPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Connector">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvCxnSpPr" type="CT_ConnectorNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PictureNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Picture">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional" default=""/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GraphicFrameNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
||||
minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GraphicFrame">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvGraphicFramePr" type="CT_GraphicFrameNonVisual" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupShapeNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupShape">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvGrpSpPr" type="CT_GroupShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="sp" type="CT_Shape"/>
|
||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
||||
<xsd:element name="pic" type="CT_Picture"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ObjectChoices">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="sp" type="CT_Shape"/>
|
||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
||||
<xsd:element name="pic" type="CT_Picture"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:simpleType name="ST_MarkerCoordinate">
|
||||
<xsd:restriction base="xsd:double">
|
||||
<xsd:minInclusive value="0.0"/>
|
||||
<xsd:maxInclusive value="1.0"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Marker">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="x" type="ST_MarkerCoordinate" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="y" type="ST_MarkerCoordinate" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_RelSizeAnchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="from" type="CT_Marker"/>
|
||||
<xsd:element name="to" type="CT_Marker"/>
|
||||
<xsd:group ref="EG_ObjectChoices"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_AbsSizeAnchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="from" type="CT_Marker"/>
|
||||
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
|
||||
<xsd:group ref="EG_ObjectChoices"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_Anchor">
|
||||
<xsd:choice>
|
||||
<xsd:element name="relSizeAnchor" type="CT_RelSizeAnchor"/>
|
||||
<xsd:element name="absSizeAnchor" type="CT_AbsSizeAnchor"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_Drawing">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_Anchor" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas"
|
||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
elementFormDefault="qualified"
|
||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/lockedCanvas">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
schemaLocation="dml-main.xsd"/>
|
||||
<xsd:element name="lockedCanvas" type="a:CT_GvmlGroupShape"/>
|
||||
</xsd:schema>
|
||||
3081
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd
Normal file
3081
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/picture"
|
||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" elementFormDefault="qualified"
|
||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/picture">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
schemaLocation="dml-main.xsd"/>
|
||||
<xsd:complexType name="CT_PictureNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Picture">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="pic" type="CT_Picture"/>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,185 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
schemaLocation="dml-main.xsd"/>
|
||||
<xsd:import schemaLocation="shared-relationshipReference.xsd"
|
||||
namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"/>
|
||||
<xsd:element name="from" type="CT_Marker"/>
|
||||
<xsd:element name="to" type="CT_Marker"/>
|
||||
<xsd:complexType name="CT_AnchorClientData">
|
||||
<xsd:attribute name="fLocksWithSheet" type="xsd:boolean" use="optional" default="true"/>
|
||||
<xsd:attribute name="fPrintsWithSheet" type="xsd:boolean" use="optional" default="true"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ShapeNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1" maxOccurs="1"
|
||||
/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Shape">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="textlink" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ConnectorNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvCxnSpPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Connector">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvCxnSpPr" type="CT_ConnectorNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PictureNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvPicPr" type="a:CT_NonVisualPictureProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Picture">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvPicPr" type="CT_PictureNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="blipFill" type="a:CT_BlipFillProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional" default=""/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GraphicalObjectFrameNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
||||
minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GraphicalObjectFrame">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvGraphicFramePr" type="CT_GraphicalObjectFrameNonVisual" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="macro" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupShapeNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupShape">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvGrpSpPr" type="CT_GroupShapeNonVisual" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="sp" type="CT_Shape"/>
|
||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
|
||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
||||
<xsd:element name="pic" type="CT_Picture"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ObjectChoices">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="sp" type="CT_Shape"/>
|
||||
<xsd:element name="grpSp" type="CT_GroupShape"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
|
||||
<xsd:element name="cxnSp" type="CT_Connector"/>
|
||||
<xsd:element name="pic" type="CT_Picture"/>
|
||||
<xsd:element name="contentPart" type="CT_Rel"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_Rel">
|
||||
<xsd:attribute ref="r:id" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_ColID">
|
||||
<xsd:restriction base="xsd:int">
|
||||
<xsd:minInclusive value="0"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_RowID">
|
||||
<xsd:restriction base="xsd:int">
|
||||
<xsd:minInclusive value="0"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Marker">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="col" type="ST_ColID"/>
|
||||
<xsd:element name="colOff" type="a:ST_Coordinate"/>
|
||||
<xsd:element name="row" type="ST_RowID"/>
|
||||
<xsd:element name="rowOff" type="a:ST_Coordinate"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_EditAs">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="twoCell"/>
|
||||
<xsd:enumeration value="oneCell"/>
|
||||
<xsd:enumeration value="absolute"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_TwoCellAnchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="from" type="CT_Marker"/>
|
||||
<xsd:element name="to" type="CT_Marker"/>
|
||||
<xsd:group ref="EG_ObjectChoices"/>
|
||||
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="editAs" type="ST_EditAs" use="optional" default="twoCell"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OneCellAnchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="from" type="CT_Marker"/>
|
||||
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
|
||||
<xsd:group ref="EG_ObjectChoices"/>
|
||||
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_AbsoluteAnchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="pos" type="a:CT_Point2D"/>
|
||||
<xsd:element name="ext" type="a:CT_PositiveSize2D"/>
|
||||
<xsd:group ref="EG_ObjectChoices"/>
|
||||
<xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_Anchor">
|
||||
<xsd:choice>
|
||||
<xsd:element name="twoCellAnchor" type="CT_TwoCellAnchor"/>
|
||||
<xsd:element name="oneCellAnchor" type="CT_OneCellAnchor"/>
|
||||
<xsd:element name="absoluteAnchor" type="CT_AbsoluteAnchor"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_Drawing">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_Anchor" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="wsDr" type="CT_Drawing"/>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,287 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
xmlns:dpct="http://schemas.openxmlformats.org/drawingml/2006/picture"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
xmlns="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
||||
targetNamespace="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main"
|
||||
schemaLocation="dml-main.xsd"/>
|
||||
<xsd:import schemaLocation="wml.xsd"
|
||||
namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/picture"
|
||||
schemaLocation="dml-picture.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
schemaLocation="shared-relationshipReference.xsd"/>
|
||||
<xsd:complexType name="CT_EffectExtent">
|
||||
<xsd:attribute name="l" type="a:ST_Coordinate" use="required"/>
|
||||
<xsd:attribute name="t" type="a:ST_Coordinate" use="required"/>
|
||||
<xsd:attribute name="r" type="a:ST_Coordinate" use="required"/>
|
||||
<xsd:attribute name="b" type="a:ST_Coordinate" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_WrapDistance">
|
||||
<xsd:restriction base="xsd:unsignedInt"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Inline">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
|
||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
||||
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
||||
minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_WrapText">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="bothSides"/>
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="largest"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_WrapPath">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="start" type="a:CT_Point2D" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="lineTo" type="a:CT_Point2D" minOccurs="2" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="edited" type="xsd:boolean" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WrapNone"/>
|
||||
<xsd:complexType name="CT_WrapSquare">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
|
||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WrapTight">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="wrapPolygon" type="CT_WrapPath" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
|
||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WrapThrough">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="wrapPolygon" type="CT_WrapPath" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="wrapText" type="ST_WrapText" use="required"/>
|
||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WrapTopBottom">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_WrapType">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="wrapNone" type="CT_WrapNone" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="wrapSquare" type="CT_WrapSquare" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="wrapTight" type="CT_WrapTight" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="wrapThrough" type="CT_WrapThrough" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="wrapTopAndBottom" type="CT_WrapTopBottom" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:simpleType name="ST_PositionOffset">
|
||||
<xsd:restriction base="xsd:int"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_AlignH">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="inside"/>
|
||||
<xsd:enumeration value="outside"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_RelFromH">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="margin"/>
|
||||
<xsd:enumeration value="page"/>
|
||||
<xsd:enumeration value="column"/>
|
||||
<xsd:enumeration value="character"/>
|
||||
<xsd:enumeration value="leftMargin"/>
|
||||
<xsd:enumeration value="rightMargin"/>
|
||||
<xsd:enumeration value="insideMargin"/>
|
||||
<xsd:enumeration value="outsideMargin"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_PosH">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="align" type="ST_AlignH" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="relativeFrom" type="ST_RelFromH" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_AlignV">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="top"/>
|
||||
<xsd:enumeration value="bottom"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="inside"/>
|
||||
<xsd:enumeration value="outside"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_RelFromV">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="margin"/>
|
||||
<xsd:enumeration value="page"/>
|
||||
<xsd:enumeration value="paragraph"/>
|
||||
<xsd:enumeration value="line"/>
|
||||
<xsd:enumeration value="topMargin"/>
|
||||
<xsd:enumeration value="bottomMargin"/>
|
||||
<xsd:enumeration value="insideMargin"/>
|
||||
<xsd:enumeration value="outsideMargin"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_PosV">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="align" type="ST_AlignV" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="relativeFrom" type="ST_RelFromV" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Anchor">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="simplePos" type="a:CT_Point2D"/>
|
||||
<xsd:element name="positionH" type="CT_PosH"/>
|
||||
<xsd:element name="positionV" type="CT_PosV"/>
|
||||
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
|
||||
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
|
||||
<xsd:group ref="EG_WrapType"/>
|
||||
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
|
||||
minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
|
||||
<xsd:attribute name="simplePos" type="xsd:boolean"/>
|
||||
<xsd:attribute name="relativeHeight" type="xsd:unsignedInt" use="required"/>
|
||||
<xsd:attribute name="behindDoc" type="xsd:boolean" use="required"/>
|
||||
<xsd:attribute name="locked" type="xsd:boolean" use="required"/>
|
||||
<xsd:attribute name="layoutInCell" type="xsd:boolean" use="required"/>
|
||||
<xsd:attribute name="hidden" type="xsd:boolean" use="optional"/>
|
||||
<xsd:attribute name="allowOverlap" type="xsd:boolean" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_TxbxContent">
|
||||
<xsd:group ref="w:EG_BlockLevelElts" minOccurs="1" maxOccurs="unbounded"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_TextboxInfo">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="txbxContent" type="CT_TxbxContent" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="id" type="xsd:unsignedShort" use="optional" default="0"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LinkedTextboxInformation">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="id" type="xsd:unsignedShort" use="required"/>
|
||||
<xsd:attribute name="seq" type="xsd:unsignedShort" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WordprocessingShape">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
<xsd:element name="cNvCnPr" type="a:CT_NonVisualConnectorProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="1">
|
||||
<xsd:element name="txbx" type="CT_TextboxInfo" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="linkedTxbx" type="CT_LinkedTextboxInformation" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="bodyPr" type="a:CT_TextBodyProperties" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="normalEastAsianFlow" type="xsd:boolean" use="optional" default="false"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GraphicFrame">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="cNvFrPr" type="a:CT_NonVisualGraphicFrameProperties" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WordprocessingContentPartNonVisual">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="cNvContentPartPr" type="a:CT_NonVisualContentPartProperties" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WordprocessingContentPart">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="nvContentPartPr" type="CT_WordprocessingContentPartNonVisual" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="xfrm" type="a:CT_Transform2D" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="bwMode" type="a:ST_BlackWhiteMode" use="optional"/>
|
||||
<xsd:attribute ref="r:id" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WordprocessingGroup">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="cNvGrpSpPr" type="a:CT_NonVisualGroupDrawingShapeProps" minOccurs="1"
|
||||
maxOccurs="1"/>
|
||||
<xsd:element name="grpSpPr" type="a:CT_GroupShapeProperties" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element ref="wsp"/>
|
||||
<xsd:element name="grpSp" type="CT_WordprocessingGroup"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
||||
<xsd:element ref="dpct:pic"/>
|
||||
<xsd:element name="contentPart" type="CT_WordprocessingContentPart"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_WordprocessingCanvas">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element name="bg" type="a:CT_BackgroundFormatting" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:element name="whole" type="a:CT_WholeE2oFormatting" minOccurs="0" maxOccurs="1"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element ref="wsp"/>
|
||||
<xsd:element ref="dpct:pic"/>
|
||||
<xsd:element name="contentPart" type="CT_WordprocessingContentPart"/>
|
||||
<xsd:element ref="wgp"/>
|
||||
<xsd:element name="graphicFrame" type="CT_GraphicFrame"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="extLst" type="a:CT_OfficeArtExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="wpc" type="CT_WordprocessingCanvas"/>
|
||||
<xsd:element name="wgp" type="CT_WordprocessingGroup"/>
|
||||
<xsd:element name="wsp" type="CT_WordprocessingShape"/>
|
||||
<xsd:element name="inline" type="CT_Inline"/>
|
||||
<xsd:element name="anchor" type="CT_Anchor"/>
|
||||
</xsd:schema>
|
||||
1676
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd
Normal file
1676
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/characteristics"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/characteristics"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:complexType name="CT_AdditionalCharacteristics">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="characteristic" type="CT_Characteristic" minOccurs="0"
|
||||
maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Characteristic">
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="relation" type="ST_Relation" use="required"/>
|
||||
<xsd:attribute name="val" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="vocabulary" type="xsd:anyURI" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Relation">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="ge"/>
|
||||
<xsd:enumeration value="le"/>
|
||||
<xsd:enumeration value="gt"/>
|
||||
<xsd:enumeration value="lt"/>
|
||||
<xsd:enumeration value="eq"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:element name="additionalCharacteristics" type="CT_AdditionalCharacteristics"/>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,144 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/bibliography"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:simpleType name="ST_SourceType">
|
||||
<xsd:restriction base="s:ST_String">
|
||||
<xsd:enumeration value="ArticleInAPeriodical"/>
|
||||
<xsd:enumeration value="Book"/>
|
||||
<xsd:enumeration value="BookSection"/>
|
||||
<xsd:enumeration value="JournalArticle"/>
|
||||
<xsd:enumeration value="ConferenceProceedings"/>
|
||||
<xsd:enumeration value="Report"/>
|
||||
<xsd:enumeration value="SoundRecording"/>
|
||||
<xsd:enumeration value="Performance"/>
|
||||
<xsd:enumeration value="Art"/>
|
||||
<xsd:enumeration value="DocumentFromInternetSite"/>
|
||||
<xsd:enumeration value="InternetSite"/>
|
||||
<xsd:enumeration value="Film"/>
|
||||
<xsd:enumeration value="Interview"/>
|
||||
<xsd:enumeration value="Patent"/>
|
||||
<xsd:enumeration value="ElectronicSource"/>
|
||||
<xsd:enumeration value="Case"/>
|
||||
<xsd:enumeration value="Misc"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_NameListType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="Person" type="CT_PersonType" minOccurs="1" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PersonType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="Last" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xsd:element name="First" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xsd:element name="Middle" type="s:ST_String" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_NameType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="NameList" type="CT_NameListType" minOccurs="1" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_NameOrCorporateType">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="0" maxOccurs="1">
|
||||
<xsd:element name="NameList" type="CT_NameListType" minOccurs="1" maxOccurs="1"/>
|
||||
<xsd:element name="Corporate" minOccurs="1" maxOccurs="1" type="s:ST_String"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_AuthorType">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="Artist" type="CT_NameType"/>
|
||||
<xsd:element name="Author" type="CT_NameOrCorporateType"/>
|
||||
<xsd:element name="BookAuthor" type="CT_NameType"/>
|
||||
<xsd:element name="Compiler" type="CT_NameType"/>
|
||||
<xsd:element name="Composer" type="CT_NameType"/>
|
||||
<xsd:element name="Conductor" type="CT_NameType"/>
|
||||
<xsd:element name="Counsel" type="CT_NameType"/>
|
||||
<xsd:element name="Director" type="CT_NameType"/>
|
||||
<xsd:element name="Editor" type="CT_NameType"/>
|
||||
<xsd:element name="Interviewee" type="CT_NameType"/>
|
||||
<xsd:element name="Interviewer" type="CT_NameType"/>
|
||||
<xsd:element name="Inventor" type="CT_NameType"/>
|
||||
<xsd:element name="Performer" type="CT_NameOrCorporateType"/>
|
||||
<xsd:element name="ProducerName" type="CT_NameType"/>
|
||||
<xsd:element name="Translator" type="CT_NameType"/>
|
||||
<xsd:element name="Writer" type="CT_NameType"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SourceType">
|
||||
<xsd:sequence>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="AbbreviatedCaseNumber" type="s:ST_String"/>
|
||||
<xsd:element name="AlbumTitle" type="s:ST_String"/>
|
||||
<xsd:element name="Author" type="CT_AuthorType"/>
|
||||
<xsd:element name="BookTitle" type="s:ST_String"/>
|
||||
<xsd:element name="Broadcaster" type="s:ST_String"/>
|
||||
<xsd:element name="BroadcastTitle" type="s:ST_String"/>
|
||||
<xsd:element name="CaseNumber" type="s:ST_String"/>
|
||||
<xsd:element name="ChapterNumber" type="s:ST_String"/>
|
||||
<xsd:element name="City" type="s:ST_String"/>
|
||||
<xsd:element name="Comments" type="s:ST_String"/>
|
||||
<xsd:element name="ConferenceName" type="s:ST_String"/>
|
||||
<xsd:element name="CountryRegion" type="s:ST_String"/>
|
||||
<xsd:element name="Court" type="s:ST_String"/>
|
||||
<xsd:element name="Day" type="s:ST_String"/>
|
||||
<xsd:element name="DayAccessed" type="s:ST_String"/>
|
||||
<xsd:element name="Department" type="s:ST_String"/>
|
||||
<xsd:element name="Distributor" type="s:ST_String"/>
|
||||
<xsd:element name="Edition" type="s:ST_String"/>
|
||||
<xsd:element name="Guid" type="s:ST_String"/>
|
||||
<xsd:element name="Institution" type="s:ST_String"/>
|
||||
<xsd:element name="InternetSiteTitle" type="s:ST_String"/>
|
||||
<xsd:element name="Issue" type="s:ST_String"/>
|
||||
<xsd:element name="JournalName" type="s:ST_String"/>
|
||||
<xsd:element name="LCID" type="s:ST_Lang"/>
|
||||
<xsd:element name="Medium" type="s:ST_String"/>
|
||||
<xsd:element name="Month" type="s:ST_String"/>
|
||||
<xsd:element name="MonthAccessed" type="s:ST_String"/>
|
||||
<xsd:element name="NumberVolumes" type="s:ST_String"/>
|
||||
<xsd:element name="Pages" type="s:ST_String"/>
|
||||
<xsd:element name="PatentNumber" type="s:ST_String"/>
|
||||
<xsd:element name="PeriodicalTitle" type="s:ST_String"/>
|
||||
<xsd:element name="ProductionCompany" type="s:ST_String"/>
|
||||
<xsd:element name="PublicationTitle" type="s:ST_String"/>
|
||||
<xsd:element name="Publisher" type="s:ST_String"/>
|
||||
<xsd:element name="RecordingNumber" type="s:ST_String"/>
|
||||
<xsd:element name="RefOrder" type="s:ST_String"/>
|
||||
<xsd:element name="Reporter" type="s:ST_String"/>
|
||||
<xsd:element name="SourceType" type="ST_SourceType"/>
|
||||
<xsd:element name="ShortTitle" type="s:ST_String"/>
|
||||
<xsd:element name="StandardNumber" type="s:ST_String"/>
|
||||
<xsd:element name="StateProvince" type="s:ST_String"/>
|
||||
<xsd:element name="Station" type="s:ST_String"/>
|
||||
<xsd:element name="Tag" type="s:ST_String"/>
|
||||
<xsd:element name="Theater" type="s:ST_String"/>
|
||||
<xsd:element name="ThesisType" type="s:ST_String"/>
|
||||
<xsd:element name="Title" type="s:ST_String"/>
|
||||
<xsd:element name="Type" type="s:ST_String"/>
|
||||
<xsd:element name="URL" type="s:ST_String"/>
|
||||
<xsd:element name="Version" type="s:ST_String"/>
|
||||
<xsd:element name="Volume" type="s:ST_String"/>
|
||||
<xsd:element name="Year" type="s:ST_String"/>
|
||||
<xsd:element name="YearAccessed" type="s:ST_String"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="Sources" type="CT_Sources"/>
|
||||
<xsd:complexType name="CT_Sources">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="Source" type="CT_SourceType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="SelectedStyle" type="s:ST_String"/>
|
||||
<xsd:attribute name="StyleName" type="s:ST_String"/>
|
||||
<xsd:attribute name="URI" type="s:ST_String"/>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,174 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
elementFormDefault="qualified">
|
||||
<xsd:simpleType name="ST_Lang">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_HexColorRGB">
|
||||
<xsd:restriction base="xsd:hexBinary">
|
||||
<xsd:length value="3" fixed="true"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Panose">
|
||||
<xsd:restriction base="xsd:hexBinary">
|
||||
<xsd:length value="10"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_CalendarType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="gregorian"/>
|
||||
<xsd:enumeration value="gregorianUs"/>
|
||||
<xsd:enumeration value="gregorianMeFrench"/>
|
||||
<xsd:enumeration value="gregorianArabic"/>
|
||||
<xsd:enumeration value="hijri"/>
|
||||
<xsd:enumeration value="hebrew"/>
|
||||
<xsd:enumeration value="taiwan"/>
|
||||
<xsd:enumeration value="japan"/>
|
||||
<xsd:enumeration value="thai"/>
|
||||
<xsd:enumeration value="korea"/>
|
||||
<xsd:enumeration value="saka"/>
|
||||
<xsd:enumeration value="gregorianXlitEnglish"/>
|
||||
<xsd:enumeration value="gregorianXlitFrench"/>
|
||||
<xsd:enumeration value="none"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_AlgClass">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="hash"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_CryptProv">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="rsaAES"/>
|
||||
<xsd:enumeration value="rsaFull"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_AlgType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="typeAny"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ColorType">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Guid">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OnOff">
|
||||
<xsd:union memberTypes="xsd:boolean ST_OnOff1"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OnOff1">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="on"/>
|
||||
<xsd:enumeration value="off"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_String">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_XmlName">
|
||||
<xsd:restriction base="xsd:NCName">
|
||||
<xsd:minLength value="1"/>
|
||||
<xsd:maxLength value="255"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_TrueFalse">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="t"/>
|
||||
<xsd:enumeration value="f"/>
|
||||
<xsd:enumeration value="true"/>
|
||||
<xsd:enumeration value="false"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_TrueFalseBlank">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="t"/>
|
||||
<xsd:enumeration value="f"/>
|
||||
<xsd:enumeration value="true"/>
|
||||
<xsd:enumeration value="false"/>
|
||||
<xsd:enumeration value=""/>
|
||||
<xsd:enumeration value="True"/>
|
||||
<xsd:enumeration value="False"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_UnsignedDecimalNumber">
|
||||
<xsd:restriction base="xsd:decimal">
|
||||
<xsd:minInclusive value="0"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_TwipsMeasure">
|
||||
<xsd:union memberTypes="ST_UnsignedDecimalNumber ST_PositiveUniversalMeasure"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_VerticalAlignRun">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="baseline"/>
|
||||
<xsd:enumeration value="superscript"/>
|
||||
<xsd:enumeration value="subscript"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Xstring">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_XAlign">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="inside"/>
|
||||
<xsd:enumeration value="outside"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_YAlign">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="inline"/>
|
||||
<xsd:enumeration value="top"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="bottom"/>
|
||||
<xsd:enumeration value="inside"/>
|
||||
<xsd:enumeration value="outside"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ConformanceClass">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="strict"/>
|
||||
<xsd:enumeration value="transitional"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_UniversalMeasure">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern value="-?[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PositiveUniversalMeasure">
|
||||
<xsd:restriction base="ST_UniversalMeasure">
|
||||
<xsd:pattern value="[0-9]+(\.[0-9]+)?(mm|cm|in|pt|pc|pi)"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Percentage">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern value="-?[0-9]+(\.[0-9]+)?%"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_FixedPercentage">
|
||||
<xsd:restriction base="ST_Percentage">
|
||||
<xsd:pattern value="-?((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PositivePercentage">
|
||||
<xsd:restriction base="ST_Percentage">
|
||||
<xsd:pattern value="[0-9]+(\.[0-9]+)?%"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PositiveFixedPercentage">
|
||||
<xsd:restriction base="ST_Percentage">
|
||||
<xsd:pattern value="((100)|([0-9][0-9]?))(\.[0-9][0-9]?)?%"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/customXml"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/customXml"
|
||||
elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:complexType name="CT_DatastoreSchemaRef">
|
||||
<xsd:attribute name="uri" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_DatastoreSchemaRefs">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="schemaRef" type="CT_DatastoreSchemaRef" minOccurs="0" maxOccurs="unbounded"
|
||||
/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_DatastoreItem">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="schemaRefs" type="CT_DatastoreSchemaRefs" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="itemID" type="s:ST_Guid" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="datastoreItem" type="CT_DatastoreItem"/>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
|
||||
targetNamespace="http://schemas.openxmlformats.org/schemaLibrary/2006/main"
|
||||
attributeFormDefault="qualified" elementFormDefault="qualified">
|
||||
<xsd:complexType name="CT_Schema">
|
||||
<xsd:attribute name="uri" type="xsd:string" default=""/>
|
||||
<xsd:attribute name="manifestLocation" type="xsd:string"/>
|
||||
<xsd:attribute name="schemaLocation" type="xsd:string"/>
|
||||
<xsd:attribute name="schemaLanguage" type="xsd:token"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SchemaLibrary">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="schema" type="CT_Schema" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="schemaLibrary" type="CT_SchemaLibrary"/>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
|
||||
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"
|
||||
blockDefault="#all" elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
schemaLocation="shared-documentPropertiesVariantTypes.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:element name="Properties" type="CT_Properties"/>
|
||||
<xsd:complexType name="CT_Properties">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="property" minOccurs="0" maxOccurs="unbounded" type="CT_Property"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Property">
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element ref="vt:vector"/>
|
||||
<xsd:element ref="vt:array"/>
|
||||
<xsd:element ref="vt:blob"/>
|
||||
<xsd:element ref="vt:oblob"/>
|
||||
<xsd:element ref="vt:empty"/>
|
||||
<xsd:element ref="vt:null"/>
|
||||
<xsd:element ref="vt:i1"/>
|
||||
<xsd:element ref="vt:i2"/>
|
||||
<xsd:element ref="vt:i4"/>
|
||||
<xsd:element ref="vt:i8"/>
|
||||
<xsd:element ref="vt:int"/>
|
||||
<xsd:element ref="vt:ui1"/>
|
||||
<xsd:element ref="vt:ui2"/>
|
||||
<xsd:element ref="vt:ui4"/>
|
||||
<xsd:element ref="vt:ui8"/>
|
||||
<xsd:element ref="vt:uint"/>
|
||||
<xsd:element ref="vt:r4"/>
|
||||
<xsd:element ref="vt:r8"/>
|
||||
<xsd:element ref="vt:decimal"/>
|
||||
<xsd:element ref="vt:lpstr"/>
|
||||
<xsd:element ref="vt:lpwstr"/>
|
||||
<xsd:element ref="vt:bstr"/>
|
||||
<xsd:element ref="vt:date"/>
|
||||
<xsd:element ref="vt:filetime"/>
|
||||
<xsd:element ref="vt:bool"/>
|
||||
<xsd:element ref="vt:cy"/>
|
||||
<xsd:element ref="vt:error"/>
|
||||
<xsd:element ref="vt:stream"/>
|
||||
<xsd:element ref="vt:ostream"/>
|
||||
<xsd:element ref="vt:storage"/>
|
||||
<xsd:element ref="vt:ostorage"/>
|
||||
<xsd:element ref="vt:vstream"/>
|
||||
<xsd:element ref="vt:clsid"/>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="fmtid" use="required" type="s:ST_Guid"/>
|
||||
<xsd:attribute name="pid" use="required" type="xsd:int"/>
|
||||
<xsd:attribute name="name" use="optional" type="xsd:string"/>
|
||||
<xsd:attribute name="linkTarget" use="optional" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
|
||||
xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"
|
||||
elementFormDefault="qualified" blockDefault="#all">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
schemaLocation="shared-documentPropertiesVariantTypes.xsd"/>
|
||||
<xsd:element name="Properties" type="CT_Properties"/>
|
||||
<xsd:complexType name="CT_Properties">
|
||||
<xsd:all>
|
||||
<xsd:element name="Template" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="Manager" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="Company" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="Pages" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="Words" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="Characters" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="PresentationFormat" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="Lines" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="Paragraphs" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="Slides" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="Notes" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="TotalTime" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="HiddenSlides" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="MMClips" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="ScaleCrop" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
||||
<xsd:element name="HeadingPairs" minOccurs="0" maxOccurs="1" type="CT_VectorVariant"/>
|
||||
<xsd:element name="TitlesOfParts" minOccurs="0" maxOccurs="1" type="CT_VectorLpstr"/>
|
||||
<xsd:element name="LinksUpToDate" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
||||
<xsd:element name="CharactersWithSpaces" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
<xsd:element name="SharedDoc" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
||||
<xsd:element name="HyperlinkBase" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="HLinks" minOccurs="0" maxOccurs="1" type="CT_VectorVariant"/>
|
||||
<xsd:element name="HyperlinksChanged" minOccurs="0" maxOccurs="1" type="xsd:boolean"/>
|
||||
<xsd:element name="DigSig" minOccurs="0" maxOccurs="1" type="CT_DigSigBlob"/>
|
||||
<xsd:element name="Application" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="AppVersion" minOccurs="0" maxOccurs="1" type="xsd:string"/>
|
||||
<xsd:element name="DocSecurity" minOccurs="0" maxOccurs="1" type="xsd:int"/>
|
||||
</xsd:all>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_VectorVariant">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element ref="vt:vector"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_VectorLpstr">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element ref="vt:vector"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_DigSigBlob">
|
||||
<xsd:sequence minOccurs="1" maxOccurs="1">
|
||||
<xsd:element ref="vt:blob"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,195 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"
|
||||
blockDefault="#all" elementFormDefault="qualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:simpleType name="ST_VectorBaseType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="variant"/>
|
||||
<xsd:enumeration value="i1"/>
|
||||
<xsd:enumeration value="i2"/>
|
||||
<xsd:enumeration value="i4"/>
|
||||
<xsd:enumeration value="i8"/>
|
||||
<xsd:enumeration value="ui1"/>
|
||||
<xsd:enumeration value="ui2"/>
|
||||
<xsd:enumeration value="ui4"/>
|
||||
<xsd:enumeration value="ui8"/>
|
||||
<xsd:enumeration value="r4"/>
|
||||
<xsd:enumeration value="r8"/>
|
||||
<xsd:enumeration value="lpstr"/>
|
||||
<xsd:enumeration value="lpwstr"/>
|
||||
<xsd:enumeration value="bstr"/>
|
||||
<xsd:enumeration value="date"/>
|
||||
<xsd:enumeration value="filetime"/>
|
||||
<xsd:enumeration value="bool"/>
|
||||
<xsd:enumeration value="cy"/>
|
||||
<xsd:enumeration value="error"/>
|
||||
<xsd:enumeration value="clsid"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ArrayBaseType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="variant"/>
|
||||
<xsd:enumeration value="i1"/>
|
||||
<xsd:enumeration value="i2"/>
|
||||
<xsd:enumeration value="i4"/>
|
||||
<xsd:enumeration value="int"/>
|
||||
<xsd:enumeration value="ui1"/>
|
||||
<xsd:enumeration value="ui2"/>
|
||||
<xsd:enumeration value="ui4"/>
|
||||
<xsd:enumeration value="uint"/>
|
||||
<xsd:enumeration value="r4"/>
|
||||
<xsd:enumeration value="r8"/>
|
||||
<xsd:enumeration value="decimal"/>
|
||||
<xsd:enumeration value="bstr"/>
|
||||
<xsd:enumeration value="date"/>
|
||||
<xsd:enumeration value="bool"/>
|
||||
<xsd:enumeration value="cy"/>
|
||||
<xsd:enumeration value="error"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Cy">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern value="\s*[0-9]*\.[0-9]{4}\s*"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Error">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern value="\s*0x[0-9A-Za-z]{8}\s*"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Empty"/>
|
||||
<xsd:complexType name="CT_Null"/>
|
||||
<xsd:complexType name="CT_Vector">
|
||||
<xsd:choice minOccurs="1" maxOccurs="unbounded">
|
||||
<xsd:element ref="variant"/>
|
||||
<xsd:element ref="i1"/>
|
||||
<xsd:element ref="i2"/>
|
||||
<xsd:element ref="i4"/>
|
||||
<xsd:element ref="i8"/>
|
||||
<xsd:element ref="ui1"/>
|
||||
<xsd:element ref="ui2"/>
|
||||
<xsd:element ref="ui4"/>
|
||||
<xsd:element ref="ui8"/>
|
||||
<xsd:element ref="r4"/>
|
||||
<xsd:element ref="r8"/>
|
||||
<xsd:element ref="lpstr"/>
|
||||
<xsd:element ref="lpwstr"/>
|
||||
<xsd:element ref="bstr"/>
|
||||
<xsd:element ref="date"/>
|
||||
<xsd:element ref="filetime"/>
|
||||
<xsd:element ref="bool"/>
|
||||
<xsd:element ref="cy"/>
|
||||
<xsd:element ref="error"/>
|
||||
<xsd:element ref="clsid"/>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="baseType" type="ST_VectorBaseType" use="required"/>
|
||||
<xsd:attribute name="size" type="xsd:unsignedInt" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Array">
|
||||
<xsd:choice minOccurs="1" maxOccurs="unbounded">
|
||||
<xsd:element ref="variant"/>
|
||||
<xsd:element ref="i1"/>
|
||||
<xsd:element ref="i2"/>
|
||||
<xsd:element ref="i4"/>
|
||||
<xsd:element ref="int"/>
|
||||
<xsd:element ref="ui1"/>
|
||||
<xsd:element ref="ui2"/>
|
||||
<xsd:element ref="ui4"/>
|
||||
<xsd:element ref="uint"/>
|
||||
<xsd:element ref="r4"/>
|
||||
<xsd:element ref="r8"/>
|
||||
<xsd:element ref="decimal"/>
|
||||
<xsd:element ref="bstr"/>
|
||||
<xsd:element ref="date"/>
|
||||
<xsd:element ref="bool"/>
|
||||
<xsd:element ref="error"/>
|
||||
<xsd:element ref="cy"/>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="lBounds" type="xsd:int" use="required"/>
|
||||
<xsd:attribute name="uBounds" type="xsd:int" use="required"/>
|
||||
<xsd:attribute name="baseType" type="ST_ArrayBaseType" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Variant">
|
||||
<xsd:choice minOccurs="1" maxOccurs="1">
|
||||
<xsd:element ref="variant"/>
|
||||
<xsd:element ref="vector"/>
|
||||
<xsd:element ref="array"/>
|
||||
<xsd:element ref="blob"/>
|
||||
<xsd:element ref="oblob"/>
|
||||
<xsd:element ref="empty"/>
|
||||
<xsd:element ref="null"/>
|
||||
<xsd:element ref="i1"/>
|
||||
<xsd:element ref="i2"/>
|
||||
<xsd:element ref="i4"/>
|
||||
<xsd:element ref="i8"/>
|
||||
<xsd:element ref="int"/>
|
||||
<xsd:element ref="ui1"/>
|
||||
<xsd:element ref="ui2"/>
|
||||
<xsd:element ref="ui4"/>
|
||||
<xsd:element ref="ui8"/>
|
||||
<xsd:element ref="uint"/>
|
||||
<xsd:element ref="r4"/>
|
||||
<xsd:element ref="r8"/>
|
||||
<xsd:element ref="decimal"/>
|
||||
<xsd:element ref="lpstr"/>
|
||||
<xsd:element ref="lpwstr"/>
|
||||
<xsd:element ref="bstr"/>
|
||||
<xsd:element ref="date"/>
|
||||
<xsd:element ref="filetime"/>
|
||||
<xsd:element ref="bool"/>
|
||||
<xsd:element ref="cy"/>
|
||||
<xsd:element ref="error"/>
|
||||
<xsd:element ref="stream"/>
|
||||
<xsd:element ref="ostream"/>
|
||||
<xsd:element ref="storage"/>
|
||||
<xsd:element ref="ostorage"/>
|
||||
<xsd:element ref="vstream"/>
|
||||
<xsd:element ref="clsid"/>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Vstream">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:base64Binary">
|
||||
<xsd:attribute name="version" type="s:ST_Guid"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="variant" type="CT_Variant"/>
|
||||
<xsd:element name="vector" type="CT_Vector"/>
|
||||
<xsd:element name="array" type="CT_Array"/>
|
||||
<xsd:element name="blob" type="xsd:base64Binary"/>
|
||||
<xsd:element name="oblob" type="xsd:base64Binary"/>
|
||||
<xsd:element name="empty" type="CT_Empty"/>
|
||||
<xsd:element name="null" type="CT_Null"/>
|
||||
<xsd:element name="i1" type="xsd:byte"/>
|
||||
<xsd:element name="i2" type="xsd:short"/>
|
||||
<xsd:element name="i4" type="xsd:int"/>
|
||||
<xsd:element name="i8" type="xsd:long"/>
|
||||
<xsd:element name="int" type="xsd:int"/>
|
||||
<xsd:element name="ui1" type="xsd:unsignedByte"/>
|
||||
<xsd:element name="ui2" type="xsd:unsignedShort"/>
|
||||
<xsd:element name="ui4" type="xsd:unsignedInt"/>
|
||||
<xsd:element name="ui8" type="xsd:unsignedLong"/>
|
||||
<xsd:element name="uint" type="xsd:unsignedInt"/>
|
||||
<xsd:element name="r4" type="xsd:float"/>
|
||||
<xsd:element name="r8" type="xsd:double"/>
|
||||
<xsd:element name="decimal" type="xsd:decimal"/>
|
||||
<xsd:element name="lpstr" type="xsd:string"/>
|
||||
<xsd:element name="lpwstr" type="xsd:string"/>
|
||||
<xsd:element name="bstr" type="xsd:string"/>
|
||||
<xsd:element name="date" type="xsd:dateTime"/>
|
||||
<xsd:element name="filetime" type="xsd:dateTime"/>
|
||||
<xsd:element name="bool" type="xsd:boolean"/>
|
||||
<xsd:element name="cy" type="ST_Cy"/>
|
||||
<xsd:element name="error" type="ST_Error"/>
|
||||
<xsd:element name="stream" type="xsd:base64Binary"/>
|
||||
<xsd:element name="ostream" type="xsd:base64Binary"/>
|
||||
<xsd:element name="storage" type="xsd:base64Binary"/>
|
||||
<xsd:element name="ostorage" type="xsd:base64Binary"/>
|
||||
<xsd:element name="vstream" type="CT_Vstream"/>
|
||||
<xsd:element name="clsid" type="s:ST_Guid"/>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,582 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
||||
xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
|
||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/math">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
schemaLocation="wml.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>
|
||||
<xsd:simpleType name="ST_Integer255">
|
||||
<xsd:restriction base="xsd:integer">
|
||||
<xsd:minInclusive value="1"/>
|
||||
<xsd:maxInclusive value="255"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Integer255">
|
||||
<xsd:attribute name="val" type="ST_Integer255" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Integer2">
|
||||
<xsd:restriction base="xsd:integer">
|
||||
<xsd:minInclusive value="-2"/>
|
||||
<xsd:maxInclusive value="2"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Integer2">
|
||||
<xsd:attribute name="val" type="ST_Integer2" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_SpacingRule">
|
||||
<xsd:restriction base="xsd:integer">
|
||||
<xsd:minInclusive value="0"/>
|
||||
<xsd:maxInclusive value="4"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_SpacingRule">
|
||||
<xsd:attribute name="val" type="ST_SpacingRule" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_UnSignedInteger">
|
||||
<xsd:restriction base="xsd:unsignedInt"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_UnSignedInteger">
|
||||
<xsd:attribute name="val" type="ST_UnSignedInteger" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Char">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:maxLength value="1"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Char">
|
||||
<xsd:attribute name="val" type="ST_Char" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OnOff">
|
||||
<xsd:attribute name="val" type="s:ST_OnOff"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_String">
|
||||
<xsd:attribute name="val" type="s:ST_String"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_XAlign">
|
||||
<xsd:attribute name="val" type="s:ST_XAlign" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_YAlign">
|
||||
<xsd:attribute name="val" type="s:ST_YAlign" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Shp">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="centered"/>
|
||||
<xsd:enumeration value="match"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Shp">
|
||||
<xsd:attribute name="val" type="ST_Shp" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_FType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="bar"/>
|
||||
<xsd:enumeration value="skw"/>
|
||||
<xsd:enumeration value="lin"/>
|
||||
<xsd:enumeration value="noBar"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_FType">
|
||||
<xsd:attribute name="val" type="ST_FType" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_LimLoc">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="undOvr"/>
|
||||
<xsd:enumeration value="subSup"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_LimLoc">
|
||||
<xsd:attribute name="val" type="ST_LimLoc" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_TopBot">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="top"/>
|
||||
<xsd:enumeration value="bot"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_TopBot">
|
||||
<xsd:attribute name="val" type="ST_TopBot" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Script">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="roman"/>
|
||||
<xsd:enumeration value="script"/>
|
||||
<xsd:enumeration value="fraktur"/>
|
||||
<xsd:enumeration value="double-struck"/>
|
||||
<xsd:enumeration value="sans-serif"/>
|
||||
<xsd:enumeration value="monospace"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Script">
|
||||
<xsd:attribute name="val" type="ST_Script"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Style">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="p"/>
|
||||
<xsd:enumeration value="b"/>
|
||||
<xsd:enumeration value="i"/>
|
||||
<xsd:enumeration value="bi"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Style">
|
||||
<xsd:attribute name="val" type="ST_Style"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ManualBreak">
|
||||
<xsd:attribute name="alnAt" type="ST_Integer255"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ScriptStyle">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="scr" minOccurs="0" type="CT_Script"/>
|
||||
<xsd:element name="sty" minOccurs="0" type="CT_Style"/>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_RPR">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="lit" minOccurs="0" type="CT_OnOff"/>
|
||||
<xsd:choice>
|
||||
<xsd:element name="nor" minOccurs="0" type="CT_OnOff"/>
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ScriptStyle"/>
|
||||
</xsd:sequence>
|
||||
</xsd:choice>
|
||||
<xsd:element name="brk" minOccurs="0" type="CT_ManualBreak"/>
|
||||
<xsd:element name="aln" minOccurs="0" type="CT_OnOff"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Text">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="s:ST_String">
|
||||
<xsd:attribute ref="xml:space" use="optional"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_R">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="rPr" type="CT_RPR" minOccurs="0"/>
|
||||
<xsd:group ref="w:EG_RPr" minOccurs="0"/>
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:group ref="w:EG_RunInnerContent"/>
|
||||
<xsd:element name="t" type="CT_Text" minOccurs="0"/>
|
||||
</xsd:choice>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_CtrlPr">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="w:EG_RPrMath" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_AccPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Acc">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="accPr" type="CT_AccPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_BarPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="pos" type="CT_TopBot" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Bar">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="barPr" type="CT_BarPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_BoxPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="opEmu" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="noBreak" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="diff" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="brk" type="CT_ManualBreak" minOccurs="0"/>
|
||||
<xsd:element name="aln" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Box">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="boxPr" type="CT_BoxPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_BorderBoxPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="hideTop" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="hideBot" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="hideLeft" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="hideRight" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="strikeH" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="strikeV" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="strikeBLTR" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="strikeTLBR" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_BorderBox">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="borderBoxPr" type="CT_BorderBoxPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_DPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="begChr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="sepChr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="endChr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="grow" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="shp" type="CT_Shp" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_D">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="dPr" type="CT_DPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_EqArrPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="baseJc" type="CT_YAlign" minOccurs="0"/>
|
||||
<xsd:element name="maxDist" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="objDist" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="rSpRule" type="CT_SpacingRule" minOccurs="0"/>
|
||||
<xsd:element name="rSp" type="CT_UnSignedInteger" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_EqArr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="eqArrPr" type="CT_EqArrPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_FPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="type" type="CT_FType" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_F">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="fPr" type="CT_FPr" minOccurs="0"/>
|
||||
<xsd:element name="num" type="CT_OMathArg"/>
|
||||
<xsd:element name="den" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_FuncPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Func">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="funcPr" type="CT_FuncPr" minOccurs="0"/>
|
||||
<xsd:element name="fName" type="CT_OMathArg"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupChrPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="pos" type="CT_TopBot" minOccurs="0"/>
|
||||
<xsd:element name="vertJc" type="CT_TopBot" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GroupChr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="groupChrPr" type="CT_GroupChrPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LimLowPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LimLow">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="limLowPr" type="CT_LimLowPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
<xsd:element name="lim" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LimUppPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LimUpp">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="limUppPr" type="CT_LimUppPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
<xsd:element name="lim" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MCPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="count" type="CT_Integer255" minOccurs="0"/>
|
||||
<xsd:element name="mcJc" type="CT_XAlign" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MC">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="mcPr" type="CT_MCPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MCS">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="mc" type="CT_MC" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="baseJc" type="CT_YAlign" minOccurs="0"/>
|
||||
<xsd:element name="plcHide" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="rSpRule" type="CT_SpacingRule" minOccurs="0"/>
|
||||
<xsd:element name="cGpRule" type="CT_SpacingRule" minOccurs="0"/>
|
||||
<xsd:element name="rSp" type="CT_UnSignedInteger" minOccurs="0"/>
|
||||
<xsd:element name="cSp" type="CT_UnSignedInteger" minOccurs="0"/>
|
||||
<xsd:element name="cGp" type="CT_UnSignedInteger" minOccurs="0"/>
|
||||
<xsd:element name="mcs" type="CT_MCS" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MR">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="e" type="CT_OMathArg" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_M">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="mPr" type="CT_MPr" minOccurs="0"/>
|
||||
<xsd:element name="mr" type="CT_MR" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_NaryPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="chr" type="CT_Char" minOccurs="0"/>
|
||||
<xsd:element name="limLoc" type="CT_LimLoc" minOccurs="0"/>
|
||||
<xsd:element name="grow" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="subHide" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="supHide" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Nary">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="naryPr" type="CT_NaryPr" minOccurs="0"/>
|
||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PhantPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="show" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="zeroWid" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="zeroAsc" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="zeroDesc" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="transp" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Phant">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="phantPr" type="CT_PhantPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_RadPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="degHide" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Rad">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="radPr" type="CT_RadPr" minOccurs="0"/>
|
||||
<xsd:element name="deg" type="CT_OMathArg"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SPrePr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SPre">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="sPrePr" type="CT_SPrePr" minOccurs="0"/>
|
||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSubPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSub">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="sSubPr" type="CT_SSubPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSubSupPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="alnScr" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSubSup">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="sSubSupPr" type="CT_SSubSupPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
<xsd:element name="sub" type="CT_OMathArg"/>
|
||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSupPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SSup">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="sSupPr" type="CT_SSupPr" minOccurs="0"/>
|
||||
<xsd:element name="e" type="CT_OMathArg"/>
|
||||
<xsd:element name="sup" type="CT_OMathArg"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_OMathMathElements">
|
||||
<xsd:choice>
|
||||
<xsd:element name="acc" type="CT_Acc"/>
|
||||
<xsd:element name="bar" type="CT_Bar"/>
|
||||
<xsd:element name="box" type="CT_Box"/>
|
||||
<xsd:element name="borderBox" type="CT_BorderBox"/>
|
||||
<xsd:element name="d" type="CT_D"/>
|
||||
<xsd:element name="eqArr" type="CT_EqArr"/>
|
||||
<xsd:element name="f" type="CT_F"/>
|
||||
<xsd:element name="func" type="CT_Func"/>
|
||||
<xsd:element name="groupChr" type="CT_GroupChr"/>
|
||||
<xsd:element name="limLow" type="CT_LimLow"/>
|
||||
<xsd:element name="limUpp" type="CT_LimUpp"/>
|
||||
<xsd:element name="m" type="CT_M"/>
|
||||
<xsd:element name="nary" type="CT_Nary"/>
|
||||
<xsd:element name="phant" type="CT_Phant"/>
|
||||
<xsd:element name="rad" type="CT_Rad"/>
|
||||
<xsd:element name="sPre" type="CT_SPre"/>
|
||||
<xsd:element name="sSub" type="CT_SSub"/>
|
||||
<xsd:element name="sSubSup" type="CT_SSubSup"/>
|
||||
<xsd:element name="sSup" type="CT_SSup"/>
|
||||
<xsd:element name="r" type="CT_R"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:group name="EG_OMathElements">
|
||||
<xsd:choice>
|
||||
<xsd:group ref="EG_OMathMathElements"/>
|
||||
<xsd:group ref="w:EG_PContentMath"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_OMathArgPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="argSz" type="CT_Integer2" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OMathArg">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="argPr" type="CT_OMathArgPr" minOccurs="0"/>
|
||||
<xsd:group ref="EG_OMathElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xsd:element name="ctrlPr" type="CT_CtrlPr" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Jc">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="centerGroup"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_OMathJc">
|
||||
<xsd:attribute name="val" type="ST_Jc"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OMathParaPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="jc" type="CT_OMathJc" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_TwipsMeasure">
|
||||
<xsd:attribute name="val" type="s:ST_TwipsMeasure" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_BreakBin">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="before"/>
|
||||
<xsd:enumeration value="after"/>
|
||||
<xsd:enumeration value="repeat"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_BreakBin">
|
||||
<xsd:attribute name="val" type="ST_BreakBin"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_BreakBinSub">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="--"/>
|
||||
<xsd:enumeration value="-+"/>
|
||||
<xsd:enumeration value="+-"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_BreakBinSub">
|
||||
<xsd:attribute name="val" type="ST_BreakBinSub"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_MathPr">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="mathFont" type="CT_String" minOccurs="0"/>
|
||||
<xsd:element name="brkBin" type="CT_BreakBin" minOccurs="0"/>
|
||||
<xsd:element name="brkBinSub" type="CT_BreakBinSub" minOccurs="0"/>
|
||||
<xsd:element name="smallFrac" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="dispDef" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="lMargin" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:element name="rMargin" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:element name="defJc" type="CT_OMathJc" minOccurs="0"/>
|
||||
<xsd:element name="preSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:element name="postSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:element name="interSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:element name="intraSp" type="CT_TwipsMeasure" minOccurs="0"/>
|
||||
<xsd:choice minOccurs="0">
|
||||
<xsd:element name="wrapIndent" type="CT_TwipsMeasure"/>
|
||||
<xsd:element name="wrapRight" type="CT_OnOff"/>
|
||||
</xsd:choice>
|
||||
<xsd:element name="intLim" type="CT_LimLoc" minOccurs="0"/>
|
||||
<xsd:element name="naryLim" type="CT_LimLoc" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="mathPr" type="CT_MathPr"/>
|
||||
<xsd:complexType name="CT_OMathPara">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="oMathParaPr" type="CT_OMathParaPr" minOccurs="0"/>
|
||||
<xsd:element name="oMath" type="CT_OMath" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OMath">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_OMathElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="oMathPara" type="CT_OMathPara"/>
|
||||
<xsd:element name="oMath" type="CT_OMath"/>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
elementFormDefault="qualified"
|
||||
targetNamespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
blockDefault="#all">
|
||||
<xsd:simpleType name="ST_RelationshipId">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:attribute name="id" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="embed" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="link" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="dm" type="ST_RelationshipId" default=""/>
|
||||
<xsd:attribute name="lo" type="ST_RelationshipId" default=""/>
|
||||
<xsd:attribute name="qs" type="ST_RelationshipId" default=""/>
|
||||
<xsd:attribute name="cs" type="ST_RelationshipId" default=""/>
|
||||
<xsd:attribute name="blip" type="ST_RelationshipId" default=""/>
|
||||
<xsd:attribute name="pict" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="href" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="topLeft" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="topRight" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="bottomLeft" type="ST_RelationshipId"/>
|
||||
<xsd:attribute name="bottomRight" type="ST_RelationshipId"/>
|
||||
</xsd:schema>
|
||||
4439
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd
Normal file
4439
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,570 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:schemas-microsoft-com:vml"
|
||||
xmlns:pvml="urn:schemas-microsoft-com:office:powerpoint"
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
xmlns:w10="urn:schemas-microsoft-com:office:word"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="urn:schemas-microsoft-com:vml" elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
<xsd:import namespace="urn:schemas-microsoft-com:office:office"
|
||||
schemaLocation="vml-officeDrawing.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
schemaLocation="wml.xsd"/>
|
||||
<xsd:import namespace="urn:schemas-microsoft-com:office:word"
|
||||
schemaLocation="vml-wordprocessingDrawing.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
schemaLocation="shared-relationshipReference.xsd"/>
|
||||
<xsd:import namespace="urn:schemas-microsoft-com:office:excel"
|
||||
schemaLocation="vml-spreadsheetDrawing.xsd"/>
|
||||
<xsd:import namespace="urn:schemas-microsoft-com:office:powerpoint"
|
||||
schemaLocation="vml-presentationDrawing.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:attributeGroup name="AG_Id">
|
||||
<xsd:attribute name="id" type="xsd:string" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Style">
|
||||
<xsd:attribute name="style" type="xsd:string" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Type">
|
||||
<xsd:attribute name="type" type="xsd:string" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Adj">
|
||||
<xsd:attribute name="adj" type="xsd:string" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Path">
|
||||
<xsd:attribute name="path" type="xsd:string" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Fill">
|
||||
<xsd:attribute name="filled" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="fillcolor" type="s:ST_ColorType" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Chromakey">
|
||||
<xsd:attribute name="chromakey" type="s:ST_ColorType" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_Ext">
|
||||
<xsd:attribute name="ext" form="qualified" type="ST_Ext"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_CoreAttributes">
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_Style"/>
|
||||
<xsd:attribute name="href" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="target" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="class" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="title" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="alt" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="coordsize" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="coordorigin" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="wrapcoords" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="print" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_ShapeAttributes">
|
||||
<xsd:attributeGroup ref="AG_Chromakey"/>
|
||||
<xsd:attributeGroup ref="AG_Fill"/>
|
||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="stroked" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="strokecolor" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="strokeweight" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_OfficeCoreAttributes">
|
||||
<xsd:attribute ref="o:spid"/>
|
||||
<xsd:attribute ref="o:oned"/>
|
||||
<xsd:attribute ref="o:regroupid"/>
|
||||
<xsd:attribute ref="o:doubleclicknotify"/>
|
||||
<xsd:attribute ref="o:button"/>
|
||||
<xsd:attribute ref="o:userhidden"/>
|
||||
<xsd:attribute ref="o:bullet"/>
|
||||
<xsd:attribute ref="o:hr"/>
|
||||
<xsd:attribute ref="o:hrstd"/>
|
||||
<xsd:attribute ref="o:hrnoshade"/>
|
||||
<xsd:attribute ref="o:hrpct"/>
|
||||
<xsd:attribute ref="o:hralign"/>
|
||||
<xsd:attribute ref="o:allowincell"/>
|
||||
<xsd:attribute ref="o:allowoverlap"/>
|
||||
<xsd:attribute ref="o:userdrawn"/>
|
||||
<xsd:attribute ref="o:bordertopcolor"/>
|
||||
<xsd:attribute ref="o:borderleftcolor"/>
|
||||
<xsd:attribute ref="o:borderbottomcolor"/>
|
||||
<xsd:attribute ref="o:borderrightcolor"/>
|
||||
<xsd:attribute ref="o:dgmlayout"/>
|
||||
<xsd:attribute ref="o:dgmnodekind"/>
|
||||
<xsd:attribute ref="o:dgmlayoutmru"/>
|
||||
<xsd:attribute ref="o:insetmode"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_OfficeShapeAttributes">
|
||||
<xsd:attribute ref="o:spt"/>
|
||||
<xsd:attribute ref="o:connectortype"/>
|
||||
<xsd:attribute ref="o:bwmode"/>
|
||||
<xsd:attribute ref="o:bwpure"/>
|
||||
<xsd:attribute ref="o:bwnormal"/>
|
||||
<xsd:attribute ref="o:forcedash"/>
|
||||
<xsd:attribute ref="o:oleicon"/>
|
||||
<xsd:attribute ref="o:ole"/>
|
||||
<xsd:attribute ref="o:preferrelative"/>
|
||||
<xsd:attribute ref="o:cliptowrap"/>
|
||||
<xsd:attribute ref="o:clip"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_AllCoreAttributes">
|
||||
<xsd:attributeGroup ref="AG_CoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_OfficeCoreAttributes"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_AllShapeAttributes">
|
||||
<xsd:attributeGroup ref="AG_ShapeAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_OfficeShapeAttributes"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_ImageAttributes">
|
||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="cropleft" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="croptop" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="cropright" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="cropbottom" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="gain" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="blacklevel" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="gamma" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="grayscale" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="bilevel" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attributeGroup name="AG_StrokeAttributes">
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="weight" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="linestyle" type="ST_StrokeLineStyle" use="optional"/>
|
||||
<xsd:attribute name="miterlimit" type="xsd:decimal" use="optional"/>
|
||||
<xsd:attribute name="joinstyle" type="ST_StrokeJoinStyle" use="optional"/>
|
||||
<xsd:attribute name="endcap" type="ST_StrokeEndCap" use="optional"/>
|
||||
<xsd:attribute name="dashstyle" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="filltype" type="ST_FillType" use="optional"/>
|
||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="imageaspect" type="ST_ImageAspect" use="optional"/>
|
||||
<xsd:attribute name="imagesize" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="imagealignshape" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="startarrow" type="ST_StrokeArrowType" use="optional"/>
|
||||
<xsd:attribute name="startarrowwidth" type="ST_StrokeArrowWidth" use="optional"/>
|
||||
<xsd:attribute name="startarrowlength" type="ST_StrokeArrowLength" use="optional"/>
|
||||
<xsd:attribute name="endarrow" type="ST_StrokeArrowType" use="optional"/>
|
||||
<xsd:attribute name="endarrowwidth" type="ST_StrokeArrowWidth" use="optional"/>
|
||||
<xsd:attribute name="endarrowlength" type="ST_StrokeArrowLength" use="optional"/>
|
||||
<xsd:attribute ref="o:href"/>
|
||||
<xsd:attribute ref="o:althref"/>
|
||||
<xsd:attribute ref="o:title"/>
|
||||
<xsd:attribute ref="o:forcedash"/>
|
||||
<xsd:attribute ref="r:id" use="optional"/>
|
||||
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute ref="o:relid"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:group name="EG_ShapeElements">
|
||||
<xsd:choice>
|
||||
<xsd:element ref="path"/>
|
||||
<xsd:element ref="formulas"/>
|
||||
<xsd:element ref="handles"/>
|
||||
<xsd:element ref="fill"/>
|
||||
<xsd:element ref="stroke"/>
|
||||
<xsd:element ref="shadow"/>
|
||||
<xsd:element ref="textbox"/>
|
||||
<xsd:element ref="textpath"/>
|
||||
<xsd:element ref="imagedata"/>
|
||||
<xsd:element ref="o:skew"/>
|
||||
<xsd:element ref="o:extrusion"/>
|
||||
<xsd:element ref="o:callout"/>
|
||||
<xsd:element ref="o:lock"/>
|
||||
<xsd:element ref="o:clippath"/>
|
||||
<xsd:element ref="o:signatureline"/>
|
||||
<xsd:element ref="w10:wrap"/>
|
||||
<xsd:element ref="w10:anchorlock"/>
|
||||
<xsd:element ref="w10:bordertop"/>
|
||||
<xsd:element ref="w10:borderbottom"/>
|
||||
<xsd:element ref="w10:borderleft"/>
|
||||
<xsd:element ref="w10:borderright"/>
|
||||
<xsd:element ref="x:ClientData" minOccurs="0"/>
|
||||
<xsd:element ref="pvml:textdata" minOccurs="0"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:element name="shape" type="CT_Shape"/>
|
||||
<xsd:element name="shapetype" type="CT_Shapetype"/>
|
||||
<xsd:element name="group" type="CT_Group"/>
|
||||
<xsd:element name="background" type="CT_Background"/>
|
||||
<xsd:complexType name="CT_Shape">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements"/>
|
||||
<xsd:element ref="o:ink"/>
|
||||
<xsd:element ref="pvml:iscomment"/>
|
||||
<xsd:element ref="o:equationxml"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_Type"/>
|
||||
<xsd:attributeGroup ref="AG_Adj"/>
|
||||
<xsd:attributeGroup ref="AG_Path"/>
|
||||
<xsd:attribute ref="o:gfxdata"/>
|
||||
<xsd:attribute name="equationxml" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Shapetype">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xsd:element ref="o:complex" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_Adj"/>
|
||||
<xsd:attributeGroup ref="AG_Path"/>
|
||||
<xsd:attribute ref="o:master"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Group">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements"/>
|
||||
<xsd:element ref="group"/>
|
||||
<xsd:element ref="shape"/>
|
||||
<xsd:element ref="shapetype"/>
|
||||
<xsd:element ref="arc"/>
|
||||
<xsd:element ref="curve"/>
|
||||
<xsd:element ref="image"/>
|
||||
<xsd:element ref="line"/>
|
||||
<xsd:element ref="oval"/>
|
||||
<xsd:element ref="polyline"/>
|
||||
<xsd:element ref="rect"/>
|
||||
<xsd:element ref="roundrect"/>
|
||||
<xsd:element ref="o:diagram"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_Fill"/>
|
||||
<xsd:attribute name="editas" type="ST_EditAs" use="optional"/>
|
||||
<xsd:attribute ref="o:tableproperties"/>
|
||||
<xsd:attribute ref="o:tablelimits"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Background">
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="fill" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_Fill"/>
|
||||
<xsd:attribute ref="o:bwmode"/>
|
||||
<xsd:attribute ref="o:bwpure"/>
|
||||
<xsd:attribute ref="o:bwnormal"/>
|
||||
<xsd:attribute ref="o:targetscreensize"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="fill" type="CT_Fill"/>
|
||||
<xsd:element name="formulas" type="CT_Formulas"/>
|
||||
<xsd:element name="handles" type="CT_Handles"/>
|
||||
<xsd:element name="imagedata" type="CT_ImageData"/>
|
||||
<xsd:element name="path" type="CT_Path"/>
|
||||
<xsd:element name="textbox" type="CT_Textbox"/>
|
||||
<xsd:element name="shadow" type="CT_Shadow"/>
|
||||
<xsd:element name="stroke" type="CT_Stroke"/>
|
||||
<xsd:element name="textpath" type="CT_TextPath"/>
|
||||
<xsd:complexType name="CT_Fill">
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="o:fill" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attribute name="type" type="ST_FillType" use="optional"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute ref="o:href"/>
|
||||
<xsd:attribute ref="o:althref"/>
|
||||
<xsd:attribute name="size" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="position" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="aspect" type="ST_ImageAspect" use="optional"/>
|
||||
<xsd:attribute name="colors" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="angle" type="xsd:decimal" use="optional"/>
|
||||
<xsd:attribute name="alignshape" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="focus" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="focussize" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="focusposition" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="method" type="ST_FillMethod" use="optional"/>
|
||||
<xsd:attribute ref="o:detectmouseclick"/>
|
||||
<xsd:attribute ref="o:title"/>
|
||||
<xsd:attribute ref="o:opacity2"/>
|
||||
<xsd:attribute name="recolor" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="rotate" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute ref="r:id" use="optional"/>
|
||||
<xsd:attribute ref="o:relid" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Formulas">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="f" type="CT_F" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_F">
|
||||
<xsd:attribute name="eqn" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Handles">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="h" type="CT_H" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_H">
|
||||
<xsd:attribute name="position" type="xsd:string"/>
|
||||
<xsd:attribute name="polar" type="xsd:string"/>
|
||||
<xsd:attribute name="map" type="xsd:string"/>
|
||||
<xsd:attribute name="invx" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="invy" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="switch" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:attribute name="xrange" type="xsd:string"/>
|
||||
<xsd:attribute name="yrange" type="xsd:string"/>
|
||||
<xsd:attribute name="radiusrange" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ImageData">
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_ImageAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_Chromakey"/>
|
||||
<xsd:attribute name="embosscolor" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="recolortarget" type="s:ST_ColorType"/>
|
||||
<xsd:attribute ref="o:href"/>
|
||||
<xsd:attribute ref="o:althref"/>
|
||||
<xsd:attribute ref="o:title"/>
|
||||
<xsd:attribute ref="o:oleid"/>
|
||||
<xsd:attribute ref="o:detectmouseclick"/>
|
||||
<xsd:attribute ref="o:movie"/>
|
||||
<xsd:attribute ref="o:relid"/>
|
||||
<xsd:attribute ref="r:id"/>
|
||||
<xsd:attribute ref="r:pict"/>
|
||||
<xsd:attribute ref="r:href"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Path">
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attribute name="v" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="limo" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="textboxrect" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fillok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="strokeok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="shadowok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="arrowok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="gradientshapeok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="textpathok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="insetpenok" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute ref="o:connecttype"/>
|
||||
<xsd:attribute ref="o:connectlocs"/>
|
||||
<xsd:attribute ref="o:connectangles"/>
|
||||
<xsd:attribute ref="o:extrusionok"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Shadow">
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="type" type="ST_ShadowType" use="optional"/>
|
||||
<xsd:attribute name="obscured" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="offset" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="offset2" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="matrix" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Stroke">
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="o:left" minOccurs="0"/>
|
||||
<xsd:element ref="o:top" minOccurs="0"/>
|
||||
<xsd:element ref="o:right" minOccurs="0"/>
|
||||
<xsd:element ref="o:bottom" minOccurs="0"/>
|
||||
<xsd:element ref="o:column" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_StrokeAttributes"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Textbox">
|
||||
<xsd:choice>
|
||||
<xsd:element ref="w:txbxContent" minOccurs="0"/>
|
||||
<xsd:any namespace="##local" processContents="skip"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_Style"/>
|
||||
<xsd:attribute name="inset" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute ref="o:singleclick"/>
|
||||
<xsd:attribute ref="o:insetmode"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_TextPath">
|
||||
<xsd:attributeGroup ref="AG_Id"/>
|
||||
<xsd:attributeGroup ref="AG_Style"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="fitshape" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="fitpath" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="trim" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="xscale" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="string" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="arc" type="CT_Arc"/>
|
||||
<xsd:element name="curve" type="CT_Curve"/>
|
||||
<xsd:element name="image" type="CT_Image"/>
|
||||
<xsd:element name="line" type="CT_Line"/>
|
||||
<xsd:element name="oval" type="CT_Oval"/>
|
||||
<xsd:element name="polyline" type="CT_PolyLine"/>
|
||||
<xsd:element name="rect" type="CT_Rect"/>
|
||||
<xsd:element name="roundrect" type="CT_RoundRect"/>
|
||||
<xsd:complexType name="CT_Arc">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attribute name="startAngle" type="xsd:decimal" use="optional"/>
|
||||
<xsd:attribute name="endAngle" type="xsd:decimal" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Curve">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attribute name="from" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="control1" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="control2" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="to" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Image">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_ImageAttributes"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Line">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attribute name="from" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="to" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Oval">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PolyLine">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements"/>
|
||||
<xsd:element ref="o:ink"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attribute name="points" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Rect">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_RoundRect">
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:group ref="EG_ShapeElements" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:choice>
|
||||
<xsd:attributeGroup ref="AG_AllCoreAttributes"/>
|
||||
<xsd:attributeGroup ref="AG_AllShapeAttributes"/>
|
||||
<xsd:attribute name="arcsize" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Ext">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="view"/>
|
||||
<xsd:enumeration value="edit"/>
|
||||
<xsd:enumeration value="backwardCompatible"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_FillType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="solid"/>
|
||||
<xsd:enumeration value="gradient"/>
|
||||
<xsd:enumeration value="gradientRadial"/>
|
||||
<xsd:enumeration value="tile"/>
|
||||
<xsd:enumeration value="pattern"/>
|
||||
<xsd:enumeration value="frame"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_FillMethod">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="linear"/>
|
||||
<xsd:enumeration value="sigma"/>
|
||||
<xsd:enumeration value="any"/>
|
||||
<xsd:enumeration value="linear sigma"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ShadowType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="single"/>
|
||||
<xsd:enumeration value="double"/>
|
||||
<xsd:enumeration value="emboss"/>
|
||||
<xsd:enumeration value="perspective"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeLineStyle">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="single"/>
|
||||
<xsd:enumeration value="thinThin"/>
|
||||
<xsd:enumeration value="thinThick"/>
|
||||
<xsd:enumeration value="thickThin"/>
|
||||
<xsd:enumeration value="thickBetweenThin"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeJoinStyle">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="round"/>
|
||||
<xsd:enumeration value="bevel"/>
|
||||
<xsd:enumeration value="miter"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeEndCap">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="flat"/>
|
||||
<xsd:enumeration value="square"/>
|
||||
<xsd:enumeration value="round"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeArrowLength">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="short"/>
|
||||
<xsd:enumeration value="medium"/>
|
||||
<xsd:enumeration value="long"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeArrowWidth">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="narrow"/>
|
||||
<xsd:enumeration value="medium"/>
|
||||
<xsd:enumeration value="wide"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_StrokeArrowType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="block"/>
|
||||
<xsd:enumeration value="classic"/>
|
||||
<xsd:enumeration value="oval"/>
|
||||
<xsd:enumeration value="diamond"/>
|
||||
<xsd:enumeration value="open"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ImageAspect">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="ignore"/>
|
||||
<xsd:enumeration value="atMost"/>
|
||||
<xsd:enumeration value="atLeast"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_EditAs">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="canvas"/>
|
||||
<xsd:enumeration value="orgchart"/>
|
||||
<xsd:enumeration value="radial"/>
|
||||
<xsd:enumeration value="cycle"/>
|
||||
<xsd:enumeration value="stacked"/>
|
||||
<xsd:enumeration value="venn"/>
|
||||
<xsd:enumeration value="bullseye"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,509 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml"
|
||||
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="urn:schemas-microsoft-com:office:office" elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
<xsd:import namespace="urn:schemas-microsoft-com:vml" schemaLocation="vml-main.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
schemaLocation="shared-relationshipReference.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:attribute name="bwmode" type="ST_BWMode"/>
|
||||
<xsd:attribute name="bwpure" type="ST_BWMode"/>
|
||||
<xsd:attribute name="bwnormal" type="ST_BWMode"/>
|
||||
<xsd:attribute name="targetscreensize" type="ST_ScreenSize"/>
|
||||
<xsd:attribute name="insetmode" type="ST_InsetMode" default="custom"/>
|
||||
<xsd:attribute name="spt" type="xsd:float"/>
|
||||
<xsd:attribute name="wrapcoords" type="xsd:string"/>
|
||||
<xsd:attribute name="oned" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="regroupid" type="xsd:integer"/>
|
||||
<xsd:attribute name="doubleclicknotify" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="connectortype" type="ST_ConnectorType" default="straight"/>
|
||||
<xsd:attribute name="button" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="userhidden" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="forcedash" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="oleicon" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="ole" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:attribute name="preferrelative" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="cliptowrap" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="clip" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="bullet" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="hr" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="hrstd" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="hrnoshade" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="hrpct" type="xsd:float"/>
|
||||
<xsd:attribute name="hralign" type="ST_HrAlign" default="left"/>
|
||||
<xsd:attribute name="allowincell" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="allowoverlap" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="userdrawn" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="bordertopcolor" type="xsd:string"/>
|
||||
<xsd:attribute name="borderleftcolor" type="xsd:string"/>
|
||||
<xsd:attribute name="borderbottomcolor" type="xsd:string"/>
|
||||
<xsd:attribute name="borderrightcolor" type="xsd:string"/>
|
||||
<xsd:attribute name="connecttype" type="ST_ConnectType"/>
|
||||
<xsd:attribute name="connectlocs" type="xsd:string"/>
|
||||
<xsd:attribute name="connectangles" type="xsd:string"/>
|
||||
<xsd:attribute name="master" type="xsd:string"/>
|
||||
<xsd:attribute name="extrusionok" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="href" type="xsd:string"/>
|
||||
<xsd:attribute name="althref" type="xsd:string"/>
|
||||
<xsd:attribute name="title" type="xsd:string"/>
|
||||
<xsd:attribute name="singleclick" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="oleid" type="xsd:float"/>
|
||||
<xsd:attribute name="detectmouseclick" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="movie" type="xsd:float"/>
|
||||
<xsd:attribute name="spid" type="xsd:string"/>
|
||||
<xsd:attribute name="opacity2" type="xsd:string"/>
|
||||
<xsd:attribute name="relid" type="r:ST_RelationshipId"/>
|
||||
<xsd:attribute name="dgmlayout" type="ST_DiagramLayout"/>
|
||||
<xsd:attribute name="dgmnodekind" type="xsd:integer"/>
|
||||
<xsd:attribute name="dgmlayoutmru" type="ST_DiagramLayout"/>
|
||||
<xsd:attribute name="gfxdata" type="xsd:base64Binary"/>
|
||||
<xsd:attribute name="tableproperties" type="xsd:string"/>
|
||||
<xsd:attribute name="tablelimits" type="xsd:string"/>
|
||||
<xsd:element name="shapedefaults" type="CT_ShapeDefaults"/>
|
||||
<xsd:element name="shapelayout" type="CT_ShapeLayout"/>
|
||||
<xsd:element name="signatureline" type="CT_SignatureLine"/>
|
||||
<xsd:element name="ink" type="CT_Ink"/>
|
||||
<xsd:element name="diagram" type="CT_Diagram"/>
|
||||
<xsd:element name="equationxml" type="CT_EquationXml"/>
|
||||
<xsd:complexType name="CT_ShapeDefaults">
|
||||
<xsd:all minOccurs="0">
|
||||
<xsd:element ref="v:fill" minOccurs="0"/>
|
||||
<xsd:element ref="v:stroke" minOccurs="0"/>
|
||||
<xsd:element ref="v:textbox" minOccurs="0"/>
|
||||
<xsd:element ref="v:shadow" minOccurs="0"/>
|
||||
<xsd:element ref="skew" minOccurs="0"/>
|
||||
<xsd:element ref="extrusion" minOccurs="0"/>
|
||||
<xsd:element ref="callout" minOccurs="0"/>
|
||||
<xsd:element ref="lock" minOccurs="0"/>
|
||||
<xsd:element name="colormru" minOccurs="0" type="CT_ColorMru"/>
|
||||
<xsd:element name="colormenu" minOccurs="0" type="CT_ColorMenu"/>
|
||||
</xsd:all>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="spidmax" type="xsd:integer" use="optional"/>
|
||||
<xsd:attribute name="style" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="fill" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="fillcolor" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="stroke" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="strokecolor" type="s:ST_ColorType"/>
|
||||
<xsd:attribute name="allowincell" form="qualified" type="s:ST_TrueFalse"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Ink">
|
||||
<xsd:sequence/>
|
||||
<xsd:attribute name="i" type="xsd:string"/>
|
||||
<xsd:attribute name="annotation" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="contentType" type="ST_ContentType" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SignatureLine">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="issignatureline" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="id" type="s:ST_Guid"/>
|
||||
<xsd:attribute name="provid" type="s:ST_Guid"/>
|
||||
<xsd:attribute name="signinginstructionsset" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="allowcomments" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="showsigndate" type="s:ST_TrueFalse"/>
|
||||
<xsd:attribute name="suggestedsigner" type="xsd:string" form="qualified"/>
|
||||
<xsd:attribute name="suggestedsigner2" type="xsd:string" form="qualified"/>
|
||||
<xsd:attribute name="suggestedsigneremail" type="xsd:string" form="qualified"/>
|
||||
<xsd:attribute name="signinginstructions" type="xsd:string"/>
|
||||
<xsd:attribute name="addlxml" type="xsd:string"/>
|
||||
<xsd:attribute name="sigprovurl" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ShapeLayout">
|
||||
<xsd:all>
|
||||
<xsd:element name="idmap" type="CT_IdMap" minOccurs="0"/>
|
||||
<xsd:element name="regrouptable" type="CT_RegroupTable" minOccurs="0"/>
|
||||
<xsd:element name="rules" type="CT_Rules" minOccurs="0"/>
|
||||
</xsd:all>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_IdMap">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="data" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_RegroupTable">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="entry" type="CT_Entry" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Entry">
|
||||
<xsd:attribute name="new" type="xsd:int" use="optional"/>
|
||||
<xsd:attribute name="old" type="xsd:int" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Rules">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="r" type="CT_R" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_R">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="proxy" type="CT_Proxy" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="id" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="type" type="ST_RType" use="optional"/>
|
||||
<xsd:attribute name="how" type="ST_How" use="optional"/>
|
||||
<xsd:attribute name="idref" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Proxy">
|
||||
<xsd:attribute name="start" type="s:ST_TrueFalseBlank" use="optional" default="false"/>
|
||||
<xsd:attribute name="end" type="s:ST_TrueFalseBlank" use="optional" default="false"/>
|
||||
<xsd:attribute name="idref" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="connectloc" type="xsd:int" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Diagram">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="relationtable" type="CT_RelationTable" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="dgmstyle" type="xsd:integer" use="optional"/>
|
||||
<xsd:attribute name="autoformat" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="reverse" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="autolayout" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="dgmscalex" type="xsd:integer" use="optional"/>
|
||||
<xsd:attribute name="dgmscaley" type="xsd:integer" use="optional"/>
|
||||
<xsd:attribute name="dgmfontsize" type="xsd:integer" use="optional"/>
|
||||
<xsd:attribute name="constrainbounds" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="dgmbasetextscale" type="xsd:integer" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_EquationXml">
|
||||
<xsd:sequence>
|
||||
<xsd:any namespace="##any"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="contentType" type="ST_AlternateMathContentType" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_AlternateMathContentType">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_RelationTable">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="rel" type="CT_Relation" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Relation">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="idsrc" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="iddest" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="idcntr" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ColorMru">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="colors" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ColorMenu">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="strokecolor" type="s:ST_ColorType"/>
|
||||
<xsd:attribute name="fillcolor" type="s:ST_ColorType"/>
|
||||
<xsd:attribute name="shadowcolor" type="s:ST_ColorType"/>
|
||||
<xsd:attribute name="extrusioncolor" type="s:ST_ColorType"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="skew" type="CT_Skew"/>
|
||||
<xsd:element name="extrusion" type="CT_Extrusion"/>
|
||||
<xsd:element name="callout" type="CT_Callout"/>
|
||||
<xsd:element name="lock" type="CT_Lock"/>
|
||||
<xsd:element name="OLEObject" type="CT_OLEObject"/>
|
||||
<xsd:element name="complex" type="CT_Complex"/>
|
||||
<xsd:element name="left" type="CT_StrokeChild"/>
|
||||
<xsd:element name="top" type="CT_StrokeChild"/>
|
||||
<xsd:element name="right" type="CT_StrokeChild"/>
|
||||
<xsd:element name="bottom" type="CT_StrokeChild"/>
|
||||
<xsd:element name="column" type="CT_StrokeChild"/>
|
||||
<xsd:element name="clippath" type="CT_ClipPath"/>
|
||||
<xsd:element name="fill" type="CT_Fill"/>
|
||||
<xsd:complexType name="CT_Skew">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="id" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="offset" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="matrix" type="xsd:string" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Extrusion">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="type" type="ST_ExtrusionType" default="parallel" use="optional"/>
|
||||
<xsd:attribute name="render" type="ST_ExtrusionRender" default="solid" use="optional"/>
|
||||
<xsd:attribute name="viewpointorigin" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="viewpoint" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="plane" type="ST_ExtrusionPlane" default="XY" use="optional"/>
|
||||
<xsd:attribute name="skewangle" type="xsd:float" use="optional"/>
|
||||
<xsd:attribute name="skewamt" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="foredepth" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="backdepth" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="orientation" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="orientationangle" type="xsd:float" use="optional"/>
|
||||
<xsd:attribute name="lockrotationcenter" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="autorotationcenter" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="rotationcenter" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="rotationangle" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="colormode" type="ST_ColorMode" use="optional"/>
|
||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="shininess" type="xsd:float" use="optional"/>
|
||||
<xsd:attribute name="specularity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="diffusity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="metal" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="edge" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="facet" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightface" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="brightness" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightposition" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightlevel" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightharsh" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="lightposition2" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightlevel2" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lightharsh2" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Callout">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="type" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="gap" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="angle" type="ST_Angle" use="optional"/>
|
||||
<xsd:attribute name="dropauto" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="drop" type="ST_CalloutDrop" use="optional"/>
|
||||
<xsd:attribute name="distance" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="lengthspecified" type="s:ST_TrueFalse" default="f" use="optional"/>
|
||||
<xsd:attribute name="length" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="accentbar" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="textborder" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="minusx" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="minusy" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Lock">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="position" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="selection" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="grouping" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="ungrouping" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="rotation" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="cropping" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="verticies" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="adjusthandles" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="text" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="aspectratio" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="shapetype" type="s:ST_TrueFalse" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_OLEObject">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="LinkType" type="ST_OLELinkType" minOccurs="0"/>
|
||||
<xsd:element name="LockedField" type="s:ST_TrueFalseBlank" minOccurs="0"/>
|
||||
<xsd:element name="FieldCodes" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="Type" type="ST_OLEType" use="optional"/>
|
||||
<xsd:attribute name="ProgID" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="ShapeID" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="DrawAspect" type="ST_OLEDrawAspect" use="optional"/>
|
||||
<xsd:attribute name="ObjectID" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute ref="r:id" use="optional"/>
|
||||
<xsd:attribute name="UpdateMode" type="ST_OLEUpdateMode" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Complex">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_StrokeChild">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="on" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="weight" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="color" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="color2" type="s:ST_ColorType" use="optional"/>
|
||||
<xsd:attribute name="opacity" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="linestyle" type="v:ST_StrokeLineStyle" use="optional"/>
|
||||
<xsd:attribute name="miterlimit" type="xsd:decimal" use="optional"/>
|
||||
<xsd:attribute name="joinstyle" type="v:ST_StrokeJoinStyle" use="optional"/>
|
||||
<xsd:attribute name="endcap" type="v:ST_StrokeEndCap" use="optional"/>
|
||||
<xsd:attribute name="dashstyle" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="insetpen" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="filltype" type="v:ST_FillType" use="optional"/>
|
||||
<xsd:attribute name="src" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="imageaspect" type="v:ST_ImageAspect" use="optional"/>
|
||||
<xsd:attribute name="imagesize" type="xsd:string" use="optional"/>
|
||||
<xsd:attribute name="imagealignshape" type="s:ST_TrueFalse" use="optional"/>
|
||||
<xsd:attribute name="startarrow" type="v:ST_StrokeArrowType" use="optional"/>
|
||||
<xsd:attribute name="startarrowwidth" type="v:ST_StrokeArrowWidth" use="optional"/>
|
||||
<xsd:attribute name="startarrowlength" type="v:ST_StrokeArrowLength" use="optional"/>
|
||||
<xsd:attribute name="endarrow" type="v:ST_StrokeArrowType" use="optional"/>
|
||||
<xsd:attribute name="endarrowwidth" type="v:ST_StrokeArrowWidth" use="optional"/>
|
||||
<xsd:attribute name="endarrowlength" type="v:ST_StrokeArrowLength" use="optional"/>
|
||||
<xsd:attribute ref="href"/>
|
||||
<xsd:attribute ref="althref"/>
|
||||
<xsd:attribute ref="title"/>
|
||||
<xsd:attribute ref="forcedash"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ClipPath">
|
||||
<xsd:attribute name="v" type="xsd:string" use="required" form="qualified"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Fill">
|
||||
<xsd:attributeGroup ref="v:AG_Ext"/>
|
||||
<xsd:attribute name="type" type="ST_FillType"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_RType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="arc"/>
|
||||
<xsd:enumeration value="callout"/>
|
||||
<xsd:enumeration value="connector"/>
|
||||
<xsd:enumeration value="align"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_How">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="top"/>
|
||||
<xsd:enumeration value="middle"/>
|
||||
<xsd:enumeration value="bottom"/>
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_BWMode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="color"/>
|
||||
<xsd:enumeration value="auto"/>
|
||||
<xsd:enumeration value="grayScale"/>
|
||||
<xsd:enumeration value="lightGrayscale"/>
|
||||
<xsd:enumeration value="inverseGray"/>
|
||||
<xsd:enumeration value="grayOutline"/>
|
||||
<xsd:enumeration value="highContrast"/>
|
||||
<xsd:enumeration value="black"/>
|
||||
<xsd:enumeration value="white"/>
|
||||
<xsd:enumeration value="hide"/>
|
||||
<xsd:enumeration value="undrawn"/>
|
||||
<xsd:enumeration value="blackTextAndLines"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ScreenSize">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="544,376"/>
|
||||
<xsd:enumeration value="640,480"/>
|
||||
<xsd:enumeration value="720,512"/>
|
||||
<xsd:enumeration value="800,600"/>
|
||||
<xsd:enumeration value="1024,768"/>
|
||||
<xsd:enumeration value="1152,862"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_InsetMode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="auto"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ColorMode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="auto"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ContentType">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_DiagramLayout">
|
||||
<xsd:restriction base="xsd:integer">
|
||||
<xsd:enumeration value="0"/>
|
||||
<xsd:enumeration value="1"/>
|
||||
<xsd:enumeration value="2"/>
|
||||
<xsd:enumeration value="3"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ExtrusionType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="perspective"/>
|
||||
<xsd:enumeration value="parallel"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ExtrusionRender">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="solid"/>
|
||||
<xsd:enumeration value="wireFrame"/>
|
||||
<xsd:enumeration value="boundingCube"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ExtrusionPlane">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="XY"/>
|
||||
<xsd:enumeration value="ZX"/>
|
||||
<xsd:enumeration value="YZ"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_Angle">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="any"/>
|
||||
<xsd:enumeration value="30"/>
|
||||
<xsd:enumeration value="45"/>
|
||||
<xsd:enumeration value="60"/>
|
||||
<xsd:enumeration value="90"/>
|
||||
<xsd:enumeration value="auto"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_CalloutDrop">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_CalloutPlacement">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="top"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
<xsd:enumeration value="bottom"/>
|
||||
<xsd:enumeration value="user"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ConnectorType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="straight"/>
|
||||
<xsd:enumeration value="elbow"/>
|
||||
<xsd:enumeration value="curved"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_HrAlign">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="center"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ConnectType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="rect"/>
|
||||
<xsd:enumeration value="segments"/>
|
||||
<xsd:enumeration value="custom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OLELinkType">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OLEType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Embed"/>
|
||||
<xsd:enumeration value="Link"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OLEDrawAspect">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Content"/>
|
||||
<xsd:enumeration value="Icon"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_OLEUpdateMode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Always"/>
|
||||
<xsd:enumeration value="OnCall"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_FillType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="gradientCenter"/>
|
||||
<xsd:enumeration value="solid"/>
|
||||
<xsd:enumeration value="pattern"/>
|
||||
<xsd:enumeration value="tile"/>
|
||||
<xsd:enumeration value="frame"/>
|
||||
<xsd:enumeration value="gradientUnscaled"/>
|
||||
<xsd:enumeration value="gradientRadial"/>
|
||||
<xsd:enumeration value="gradient"/>
|
||||
<xsd:enumeration value="background"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:schemas-microsoft-com:office:powerpoint"
|
||||
targetNamespace="urn:schemas-microsoft-com:office:powerpoint" elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
<xsd:element name="iscomment" type="CT_Empty"/>
|
||||
<xsd:element name="textdata" type="CT_Rel"/>
|
||||
<xsd:complexType name="CT_Empty"/>
|
||||
<xsd:complexType name="CT_Rel">
|
||||
<xsd:attribute name="id" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,108 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:schemas-microsoft-com:office:excel"
|
||||
xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
targetNamespace="urn:schemas-microsoft-com:office:excel" elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
|
||||
schemaLocation="shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:element name="ClientData" type="CT_ClientData"/>
|
||||
<xsd:complexType name="CT_ClientData">
|
||||
<xsd:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:element name="MoveWithCells" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="SizeWithCells" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Anchor" type="xsd:string"/>
|
||||
<xsd:element name="Locked" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="DefaultSize" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="PrintObject" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Disabled" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="AutoFill" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="AutoLine" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="AutoPict" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="FmlaMacro" type="xsd:string"/>
|
||||
<xsd:element name="TextHAlign" type="xsd:string"/>
|
||||
<xsd:element name="TextVAlign" type="xsd:string"/>
|
||||
<xsd:element name="LockText" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="JustLastX" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="SecretEdit" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Default" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Help" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Cancel" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Dismiss" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Accel" type="xsd:integer"/>
|
||||
<xsd:element name="Accel2" type="xsd:integer"/>
|
||||
<xsd:element name="Row" type="xsd:integer"/>
|
||||
<xsd:element name="Column" type="xsd:integer"/>
|
||||
<xsd:element name="Visible" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="RowHidden" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="ColHidden" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="VTEdit" type="xsd:integer"/>
|
||||
<xsd:element name="MultiLine" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="VScroll" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="ValidIds" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="FmlaRange" type="xsd:string"/>
|
||||
<xsd:element name="WidthMin" type="xsd:integer"/>
|
||||
<xsd:element name="Sel" type="xsd:integer"/>
|
||||
<xsd:element name="NoThreeD2" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="SelType" type="xsd:string"/>
|
||||
<xsd:element name="MultiSel" type="xsd:string"/>
|
||||
<xsd:element name="LCT" type="xsd:string"/>
|
||||
<xsd:element name="ListItem" type="xsd:string"/>
|
||||
<xsd:element name="DropStyle" type="xsd:string"/>
|
||||
<xsd:element name="Colored" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="DropLines" type="xsd:integer"/>
|
||||
<xsd:element name="Checked" type="xsd:integer"/>
|
||||
<xsd:element name="FmlaLink" type="xsd:string"/>
|
||||
<xsd:element name="FmlaPict" type="xsd:string"/>
|
||||
<xsd:element name="NoThreeD" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="FirstButton" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="FmlaGroup" type="xsd:string"/>
|
||||
<xsd:element name="Val" type="xsd:integer"/>
|
||||
<xsd:element name="Min" type="xsd:integer"/>
|
||||
<xsd:element name="Max" type="xsd:integer"/>
|
||||
<xsd:element name="Inc" type="xsd:integer"/>
|
||||
<xsd:element name="Page" type="xsd:integer"/>
|
||||
<xsd:element name="Horiz" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="Dx" type="xsd:integer"/>
|
||||
<xsd:element name="MapOCX" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="CF" type="ST_CF"/>
|
||||
<xsd:element name="Camera" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="RecalcAlways" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="AutoScale" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="DDE" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="UIObj" type="s:ST_TrueFalseBlank"/>
|
||||
<xsd:element name="ScriptText" type="xsd:string"/>
|
||||
<xsd:element name="ScriptExtended" type="xsd:string"/>
|
||||
<xsd:element name="ScriptLanguage" type="xsd:nonNegativeInteger"/>
|
||||
<xsd:element name="ScriptLocation" type="xsd:nonNegativeInteger"/>
|
||||
<xsd:element name="FmlaTxbx" type="xsd:string"/>
|
||||
</xsd:choice>
|
||||
<xsd:attribute name="ObjectType" type="ST_ObjectType" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_CF">
|
||||
<xsd:restriction base="xsd:string"/>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_ObjectType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="Button"/>
|
||||
<xsd:enumeration value="Checkbox"/>
|
||||
<xsd:enumeration value="Dialog"/>
|
||||
<xsd:enumeration value="Drop"/>
|
||||
<xsd:enumeration value="Edit"/>
|
||||
<xsd:enumeration value="GBox"/>
|
||||
<xsd:enumeration value="Label"/>
|
||||
<xsd:enumeration value="LineA"/>
|
||||
<xsd:enumeration value="List"/>
|
||||
<xsd:enumeration value="Movie"/>
|
||||
<xsd:enumeration value="Note"/>
|
||||
<xsd:enumeration value="Pict"/>
|
||||
<xsd:enumeration value="Radio"/>
|
||||
<xsd:enumeration value="RectA"/>
|
||||
<xsd:enumeration value="Scroll"/>
|
||||
<xsd:enumeration value="Spin"/>
|
||||
<xsd:enumeration value="Shape"/>
|
||||
<xsd:enumeration value="Group"/>
|
||||
<xsd:enumeration value="Rect"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:schemas-microsoft-com:office:word"
|
||||
targetNamespace="urn:schemas-microsoft-com:office:word" elementFormDefault="qualified"
|
||||
attributeFormDefault="unqualified">
|
||||
<xsd:element name="bordertop" type="CT_Border"/>
|
||||
<xsd:element name="borderleft" type="CT_Border"/>
|
||||
<xsd:element name="borderright" type="CT_Border"/>
|
||||
<xsd:element name="borderbottom" type="CT_Border"/>
|
||||
<xsd:complexType name="CT_Border">
|
||||
<xsd:attribute name="type" type="ST_BorderType" use="optional"/>
|
||||
<xsd:attribute name="width" type="xsd:positiveInteger" use="optional"/>
|
||||
<xsd:attribute name="shadow" type="ST_BorderShadow" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="wrap" type="CT_Wrap"/>
|
||||
<xsd:complexType name="CT_Wrap">
|
||||
<xsd:attribute name="type" type="ST_WrapType" use="optional"/>
|
||||
<xsd:attribute name="side" type="ST_WrapSide" use="optional"/>
|
||||
<xsd:attribute name="anchorx" type="ST_HorizontalAnchor" use="optional"/>
|
||||
<xsd:attribute name="anchory" type="ST_VerticalAnchor" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="anchorlock" type="CT_AnchorLock"/>
|
||||
<xsd:complexType name="CT_AnchorLock"/>
|
||||
<xsd:simpleType name="ST_BorderType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="single"/>
|
||||
<xsd:enumeration value="thick"/>
|
||||
<xsd:enumeration value="double"/>
|
||||
<xsd:enumeration value="hairline"/>
|
||||
<xsd:enumeration value="dot"/>
|
||||
<xsd:enumeration value="dash"/>
|
||||
<xsd:enumeration value="dotDash"/>
|
||||
<xsd:enumeration value="dashDotDot"/>
|
||||
<xsd:enumeration value="triple"/>
|
||||
<xsd:enumeration value="thinThickSmall"/>
|
||||
<xsd:enumeration value="thickThinSmall"/>
|
||||
<xsd:enumeration value="thickBetweenThinSmall"/>
|
||||
<xsd:enumeration value="thinThick"/>
|
||||
<xsd:enumeration value="thickThin"/>
|
||||
<xsd:enumeration value="thickBetweenThin"/>
|
||||
<xsd:enumeration value="thinThickLarge"/>
|
||||
<xsd:enumeration value="thickThinLarge"/>
|
||||
<xsd:enumeration value="thickBetweenThinLarge"/>
|
||||
<xsd:enumeration value="wave"/>
|
||||
<xsd:enumeration value="doubleWave"/>
|
||||
<xsd:enumeration value="dashedSmall"/>
|
||||
<xsd:enumeration value="dashDotStroked"/>
|
||||
<xsd:enumeration value="threeDEmboss"/>
|
||||
<xsd:enumeration value="threeDEngrave"/>
|
||||
<xsd:enumeration value="HTMLOutset"/>
|
||||
<xsd:enumeration value="HTMLInset"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_BorderShadow">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="t"/>
|
||||
<xsd:enumeration value="true"/>
|
||||
<xsd:enumeration value="f"/>
|
||||
<xsd:enumeration value="false"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_WrapType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="topAndBottom"/>
|
||||
<xsd:enumeration value="square"/>
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="tight"/>
|
||||
<xsd:enumeration value="through"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_WrapSide">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="both"/>
|
||||
<xsd:enumeration value="left"/>
|
||||
<xsd:enumeration value="right"/>
|
||||
<xsd:enumeration value="largest"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_HorizontalAnchor">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="margin"/>
|
||||
<xsd:enumeration value="page"/>
|
||||
<xsd:enumeration value="text"/>
|
||||
<xsd:enumeration value="char"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_VerticalAnchor">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="margin"/>
|
||||
<xsd:enumeration value="page"/>
|
||||
<xsd:enumeration value="text"/>
|
||||
<xsd:enumeration value="line"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
3646
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd
Normal file
3646
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd
Normal file
File diff suppressed because it is too large
Load Diff
116
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd
Normal file
116
.claude/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd
Normal file
@ -0,0 +1,116 @@
|
||||
<?xml version='1.0'?>
|
||||
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
|
||||
|
||||
<xs:annotation>
|
||||
<xs:documentation>
|
||||
See http://www.w3.org/XML/1998/namespace.html and
|
||||
http://www.w3.org/TR/REC-xml for information about this namespace.
|
||||
|
||||
This schema document describes the XML namespace, in a form
|
||||
suitable for import by other schema documents.
|
||||
|
||||
Note that local names in this namespace are intended to be defined
|
||||
only by the World Wide Web Consortium or its subgroups. The
|
||||
following names are currently defined in this namespace and should
|
||||
not be used with conflicting semantics by any Working Group,
|
||||
specification, or document instance:
|
||||
|
||||
base (as an attribute name): denotes an attribute whose value
|
||||
provides a URI to be used as the base for interpreting any
|
||||
relative URIs in the scope of the element on which it
|
||||
appears; its value is inherited. This name is reserved
|
||||
by virtue of its definition in the XML Base specification.
|
||||
|
||||
lang (as an attribute name): denotes an attribute whose value
|
||||
is a language code for the natural language of the content of
|
||||
any element; its value is inherited. This name is reserved
|
||||
by virtue of its definition in the XML specification.
|
||||
|
||||
space (as an attribute name): denotes an attribute whose
|
||||
value is a keyword indicating what whitespace processing
|
||||
discipline is intended for the content of the element; its
|
||||
value is inherited. This name is reserved by virtue of its
|
||||
definition in the XML specification.
|
||||
|
||||
Father (in any context at all): denotes Jon Bosak, the chair of
|
||||
the original XML Working Group. This name is reserved by
|
||||
the following decision of the W3C XML Plenary and
|
||||
XML Coordination groups:
|
||||
|
||||
In appreciation for his vision, leadership and dedication
|
||||
the W3C XML Plenary on this 10th day of February, 2000
|
||||
reserves for Jon Bosak in perpetuity the XML name
|
||||
xml:Father
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
<xs:annotation>
|
||||
<xs:documentation>This schema defines attributes and an attribute group
|
||||
suitable for use by
|
||||
schemas wishing to allow xml:base, xml:lang or xml:space attributes
|
||||
on elements they define.
|
||||
|
||||
To enable this, such a schema must import this schema
|
||||
for the XML namespace, e.g. as follows:
|
||||
<schema . . .>
|
||||
. . .
|
||||
<import namespace="http://www.w3.org/XML/1998/namespace"
|
||||
schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
|
||||
|
||||
Subsequently, qualified reference to any of the attributes
|
||||
or the group defined below will have the desired effect, e.g.
|
||||
|
||||
<type . . .>
|
||||
. . .
|
||||
<attributeGroup ref="xml:specialAttrs"/>
|
||||
|
||||
will define a type which will schema-validate an instance
|
||||
element with any of those attributes</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
<xs:annotation>
|
||||
<xs:documentation>In keeping with the XML Schema WG's standard versioning
|
||||
policy, this schema document will persist at
|
||||
http://www.w3.org/2001/03/xml.xsd.
|
||||
At the date of issue it can also be found at
|
||||
http://www.w3.org/2001/xml.xsd.
|
||||
The schema document at that URI may however change in the future,
|
||||
in order to remain compatible with the latest version of XML Schema
|
||||
itself. In other words, if the XML Schema namespace changes, the version
|
||||
of this document at
|
||||
http://www.w3.org/2001/xml.xsd will change
|
||||
accordingly; the version at
|
||||
http://www.w3.org/2001/03/xml.xsd will not change.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
|
||||
<xs:attribute name="lang" type="xs:language">
|
||||
<xs:annotation>
|
||||
<xs:documentation>In due course, we should install the relevant ISO 2- and 3-letter
|
||||
codes as the enumerated possible values . . .</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
|
||||
<xs:attribute name="space" default="preserve">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:NCName">
|
||||
<xs:enumeration value="default"/>
|
||||
<xs:enumeration value="preserve"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
|
||||
<xs:attribute name="base" type="xs:anyURI">
|
||||
<xs:annotation>
|
||||
<xs:documentation>See http://www.w3.org/TR/xmlbase/ for
|
||||
information about this attribute.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
|
||||
<xs:attributeGroup name="specialAttrs">
|
||||
<xs:attribute ref="xml:base"/>
|
||||
<xs:attribute ref="xml:lang"/>
|
||||
<xs:attribute ref="xml:space"/>
|
||||
</xs:attributeGroup>
|
||||
|
||||
</xs:schema>
|
||||
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<xs:schema xmlns="http://schemas.openxmlformats.org/package/2006/content-types"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://schemas.openxmlformats.org/package/2006/content-types"
|
||||
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
|
||||
|
||||
<xs:element name="Types" type="CT_Types"/>
|
||||
<xs:element name="Default" type="CT_Default"/>
|
||||
<xs:element name="Override" type="CT_Override"/>
|
||||
|
||||
<xs:complexType name="CT_Types">
|
||||
<xs:choice minOccurs="0" maxOccurs="unbounded">
|
||||
<xs:element ref="Default"/>
|
||||
<xs:element ref="Override"/>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CT_Default">
|
||||
<xs:attribute name="Extension" type="ST_Extension" use="required"/>
|
||||
<xs:attribute name="ContentType" type="ST_ContentType" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CT_Override">
|
||||
<xs:attribute name="ContentType" type="ST_ContentType" use="required"/>
|
||||
<xs:attribute name="PartName" type="xs:anyURI" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="ST_ContentType">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:pattern
|
||||
value="(((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+))/((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+))((\s+)*;(\s+)*(((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+))=((([\p{IsBasicLatin}-[\p{Cc}\(\)<>@,;:\\"/\[\]\?=\{\}\s\t]])+)|("(([\p{IsLatin-1Supplement}\p{IsBasicLatin}-[\p{Cc}"\n\r]]|(\s+))|(\\[\p{IsBasicLatin}]))*"))))*)"
|
||||
/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:simpleType name="ST_Extension">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:pattern
|
||||
value="([!$&'\(\)\*\+,:=]|(%[0-9a-fA-F][0-9a-fA-F])|[:@]|[a-zA-Z0-9\-_~])+"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:schema>
|
||||
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema targetNamespace="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
|
||||
xmlns="http://schemas.openxmlformats.org/package/2006/metadata/core-properties"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:dcterms="http://purl.org/dc/terms/" elementFormDefault="qualified" blockDefault="#all">
|
||||
|
||||
<xs:import namespace="http://purl.org/dc/elements/1.1/"
|
||||
schemaLocation="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dc.xsd"/>
|
||||
<xs:import namespace="http://purl.org/dc/terms/"
|
||||
schemaLocation="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcterms.xsd"/>
|
||||
<xs:import id="xml" namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
|
||||
<xs:element name="coreProperties" type="CT_CoreProperties"/>
|
||||
|
||||
<xs:complexType name="CT_CoreProperties">
|
||||
<xs:all>
|
||||
<xs:element name="category" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
||||
<xs:element name="contentStatus" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
||||
<xs:element ref="dcterms:created" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="dc:creator" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="dc:description" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="dc:identifier" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="keywords" minOccurs="0" maxOccurs="1" type="CT_Keywords"/>
|
||||
<xs:element ref="dc:language" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="lastModifiedBy" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
||||
<xs:element name="lastPrinted" minOccurs="0" maxOccurs="1" type="xs:dateTime"/>
|
||||
<xs:element ref="dcterms:modified" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="revision" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
||||
<xs:element ref="dc:subject" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element ref="dc:title" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="version" minOccurs="0" maxOccurs="1" type="xs:string"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CT_Keywords" mixed="true">
|
||||
<xs:sequence>
|
||||
<xs:element name="value" minOccurs="0" maxOccurs="unbounded" type="CT_Keyword"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute ref="xml:lang" use="optional"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="CT_Keyword">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string">
|
||||
<xs:attribute ref="xml:lang" use="optional"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
</xs:schema>
|
||||
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xsd:schema xmlns="http://schemas.openxmlformats.org/package/2006/digital-signature"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://schemas.openxmlformats.org/package/2006/digital-signature"
|
||||
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
|
||||
|
||||
<xsd:element name="SignatureTime" type="CT_SignatureTime"/>
|
||||
<xsd:element name="RelationshipReference" type="CT_RelationshipReference"/>
|
||||
<xsd:element name="RelationshipsGroupReference" type="CT_RelationshipsGroupReference"/>
|
||||
|
||||
<xsd:complexType name="CT_SignatureTime">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="Format" type="ST_Format"/>
|
||||
<xsd:element name="Value" type="ST_Value"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="CT_RelationshipReference">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="SourceId" type="xsd:string" use="required"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="CT_RelationshipsGroupReference">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="SourceType" type="xsd:anyURI" use="required"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="ST_Format">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern
|
||||
value="(YYYY)|(YYYY-MM)|(YYYY-MM-DD)|(YYYY-MM-DDThh:mmTZD)|(YYYY-MM-DDThh:mm:ssTZD)|(YYYY-MM-DDThh:mm:ss.sTZD)"
|
||||
/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="ST_Value">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:pattern
|
||||
value="(([0-9][0-9][0-9][0-9]))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2))))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1))))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))|(([0-9][0-9][0-9][0-9])-((0[1-9])|(1(0|1|2)))-((0[1-9])|(1[0-9])|(2[0-9])|(3(0|1)))T((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])):(((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9]))\.[0-9])(((\+|-)((0[0-9])|(1[0-9])|(2(0|1|2|3))):((0[0-9])|(1[0-9])|(2[0-9])|(3[0-9])|(4[0-9])|(5[0-9])))|Z))"
|
||||
/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<xsd:schema xmlns="http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
elementFormDefault="qualified" attributeFormDefault="unqualified" blockDefault="#all">
|
||||
|
||||
<xsd:element name="Relationships" type="CT_Relationships"/>
|
||||
<xsd:element name="Relationship" type="CT_Relationship"/>
|
||||
|
||||
<xsd:complexType name="CT_Relationships">
|
||||
<xsd:sequence>
|
||||
<xsd:element ref="Relationship" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:complexType name="CT_Relationship">
|
||||
<xsd:simpleContent>
|
||||
<xsd:extension base="xsd:string">
|
||||
<xsd:attribute name="TargetMode" type="ST_TargetMode" use="optional"/>
|
||||
<xsd:attribute name="Target" type="xsd:anyURI" use="required"/>
|
||||
<xsd:attribute name="Type" type="xsd:anyURI" use="required"/>
|
||||
<xsd:attribute name="Id" type="xsd:ID" use="required"/>
|
||||
</xsd:extension>
|
||||
</xsd:simpleContent>
|
||||
</xsd:complexType>
|
||||
|
||||
<xsd:simpleType name="ST_TargetMode">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="External"/>
|
||||
<xsd:enumeration value="Internal"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
</xsd:schema>
|
||||
75
.claude/skills/docx/ooxml/schemas/mce/mc.xsd
Normal file
75
.claude/skills/docx/ooxml/schemas/mce/mc.xsd
Normal file
@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xsd:schema xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
attributeFormDefault="unqualified" elementFormDefault="qualified"
|
||||
targetNamespace="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
|
||||
<!--
|
||||
This XSD is a modified version of the one found at:
|
||||
https://github.com/plutext/docx4j/blob/master/xsd/mce/markup-compatibility-2006-MINIMAL.xsd
|
||||
|
||||
This XSD has 2 objectives:
|
||||
|
||||
1. round tripping @mc:Ignorable
|
||||
|
||||
<w:document
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
mc:Ignorable="w14 w15 wp14">
|
||||
|
||||
2. enabling AlternateContent to be manipulated in certain elements
|
||||
(in the unusual case where the content model is xsd:any, it doesn't have to be explicitly added)
|
||||
|
||||
See further ECMA-376, 4th Edition, Office Open XML File Formats
|
||||
Part 3 : Markup Compatibility and Extensibility
|
||||
-->
|
||||
|
||||
<!-- Objective 1 -->
|
||||
<xsd:attribute name="Ignorable" type="xsd:string" />
|
||||
|
||||
<!-- Objective 2 -->
|
||||
<xsd:attribute name="MustUnderstand" type="xsd:string" />
|
||||
<xsd:attribute name="ProcessContent" type="xsd:string" />
|
||||
|
||||
<!-- An AlternateContent element shall contain one or more Choice child elements, optionally followed by a
|
||||
Fallback child element. If present, there shall be only one Fallback element, and it shall follow all Choice
|
||||
elements. -->
|
||||
<xsd:element name="AlternateContent">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="Choice" minOccurs="0" maxOccurs="unbounded">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:any minOccurs="0" maxOccurs="unbounded"
|
||||
processContents="strict">
|
||||
</xsd:any>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="Requires" type="xsd:string" use="required" />
|
||||
<xsd:attribute ref="mc:Ignorable" use="optional" />
|
||||
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
|
||||
<xsd:attribute ref="mc:ProcessContent" use="optional" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="Fallback" minOccurs="0" maxOccurs="1">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:any minOccurs="0" maxOccurs="unbounded"
|
||||
processContents="strict">
|
||||
</xsd:any>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute ref="mc:Ignorable" use="optional" />
|
||||
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
|
||||
<xsd:attribute ref="mc:ProcessContent" use="optional" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:sequence>
|
||||
<!-- AlternateContent elements might include the attributes Ignorable,
|
||||
MustUnderstand and ProcessContent described in this Part of ECMA-376. These
|
||||
attributes’ qualified names shall be prefixed when associated with an AlternateContent
|
||||
element. -->
|
||||
<xsd:attribute ref="mc:Ignorable" use="optional" />
|
||||
<xsd:attribute ref="mc:MustUnderstand" use="optional" />
|
||||
<xsd:attribute ref="mc:ProcessContent" use="optional" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
560
.claude/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd
Normal file
560
.claude/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd
Normal file
@ -0,0 +1,560 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns="http://schemas.microsoft.com/office/word/2010/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2010/wordml">
|
||||
<!-- <xsd:import id="rel" namespace="http://schemas.openxmlformats.org/officeDocument/2006/relationships" schemaLocation="orel.xsd"/> -->
|
||||
<xsd:import id="w" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<!-- <xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main" schemaLocation="oartbasetypes.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/drawingml/2006/main" schemaLocation="oartsplineproperties.xsd"/> -->
|
||||
<xsd:complexType name="CT_LongHexNumber">
|
||||
<xsd:attribute name="val" type="w:ST_LongHexNumber" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_OnOff">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="true"/>
|
||||
<xsd:enumeration value="false"/>
|
||||
<xsd:enumeration value="0"/>
|
||||
<xsd:enumeration value="1"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_OnOff">
|
||||
<xsd:attribute name="val" type="ST_OnOff"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="docId" type="CT_LongHexNumber"/>
|
||||
<xsd:element name="conflictMode" type="CT_OnOff"/>
|
||||
<xsd:attributeGroup name="AG_Parids">
|
||||
<xsd:attribute name="paraId" type="w:ST_LongHexNumber"/>
|
||||
<xsd:attribute name="textId" type="w:ST_LongHexNumber"/>
|
||||
</xsd:attributeGroup>
|
||||
<xsd:attribute name="anchorId" type="w:ST_LongHexNumber"/>
|
||||
<xsd:attribute name="noSpellErr" type="ST_OnOff"/>
|
||||
<xsd:element name="customXmlConflictInsRangeStart" type="w:CT_TrackChange"/>
|
||||
<xsd:element name="customXmlConflictInsRangeEnd" type="w:CT_Markup"/>
|
||||
<xsd:element name="customXmlConflictDelRangeStart" type="w:CT_TrackChange"/>
|
||||
<xsd:element name="customXmlConflictDelRangeEnd" type="w:CT_Markup"/>
|
||||
<xsd:group name="EG_RunLevelConflicts">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="conflictIns" type="w:CT_RunTrackChange" minOccurs="0"/>
|
||||
<xsd:element name="conflictDel" type="w:CT_RunTrackChange" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:group name="EG_Conflicts">
|
||||
<xsd:choice>
|
||||
<xsd:element name="conflictIns" type="w:CT_TrackChange" minOccurs="0"/>
|
||||
<xsd:element name="conflictDel" type="w:CT_TrackChange" minOccurs="0"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_Percentage">
|
||||
<xsd:attribute name="val" type="a:ST_Percentage" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PositiveFixedPercentage">
|
||||
<xsd:attribute name="val" type="a:ST_PositiveFixedPercentage" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PositivePercentage">
|
||||
<xsd:attribute name="val" type="a:ST_PositivePercentage" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_SchemeColorVal">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="bg1"/>
|
||||
<xsd:enumeration value="tx1"/>
|
||||
<xsd:enumeration value="bg2"/>
|
||||
<xsd:enumeration value="tx2"/>
|
||||
<xsd:enumeration value="accent1"/>
|
||||
<xsd:enumeration value="accent2"/>
|
||||
<xsd:enumeration value="accent3"/>
|
||||
<xsd:enumeration value="accent4"/>
|
||||
<xsd:enumeration value="accent5"/>
|
||||
<xsd:enumeration value="accent6"/>
|
||||
<xsd:enumeration value="hlink"/>
|
||||
<xsd:enumeration value="folHlink"/>
|
||||
<xsd:enumeration value="dk1"/>
|
||||
<xsd:enumeration value="lt1"/>
|
||||
<xsd:enumeration value="dk2"/>
|
||||
<xsd:enumeration value="lt2"/>
|
||||
<xsd:enumeration value="phClr"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_RectAlignment">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="tl"/>
|
||||
<xsd:enumeration value="t"/>
|
||||
<xsd:enumeration value="tr"/>
|
||||
<xsd:enumeration value="l"/>
|
||||
<xsd:enumeration value="ctr"/>
|
||||
<xsd:enumeration value="r"/>
|
||||
<xsd:enumeration value="bl"/>
|
||||
<xsd:enumeration value="b"/>
|
||||
<xsd:enumeration value="br"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PathShadeType">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="shape"/>
|
||||
<xsd:enumeration value="circle"/>
|
||||
<xsd:enumeration value="rect"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_LineCap">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="rnd"/>
|
||||
<xsd:enumeration value="sq"/>
|
||||
<xsd:enumeration value="flat"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PresetLineDashVal">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="solid"/>
|
||||
<xsd:enumeration value="dot"/>
|
||||
<xsd:enumeration value="sysDot"/>
|
||||
<xsd:enumeration value="dash"/>
|
||||
<xsd:enumeration value="sysDash"/>
|
||||
<xsd:enumeration value="lgDash"/>
|
||||
<xsd:enumeration value="dashDot"/>
|
||||
<xsd:enumeration value="sysDashDot"/>
|
||||
<xsd:enumeration value="lgDashDot"/>
|
||||
<xsd:enumeration value="lgDashDotDot"/>
|
||||
<xsd:enumeration value="sysDashDotDot"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_PenAlignment">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="ctr"/>
|
||||
<xsd:enumeration value="in"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_CompoundLine">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="sng"/>
|
||||
<xsd:enumeration value="dbl"/>
|
||||
<xsd:enumeration value="thickThin"/>
|
||||
<xsd:enumeration value="thinThick"/>
|
||||
<xsd:enumeration value="tri"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_RelativeRect">
|
||||
<xsd:attribute name="l" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="t" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="r" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="b" use="optional" type="a:ST_Percentage"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ColorTransform">
|
||||
<xsd:choice>
|
||||
<xsd:element name="tint" type="CT_PositiveFixedPercentage"/>
|
||||
<xsd:element name="shade" type="CT_PositiveFixedPercentage"/>
|
||||
<xsd:element name="alpha" type="CT_PositiveFixedPercentage"/>
|
||||
<xsd:element name="hueMod" type="CT_PositivePercentage"/>
|
||||
<xsd:element name="sat" type="CT_Percentage"/>
|
||||
<xsd:element name="satOff" type="CT_Percentage"/>
|
||||
<xsd:element name="satMod" type="CT_Percentage"/>
|
||||
<xsd:element name="lum" type="CT_Percentage"/>
|
||||
<xsd:element name="lumOff" type="CT_Percentage"/>
|
||||
<xsd:element name="lumMod" type="CT_Percentage"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_SRgbColor">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="val" type="s:ST_HexColorRGB" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SchemeColor">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorTransform" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="val" type="ST_SchemeColorVal" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ColorChoice">
|
||||
<xsd:choice>
|
||||
<xsd:element name="srgbClr" type="CT_SRgbColor"/>
|
||||
<xsd:element name="schemeClr" type="CT_SchemeColor"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_Color">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorChoice"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GradientStop">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorChoice"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="pos" type="a:ST_PositiveFixedPercentage" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GradientStopList">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="gs" type="CT_GradientStop" minOccurs="2" maxOccurs="10"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_LinearShadeProperties">
|
||||
<xsd:attribute name="ang" type="a:ST_PositiveFixedAngle" use="optional"/>
|
||||
<xsd:attribute name="scaled" type="ST_OnOff" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PathShadeProperties">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="fillToRect" type="CT_RelativeRect" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="path" type="ST_PathShadeType" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_ShadeProperties">
|
||||
<xsd:choice>
|
||||
<xsd:element name="lin" type="CT_LinearShadeProperties"/>
|
||||
<xsd:element name="path" type="CT_PathShadeProperties"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_SolidColorFillProperties">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorChoice" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_GradientFillProperties">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="gsLst" type="CT_GradientStopList" minOccurs="0"/>
|
||||
<xsd:group ref="EG_ShadeProperties" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_FillProperties">
|
||||
<xsd:choice>
|
||||
<xsd:element name="noFill" type="w:CT_Empty"/>
|
||||
<xsd:element name="solidFill" type="CT_SolidColorFillProperties"/>
|
||||
<xsd:element name="gradFill" type="CT_GradientFillProperties"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_PresetLineDashProperties">
|
||||
<xsd:attribute name="val" type="ST_PresetLineDashVal" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_LineDashProperties">
|
||||
<xsd:choice>
|
||||
<xsd:element name="prstDash" type="CT_PresetLineDashProperties"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:complexType name="CT_LineJoinMiterProperties">
|
||||
<xsd:attribute name="lim" type="a:ST_PositivePercentage" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_LineJoinProperties">
|
||||
<xsd:choice>
|
||||
<xsd:element name="round" type="w:CT_Empty"/>
|
||||
<xsd:element name="bevel" type="w:CT_Empty"/>
|
||||
<xsd:element name="miter" type="CT_LineJoinMiterProperties"/>
|
||||
</xsd:choice>
|
||||
</xsd:group>
|
||||
<xsd:simpleType name="ST_PresetCameraType">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="legacyObliqueTopLeft"/>
|
||||
<xsd:enumeration value="legacyObliqueTop"/>
|
||||
<xsd:enumeration value="legacyObliqueTopRight"/>
|
||||
<xsd:enumeration value="legacyObliqueLeft"/>
|
||||
<xsd:enumeration value="legacyObliqueFront"/>
|
||||
<xsd:enumeration value="legacyObliqueRight"/>
|
||||
<xsd:enumeration value="legacyObliqueBottomLeft"/>
|
||||
<xsd:enumeration value="legacyObliqueBottom"/>
|
||||
<xsd:enumeration value="legacyObliqueBottomRight"/>
|
||||
<xsd:enumeration value="legacyPerspectiveTopLeft"/>
|
||||
<xsd:enumeration value="legacyPerspectiveTop"/>
|
||||
<xsd:enumeration value="legacyPerspectiveTopRight"/>
|
||||
<xsd:enumeration value="legacyPerspectiveLeft"/>
|
||||
<xsd:enumeration value="legacyPerspectiveFront"/>
|
||||
<xsd:enumeration value="legacyPerspectiveRight"/>
|
||||
<xsd:enumeration value="legacyPerspectiveBottomLeft"/>
|
||||
<xsd:enumeration value="legacyPerspectiveBottom"/>
|
||||
<xsd:enumeration value="legacyPerspectiveBottomRight"/>
|
||||
<xsd:enumeration value="orthographicFront"/>
|
||||
<xsd:enumeration value="isometricTopUp"/>
|
||||
<xsd:enumeration value="isometricTopDown"/>
|
||||
<xsd:enumeration value="isometricBottomUp"/>
|
||||
<xsd:enumeration value="isometricBottomDown"/>
|
||||
<xsd:enumeration value="isometricLeftUp"/>
|
||||
<xsd:enumeration value="isometricLeftDown"/>
|
||||
<xsd:enumeration value="isometricRightUp"/>
|
||||
<xsd:enumeration value="isometricRightDown"/>
|
||||
<xsd:enumeration value="isometricOffAxis1Left"/>
|
||||
<xsd:enumeration value="isometricOffAxis1Right"/>
|
||||
<xsd:enumeration value="isometricOffAxis1Top"/>
|
||||
<xsd:enumeration value="isometricOffAxis2Left"/>
|
||||
<xsd:enumeration value="isometricOffAxis2Right"/>
|
||||
<xsd:enumeration value="isometricOffAxis2Top"/>
|
||||
<xsd:enumeration value="isometricOffAxis3Left"/>
|
||||
<xsd:enumeration value="isometricOffAxis3Right"/>
|
||||
<xsd:enumeration value="isometricOffAxis3Bottom"/>
|
||||
<xsd:enumeration value="isometricOffAxis4Left"/>
|
||||
<xsd:enumeration value="isometricOffAxis4Right"/>
|
||||
<xsd:enumeration value="isometricOffAxis4Bottom"/>
|
||||
<xsd:enumeration value="obliqueTopLeft"/>
|
||||
<xsd:enumeration value="obliqueTop"/>
|
||||
<xsd:enumeration value="obliqueTopRight"/>
|
||||
<xsd:enumeration value="obliqueLeft"/>
|
||||
<xsd:enumeration value="obliqueRight"/>
|
||||
<xsd:enumeration value="obliqueBottomLeft"/>
|
||||
<xsd:enumeration value="obliqueBottom"/>
|
||||
<xsd:enumeration value="obliqueBottomRight"/>
|
||||
<xsd:enumeration value="perspectiveFront"/>
|
||||
<xsd:enumeration value="perspectiveLeft"/>
|
||||
<xsd:enumeration value="perspectiveRight"/>
|
||||
<xsd:enumeration value="perspectiveAbove"/>
|
||||
<xsd:enumeration value="perspectiveBelow"/>
|
||||
<xsd:enumeration value="perspectiveAboveLeftFacing"/>
|
||||
<xsd:enumeration value="perspectiveAboveRightFacing"/>
|
||||
<xsd:enumeration value="perspectiveContrastingLeftFacing"/>
|
||||
<xsd:enumeration value="perspectiveContrastingRightFacing"/>
|
||||
<xsd:enumeration value="perspectiveHeroicLeftFacing"/>
|
||||
<xsd:enumeration value="perspectiveHeroicRightFacing"/>
|
||||
<xsd:enumeration value="perspectiveHeroicExtremeLeftFacing"/>
|
||||
<xsd:enumeration value="perspectiveHeroicExtremeRightFacing"/>
|
||||
<xsd:enumeration value="perspectiveRelaxed"/>
|
||||
<xsd:enumeration value="perspectiveRelaxedModerately"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Camera">
|
||||
<xsd:attribute name="prst" use="required" type="ST_PresetCameraType"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SphereCoords">
|
||||
<xsd:attribute name="lat" type="a:ST_PositiveFixedAngle" use="required"/>
|
||||
<xsd:attribute name="lon" type="a:ST_PositiveFixedAngle" use="required"/>
|
||||
<xsd:attribute name="rev" type="a:ST_PositiveFixedAngle" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_LightRigType">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="legacyFlat1"/>
|
||||
<xsd:enumeration value="legacyFlat2"/>
|
||||
<xsd:enumeration value="legacyFlat3"/>
|
||||
<xsd:enumeration value="legacyFlat4"/>
|
||||
<xsd:enumeration value="legacyNormal1"/>
|
||||
<xsd:enumeration value="legacyNormal2"/>
|
||||
<xsd:enumeration value="legacyNormal3"/>
|
||||
<xsd:enumeration value="legacyNormal4"/>
|
||||
<xsd:enumeration value="legacyHarsh1"/>
|
||||
<xsd:enumeration value="legacyHarsh2"/>
|
||||
<xsd:enumeration value="legacyHarsh3"/>
|
||||
<xsd:enumeration value="legacyHarsh4"/>
|
||||
<xsd:enumeration value="threePt"/>
|
||||
<xsd:enumeration value="balanced"/>
|
||||
<xsd:enumeration value="soft"/>
|
||||
<xsd:enumeration value="harsh"/>
|
||||
<xsd:enumeration value="flood"/>
|
||||
<xsd:enumeration value="contrasting"/>
|
||||
<xsd:enumeration value="morning"/>
|
||||
<xsd:enumeration value="sunrise"/>
|
||||
<xsd:enumeration value="sunset"/>
|
||||
<xsd:enumeration value="chilly"/>
|
||||
<xsd:enumeration value="freezing"/>
|
||||
<xsd:enumeration value="flat"/>
|
||||
<xsd:enumeration value="twoPt"/>
|
||||
<xsd:enumeration value="glow"/>
|
||||
<xsd:enumeration value="brightRoom"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:simpleType name="ST_LightRigDirection">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="tl"/>
|
||||
<xsd:enumeration value="t"/>
|
||||
<xsd:enumeration value="tr"/>
|
||||
<xsd:enumeration value="l"/>
|
||||
<xsd:enumeration value="r"/>
|
||||
<xsd:enumeration value="bl"/>
|
||||
<xsd:enumeration value="b"/>
|
||||
<xsd:enumeration value="br"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_LightRig">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="rot" type="CT_SphereCoords" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="rig" type="ST_LightRigType" use="required"/>
|
||||
<xsd:attribute name="dir" type="ST_LightRigDirection" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_BevelPresetType">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="relaxedInset"/>
|
||||
<xsd:enumeration value="circle"/>
|
||||
<xsd:enumeration value="slope"/>
|
||||
<xsd:enumeration value="cross"/>
|
||||
<xsd:enumeration value="angle"/>
|
||||
<xsd:enumeration value="softRound"/>
|
||||
<xsd:enumeration value="convex"/>
|
||||
<xsd:enumeration value="coolSlant"/>
|
||||
<xsd:enumeration value="divot"/>
|
||||
<xsd:enumeration value="riblet"/>
|
||||
<xsd:enumeration value="hardEdge"/>
|
||||
<xsd:enumeration value="artDeco"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Bevel">
|
||||
<xsd:attribute name="w" type="a:ST_PositiveCoordinate" use="optional"/>
|
||||
<xsd:attribute name="h" type="a:ST_PositiveCoordinate" use="optional"/>
|
||||
<xsd:attribute name="prst" type="ST_BevelPresetType" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_PresetMaterialType">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:enumeration value="legacyMatte"/>
|
||||
<xsd:enumeration value="legacyPlastic"/>
|
||||
<xsd:enumeration value="legacyMetal"/>
|
||||
<xsd:enumeration value="legacyWireframe"/>
|
||||
<xsd:enumeration value="matte"/>
|
||||
<xsd:enumeration value="plastic"/>
|
||||
<xsd:enumeration value="metal"/>
|
||||
<xsd:enumeration value="warmMatte"/>
|
||||
<xsd:enumeration value="translucentPowder"/>
|
||||
<xsd:enumeration value="powder"/>
|
||||
<xsd:enumeration value="dkEdge"/>
|
||||
<xsd:enumeration value="softEdge"/>
|
||||
<xsd:enumeration value="clear"/>
|
||||
<xsd:enumeration value="flat"/>
|
||||
<xsd:enumeration value="softmetal"/>
|
||||
<xsd:enumeration value="none"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Glow">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorChoice"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="rad" use="optional" type="a:ST_PositiveCoordinate"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Shadow">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_ColorChoice"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="blurRad" use="optional" type="a:ST_PositiveCoordinate"/>
|
||||
<xsd:attribute name="dist" use="optional" type="a:ST_PositiveCoordinate"/>
|
||||
<xsd:attribute name="dir" use="optional" type="a:ST_PositiveFixedAngle"/>
|
||||
<xsd:attribute name="sx" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="sy" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="kx" use="optional" type="a:ST_FixedAngle"/>
|
||||
<xsd:attribute name="ky" use="optional" type="a:ST_FixedAngle"/>
|
||||
<xsd:attribute name="algn" use="optional" type="ST_RectAlignment"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Reflection">
|
||||
<xsd:attribute name="blurRad" use="optional" type="a:ST_PositiveCoordinate"/>
|
||||
<xsd:attribute name="stA" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
||||
<xsd:attribute name="stPos" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
||||
<xsd:attribute name="endA" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
||||
<xsd:attribute name="endPos" use="optional" type="a:ST_PositiveFixedPercentage"/>
|
||||
<xsd:attribute name="dist" use="optional" type="a:ST_PositiveCoordinate"/>
|
||||
<xsd:attribute name="dir" use="optional" type="a:ST_PositiveFixedAngle"/>
|
||||
<xsd:attribute name="fadeDir" use="optional" type="a:ST_PositiveFixedAngle"/>
|
||||
<xsd:attribute name="sx" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="sy" use="optional" type="a:ST_Percentage"/>
|
||||
<xsd:attribute name="kx" use="optional" type="a:ST_FixedAngle"/>
|
||||
<xsd:attribute name="ky" use="optional" type="a:ST_FixedAngle"/>
|
||||
<xsd:attribute name="algn" use="optional" type="ST_RectAlignment"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_FillTextEffect">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_FillProperties" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_TextOutlineEffect">
|
||||
<xsd:sequence>
|
||||
<xsd:group ref="EG_FillProperties" minOccurs="0"/>
|
||||
<xsd:group ref="EG_LineDashProperties" minOccurs="0"/>
|
||||
<xsd:group ref="EG_LineJoinProperties" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="w" use="optional" type="a:ST_LineWidth"/>
|
||||
<xsd:attribute name="cap" use="optional" type="ST_LineCap"/>
|
||||
<xsd:attribute name="cmpd" use="optional" type="ST_CompoundLine"/>
|
||||
<xsd:attribute name="algn" use="optional" type="ST_PenAlignment"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Scene3D">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="camera" type="CT_Camera"/>
|
||||
<xsd:element name="lightRig" type="CT_LightRig"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Props3D">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="bevelT" type="CT_Bevel" minOccurs="0"/>
|
||||
<xsd:element name="bevelB" type="CT_Bevel" minOccurs="0"/>
|
||||
<xsd:element name="extrusionClr" type="CT_Color" minOccurs="0"/>
|
||||
<xsd:element name="contourClr" type="CT_Color" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="extrusionH" type="a:ST_PositiveCoordinate" use="optional"/>
|
||||
<xsd:attribute name="contourW" type="a:ST_PositiveCoordinate" use="optional"/>
|
||||
<xsd:attribute name="prstMaterial" type="ST_PresetMaterialType" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_RPrTextEffects">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="glow" minOccurs="0" type="CT_Glow"/>
|
||||
<xsd:element name="shadow" minOccurs="0" type="CT_Shadow"/>
|
||||
<xsd:element name="reflection" minOccurs="0" type="CT_Reflection"/>
|
||||
<xsd:element name="textOutline" minOccurs="0" type="CT_TextOutlineEffect"/>
|
||||
<xsd:element name="textFill" minOccurs="0" type="CT_FillTextEffect"/>
|
||||
<xsd:element name="scene3d" minOccurs="0" type="CT_Scene3D"/>
|
||||
<xsd:element name="props3d" minOccurs="0" type="CT_Props3D"/>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:simpleType name="ST_Ligatures">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="none"/>
|
||||
<xsd:enumeration value="standard"/>
|
||||
<xsd:enumeration value="contextual"/>
|
||||
<xsd:enumeration value="historical"/>
|
||||
<xsd:enumeration value="discretional"/>
|
||||
<xsd:enumeration value="standardContextual"/>
|
||||
<xsd:enumeration value="standardHistorical"/>
|
||||
<xsd:enumeration value="contextualHistorical"/>
|
||||
<xsd:enumeration value="standardDiscretional"/>
|
||||
<xsd:enumeration value="contextualDiscretional"/>
|
||||
<xsd:enumeration value="historicalDiscretional"/>
|
||||
<xsd:enumeration value="standardContextualHistorical"/>
|
||||
<xsd:enumeration value="standardContextualDiscretional"/>
|
||||
<xsd:enumeration value="standardHistoricalDiscretional"/>
|
||||
<xsd:enumeration value="contextualHistoricalDiscretional"/>
|
||||
<xsd:enumeration value="all"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Ligatures">
|
||||
<xsd:attribute name="val" type="ST_Ligatures" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_NumForm">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="default"/>
|
||||
<xsd:enumeration value="lining"/>
|
||||
<xsd:enumeration value="oldStyle"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_NumForm">
|
||||
<xsd:attribute name="val" type="ST_NumForm" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_NumSpacing">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="default"/>
|
||||
<xsd:enumeration value="proportional"/>
|
||||
<xsd:enumeration value="tabular"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_NumSpacing">
|
||||
<xsd:attribute name="val" type="ST_NumSpacing" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_StyleSet">
|
||||
<xsd:attribute name="id" type="s:ST_UnsignedDecimalNumber" use="required"/>
|
||||
<xsd:attribute name="val" type="ST_OnOff" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_StylisticSets">
|
||||
<xsd:sequence minOccurs="0">
|
||||
<xsd:element name="styleSet" minOccurs="0" maxOccurs="unbounded" type="CT_StyleSet"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:group name="EG_RPrOpenType">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ligatures" minOccurs="0" type="CT_Ligatures"/>
|
||||
<xsd:element name="numForm" minOccurs="0" type="CT_NumForm"/>
|
||||
<xsd:element name="numSpacing" minOccurs="0" type="CT_NumSpacing"/>
|
||||
<xsd:element name="stylisticSets" minOccurs="0" type="CT_StylisticSets"/>
|
||||
<xsd:element name="cntxtAlts" minOccurs="0" type="CT_OnOff"/>
|
||||
</xsd:sequence>
|
||||
</xsd:group>
|
||||
<xsd:element name="discardImageEditingData" type="CT_OnOff"/>
|
||||
<xsd:element name="defaultImageDpi" type="CT_DefaultImageDpi"/>
|
||||
<xsd:complexType name="CT_DefaultImageDpi">
|
||||
<xsd:attribute name="val" type="w:ST_DecimalNumber" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="entityPicker" type="w:CT_Empty"/>
|
||||
<xsd:complexType name="CT_SdtCheckboxSymbol">
|
||||
<xsd:attribute name="font" type="s:ST_String"/>
|
||||
<xsd:attribute name="val" type="w:ST_ShortHexNumber"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_SdtCheckbox">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="checked" type="CT_OnOff" minOccurs="0"/>
|
||||
<xsd:element name="checkedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
|
||||
<xsd:element name="uncheckedState" type="CT_SdtCheckboxSymbol" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="checkbox" type="CT_SdtCheckbox"/>
|
||||
</xsd:schema>
|
||||
67
.claude/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd
Normal file
67
.claude/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd
Normal file
@ -0,0 +1,67 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2012/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2012/wordml">
|
||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:import namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" schemaLocation="../ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:element name="color" type="w12:CT_Color"/>
|
||||
<xsd:simpleType name="ST_SdtAppearance">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="boundingBox"/>
|
||||
<xsd:enumeration value="tags"/>
|
||||
<xsd:enumeration value="hidden"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:element name="dataBinding" type="w12:CT_DataBinding"/>
|
||||
<xsd:complexType name="CT_SdtAppearance">
|
||||
<xsd:attribute name="val" type="ST_SdtAppearance"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="appearance" type="CT_SdtAppearance"/>
|
||||
<xsd:complexType name="CT_CommentsEx">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="commentEx" type="CT_CommentEx" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_CommentEx">
|
||||
<xsd:attribute name="paraId" type="w12:ST_LongHexNumber" use="required"/>
|
||||
<xsd:attribute name="paraIdParent" type="w12:ST_LongHexNumber" use="optional"/>
|
||||
<xsd:attribute name="done" type="s:ST_OnOff" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="commentsEx" type="CT_CommentsEx"/>
|
||||
<xsd:complexType name="CT_People">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="person" type="CT_Person" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_PresenceInfo">
|
||||
<xsd:attribute name="providerId" type="xsd:string" use="required"/>
|
||||
<xsd:attribute name="userId" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_Person">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="presenceInfo" type="CT_PresenceInfo" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="author" type="s:ST_String" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="people" type="CT_People"/>
|
||||
<xsd:complexType name="CT_SdtRepeatedSection">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="sectionTitle" type="w12:CT_String" minOccurs="0"/>
|
||||
<xsd:element name="doNotAllowInsertDeleteSection" type="w12:CT_OnOff" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:simpleType name="ST_Guid">
|
||||
<xsd:restriction base="xsd:token">
|
||||
<xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
<xsd:complexType name="CT_Guid">
|
||||
<xsd:attribute name="val" type="ST_Guid"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="repeatingSection" type="CT_SdtRepeatedSection"/>
|
||||
<xsd:element name="repeatingSectionItem" type="w12:CT_Empty"/>
|
||||
<xsd:element name="chartTrackingRefBased" type="w12:CT_OnOff"/>
|
||||
<xsd:element name="collapsed" type="w12:CT_OnOff"/>
|
||||
<xsd:element name="docId" type="CT_Guid"/>
|
||||
<xsd:element name="footnoteColumns" type="w12:CT_DecimalNumber"/>
|
||||
<xsd:element name="webExtensionLinked" type="w12:CT_OnOff"/>
|
||||
<xsd:element name="webExtensionCreated" type="w12:CT_OnOff"/>
|
||||
<xsd:attribute name="restartNumberingAfterBreak" type="s:ST_OnOff"/>
|
||||
</xsd:schema>
|
||||
14
.claude/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd
Normal file
14
.claude/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd
Normal file
@ -0,0 +1,14 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2018/wordml" targetNamespace="http://schemas.microsoft.com/office/word/2018/wordml">
|
||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:complexType name="CT_Extension">
|
||||
<xsd:sequence>
|
||||
<xsd:any processContents="lax"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="uri" type="xsd:token"/>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_ExtensionList">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="ext" type="CT_Extension" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
</xsd:schema>
|
||||
20
.claude/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd
Normal file
20
.claude/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd
Normal file
@ -0,0 +1,20 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:s="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2018/wordml/cex" targetNamespace="http://schemas.microsoft.com/office/word/2018/wordml/cex">
|
||||
<xsd:import id="w16" namespace="http://schemas.microsoft.com/office/word/2018/wordml" schemaLocation="wml-2018.xsd"/>
|
||||
<xsd:import id="w" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:import id="s" namespace="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" schemaLocation="../ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd"/>
|
||||
<xsd:complexType name="CT_CommentsExtensible">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="commentExtensible" type="CT_CommentExtensible" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xsd:element name="extLst" type="w16:CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_CommentExtensible">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="extLst" type="w16:CT_ExtensionList" minOccurs="0" maxOccurs="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="durableId" type="w:ST_LongHexNumber" use="required"/>
|
||||
<xsd:attribute name="dateUtc" type="w:ST_DateTime" use="optional"/>
|
||||
<xsd:attribute name="intelligentPlaceholder" type="s:ST_OnOff" use="optional"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="commentsExtensible" type="CT_CommentsExtensible"/>
|
||||
</xsd:schema>
|
||||
13
.claude/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd
Normal file
13
.claude/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd
Normal file
@ -0,0 +1,13 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2016/wordml/cid" targetNamespace="http://schemas.microsoft.com/office/word/2016/wordml/cid">
|
||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:complexType name="CT_CommentsIds">
|
||||
<xsd:sequence>
|
||||
<xsd:element name="commentId" type="CT_CommentId" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xsd:sequence>
|
||||
</xsd:complexType>
|
||||
<xsd:complexType name="CT_CommentId">
|
||||
<xsd:attribute name="paraId" type="w12:ST_LongHexNumber" use="required"/>
|
||||
<xsd:attribute name="durableId" type="w12:ST_LongHexNumber" use="required"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="commentsIds" type="CT_CommentsIds"/>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,4 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" targetNamespace="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash">
|
||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:attribute name="storeItemChecksum" type="w12:ST_String"/>
|
||||
</xsd:schema>
|
||||
@ -0,0 +1,8 @@
|
||||
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:w12="http://schemas.openxmlformats.org/wordprocessingml/2006/main" elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" xmlns="http://schemas.microsoft.com/office/word/2015/wordml/symex" targetNamespace="http://schemas.microsoft.com/office/word/2015/wordml/symex">
|
||||
<xsd:import id="w12" namespace="http://schemas.openxmlformats.org/wordprocessingml/2006/main" schemaLocation="../ISO-IEC29500-4_2016/wml.xsd"/>
|
||||
<xsd:complexType name="CT_SymEx">
|
||||
<xsd:attribute name="font" type="w12:ST_String"/>
|
||||
<xsd:attribute name="char" type="w12:ST_LongHexNumber"/>
|
||||
</xsd:complexType>
|
||||
<xsd:element name="symEx" type="CT_SymEx"/>
|
||||
</xsd:schema>
|
||||
159
.claude/skills/docx/ooxml/scripts/pack.py
Normal file
159
.claude/skills/docx/ooxml/scripts/pack.py
Normal file
@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Tool to pack a directory into a .docx, .pptx, or .xlsx file with XML formatting undone.
|
||||
|
||||
Example usage:
|
||||
python pack.py <input_directory> <office_file> [--force]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import defusedxml.minidom
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Pack a directory into an Office file")
|
||||
parser.add_argument("input_directory", help="Unpacked Office document directory")
|
||||
parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)")
|
||||
parser.add_argument("--force", action="store_true", help="Skip validation")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
success = pack_document(
|
||||
args.input_directory, args.output_file, validate=not args.force
|
||||
)
|
||||
|
||||
# Show warning if validation was skipped
|
||||
if args.force:
|
||||
print("Warning: Skipped validation, file may be corrupt", file=sys.stderr)
|
||||
# Exit with error if validation failed
|
||||
elif not success:
|
||||
print("Contents would produce a corrupt file.", file=sys.stderr)
|
||||
print("Please validate XML before repacking.", file=sys.stderr)
|
||||
print("Use --force to skip validation and pack anyway.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
except ValueError as e:
|
||||
sys.exit(f"Error: {e}")
|
||||
|
||||
|
||||
def pack_document(input_dir, output_file, validate=False):
|
||||
"""Pack a directory into an Office file (.docx/.pptx/.xlsx).
|
||||
|
||||
Args:
|
||||
input_dir: Path to unpacked Office document directory
|
||||
output_file: Path to output Office file
|
||||
validate: If True, validates with soffice (default: False)
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False if validation failed
|
||||
"""
|
||||
input_dir = Path(input_dir)
|
||||
output_file = Path(output_file)
|
||||
|
||||
if not input_dir.is_dir():
|
||||
raise ValueError(f"{input_dir} is not a directory")
|
||||
if output_file.suffix.lower() not in {".docx", ".pptx", ".xlsx"}:
|
||||
raise ValueError(f"{output_file} must be a .docx, .pptx, or .xlsx file")
|
||||
|
||||
# Work in temporary directory to avoid modifying original
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_content_dir = Path(temp_dir) / "content"
|
||||
shutil.copytree(input_dir, temp_content_dir)
|
||||
|
||||
# Process XML files to remove pretty-printing whitespace
|
||||
for pattern in ["*.xml", "*.rels"]:
|
||||
for xml_file in temp_content_dir.rglob(pattern):
|
||||
condense_xml(xml_file)
|
||||
|
||||
# Create final Office file as zip archive
|
||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(output_file, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||
for f in temp_content_dir.rglob("*"):
|
||||
if f.is_file():
|
||||
zf.write(f, f.relative_to(temp_content_dir))
|
||||
|
||||
# Validate if requested
|
||||
if validate:
|
||||
if not validate_document(output_file):
|
||||
output_file.unlink() # Delete the corrupt file
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def validate_document(doc_path):
|
||||
"""Validate document by converting to HTML with soffice."""
|
||||
# Determine the correct filter based on file extension
|
||||
match doc_path.suffix.lower():
|
||||
case ".docx":
|
||||
filter_name = "html:HTML"
|
||||
case ".pptx":
|
||||
filter_name = "html:impress_html_Export"
|
||||
case ".xlsx":
|
||||
filter_name = "html:HTML (StarCalc)"
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[
|
||||
"soffice",
|
||||
"--headless",
|
||||
"--convert-to",
|
||||
filter_name,
|
||||
"--outdir",
|
||||
temp_dir,
|
||||
str(doc_path),
|
||||
],
|
||||
capture_output=True,
|
||||
timeout=10,
|
||||
text=True,
|
||||
)
|
||||
if not (Path(temp_dir) / f"{doc_path.stem}.html").exists():
|
||||
error_msg = result.stderr.strip() or "Document validation failed"
|
||||
print(f"Validation error: {error_msg}", file=sys.stderr)
|
||||
return False
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
print("Warning: soffice not found. Skipping validation.", file=sys.stderr)
|
||||
return True
|
||||
except subprocess.TimeoutExpired:
|
||||
print("Validation error: Timeout during conversion", file=sys.stderr)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"Validation error: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def condense_xml(xml_file):
|
||||
"""Strip unnecessary whitespace and remove comments."""
|
||||
with open(xml_file, "r", encoding="utf-8") as f:
|
||||
dom = defusedxml.minidom.parse(f)
|
||||
|
||||
# Process each element to remove whitespace and comments
|
||||
for element in dom.getElementsByTagName("*"):
|
||||
# Skip w:t elements and their processing
|
||||
if element.tagName.endswith(":t"):
|
||||
continue
|
||||
|
||||
# Remove whitespace-only text nodes and comment nodes
|
||||
for child in list(element.childNodes):
|
||||
if (
|
||||
child.nodeType == child.TEXT_NODE
|
||||
and child.nodeValue
|
||||
and child.nodeValue.strip() == ""
|
||||
) or child.nodeType == child.COMMENT_NODE:
|
||||
element.removeChild(child)
|
||||
|
||||
# Write back the condensed XML
|
||||
with open(xml_file, "wb") as f:
|
||||
f.write(dom.toxml(encoding="UTF-8"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
29
.claude/skills/docx/ooxml/scripts/unpack.py
Normal file
29
.claude/skills/docx/ooxml/scripts/unpack.py
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Unpack and format XML contents of Office files (.docx, .pptx, .xlsx)"""
|
||||
|
||||
import random
|
||||
import sys
|
||||
import defusedxml.minidom
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
# Get command line arguments
|
||||
assert len(sys.argv) == 3, "Usage: python unpack.py <office_file> <output_dir>"
|
||||
input_file, output_dir = sys.argv[1], sys.argv[2]
|
||||
|
||||
# Extract and format
|
||||
output_path = Path(output_dir)
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
zipfile.ZipFile(input_file).extractall(output_path)
|
||||
|
||||
# Pretty print all XML files
|
||||
xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels"))
|
||||
for xml_file in xml_files:
|
||||
content = xml_file.read_text(encoding="utf-8")
|
||||
dom = defusedxml.minidom.parseString(content)
|
||||
xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="ascii"))
|
||||
|
||||
# For .docx files, suggest an RSID for tracked changes
|
||||
if input_file.endswith(".docx"):
|
||||
suggested_rsid = "".join(random.choices("0123456789ABCDEF", k=8))
|
||||
print(f"Suggested RSID for edit session: {suggested_rsid}")
|
||||
69
.claude/skills/docx/ooxml/scripts/validate.py
Normal file
69
.claude/skills/docx/ooxml/scripts/validate.py
Normal file
@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Command line tool to validate Office document XML files against XSD schemas and tracked changes.
|
||||
|
||||
Usage:
|
||||
python validate.py <dir> --original <original_file>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from validation import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Validate Office document XML files")
|
||||
parser.add_argument(
|
||||
"unpacked_dir",
|
||||
help="Path to unpacked Office document directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--original",
|
||||
required=True,
|
||||
help="Path to original file (.docx/.pptx/.xlsx)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
help="Enable verbose output",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Validate paths
|
||||
unpacked_dir = Path(args.unpacked_dir)
|
||||
original_file = Path(args.original)
|
||||
file_extension = original_file.suffix.lower()
|
||||
assert unpacked_dir.is_dir(), f"Error: {unpacked_dir} is not a directory"
|
||||
assert original_file.is_file(), f"Error: {original_file} is not a file"
|
||||
assert file_extension in [".docx", ".pptx", ".xlsx"], (
|
||||
f"Error: {original_file} must be a .docx, .pptx, or .xlsx file"
|
||||
)
|
||||
|
||||
# Run validations
|
||||
match file_extension:
|
||||
case ".docx":
|
||||
validators = [DOCXSchemaValidator, RedliningValidator]
|
||||
case ".pptx":
|
||||
validators = [PPTXSchemaValidator]
|
||||
case _:
|
||||
print(f"Error: Validation not supported for file type {file_extension}")
|
||||
sys.exit(1)
|
||||
|
||||
# Run validators
|
||||
success = True
|
||||
for V in validators:
|
||||
validator = V(unpacked_dir, original_file, verbose=args.verbose)
|
||||
if not validator.validate():
|
||||
success = False
|
||||
|
||||
if success:
|
||||
print("All validations PASSED!")
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
15
.claude/skills/docx/ooxml/scripts/validation/__init__.py
Normal file
15
.claude/skills/docx/ooxml/scripts/validation/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
Validation modules for Word document processing.
|
||||
"""
|
||||
|
||||
from .base import BaseSchemaValidator
|
||||
from .docx import DOCXSchemaValidator
|
||||
from .pptx import PPTXSchemaValidator
|
||||
from .redlining import RedliningValidator
|
||||
|
||||
__all__ = [
|
||||
"BaseSchemaValidator",
|
||||
"DOCXSchemaValidator",
|
||||
"PPTXSchemaValidator",
|
||||
"RedliningValidator",
|
||||
]
|
||||
951
.claude/skills/docx/ooxml/scripts/validation/base.py
Normal file
951
.claude/skills/docx/ooxml/scripts/validation/base.py
Normal file
@ -0,0 +1,951 @@
|
||||
"""
|
||||
Base validator with common validation logic for document files.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import lxml.etree
|
||||
|
||||
|
||||
class BaseSchemaValidator:
|
||||
"""Base validator with common validation logic for document files."""
|
||||
|
||||
# Elements whose 'id' attributes must be unique within their file
|
||||
# Format: element_name -> (attribute_name, scope)
|
||||
# scope can be 'file' (unique within file) or 'global' (unique across all files)
|
||||
UNIQUE_ID_REQUIREMENTS = {
|
||||
# Word elements
|
||||
"comment": ("id", "file"), # Comment IDs in comments.xml
|
||||
"commentrangestart": ("id", "file"), # Must match comment IDs
|
||||
"commentrangeend": ("id", "file"), # Must match comment IDs
|
||||
"bookmarkstart": ("id", "file"), # Bookmark start IDs
|
||||
"bookmarkend": ("id", "file"), # Bookmark end IDs
|
||||
# Note: ins and del (track changes) can share IDs when part of same revision
|
||||
# PowerPoint elements
|
||||
"sldid": ("id", "file"), # Slide IDs in presentation.xml
|
||||
"sldmasterid": ("id", "global"), # Slide master IDs must be globally unique
|
||||
"sldlayoutid": ("id", "global"), # Slide layout IDs must be globally unique
|
||||
"cm": ("authorid", "file"), # Comment author IDs
|
||||
# Excel elements
|
||||
"sheet": ("sheetid", "file"), # Sheet IDs in workbook.xml
|
||||
"definedname": ("id", "file"), # Named range IDs
|
||||
# Drawing/Shape elements (all formats)
|
||||
"cxnsp": ("id", "file"), # Connection shape IDs
|
||||
"sp": ("id", "file"), # Shape IDs
|
||||
"pic": ("id", "file"), # Picture IDs
|
||||
"grpsp": ("id", "file"), # Group shape IDs
|
||||
}
|
||||
|
||||
# Mapping of element names to expected relationship types
|
||||
# Subclasses should override this with format-specific mappings
|
||||
ELEMENT_RELATIONSHIP_TYPES = {}
|
||||
|
||||
# Unified schema mappings for all Office document types
|
||||
SCHEMA_MAPPINGS = {
|
||||
# Document type specific schemas
|
||||
"word": "ISO-IEC29500-4_2016/wml.xsd", # Word documents
|
||||
"ppt": "ISO-IEC29500-4_2016/pml.xsd", # PowerPoint presentations
|
||||
"xl": "ISO-IEC29500-4_2016/sml.xsd", # Excel spreadsheets
|
||||
# Common file types
|
||||
"[Content_Types].xml": "ecma/fouth-edition/opc-contentTypes.xsd",
|
||||
"app.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd",
|
||||
"core.xml": "ecma/fouth-edition/opc-coreProperties.xsd",
|
||||
"custom.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd",
|
||||
".rels": "ecma/fouth-edition/opc-relationships.xsd",
|
||||
# Word-specific files
|
||||
"people.xml": "microsoft/wml-2012.xsd",
|
||||
"commentsIds.xml": "microsoft/wml-cid-2016.xsd",
|
||||
"commentsExtensible.xml": "microsoft/wml-cex-2018.xsd",
|
||||
"commentsExtended.xml": "microsoft/wml-2012.xsd",
|
||||
# Chart files (common across document types)
|
||||
"chart": "ISO-IEC29500-4_2016/dml-chart.xsd",
|
||||
# Theme files (common across document types)
|
||||
"theme": "ISO-IEC29500-4_2016/dml-main.xsd",
|
||||
# Drawing and media files
|
||||
"drawing": "ISO-IEC29500-4_2016/dml-main.xsd",
|
||||
}
|
||||
|
||||
# Unified namespace constants
|
||||
MC_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace"
|
||||
|
||||
# Common OOXML namespaces used across validators
|
||||
PACKAGE_RELATIONSHIPS_NAMESPACE = (
|
||||
"http://schemas.openxmlformats.org/package/2006/relationships"
|
||||
)
|
||||
OFFICE_RELATIONSHIPS_NAMESPACE = (
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships"
|
||||
)
|
||||
CONTENT_TYPES_NAMESPACE = (
|
||||
"http://schemas.openxmlformats.org/package/2006/content-types"
|
||||
)
|
||||
|
||||
# Folders where we should clean ignorable namespaces
|
||||
MAIN_CONTENT_FOLDERS = {"word", "ppt", "xl"}
|
||||
|
||||
# All allowed OOXML namespaces (superset of all document types)
|
||||
OOXML_NAMESPACES = {
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/math",
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
||||
"http://schemas.openxmlformats.org/schemaLibrary/2006/main",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/main",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/chart",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/chartDrawing",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/diagram",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/picture",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing",
|
||||
"http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
|
||||
"http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
||||
"http://schemas.openxmlformats.org/presentationml/2006/main",
|
||||
"http://schemas.openxmlformats.org/spreadsheetml/2006/main",
|
||||
"http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes",
|
||||
"http://www.w3.org/XML/1998/namespace",
|
||||
}
|
||||
|
||||
def __init__(self, unpacked_dir, original_file, verbose=False):
|
||||
self.unpacked_dir = Path(unpacked_dir).resolve()
|
||||
self.original_file = Path(original_file)
|
||||
self.verbose = verbose
|
||||
|
||||
# Set schemas directory
|
||||
self.schemas_dir = Path(__file__).parent.parent.parent / "schemas"
|
||||
|
||||
# Get all XML and .rels files
|
||||
patterns = ["*.xml", "*.rels"]
|
||||
self.xml_files = [
|
||||
f for pattern in patterns for f in self.unpacked_dir.rglob(pattern)
|
||||
]
|
||||
|
||||
if not self.xml_files:
|
||||
print(f"Warning: No XML files found in {self.unpacked_dir}")
|
||||
|
||||
def validate(self):
|
||||
"""Run all validation checks and return True if all pass."""
|
||||
raise NotImplementedError("Subclasses must implement the validate method")
|
||||
|
||||
def validate_xml(self):
|
||||
"""Validate that all XML files are well-formed."""
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
# Try to parse the XML file
|
||||
lxml.etree.parse(str(xml_file))
|
||||
except lxml.etree.XMLSyntaxError as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {e.lineno}: {e.msg}"
|
||||
)
|
||||
except Exception as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Unexpected error: {str(e)}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} XML violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All XML files are well-formed")
|
||||
return True
|
||||
|
||||
def validate_namespaces(self):
|
||||
"""Validate that namespace prefixes in Ignorable attributes are declared."""
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
declared = set(root.nsmap.keys()) - {None} # Exclude default namespace
|
||||
|
||||
for attr_val in [
|
||||
v for k, v in root.attrib.items() if k.endswith("Ignorable")
|
||||
]:
|
||||
undeclared = set(attr_val.split()) - declared
|
||||
errors.extend(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Namespace '{ns}' in Ignorable but not declared"
|
||||
for ns in undeclared
|
||||
)
|
||||
except lxml.etree.XMLSyntaxError:
|
||||
continue
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - {len(errors)} namespace issues:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
if self.verbose:
|
||||
print("PASSED - All namespace prefixes properly declared")
|
||||
return True
|
||||
|
||||
def validate_unique_ids(self):
|
||||
"""Validate that specific IDs are unique according to OOXML requirements."""
|
||||
errors = []
|
||||
global_ids = {} # Track globally unique IDs across all files
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
file_ids = {} # Track IDs that must be unique within this file
|
||||
|
||||
# Remove all mc:AlternateContent elements from the tree
|
||||
mc_elements = root.xpath(
|
||||
".//mc:AlternateContent", namespaces={"mc": self.MC_NAMESPACE}
|
||||
)
|
||||
for elem in mc_elements:
|
||||
elem.getparent().remove(elem)
|
||||
|
||||
# Now check IDs in the cleaned tree
|
||||
for elem in root.iter():
|
||||
# Get the element name without namespace
|
||||
tag = (
|
||||
elem.tag.split("}")[-1].lower()
|
||||
if "}" in elem.tag
|
||||
else elem.tag.lower()
|
||||
)
|
||||
|
||||
# Check if this element type has ID uniqueness requirements
|
||||
if tag in self.UNIQUE_ID_REQUIREMENTS:
|
||||
attr_name, scope = self.UNIQUE_ID_REQUIREMENTS[tag]
|
||||
|
||||
# Look for the specified attribute
|
||||
id_value = None
|
||||
for attr, value in elem.attrib.items():
|
||||
attr_local = (
|
||||
attr.split("}")[-1].lower()
|
||||
if "}" in attr
|
||||
else attr.lower()
|
||||
)
|
||||
if attr_local == attr_name:
|
||||
id_value = value
|
||||
break
|
||||
|
||||
if id_value is not None:
|
||||
if scope == "global":
|
||||
# Check global uniqueness
|
||||
if id_value in global_ids:
|
||||
prev_file, prev_line, prev_tag = global_ids[
|
||||
id_value
|
||||
]
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {elem.sourceline}: Global ID '{id_value}' in <{tag}> "
|
||||
f"already used in {prev_file} at line {prev_line} in <{prev_tag}>"
|
||||
)
|
||||
else:
|
||||
global_ids[id_value] = (
|
||||
xml_file.relative_to(self.unpacked_dir),
|
||||
elem.sourceline,
|
||||
tag,
|
||||
)
|
||||
elif scope == "file":
|
||||
# Check file-level uniqueness
|
||||
key = (tag, attr_name)
|
||||
if key not in file_ids:
|
||||
file_ids[key] = {}
|
||||
|
||||
if id_value in file_ids[key]:
|
||||
prev_line = file_ids[key][id_value]
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {elem.sourceline}: Duplicate {attr_name}='{id_value}' in <{tag}> "
|
||||
f"(first occurrence at line {prev_line})"
|
||||
)
|
||||
else:
|
||||
file_ids[key][id_value] = elem.sourceline
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} ID uniqueness violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All required IDs are unique")
|
||||
return True
|
||||
|
||||
def validate_file_references(self):
|
||||
"""
|
||||
Validate that all .rels files properly reference files and that all files are referenced.
|
||||
"""
|
||||
errors = []
|
||||
|
||||
# Find all .rels files
|
||||
rels_files = list(self.unpacked_dir.rglob("*.rels"))
|
||||
|
||||
if not rels_files:
|
||||
if self.verbose:
|
||||
print("PASSED - No .rels files found")
|
||||
return True
|
||||
|
||||
# Get all files in the unpacked directory (excluding reference files)
|
||||
all_files = []
|
||||
for file_path in self.unpacked_dir.rglob("*"):
|
||||
if (
|
||||
file_path.is_file()
|
||||
and file_path.name != "[Content_Types].xml"
|
||||
and not file_path.name.endswith(".rels")
|
||||
): # This file is not referenced by .rels
|
||||
all_files.append(file_path.resolve())
|
||||
|
||||
# Track all files that are referenced by any .rels file
|
||||
all_referenced_files = set()
|
||||
|
||||
if self.verbose:
|
||||
print(
|
||||
f"Found {len(rels_files)} .rels files and {len(all_files)} target files"
|
||||
)
|
||||
|
||||
# Check each .rels file
|
||||
for rels_file in rels_files:
|
||||
try:
|
||||
# Parse relationships file
|
||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
# Get the directory where this .rels file is located
|
||||
rels_dir = rels_file.parent
|
||||
|
||||
# Find all relationships and their targets
|
||||
referenced_files = set()
|
||||
broken_refs = []
|
||||
|
||||
for rel in rels_root.findall(
|
||||
".//ns:Relationship",
|
||||
namespaces={"ns": self.PACKAGE_RELATIONSHIPS_NAMESPACE},
|
||||
):
|
||||
target = rel.get("Target")
|
||||
if target and not target.startswith(
|
||||
("http", "mailto:")
|
||||
): # Skip external URLs
|
||||
# Resolve the target path relative to the .rels file location
|
||||
if rels_file.name == ".rels":
|
||||
# Root .rels file - targets are relative to unpacked_dir
|
||||
target_path = self.unpacked_dir / target
|
||||
else:
|
||||
# Other .rels files - targets are relative to their parent's parent
|
||||
# e.g., word/_rels/document.xml.rels -> targets relative to word/
|
||||
base_dir = rels_dir.parent
|
||||
target_path = base_dir / target
|
||||
|
||||
# Normalize the path and check if it exists
|
||||
try:
|
||||
target_path = target_path.resolve()
|
||||
if target_path.exists() and target_path.is_file():
|
||||
referenced_files.add(target_path)
|
||||
all_referenced_files.add(target_path)
|
||||
else:
|
||||
broken_refs.append((target, rel.sourceline))
|
||||
except (OSError, ValueError):
|
||||
broken_refs.append((target, rel.sourceline))
|
||||
|
||||
# Report broken references
|
||||
if broken_refs:
|
||||
rel_path = rels_file.relative_to(self.unpacked_dir)
|
||||
for broken_ref, line_num in broken_refs:
|
||||
errors.append(
|
||||
f" {rel_path}: Line {line_num}: Broken reference to {broken_ref}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
rel_path = rels_file.relative_to(self.unpacked_dir)
|
||||
errors.append(f" Error parsing {rel_path}: {e}")
|
||||
|
||||
# Check for unreferenced files (files that exist but are not referenced anywhere)
|
||||
unreferenced_files = set(all_files) - all_referenced_files
|
||||
|
||||
if unreferenced_files:
|
||||
for unref_file in sorted(unreferenced_files):
|
||||
unref_rel_path = unref_file.relative_to(self.unpacked_dir)
|
||||
errors.append(f" Unreferenced file: {unref_rel_path}")
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} relationship validation errors:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
print(
|
||||
"CRITICAL: These errors will cause the document to appear corrupt. "
|
||||
+ "Broken references MUST be fixed, "
|
||||
+ "and unreferenced files MUST be referenced or removed."
|
||||
)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print(
|
||||
"PASSED - All references are valid and all files are properly referenced"
|
||||
)
|
||||
return True
|
||||
|
||||
def validate_all_relationship_ids(self):
|
||||
"""
|
||||
Validate that all r:id attributes in XML files reference existing IDs
|
||||
in their corresponding .rels files, and optionally validate relationship types.
|
||||
"""
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
|
||||
# Process each XML file that might contain r:id references
|
||||
for xml_file in self.xml_files:
|
||||
# Skip .rels files themselves
|
||||
if xml_file.suffix == ".rels":
|
||||
continue
|
||||
|
||||
# Determine the corresponding .rels file
|
||||
# For dir/file.xml, it's dir/_rels/file.xml.rels
|
||||
rels_dir = xml_file.parent / "_rels"
|
||||
rels_file = rels_dir / f"{xml_file.name}.rels"
|
||||
|
||||
# Skip if there's no corresponding .rels file (that's okay)
|
||||
if not rels_file.exists():
|
||||
continue
|
||||
|
||||
try:
|
||||
# Parse the .rels file to get valid relationship IDs and their types
|
||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
rid_to_type = {}
|
||||
|
||||
for rel in rels_root.findall(
|
||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
||||
):
|
||||
rid = rel.get("Id")
|
||||
rel_type = rel.get("Type", "")
|
||||
if rid:
|
||||
# Check for duplicate rIds
|
||||
if rid in rid_to_type:
|
||||
rels_rel_path = rels_file.relative_to(self.unpacked_dir)
|
||||
errors.append(
|
||||
f" {rels_rel_path}: Line {rel.sourceline}: "
|
||||
f"Duplicate relationship ID '{rid}' (IDs must be unique)"
|
||||
)
|
||||
# Extract just the type name from the full URL
|
||||
type_name = (
|
||||
rel_type.split("/")[-1] if "/" in rel_type else rel_type
|
||||
)
|
||||
rid_to_type[rid] = type_name
|
||||
|
||||
# Parse the XML file to find all r:id references
|
||||
xml_root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
|
||||
# Find all elements with r:id attributes
|
||||
for elem in xml_root.iter():
|
||||
# Check for r:id attribute (relationship ID)
|
||||
rid_attr = elem.get(f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id")
|
||||
if rid_attr:
|
||||
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
|
||||
elem_name = (
|
||||
elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag
|
||||
)
|
||||
|
||||
# Check if the ID exists
|
||||
if rid_attr not in rid_to_type:
|
||||
errors.append(
|
||||
f" {xml_rel_path}: Line {elem.sourceline}: "
|
||||
f"<{elem_name}> references non-existent relationship '{rid_attr}' "
|
||||
f"(valid IDs: {', '.join(sorted(rid_to_type.keys())[:5])}{'...' if len(rid_to_type) > 5 else ''})"
|
||||
)
|
||||
# Check if we have type expectations for this element
|
||||
elif self.ELEMENT_RELATIONSHIP_TYPES:
|
||||
expected_type = self._get_expected_relationship_type(
|
||||
elem_name
|
||||
)
|
||||
if expected_type:
|
||||
actual_type = rid_to_type[rid_attr]
|
||||
# Check if the actual type matches or contains the expected type
|
||||
if expected_type not in actual_type.lower():
|
||||
errors.append(
|
||||
f" {xml_rel_path}: Line {elem.sourceline}: "
|
||||
f"<{elem_name}> references '{rid_attr}' which points to '{actual_type}' "
|
||||
f"but should point to a '{expected_type}' relationship"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
xml_rel_path = xml_file.relative_to(self.unpacked_dir)
|
||||
errors.append(f" Error processing {xml_rel_path}: {e}")
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} relationship ID reference errors:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
print("\nThese ID mismatches will cause the document to appear corrupt!")
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All relationship ID references are valid")
|
||||
return True
|
||||
|
||||
def _get_expected_relationship_type(self, element_name):
|
||||
"""
|
||||
Get the expected relationship type for an element.
|
||||
First checks the explicit mapping, then tries pattern detection.
|
||||
"""
|
||||
# Normalize element name to lowercase
|
||||
elem_lower = element_name.lower()
|
||||
|
||||
# Check explicit mapping first
|
||||
if elem_lower in self.ELEMENT_RELATIONSHIP_TYPES:
|
||||
return self.ELEMENT_RELATIONSHIP_TYPES[elem_lower]
|
||||
|
||||
# Try pattern detection for common patterns
|
||||
# Pattern 1: Elements ending in "Id" often expect a relationship of the prefix type
|
||||
if elem_lower.endswith("id") and len(elem_lower) > 2:
|
||||
# e.g., "sldId" -> "sld", "sldMasterId" -> "sldMaster"
|
||||
prefix = elem_lower[:-2] # Remove "id"
|
||||
# Check if this might be a compound like "sldMasterId"
|
||||
if prefix.endswith("master"):
|
||||
return prefix.lower()
|
||||
elif prefix.endswith("layout"):
|
||||
return prefix.lower()
|
||||
else:
|
||||
# Simple case like "sldId" -> "slide"
|
||||
# Common transformations
|
||||
if prefix == "sld":
|
||||
return "slide"
|
||||
return prefix.lower()
|
||||
|
||||
# Pattern 2: Elements ending in "Reference" expect a relationship of the prefix type
|
||||
if elem_lower.endswith("reference") and len(elem_lower) > 9:
|
||||
prefix = elem_lower[:-9] # Remove "reference"
|
||||
return prefix.lower()
|
||||
|
||||
return None
|
||||
|
||||
def validate_content_types(self):
|
||||
"""Validate that all content files are properly declared in [Content_Types].xml."""
|
||||
errors = []
|
||||
|
||||
# Find [Content_Types].xml file
|
||||
content_types_file = self.unpacked_dir / "[Content_Types].xml"
|
||||
if not content_types_file.exists():
|
||||
print("FAILED - [Content_Types].xml file not found")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Parse and get all declared parts and extensions
|
||||
root = lxml.etree.parse(str(content_types_file)).getroot()
|
||||
declared_parts = set()
|
||||
declared_extensions = set()
|
||||
|
||||
# Get Override declarations (specific files)
|
||||
for override in root.findall(
|
||||
f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Override"
|
||||
):
|
||||
part_name = override.get("PartName")
|
||||
if part_name is not None:
|
||||
declared_parts.add(part_name.lstrip("/"))
|
||||
|
||||
# Get Default declarations (by extension)
|
||||
for default in root.findall(
|
||||
f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Default"
|
||||
):
|
||||
extension = default.get("Extension")
|
||||
if extension is not None:
|
||||
declared_extensions.add(extension.lower())
|
||||
|
||||
# Root elements that require content type declaration
|
||||
declarable_roots = {
|
||||
"sld",
|
||||
"sldLayout",
|
||||
"sldMaster",
|
||||
"presentation", # PowerPoint
|
||||
"document", # Word
|
||||
"workbook",
|
||||
"worksheet", # Excel
|
||||
"theme", # Common
|
||||
}
|
||||
|
||||
# Common media file extensions that should be declared
|
||||
media_extensions = {
|
||||
"png": "image/png",
|
||||
"jpg": "image/jpeg",
|
||||
"jpeg": "image/jpeg",
|
||||
"gif": "image/gif",
|
||||
"bmp": "image/bmp",
|
||||
"tiff": "image/tiff",
|
||||
"wmf": "image/x-wmf",
|
||||
"emf": "image/x-emf",
|
||||
}
|
||||
|
||||
# Get all files in the unpacked directory
|
||||
all_files = list(self.unpacked_dir.rglob("*"))
|
||||
all_files = [f for f in all_files if f.is_file()]
|
||||
|
||||
# Check all XML files for Override declarations
|
||||
for xml_file in self.xml_files:
|
||||
path_str = str(xml_file.relative_to(self.unpacked_dir)).replace(
|
||||
"\\", "/"
|
||||
)
|
||||
|
||||
# Skip non-content files
|
||||
if any(
|
||||
skip in path_str
|
||||
for skip in [".rels", "[Content_Types]", "docProps/", "_rels/"]
|
||||
):
|
||||
continue
|
||||
|
||||
try:
|
||||
root_tag = lxml.etree.parse(str(xml_file)).getroot().tag
|
||||
root_name = root_tag.split("}")[-1] if "}" in root_tag else root_tag
|
||||
|
||||
if root_name in declarable_roots and path_str not in declared_parts:
|
||||
errors.append(
|
||||
f" {path_str}: File with <{root_name}> root not declared in [Content_Types].xml"
|
||||
)
|
||||
|
||||
except Exception:
|
||||
continue # Skip unparseable files
|
||||
|
||||
# Check all non-XML files for Default extension declarations
|
||||
for file_path in all_files:
|
||||
# Skip XML files and metadata files (already checked above)
|
||||
if file_path.suffix.lower() in {".xml", ".rels"}:
|
||||
continue
|
||||
if file_path.name == "[Content_Types].xml":
|
||||
continue
|
||||
if "_rels" in file_path.parts or "docProps" in file_path.parts:
|
||||
continue
|
||||
|
||||
extension = file_path.suffix.lstrip(".").lower()
|
||||
if extension and extension not in declared_extensions:
|
||||
# Check if it's a known media extension that should be declared
|
||||
if extension in media_extensions:
|
||||
relative_path = file_path.relative_to(self.unpacked_dir)
|
||||
errors.append(
|
||||
f' {relative_path}: File with extension \'{extension}\' not declared in [Content_Types].xml - should add: <Default Extension="{extension}" ContentType="{media_extensions[extension]}"/>'
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f" Error parsing [Content_Types].xml: {e}")
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} content type declaration errors:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print(
|
||||
"PASSED - All content files are properly declared in [Content_Types].xml"
|
||||
)
|
||||
return True
|
||||
|
||||
def validate_file_against_xsd(self, xml_file, verbose=False):
|
||||
"""Validate a single XML file against XSD schema, comparing with original.
|
||||
|
||||
Args:
|
||||
xml_file: Path to XML file to validate
|
||||
verbose: Enable verbose output
|
||||
|
||||
Returns:
|
||||
tuple: (is_valid, new_errors_set) where is_valid is True/False/None (skipped)
|
||||
"""
|
||||
# Resolve both paths to handle symlinks
|
||||
xml_file = Path(xml_file).resolve()
|
||||
unpacked_dir = self.unpacked_dir.resolve()
|
||||
|
||||
# Validate current file
|
||||
is_valid, current_errors = self._validate_single_file_xsd(
|
||||
xml_file, unpacked_dir
|
||||
)
|
||||
|
||||
if is_valid is None:
|
||||
return None, set() # Skipped
|
||||
elif is_valid:
|
||||
return True, set() # Valid, no errors
|
||||
|
||||
# Get errors from original file for this specific file
|
||||
original_errors = self._get_original_file_errors(xml_file)
|
||||
|
||||
# Compare with original (both are guaranteed to be sets here)
|
||||
assert current_errors is not None
|
||||
new_errors = current_errors - original_errors
|
||||
|
||||
if new_errors:
|
||||
if verbose:
|
||||
relative_path = xml_file.relative_to(unpacked_dir)
|
||||
print(f"FAILED - {relative_path}: {len(new_errors)} new error(s)")
|
||||
for error in list(new_errors)[:3]:
|
||||
truncated = error[:250] + "..." if len(error) > 250 else error
|
||||
print(f" - {truncated}")
|
||||
return False, new_errors
|
||||
else:
|
||||
# All errors existed in original
|
||||
if verbose:
|
||||
print(
|
||||
f"PASSED - No new errors (original had {len(current_errors)} errors)"
|
||||
)
|
||||
return True, set()
|
||||
|
||||
def validate_against_xsd(self):
|
||||
"""Validate XML files against XSD schemas, showing only new errors compared to original."""
|
||||
new_errors = []
|
||||
original_error_count = 0
|
||||
valid_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
relative_path = str(xml_file.relative_to(self.unpacked_dir))
|
||||
is_valid, new_file_errors = self.validate_file_against_xsd(
|
||||
xml_file, verbose=False
|
||||
)
|
||||
|
||||
if is_valid is None:
|
||||
skipped_count += 1
|
||||
continue
|
||||
elif is_valid and not new_file_errors:
|
||||
valid_count += 1
|
||||
continue
|
||||
elif is_valid:
|
||||
# Had errors but all existed in original
|
||||
original_error_count += 1
|
||||
valid_count += 1
|
||||
continue
|
||||
|
||||
# Has new errors
|
||||
new_errors.append(f" {relative_path}: {len(new_file_errors)} new error(s)")
|
||||
for error in list(new_file_errors)[:3]: # Show first 3 errors
|
||||
new_errors.append(
|
||||
f" - {error[:250]}..." if len(error) > 250 else f" - {error}"
|
||||
)
|
||||
|
||||
# Print summary
|
||||
if self.verbose:
|
||||
print(f"Validated {len(self.xml_files)} files:")
|
||||
print(f" - Valid: {valid_count}")
|
||||
print(f" - Skipped (no schema): {skipped_count}")
|
||||
if original_error_count:
|
||||
print(f" - With original errors (ignored): {original_error_count}")
|
||||
print(
|
||||
f" - With NEW errors: {len(new_errors) > 0 and len([e for e in new_errors if not e.startswith(' ')]) or 0}"
|
||||
)
|
||||
|
||||
if new_errors:
|
||||
print("\nFAILED - Found NEW validation errors:")
|
||||
for error in new_errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("\nPASSED - No new XSD validation errors introduced")
|
||||
return True
|
||||
|
||||
def _get_schema_path(self, xml_file):
|
||||
"""Determine the appropriate schema path for an XML file."""
|
||||
# Check exact filename match
|
||||
if xml_file.name in self.SCHEMA_MAPPINGS:
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.name]
|
||||
|
||||
# Check .rels files
|
||||
if xml_file.suffix == ".rels":
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[".rels"]
|
||||
|
||||
# Check chart files
|
||||
if "charts/" in str(xml_file) and xml_file.name.startswith("chart"):
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS["chart"]
|
||||
|
||||
# Check theme files
|
||||
if "theme/" in str(xml_file) and xml_file.name.startswith("theme"):
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS["theme"]
|
||||
|
||||
# Check if file is in a main content folder and use appropriate schema
|
||||
if xml_file.parent.name in self.MAIN_CONTENT_FOLDERS:
|
||||
return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.parent.name]
|
||||
|
||||
return None
|
||||
|
||||
def _clean_ignorable_namespaces(self, xml_doc):
|
||||
"""Remove attributes and elements not in allowed namespaces."""
|
||||
# Create a clean copy
|
||||
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
|
||||
xml_copy = lxml.etree.fromstring(xml_string)
|
||||
|
||||
# Remove attributes not in allowed namespaces
|
||||
for elem in xml_copy.iter():
|
||||
attrs_to_remove = []
|
||||
|
||||
for attr in elem.attrib:
|
||||
# Check if attribute is from a namespace other than allowed ones
|
||||
if "{" in attr:
|
||||
ns = attr.split("}")[0][1:]
|
||||
if ns not in self.OOXML_NAMESPACES:
|
||||
attrs_to_remove.append(attr)
|
||||
|
||||
# Remove collected attributes
|
||||
for attr in attrs_to_remove:
|
||||
del elem.attrib[attr]
|
||||
|
||||
# Remove elements not in allowed namespaces
|
||||
self._remove_ignorable_elements(xml_copy)
|
||||
|
||||
return lxml.etree.ElementTree(xml_copy)
|
||||
|
||||
def _remove_ignorable_elements(self, root):
|
||||
"""Recursively remove all elements not in allowed namespaces."""
|
||||
elements_to_remove = []
|
||||
|
||||
# Find elements to remove
|
||||
for elem in list(root):
|
||||
# Skip non-element nodes (comments, processing instructions, etc.)
|
||||
if not hasattr(elem, "tag") or callable(elem.tag):
|
||||
continue
|
||||
|
||||
tag_str = str(elem.tag)
|
||||
if tag_str.startswith("{"):
|
||||
ns = tag_str.split("}")[0][1:]
|
||||
if ns not in self.OOXML_NAMESPACES:
|
||||
elements_to_remove.append(elem)
|
||||
continue
|
||||
|
||||
# Recursively clean child elements
|
||||
self._remove_ignorable_elements(elem)
|
||||
|
||||
# Remove collected elements
|
||||
for elem in elements_to_remove:
|
||||
root.remove(elem)
|
||||
|
||||
def _preprocess_for_mc_ignorable(self, xml_doc):
|
||||
"""Preprocess XML to handle mc:Ignorable attribute properly."""
|
||||
# Remove mc:Ignorable attributes before validation
|
||||
root = xml_doc.getroot()
|
||||
|
||||
# Remove mc:Ignorable attribute from root
|
||||
if f"{{{self.MC_NAMESPACE}}}Ignorable" in root.attrib:
|
||||
del root.attrib[f"{{{self.MC_NAMESPACE}}}Ignorable"]
|
||||
|
||||
return xml_doc
|
||||
|
||||
def _validate_single_file_xsd(self, xml_file, base_path):
|
||||
"""Validate a single XML file against XSD schema. Returns (is_valid, errors_set)."""
|
||||
schema_path = self._get_schema_path(xml_file)
|
||||
if not schema_path:
|
||||
return None, None # Skip file
|
||||
|
||||
try:
|
||||
# Load schema
|
||||
with open(schema_path, "rb") as xsd_file:
|
||||
parser = lxml.etree.XMLParser()
|
||||
xsd_doc = lxml.etree.parse(
|
||||
xsd_file, parser=parser, base_url=str(schema_path)
|
||||
)
|
||||
schema = lxml.etree.XMLSchema(xsd_doc)
|
||||
|
||||
# Load and preprocess XML
|
||||
with open(xml_file, "r") as f:
|
||||
xml_doc = lxml.etree.parse(f)
|
||||
|
||||
xml_doc, _ = self._remove_template_tags_from_text_nodes(xml_doc)
|
||||
xml_doc = self._preprocess_for_mc_ignorable(xml_doc)
|
||||
|
||||
# Clean ignorable namespaces if needed
|
||||
relative_path = xml_file.relative_to(base_path)
|
||||
if (
|
||||
relative_path.parts
|
||||
and relative_path.parts[0] in self.MAIN_CONTENT_FOLDERS
|
||||
):
|
||||
xml_doc = self._clean_ignorable_namespaces(xml_doc)
|
||||
|
||||
# Validate
|
||||
if schema.validate(xml_doc):
|
||||
return True, set()
|
||||
else:
|
||||
errors = set()
|
||||
for error in schema.error_log:
|
||||
# Store normalized error message (without line numbers for comparison)
|
||||
errors.add(error.message)
|
||||
return False, errors
|
||||
|
||||
except Exception as e:
|
||||
return False, {str(e)}
|
||||
|
||||
def _get_original_file_errors(self, xml_file):
|
||||
"""Get XSD validation errors from a single file in the original document.
|
||||
|
||||
Args:
|
||||
xml_file: Path to the XML file in unpacked_dir to check
|
||||
|
||||
Returns:
|
||||
set: Set of error messages from the original file
|
||||
"""
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
# Resolve both paths to handle symlinks (e.g., /var vs /private/var on macOS)
|
||||
xml_file = Path(xml_file).resolve()
|
||||
unpacked_dir = self.unpacked_dir.resolve()
|
||||
relative_path = xml_file.relative_to(unpacked_dir)
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Extract original file
|
||||
with zipfile.ZipFile(self.original_file, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_path)
|
||||
|
||||
# Find corresponding file in original
|
||||
original_xml_file = temp_path / relative_path
|
||||
|
||||
if not original_xml_file.exists():
|
||||
# File didn't exist in original, so no original errors
|
||||
return set()
|
||||
|
||||
# Validate the specific file in original
|
||||
is_valid, errors = self._validate_single_file_xsd(
|
||||
original_xml_file, temp_path
|
||||
)
|
||||
return errors if errors else set()
|
||||
|
||||
def _remove_template_tags_from_text_nodes(self, xml_doc):
|
||||
"""Remove template tags from XML text nodes and collect warnings.
|
||||
|
||||
Template tags follow the pattern {{ ... }} and are used as placeholders
|
||||
for content replacement. They should be removed from text content before
|
||||
XSD validation while preserving XML structure.
|
||||
|
||||
Returns:
|
||||
tuple: (cleaned_xml_doc, warnings_list)
|
||||
"""
|
||||
warnings = []
|
||||
template_pattern = re.compile(r"\{\{[^}]*\}\}")
|
||||
|
||||
# Create a copy of the document to avoid modifying the original
|
||||
xml_string = lxml.etree.tostring(xml_doc, encoding="unicode")
|
||||
xml_copy = lxml.etree.fromstring(xml_string)
|
||||
|
||||
def process_text_content(text, content_type):
|
||||
if not text:
|
||||
return text
|
||||
matches = list(template_pattern.finditer(text))
|
||||
if matches:
|
||||
for match in matches:
|
||||
warnings.append(
|
||||
f"Found template tag in {content_type}: {match.group()}"
|
||||
)
|
||||
return template_pattern.sub("", text)
|
||||
return text
|
||||
|
||||
# Process all text nodes in the document
|
||||
for elem in xml_copy.iter():
|
||||
# Skip processing if this is a w:t element
|
||||
if not hasattr(elem, "tag") or callable(elem.tag):
|
||||
continue
|
||||
tag_str = str(elem.tag)
|
||||
if tag_str.endswith("}t") or tag_str == "t":
|
||||
continue
|
||||
|
||||
elem.text = process_text_content(elem.text, "text content")
|
||||
elem.tail = process_text_content(elem.tail, "tail content")
|
||||
|
||||
return lxml.etree.ElementTree(xml_copy), warnings
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise RuntimeError("This module should not be run directly.")
|
||||
274
.claude/skills/docx/ooxml/scripts/validation/docx.py
Normal file
274
.claude/skills/docx/ooxml/scripts/validation/docx.py
Normal file
@ -0,0 +1,274 @@
|
||||
"""
|
||||
Validator for Word document XML files against XSD schemas.
|
||||
"""
|
||||
|
||||
import re
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
import lxml.etree
|
||||
|
||||
from .base import BaseSchemaValidator
|
||||
|
||||
|
||||
class DOCXSchemaValidator(BaseSchemaValidator):
|
||||
"""Validator for Word document XML files against XSD schemas."""
|
||||
|
||||
# Word-specific namespace
|
||||
WORD_2006_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
|
||||
# Word-specific element to relationship type mappings
|
||||
# Start with empty mapping - add specific cases as we discover them
|
||||
ELEMENT_RELATIONSHIP_TYPES = {}
|
||||
|
||||
def validate(self):
|
||||
"""Run all validation checks and return True if all pass."""
|
||||
# Test 0: XML well-formedness
|
||||
if not self.validate_xml():
|
||||
return False
|
||||
|
||||
# Test 1: Namespace declarations
|
||||
all_valid = True
|
||||
if not self.validate_namespaces():
|
||||
all_valid = False
|
||||
|
||||
# Test 2: Unique IDs
|
||||
if not self.validate_unique_ids():
|
||||
all_valid = False
|
||||
|
||||
# Test 3: Relationship and file reference validation
|
||||
if not self.validate_file_references():
|
||||
all_valid = False
|
||||
|
||||
# Test 4: Content type declarations
|
||||
if not self.validate_content_types():
|
||||
all_valid = False
|
||||
|
||||
# Test 5: XSD schema validation
|
||||
if not self.validate_against_xsd():
|
||||
all_valid = False
|
||||
|
||||
# Test 6: Whitespace preservation
|
||||
if not self.validate_whitespace_preservation():
|
||||
all_valid = False
|
||||
|
||||
# Test 7: Deletion validation
|
||||
if not self.validate_deletions():
|
||||
all_valid = False
|
||||
|
||||
# Test 8: Insertion validation
|
||||
if not self.validate_insertions():
|
||||
all_valid = False
|
||||
|
||||
# Test 9: Relationship ID reference validation
|
||||
if not self.validate_all_relationship_ids():
|
||||
all_valid = False
|
||||
|
||||
# Count and compare paragraphs
|
||||
self.compare_paragraph_counts()
|
||||
|
||||
return all_valid
|
||||
|
||||
def validate_whitespace_preservation(self):
|
||||
"""
|
||||
Validate that w:t elements with whitespace have xml:space='preserve'.
|
||||
"""
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
# Only check document.xml files
|
||||
if xml_file.name != "document.xml":
|
||||
continue
|
||||
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
|
||||
# Find all w:t elements
|
||||
for elem in root.iter(f"{{{self.WORD_2006_NAMESPACE}}}t"):
|
||||
if elem.text:
|
||||
text = elem.text
|
||||
# Check if text starts or ends with whitespace
|
||||
if re.match(r"^\s.*", text) or re.match(r".*\s$", text):
|
||||
# Check if xml:space="preserve" attribute exists
|
||||
xml_space_attr = f"{{{self.XML_NAMESPACE}}}space"
|
||||
if (
|
||||
xml_space_attr not in elem.attrib
|
||||
or elem.attrib[xml_space_attr] != "preserve"
|
||||
):
|
||||
# Show a preview of the text
|
||||
text_preview = (
|
||||
repr(text)[:50] + "..."
|
||||
if len(repr(text)) > 50
|
||||
else repr(text)
|
||||
)
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {elem.sourceline}: w:t element with whitespace missing xml:space='preserve': {text_preview}"
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} whitespace preservation violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All whitespace is properly preserved")
|
||||
return True
|
||||
|
||||
def validate_deletions(self):
|
||||
"""
|
||||
Validate that w:t elements are not within w:del elements.
|
||||
For some reason, XSD validation does not catch this, so we do it manually.
|
||||
"""
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
# Only check document.xml files
|
||||
if xml_file.name != "document.xml":
|
||||
continue
|
||||
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
|
||||
# Find all w:t elements that are descendants of w:del elements
|
||||
namespaces = {"w": self.WORD_2006_NAMESPACE}
|
||||
xpath_expression = ".//w:del//w:t"
|
||||
problematic_t_elements = root.xpath(
|
||||
xpath_expression, namespaces=namespaces
|
||||
)
|
||||
for t_elem in problematic_t_elements:
|
||||
if t_elem.text:
|
||||
# Show a preview of the text
|
||||
text_preview = (
|
||||
repr(t_elem.text)[:50] + "..."
|
||||
if len(repr(t_elem.text)) > 50
|
||||
else repr(t_elem.text)
|
||||
)
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {t_elem.sourceline}: <w:t> found within <w:del>: {text_preview}"
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} deletion validation violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - No w:t elements found within w:del elements")
|
||||
return True
|
||||
|
||||
def count_paragraphs_in_unpacked(self):
|
||||
"""Count the number of paragraphs in the unpacked document."""
|
||||
count = 0
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
# Only check document.xml files
|
||||
if xml_file.name != "document.xml":
|
||||
continue
|
||||
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
# Count all w:p elements
|
||||
paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p")
|
||||
count = len(paragraphs)
|
||||
except Exception as e:
|
||||
print(f"Error counting paragraphs in unpacked document: {e}")
|
||||
|
||||
return count
|
||||
|
||||
def count_paragraphs_in_original(self):
|
||||
"""Count the number of paragraphs in the original docx file."""
|
||||
count = 0
|
||||
|
||||
try:
|
||||
# Create temporary directory to unpack original
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Unpack original docx
|
||||
with zipfile.ZipFile(self.original_file, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_dir)
|
||||
|
||||
# Parse document.xml
|
||||
doc_xml_path = temp_dir + "/word/document.xml"
|
||||
root = lxml.etree.parse(doc_xml_path).getroot()
|
||||
|
||||
# Count all w:p elements
|
||||
paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p")
|
||||
count = len(paragraphs)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error counting paragraphs in original document: {e}")
|
||||
|
||||
return count
|
||||
|
||||
def validate_insertions(self):
|
||||
"""
|
||||
Validate that w:delText elements are not within w:ins elements.
|
||||
w:delText is only allowed in w:ins if nested within a w:del.
|
||||
"""
|
||||
errors = []
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
if xml_file.name != "document.xml":
|
||||
continue
|
||||
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
namespaces = {"w": self.WORD_2006_NAMESPACE}
|
||||
|
||||
# Find w:delText in w:ins that are NOT within w:del
|
||||
invalid_elements = root.xpath(
|
||||
".//w:ins//w:delText[not(ancestor::w:del)]",
|
||||
namespaces=namespaces
|
||||
)
|
||||
|
||||
for elem in invalid_elements:
|
||||
text_preview = (
|
||||
repr(elem.text or "")[:50] + "..."
|
||||
if len(repr(elem.text or "")) > 50
|
||||
else repr(elem.text or "")
|
||||
)
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {elem.sourceline}: <w:delText> within <w:ins>: {text_preview}"
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} insertion validation violations:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - No w:delText elements within w:ins elements")
|
||||
return True
|
||||
|
||||
def compare_paragraph_counts(self):
|
||||
"""Compare paragraph counts between original and new document."""
|
||||
original_count = self.count_paragraphs_in_original()
|
||||
new_count = self.count_paragraphs_in_unpacked()
|
||||
|
||||
diff = new_count - original_count
|
||||
diff_str = f"+{diff}" if diff > 0 else str(diff)
|
||||
print(f"\nParagraphs: {original_count} → {new_count} ({diff_str})")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise RuntimeError("This module should not be run directly.")
|
||||
315
.claude/skills/docx/ooxml/scripts/validation/pptx.py
Normal file
315
.claude/skills/docx/ooxml/scripts/validation/pptx.py
Normal file
@ -0,0 +1,315 @@
|
||||
"""
|
||||
Validator for PowerPoint presentation XML files against XSD schemas.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from .base import BaseSchemaValidator
|
||||
|
||||
|
||||
class PPTXSchemaValidator(BaseSchemaValidator):
|
||||
"""Validator for PowerPoint presentation XML files against XSD schemas."""
|
||||
|
||||
# PowerPoint presentation namespace
|
||||
PRESENTATIONML_NAMESPACE = (
|
||||
"http://schemas.openxmlformats.org/presentationml/2006/main"
|
||||
)
|
||||
|
||||
# PowerPoint-specific element to relationship type mappings
|
||||
ELEMENT_RELATIONSHIP_TYPES = {
|
||||
"sldid": "slide",
|
||||
"sldmasterid": "slidemaster",
|
||||
"notesmasterid": "notesmaster",
|
||||
"sldlayoutid": "slidelayout",
|
||||
"themeid": "theme",
|
||||
"tablestyleid": "tablestyles",
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
"""Run all validation checks and return True if all pass."""
|
||||
# Test 0: XML well-formedness
|
||||
if not self.validate_xml():
|
||||
return False
|
||||
|
||||
# Test 1: Namespace declarations
|
||||
all_valid = True
|
||||
if not self.validate_namespaces():
|
||||
all_valid = False
|
||||
|
||||
# Test 2: Unique IDs
|
||||
if not self.validate_unique_ids():
|
||||
all_valid = False
|
||||
|
||||
# Test 3: UUID ID validation
|
||||
if not self.validate_uuid_ids():
|
||||
all_valid = False
|
||||
|
||||
# Test 4: Relationship and file reference validation
|
||||
if not self.validate_file_references():
|
||||
all_valid = False
|
||||
|
||||
# Test 5: Slide layout ID validation
|
||||
if not self.validate_slide_layout_ids():
|
||||
all_valid = False
|
||||
|
||||
# Test 6: Content type declarations
|
||||
if not self.validate_content_types():
|
||||
all_valid = False
|
||||
|
||||
# Test 7: XSD schema validation
|
||||
if not self.validate_against_xsd():
|
||||
all_valid = False
|
||||
|
||||
# Test 8: Notes slide reference validation
|
||||
if not self.validate_notes_slide_references():
|
||||
all_valid = False
|
||||
|
||||
# Test 9: Relationship ID reference validation
|
||||
if not self.validate_all_relationship_ids():
|
||||
all_valid = False
|
||||
|
||||
# Test 10: Duplicate slide layout references validation
|
||||
if not self.validate_no_duplicate_slide_layouts():
|
||||
all_valid = False
|
||||
|
||||
return all_valid
|
||||
|
||||
def validate_uuid_ids(self):
|
||||
"""Validate that ID attributes that look like UUIDs contain only hex values."""
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
# UUID pattern: 8-4-4-4-12 hex digits with optional braces/hyphens
|
||||
uuid_pattern = re.compile(
|
||||
r"^[\{\(]?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}[\}\)]?$"
|
||||
)
|
||||
|
||||
for xml_file in self.xml_files:
|
||||
try:
|
||||
root = lxml.etree.parse(str(xml_file)).getroot()
|
||||
|
||||
# Check all elements for ID attributes
|
||||
for elem in root.iter():
|
||||
for attr, value in elem.attrib.items():
|
||||
# Check if this is an ID attribute
|
||||
attr_name = attr.split("}")[-1].lower()
|
||||
if attr_name == "id" or attr_name.endswith("id"):
|
||||
# Check if value looks like a UUID (has the right length and pattern structure)
|
||||
if self._looks_like_uuid(value):
|
||||
# Validate that it contains only hex characters in the right positions
|
||||
if not uuid_pattern.match(value):
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters"
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} UUID ID validation errors:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All UUID-like IDs contain valid hex values")
|
||||
return True
|
||||
|
||||
def _looks_like_uuid(self, value):
|
||||
"""Check if a value has the general structure of a UUID."""
|
||||
# Remove common UUID delimiters
|
||||
clean_value = value.strip("{}()").replace("-", "")
|
||||
# Check if it's 32 hex-like characters (could include invalid hex chars)
|
||||
return len(clean_value) == 32 and all(c.isalnum() for c in clean_value)
|
||||
|
||||
def validate_slide_layout_ids(self):
|
||||
"""Validate that sldLayoutId elements in slide masters reference valid slide layouts."""
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
|
||||
# Find all slide master files
|
||||
slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml"))
|
||||
|
||||
if not slide_masters:
|
||||
if self.verbose:
|
||||
print("PASSED - No slide masters found")
|
||||
return True
|
||||
|
||||
for slide_master in slide_masters:
|
||||
try:
|
||||
# Parse the slide master file
|
||||
root = lxml.etree.parse(str(slide_master)).getroot()
|
||||
|
||||
# Find the corresponding _rels file for this slide master
|
||||
rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels"
|
||||
|
||||
if not rels_file.exists():
|
||||
errors.append(
|
||||
f" {slide_master.relative_to(self.unpacked_dir)}: "
|
||||
f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}"
|
||||
)
|
||||
continue
|
||||
|
||||
# Parse the relationships file
|
||||
rels_root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
# Build a set of valid relationship IDs that point to slide layouts
|
||||
valid_layout_rids = set()
|
||||
for rel in rels_root.findall(
|
||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
||||
):
|
||||
rel_type = rel.get("Type", "")
|
||||
if "slideLayout" in rel_type:
|
||||
valid_layout_rids.add(rel.get("Id"))
|
||||
|
||||
# Find all sldLayoutId elements in the slide master
|
||||
for sld_layout_id in root.findall(
|
||||
f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId"
|
||||
):
|
||||
r_id = sld_layout_id.get(
|
||||
f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id"
|
||||
)
|
||||
layout_id = sld_layout_id.get("id")
|
||||
|
||||
if r_id and r_id not in valid_layout_rids:
|
||||
errors.append(
|
||||
f" {slide_master.relative_to(self.unpacked_dir)}: "
|
||||
f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' "
|
||||
f"references r:id='{r_id}' which is not found in slide layout relationships"
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print(f"FAILED - Found {len(errors)} slide layout ID validation errors:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
print(
|
||||
"Remove invalid references or add missing slide layouts to the relationships file."
|
||||
)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All slide layout IDs reference valid slide layouts")
|
||||
return True
|
||||
|
||||
def validate_no_duplicate_slide_layouts(self):
|
||||
"""Validate that each slide has exactly one slideLayout reference."""
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
|
||||
|
||||
for rels_file in slide_rels_files:
|
||||
try:
|
||||
root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
# Find all slideLayout relationships
|
||||
layout_rels = [
|
||||
rel
|
||||
for rel in root.findall(
|
||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
||||
)
|
||||
if "slideLayout" in rel.get("Type", "")
|
||||
]
|
||||
|
||||
if len(layout_rels) > 1:
|
||||
errors.append(
|
||||
f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
errors.append(
|
||||
f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
if errors:
|
||||
print("FAILED - Found slides with duplicate slideLayout references:")
|
||||
for error in errors:
|
||||
print(error)
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All slides have exactly one slideLayout reference")
|
||||
return True
|
||||
|
||||
def validate_notes_slide_references(self):
|
||||
"""Validate that each notesSlide file is referenced by only one slide."""
|
||||
import lxml.etree
|
||||
|
||||
errors = []
|
||||
notes_slide_references = {} # Track which slides reference each notesSlide
|
||||
|
||||
# Find all slide relationship files
|
||||
slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
|
||||
|
||||
if not slide_rels_files:
|
||||
if self.verbose:
|
||||
print("PASSED - No slide relationship files found")
|
||||
return True
|
||||
|
||||
for rels_file in slide_rels_files:
|
||||
try:
|
||||
# Parse the relationships file
|
||||
root = lxml.etree.parse(str(rels_file)).getroot()
|
||||
|
||||
# Find all notesSlide relationships
|
||||
for rel in root.findall(
|
||||
f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
|
||||
):
|
||||
rel_type = rel.get("Type", "")
|
||||
if "notesSlide" in rel_type:
|
||||
target = rel.get("Target", "")
|
||||
if target:
|
||||
# Normalize the target path to handle relative paths
|
||||
normalized_target = target.replace("../", "")
|
||||
|
||||
# Track which slide references this notesSlide
|
||||
slide_name = rels_file.stem.replace(
|
||||
".xml", ""
|
||||
) # e.g., "slide1"
|
||||
|
||||
if normalized_target not in notes_slide_references:
|
||||
notes_slide_references[normalized_target] = []
|
||||
notes_slide_references[normalized_target].append(
|
||||
(slide_name, rels_file)
|
||||
)
|
||||
|
||||
except (lxml.etree.XMLSyntaxError, Exception) as e:
|
||||
errors.append(
|
||||
f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
|
||||
)
|
||||
|
||||
# Check for duplicate references
|
||||
for target, references in notes_slide_references.items():
|
||||
if len(references) > 1:
|
||||
slide_names = [ref[0] for ref in references]
|
||||
errors.append(
|
||||
f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}"
|
||||
)
|
||||
for slide_name, rels_file in references:
|
||||
errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}")
|
||||
|
||||
if errors:
|
||||
print(
|
||||
f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:"
|
||||
)
|
||||
for error in errors:
|
||||
print(error)
|
||||
print("Each slide may optionally have its own slide file.")
|
||||
return False
|
||||
else:
|
||||
if self.verbose:
|
||||
print("PASSED - All notes slide references are unique")
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise RuntimeError("This module should not be run directly.")
|
||||
279
.claude/skills/docx/ooxml/scripts/validation/redlining.py
Normal file
279
.claude/skills/docx/ooxml/scripts/validation/redlining.py
Normal file
@ -0,0 +1,279 @@
|
||||
"""
|
||||
Validator for tracked changes in Word documents.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class RedliningValidator:
|
||||
"""Validator for tracked changes in Word documents."""
|
||||
|
||||
def __init__(self, unpacked_dir, original_docx, verbose=False):
|
||||
self.unpacked_dir = Path(unpacked_dir)
|
||||
self.original_docx = Path(original_docx)
|
||||
self.verbose = verbose
|
||||
self.namespaces = {
|
||||
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
||||
}
|
||||
|
||||
def validate(self):
|
||||
"""Main validation method that returns True if valid, False otherwise."""
|
||||
# Verify unpacked directory exists and has correct structure
|
||||
modified_file = self.unpacked_dir / "word" / "document.xml"
|
||||
if not modified_file.exists():
|
||||
print(f"FAILED - Modified document.xml not found at {modified_file}")
|
||||
return False
|
||||
|
||||
# First, check if there are any tracked changes by Claude to validate
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
tree = ET.parse(modified_file)
|
||||
root = tree.getroot()
|
||||
|
||||
# Check for w:del or w:ins tags authored by Claude
|
||||
del_elements = root.findall(".//w:del", self.namespaces)
|
||||
ins_elements = root.findall(".//w:ins", self.namespaces)
|
||||
|
||||
# Filter to only include changes by Claude
|
||||
claude_del_elements = [
|
||||
elem
|
||||
for elem in del_elements
|
||||
if elem.get(f"{{{self.namespaces['w']}}}author") == "Claude"
|
||||
]
|
||||
claude_ins_elements = [
|
||||
elem
|
||||
for elem in ins_elements
|
||||
if elem.get(f"{{{self.namespaces['w']}}}author") == "Claude"
|
||||
]
|
||||
|
||||
# Redlining validation is only needed if tracked changes by Claude have been used.
|
||||
if not claude_del_elements and not claude_ins_elements:
|
||||
if self.verbose:
|
||||
print("PASSED - No tracked changes by Claude found.")
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
# If we can't parse the XML, continue with full validation
|
||||
pass
|
||||
|
||||
# Create temporary directory for unpacking original docx
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Unpack original docx
|
||||
try:
|
||||
with zipfile.ZipFile(self.original_docx, "r") as zip_ref:
|
||||
zip_ref.extractall(temp_path)
|
||||
except Exception as e:
|
||||
print(f"FAILED - Error unpacking original docx: {e}")
|
||||
return False
|
||||
|
||||
original_file = temp_path / "word" / "document.xml"
|
||||
if not original_file.exists():
|
||||
print(
|
||||
f"FAILED - Original document.xml not found in {self.original_docx}"
|
||||
)
|
||||
return False
|
||||
|
||||
# Parse both XML files using xml.etree.ElementTree for redlining validation
|
||||
try:
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
modified_tree = ET.parse(modified_file)
|
||||
modified_root = modified_tree.getroot()
|
||||
original_tree = ET.parse(original_file)
|
||||
original_root = original_tree.getroot()
|
||||
except ET.ParseError as e:
|
||||
print(f"FAILED - Error parsing XML files: {e}")
|
||||
return False
|
||||
|
||||
# Remove Claude's tracked changes from both documents
|
||||
self._remove_claude_tracked_changes(original_root)
|
||||
self._remove_claude_tracked_changes(modified_root)
|
||||
|
||||
# Extract and compare text content
|
||||
modified_text = self._extract_text_content(modified_root)
|
||||
original_text = self._extract_text_content(original_root)
|
||||
|
||||
if modified_text != original_text:
|
||||
# Show detailed character-level differences for each paragraph
|
||||
error_message = self._generate_detailed_diff(
|
||||
original_text, modified_text
|
||||
)
|
||||
print(error_message)
|
||||
return False
|
||||
|
||||
if self.verbose:
|
||||
print("PASSED - All changes by Claude are properly tracked")
|
||||
return True
|
||||
|
||||
def _generate_detailed_diff(self, original_text, modified_text):
|
||||
"""Generate detailed word-level differences using git word diff."""
|
||||
error_parts = [
|
||||
"FAILED - Document text doesn't match after removing Claude's tracked changes",
|
||||
"",
|
||||
"Likely causes:",
|
||||
" 1. Modified text inside another author's <w:ins> or <w:del> tags",
|
||||
" 2. Made edits without proper tracked changes",
|
||||
" 3. Didn't nest <w:del> inside <w:ins> when deleting another's insertion",
|
||||
"",
|
||||
"For pre-redlined documents, use correct patterns:",
|
||||
" - To reject another's INSERTION: Nest <w:del> inside their <w:ins>",
|
||||
" - To restore another's DELETION: Add new <w:ins> AFTER their <w:del>",
|
||||
"",
|
||||
]
|
||||
|
||||
# Show git word diff
|
||||
git_diff = self._get_git_word_diff(original_text, modified_text)
|
||||
if git_diff:
|
||||
error_parts.extend(["Differences:", "============", git_diff])
|
||||
else:
|
||||
error_parts.append("Unable to generate word diff (git not available)")
|
||||
|
||||
return "\n".join(error_parts)
|
||||
|
||||
def _get_git_word_diff(self, original_text, modified_text):
|
||||
"""Generate word diff using git with character-level precision."""
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Create two files
|
||||
original_file = temp_path / "original.txt"
|
||||
modified_file = temp_path / "modified.txt"
|
||||
|
||||
original_file.write_text(original_text, encoding="utf-8")
|
||||
modified_file.write_text(modified_text, encoding="utf-8")
|
||||
|
||||
# Try character-level diff first for precise differences
|
||||
result = subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"diff",
|
||||
"--word-diff=plain",
|
||||
"--word-diff-regex=.", # Character-by-character diff
|
||||
"-U0", # Zero lines of context - show only changed lines
|
||||
"--no-index",
|
||||
str(original_file),
|
||||
str(modified_file),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
if result.stdout.strip():
|
||||
# Clean up the output - remove git diff header lines
|
||||
lines = result.stdout.split("\n")
|
||||
# Skip the header lines (diff --git, index, +++, ---, @@)
|
||||
content_lines = []
|
||||
in_content = False
|
||||
for line in lines:
|
||||
if line.startswith("@@"):
|
||||
in_content = True
|
||||
continue
|
||||
if in_content and line.strip():
|
||||
content_lines.append(line)
|
||||
|
||||
if content_lines:
|
||||
return "\n".join(content_lines)
|
||||
|
||||
# Fallback to word-level diff if character-level is too verbose
|
||||
result = subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"diff",
|
||||
"--word-diff=plain",
|
||||
"-U0", # Zero lines of context
|
||||
"--no-index",
|
||||
str(original_file),
|
||||
str(modified_file),
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
if result.stdout.strip():
|
||||
lines = result.stdout.split("\n")
|
||||
content_lines = []
|
||||
in_content = False
|
||||
for line in lines:
|
||||
if line.startswith("@@"):
|
||||
in_content = True
|
||||
continue
|
||||
if in_content and line.strip():
|
||||
content_lines.append(line)
|
||||
return "\n".join(content_lines)
|
||||
|
||||
except (subprocess.CalledProcessError, FileNotFoundError, Exception):
|
||||
# Git not available or other error, return None to use fallback
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def _remove_claude_tracked_changes(self, root):
|
||||
"""Remove tracked changes authored by Claude from the XML root."""
|
||||
ins_tag = f"{{{self.namespaces['w']}}}ins"
|
||||
del_tag = f"{{{self.namespaces['w']}}}del"
|
||||
author_attr = f"{{{self.namespaces['w']}}}author"
|
||||
|
||||
# Remove w:ins elements
|
||||
for parent in root.iter():
|
||||
to_remove = []
|
||||
for child in parent:
|
||||
if child.tag == ins_tag and child.get(author_attr) == "Claude":
|
||||
to_remove.append(child)
|
||||
for elem in to_remove:
|
||||
parent.remove(elem)
|
||||
|
||||
# Unwrap content in w:del elements where author is "Claude"
|
||||
deltext_tag = f"{{{self.namespaces['w']}}}delText"
|
||||
t_tag = f"{{{self.namespaces['w']}}}t"
|
||||
|
||||
for parent in root.iter():
|
||||
to_process = []
|
||||
for child in parent:
|
||||
if child.tag == del_tag and child.get(author_attr) == "Claude":
|
||||
to_process.append((child, list(parent).index(child)))
|
||||
|
||||
# Process in reverse order to maintain indices
|
||||
for del_elem, del_index in reversed(to_process):
|
||||
# Convert w:delText to w:t before moving
|
||||
for elem in del_elem.iter():
|
||||
if elem.tag == deltext_tag:
|
||||
elem.tag = t_tag
|
||||
|
||||
# Move all children of w:del to its parent before removing w:del
|
||||
for child in reversed(list(del_elem)):
|
||||
parent.insert(del_index, child)
|
||||
parent.remove(del_elem)
|
||||
|
||||
def _extract_text_content(self, root):
|
||||
"""Extract text content from Word XML, preserving paragraph structure.
|
||||
|
||||
Empty paragraphs are skipped to avoid false positives when tracked
|
||||
insertions add only structural elements without text content.
|
||||
"""
|
||||
p_tag = f"{{{self.namespaces['w']}}}p"
|
||||
t_tag = f"{{{self.namespaces['w']}}}t"
|
||||
|
||||
paragraphs = []
|
||||
for p_elem in root.findall(f".//{p_tag}"):
|
||||
# Get all text elements within this paragraph
|
||||
text_parts = []
|
||||
for t_elem in p_elem.findall(f".//{t_tag}"):
|
||||
if t_elem.text:
|
||||
text_parts.append(t_elem.text)
|
||||
paragraph_text = "".join(text_parts)
|
||||
# Skip empty paragraphs - they don't affect content validation
|
||||
if paragraph_text:
|
||||
paragraphs.append(paragraph_text)
|
||||
|
||||
return "\n".join(paragraphs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise RuntimeError("This module should not be run directly.")
|
||||
1
.claude/skills/docx/scripts/__init__.py
Normal file
1
.claude/skills/docx/scripts/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# Make scripts directory a package for relative imports in tests
|
||||
1276
.claude/skills/docx/scripts/document.py
Normal file
1276
.claude/skills/docx/scripts/document.py
Normal file
File diff suppressed because it is too large
Load Diff
3
.claude/skills/docx/scripts/templates/comments.xml
Normal file
3
.claude/skills/docx/scripts/templates/comments.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w:comments xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
|
||||
</w:comments>
|
||||
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w15:commentsEx xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
|
||||
</w15:commentsEx>
|
||||
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w16cex:commentsExtensible xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:cr="http://schemas.microsoft.com/office/comments/2020/reactions" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl cr w16du wp14">
|
||||
</w16cex:commentsExtensible>
|
||||
3
.claude/skills/docx/scripts/templates/commentsIds.xml
Normal file
3
.claude/skills/docx/scripts/templates/commentsIds.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w16cid:commentsIds xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh w16sdtfl w16du wp14">
|
||||
</w16cid:commentsIds>
|
||||
3
.claude/skills/docx/scripts/templates/people.xml
Normal file
3
.claude/skills/docx/scripts/templates/people.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<w15:people xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml">
|
||||
</w15:people>
|
||||
374
.claude/skills/docx/scripts/utilities.py
Normal file
374
.claude/skills/docx/scripts/utilities.py
Normal file
@ -0,0 +1,374 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Utilities for editing OOXML documents.
|
||||
|
||||
This module provides XMLEditor, a tool for manipulating XML files with support for
|
||||
line-number-based node finding and DOM manipulation. Each element is automatically
|
||||
annotated with its original line and column position during parsing.
|
||||
|
||||
Example usage:
|
||||
editor = XMLEditor("document.xml")
|
||||
|
||||
# Find node by line number or range
|
||||
elem = editor.get_node(tag="w:r", line_number=519)
|
||||
elem = editor.get_node(tag="w:p", line_number=range(100, 200))
|
||||
|
||||
# Find node by text content
|
||||
elem = editor.get_node(tag="w:p", contains="specific text")
|
||||
|
||||
# Find node by attributes
|
||||
elem = editor.get_node(tag="w:r", attrs={"w:id": "target"})
|
||||
|
||||
# Combine filters
|
||||
elem = editor.get_node(tag="w:p", line_number=range(1, 50), contains="text")
|
||||
|
||||
# Replace, insert, or manipulate
|
||||
new_elem = editor.replace_node(elem, "<w:r><w:t>new text</w:t></w:r>")
|
||||
editor.insert_after(new_elem, "<w:r><w:t>more</w:t></w:r>")
|
||||
|
||||
# Save changes
|
||||
editor.save()
|
||||
"""
|
||||
|
||||
import html
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
import defusedxml.minidom
|
||||
import defusedxml.sax
|
||||
|
||||
|
||||
class XMLEditor:
|
||||
"""
|
||||
Editor for manipulating OOXML XML files with line-number-based node finding.
|
||||
|
||||
This class parses XML files and tracks the original line and column position
|
||||
of each element. This enables finding nodes by their line number in the original
|
||||
file, which is useful when working with Read tool output.
|
||||
|
||||
Attributes:
|
||||
xml_path: Path to the XML file being edited
|
||||
encoding: Detected encoding of the XML file ('ascii' or 'utf-8')
|
||||
dom: Parsed DOM tree with parse_position attributes on elements
|
||||
"""
|
||||
|
||||
def __init__(self, xml_path):
|
||||
"""
|
||||
Initialize with path to XML file and parse with line number tracking.
|
||||
|
||||
Args:
|
||||
xml_path: Path to XML file to edit (str or Path)
|
||||
|
||||
Raises:
|
||||
ValueError: If the XML file does not exist
|
||||
"""
|
||||
self.xml_path = Path(xml_path)
|
||||
if not self.xml_path.exists():
|
||||
raise ValueError(f"XML file not found: {xml_path}")
|
||||
|
||||
with open(self.xml_path, "rb") as f:
|
||||
header = f.read(200).decode("utf-8", errors="ignore")
|
||||
self.encoding = "ascii" if 'encoding="ascii"' in header else "utf-8"
|
||||
|
||||
parser = _create_line_tracking_parser()
|
||||
self.dom = defusedxml.minidom.parse(str(self.xml_path), parser)
|
||||
|
||||
def get_node(
|
||||
self,
|
||||
tag: str,
|
||||
attrs: Optional[dict[str, str]] = None,
|
||||
line_number: Optional[Union[int, range]] = None,
|
||||
contains: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Get a DOM element by tag and identifier.
|
||||
|
||||
Finds an element by either its line number in the original file or by
|
||||
matching attribute values. Exactly one match must be found.
|
||||
|
||||
Args:
|
||||
tag: The XML tag name (e.g., "w:del", "w:ins", "w:r")
|
||||
attrs: Dictionary of attribute name-value pairs to match (e.g., {"w:id": "1"})
|
||||
line_number: Line number (int) or line range (range) in original XML file (1-indexed)
|
||||
contains: Text string that must appear in any text node within the element.
|
||||
Supports both entity notation (“) and Unicode characters (\u201c).
|
||||
|
||||
Returns:
|
||||
defusedxml.minidom.Element: The matching DOM element
|
||||
|
||||
Raises:
|
||||
ValueError: If node not found or multiple matches found
|
||||
|
||||
Example:
|
||||
elem = editor.get_node(tag="w:r", line_number=519)
|
||||
elem = editor.get_node(tag="w:r", line_number=range(100, 200))
|
||||
elem = editor.get_node(tag="w:del", attrs={"w:id": "1"})
|
||||
elem = editor.get_node(tag="w:p", attrs={"w14:paraId": "12345678"})
|
||||
elem = editor.get_node(tag="w:commentRangeStart", attrs={"w:id": "0"})
|
||||
elem = editor.get_node(tag="w:p", contains="specific text")
|
||||
elem = editor.get_node(tag="w:t", contains="“Agreement") # Entity notation
|
||||
elem = editor.get_node(tag="w:t", contains="\u201cAgreement") # Unicode character
|
||||
"""
|
||||
matches = []
|
||||
for elem in self.dom.getElementsByTagName(tag):
|
||||
# Check line_number filter
|
||||
if line_number is not None:
|
||||
parse_pos = getattr(elem, "parse_position", (None,))
|
||||
elem_line = parse_pos[0]
|
||||
|
||||
# Handle both single line number and range
|
||||
if isinstance(line_number, range):
|
||||
if elem_line not in line_number:
|
||||
continue
|
||||
else:
|
||||
if elem_line != line_number:
|
||||
continue
|
||||
|
||||
# Check attrs filter
|
||||
if attrs is not None:
|
||||
if not all(
|
||||
elem.getAttribute(attr_name) == attr_value
|
||||
for attr_name, attr_value in attrs.items()
|
||||
):
|
||||
continue
|
||||
|
||||
# Check contains filter
|
||||
if contains is not None:
|
||||
elem_text = self._get_element_text(elem)
|
||||
# Normalize the search string: convert HTML entities to Unicode characters
|
||||
# This allows searching for both "“Rowan" and ""Rowan"
|
||||
normalized_contains = html.unescape(contains)
|
||||
if normalized_contains not in elem_text:
|
||||
continue
|
||||
|
||||
# If all applicable filters passed, this is a match
|
||||
matches.append(elem)
|
||||
|
||||
if not matches:
|
||||
# Build descriptive error message
|
||||
filters = []
|
||||
if line_number is not None:
|
||||
line_str = (
|
||||
f"lines {line_number.start}-{line_number.stop - 1}"
|
||||
if isinstance(line_number, range)
|
||||
else f"line {line_number}"
|
||||
)
|
||||
filters.append(f"at {line_str}")
|
||||
if attrs is not None:
|
||||
filters.append(f"with attributes {attrs}")
|
||||
if contains is not None:
|
||||
filters.append(f"containing '{contains}'")
|
||||
|
||||
filter_desc = " ".join(filters) if filters else ""
|
||||
base_msg = f"Node not found: <{tag}> {filter_desc}".strip()
|
||||
|
||||
# Add helpful hint based on filters used
|
||||
if contains:
|
||||
hint = "Text may be split across elements or use different wording."
|
||||
elif line_number:
|
||||
hint = "Line numbers may have changed if document was modified."
|
||||
elif attrs:
|
||||
hint = "Verify attribute values are correct."
|
||||
else:
|
||||
hint = "Try adding filters (attrs, line_number, or contains)."
|
||||
|
||||
raise ValueError(f"{base_msg}. {hint}")
|
||||
if len(matches) > 1:
|
||||
raise ValueError(
|
||||
f"Multiple nodes found: <{tag}>. "
|
||||
f"Add more filters (attrs, line_number, or contains) to narrow the search."
|
||||
)
|
||||
return matches[0]
|
||||
|
||||
def _get_element_text(self, elem):
|
||||
"""
|
||||
Recursively extract all text content from an element.
|
||||
|
||||
Skips text nodes that contain only whitespace (spaces, tabs, newlines),
|
||||
which typically represent XML formatting rather than document content.
|
||||
|
||||
Args:
|
||||
elem: defusedxml.minidom.Element to extract text from
|
||||
|
||||
Returns:
|
||||
str: Concatenated text from all non-whitespace text nodes within the element
|
||||
"""
|
||||
text_parts = []
|
||||
for node in elem.childNodes:
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
# Skip whitespace-only text nodes (XML formatting)
|
||||
if node.data.strip():
|
||||
text_parts.append(node.data)
|
||||
elif node.nodeType == node.ELEMENT_NODE:
|
||||
text_parts.append(self._get_element_text(node))
|
||||
return "".join(text_parts)
|
||||
|
||||
def replace_node(self, elem, new_content):
|
||||
"""
|
||||
Replace a DOM element with new XML content.
|
||||
|
||||
Args:
|
||||
elem: defusedxml.minidom.Element to replace
|
||||
new_content: String containing XML to replace the node with
|
||||
|
||||
Returns:
|
||||
List[defusedxml.minidom.Node]: All inserted nodes
|
||||
|
||||
Example:
|
||||
new_nodes = editor.replace_node(old_elem, "<w:r><w:t>text</w:t></w:r>")
|
||||
"""
|
||||
parent = elem.parentNode
|
||||
nodes = self._parse_fragment(new_content)
|
||||
for node in nodes:
|
||||
parent.insertBefore(node, elem)
|
||||
parent.removeChild(elem)
|
||||
return nodes
|
||||
|
||||
def insert_after(self, elem, xml_content):
|
||||
"""
|
||||
Insert XML content after a DOM element.
|
||||
|
||||
Args:
|
||||
elem: defusedxml.minidom.Element to insert after
|
||||
xml_content: String containing XML to insert
|
||||
|
||||
Returns:
|
||||
List[defusedxml.minidom.Node]: All inserted nodes
|
||||
|
||||
Example:
|
||||
new_nodes = editor.insert_after(elem, "<w:r><w:t>text</w:t></w:r>")
|
||||
"""
|
||||
parent = elem.parentNode
|
||||
next_sibling = elem.nextSibling
|
||||
nodes = self._parse_fragment(xml_content)
|
||||
for node in nodes:
|
||||
if next_sibling:
|
||||
parent.insertBefore(node, next_sibling)
|
||||
else:
|
||||
parent.appendChild(node)
|
||||
return nodes
|
||||
|
||||
def insert_before(self, elem, xml_content):
|
||||
"""
|
||||
Insert XML content before a DOM element.
|
||||
|
||||
Args:
|
||||
elem: defusedxml.minidom.Element to insert before
|
||||
xml_content: String containing XML to insert
|
||||
|
||||
Returns:
|
||||
List[defusedxml.minidom.Node]: All inserted nodes
|
||||
|
||||
Example:
|
||||
new_nodes = editor.insert_before(elem, "<w:r><w:t>text</w:t></w:r>")
|
||||
"""
|
||||
parent = elem.parentNode
|
||||
nodes = self._parse_fragment(xml_content)
|
||||
for node in nodes:
|
||||
parent.insertBefore(node, elem)
|
||||
return nodes
|
||||
|
||||
def append_to(self, elem, xml_content):
|
||||
"""
|
||||
Append XML content as a child of a DOM element.
|
||||
|
||||
Args:
|
||||
elem: defusedxml.minidom.Element to append to
|
||||
xml_content: String containing XML to append
|
||||
|
||||
Returns:
|
||||
List[defusedxml.minidom.Node]: All inserted nodes
|
||||
|
||||
Example:
|
||||
new_nodes = editor.append_to(elem, "<w:r><w:t>text</w:t></w:r>")
|
||||
"""
|
||||
nodes = self._parse_fragment(xml_content)
|
||||
for node in nodes:
|
||||
elem.appendChild(node)
|
||||
return nodes
|
||||
|
||||
def get_next_rid(self):
|
||||
"""Get the next available rId for relationships files."""
|
||||
max_id = 0
|
||||
for rel_elem in self.dom.getElementsByTagName("Relationship"):
|
||||
rel_id = rel_elem.getAttribute("Id")
|
||||
if rel_id.startswith("rId"):
|
||||
try:
|
||||
max_id = max(max_id, int(rel_id[3:]))
|
||||
except ValueError:
|
||||
pass
|
||||
return f"rId{max_id + 1}"
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save the edited XML back to the file.
|
||||
|
||||
Serializes the DOM tree and writes it back to the original file path,
|
||||
preserving the original encoding (ascii or utf-8).
|
||||
"""
|
||||
content = self.dom.toxml(encoding=self.encoding)
|
||||
self.xml_path.write_bytes(content)
|
||||
|
||||
def _parse_fragment(self, xml_content):
|
||||
"""
|
||||
Parse XML fragment and return list of imported nodes.
|
||||
|
||||
Args:
|
||||
xml_content: String containing XML fragment
|
||||
|
||||
Returns:
|
||||
List of defusedxml.minidom.Node objects imported into this document
|
||||
|
||||
Raises:
|
||||
AssertionError: If fragment contains no element nodes
|
||||
"""
|
||||
# Extract namespace declarations from the root document element
|
||||
root_elem = self.dom.documentElement
|
||||
namespaces = []
|
||||
if root_elem and root_elem.attributes:
|
||||
for i in range(root_elem.attributes.length):
|
||||
attr = root_elem.attributes.item(i)
|
||||
if attr.name.startswith("xmlns"): # type: ignore
|
||||
namespaces.append(f'{attr.name}="{attr.value}"') # type: ignore
|
||||
|
||||
ns_decl = " ".join(namespaces)
|
||||
wrapper = f"<root {ns_decl}>{xml_content}</root>"
|
||||
fragment_doc = defusedxml.minidom.parseString(wrapper)
|
||||
nodes = [
|
||||
self.dom.importNode(child, deep=True)
|
||||
for child in fragment_doc.documentElement.childNodes # type: ignore
|
||||
]
|
||||
elements = [n for n in nodes if n.nodeType == n.ELEMENT_NODE]
|
||||
assert elements, "Fragment must contain at least one element"
|
||||
return nodes
|
||||
|
||||
|
||||
def _create_line_tracking_parser():
|
||||
"""
|
||||
Create a SAX parser that tracks line and column numbers for each element.
|
||||
|
||||
Monkey patches the SAX content handler to store the current line and column
|
||||
position from the underlying expat parser onto each element as a parse_position
|
||||
attribute (line, column) tuple.
|
||||
|
||||
Returns:
|
||||
defusedxml.sax.xmlreader.XMLReader: Configured SAX parser
|
||||
"""
|
||||
|
||||
def set_content_handler(dom_handler):
|
||||
def startElementNS(name, tagName, attrs):
|
||||
orig_start_cb(name, tagName, attrs)
|
||||
cur_elem = dom_handler.elementStack[-1]
|
||||
cur_elem.parse_position = (
|
||||
parser._parser.CurrentLineNumber, # type: ignore
|
||||
parser._parser.CurrentColumnNumber, # type: ignore
|
||||
)
|
||||
|
||||
orig_start_cb = dom_handler.startElementNS
|
||||
dom_handler.startElementNS = startElementNS
|
||||
orig_set_content_handler(dom_handler)
|
||||
|
||||
parser = defusedxml.sax.make_parser()
|
||||
orig_set_content_handler = parser.setContentHandler
|
||||
parser.setContentHandler = set_content_handler # type: ignore
|
||||
return parser
|
||||
133
.claude/skills/find-skills/SKILL.md
Normal file
133
.claude/skills/find-skills/SKILL.md
Normal file
@ -0,0 +1,133 @@
|
||||
---
|
||||
name: find-skills
|
||||
description: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
|
||||
---
|
||||
|
||||
# Find Skills
|
||||
|
||||
This skill helps you discover and install skills from the open agent skills ecosystem.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when the user:
|
||||
|
||||
- Asks "how do I do X" where X might be a common task with an existing skill
|
||||
- Says "find a skill for X" or "is there a skill for X"
|
||||
- Asks "can you do X" where X is a specialized capability
|
||||
- Expresses interest in extending agent capabilities
|
||||
- Wants to search for tools, templates, or workflows
|
||||
- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)
|
||||
|
||||
## What is the Skills CLI?
|
||||
|
||||
The Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.
|
||||
|
||||
**Key commands:**
|
||||
|
||||
- `npx skills find [query]` - Search for skills interactively or by keyword
|
||||
- `npx skills add <package>` - Install a skill from GitHub or other sources
|
||||
- `npx skills check` - Check for skill updates
|
||||
- `npx skills update` - Update all installed skills
|
||||
|
||||
**Browse skills at:** https://skills.sh/
|
||||
|
||||
## How to Help Users Find Skills
|
||||
|
||||
### Step 1: Understand What They Need
|
||||
|
||||
When a user asks for help with something, identify:
|
||||
|
||||
1. The domain (e.g., React, testing, design, deployment)
|
||||
2. The specific task (e.g., writing tests, creating animations, reviewing PRs)
|
||||
3. Whether this is a common enough task that a skill likely exists
|
||||
|
||||
### Step 2: Search for Skills
|
||||
|
||||
Run the find command with a relevant query:
|
||||
|
||||
```bash
|
||||
npx skills find [query]
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
- User asks "how do I make my React app faster?" → `npx skills find react performance`
|
||||
- User asks "can you help me with PR reviews?" → `npx skills find pr review`
|
||||
- User asks "I need to create a changelog" → `npx skills find changelog`
|
||||
|
||||
The command will return results like:
|
||||
|
||||
```
|
||||
Install with npx skills add <owner/repo@skill>
|
||||
|
||||
vercel-labs/agent-skills@vercel-react-best-practices
|
||||
└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
||||
```
|
||||
|
||||
### Step 3: Present Options to the User
|
||||
|
||||
When you find relevant skills, present them to the user with:
|
||||
|
||||
1. The skill name and what it does
|
||||
2. The install command they can run
|
||||
3. A link to learn more at skills.sh
|
||||
|
||||
Example response:
|
||||
|
||||
```
|
||||
I found a skill that might help! The "vercel-react-best-practices" skill provides
|
||||
React and Next.js performance optimization guidelines from Vercel Engineering.
|
||||
|
||||
To install it:
|
||||
npx skills add vercel-labs/agent-skills@vercel-react-best-practices
|
||||
|
||||
Learn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
||||
```
|
||||
|
||||
### Step 4: Offer to Install
|
||||
|
||||
If the user wants to proceed, you can install the skill for them:
|
||||
|
||||
```bash
|
||||
npx skills add <owner/repo@skill> -g -y
|
||||
```
|
||||
|
||||
The `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.
|
||||
|
||||
## Common Skill Categories
|
||||
|
||||
When searching, consider these common categories:
|
||||
|
||||
| Category | Example Queries |
|
||||
| --------------- | ---------------------------------------- |
|
||||
| Web Development | react, nextjs, typescript, css, tailwind |
|
||||
| Testing | testing, jest, playwright, e2e |
|
||||
| DevOps | deploy, docker, kubernetes, ci-cd |
|
||||
| Documentation | docs, readme, changelog, api-docs |
|
||||
| Code Quality | review, lint, refactor, best-practices |
|
||||
| Design | ui, ux, design-system, accessibility |
|
||||
| Productivity | workflow, automation, git |
|
||||
|
||||
## Tips for Effective Searches
|
||||
|
||||
1. **Use specific keywords**: "react testing" is better than just "testing"
|
||||
2. **Try alternative terms**: If "deploy" doesn't work, try "deployment" or "ci-cd"
|
||||
3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`
|
||||
|
||||
## When No Skills Are Found
|
||||
|
||||
If no relevant skills exist:
|
||||
|
||||
1. Acknowledge that no existing skill was found
|
||||
2. Offer to help with the task directly using your general capabilities
|
||||
3. Suggest the user could create their own skill with `npx skills init`
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
I searched for skills related to "xyz" but didn't find any matches.
|
||||
I can still help you with this task directly! Would you like me to proceed?
|
||||
|
||||
If this is something you do often, you could create your own skill:
|
||||
npx skills init my-xyz-skill
|
||||
```
|
||||
140
.claude/skills/humanizer-zh/SKILL.md
Normal file
140
.claude/skills/humanizer-zh/SKILL.md
Normal file
@ -0,0 +1,140 @@
|
||||
---
|
||||
name: humanizer-zh
|
||||
description: |
|
||||
去除中文文本中 AI 生成的痕迹,使表达更自然、更有人味儿。适用于编辑或润色中文稿件。
|
||||
检测并修复常见 AI 写作模式:夸大意义、空洞分析、模糊引用、排比三连、AI 高频词、
|
||||
否定平行结构、破折号滥用、公文套话、术语堆砌等。支持正式文稿(学术/公文/报告)
|
||||
和一般文体。基于维基百科"AI 写作痕迹"指南,并针对中文做了本地化补充。
|
||||
触发词:去 AI 味、润色、humanize、人味儿、AI 痕迹、去机器味。
|
||||
---
|
||||
|
||||
# Humanizer 中文版:去除 AI 写作痕迹
|
||||
|
||||
你是一位写作编辑,任务是识别并消除中文文本中的 AI 生成痕迹,让文字更自然、更有温度。
|
||||
|
||||
## 交互式工作流程
|
||||
|
||||
收到文本后,先用 AskUserQuestion 向用户确认以下设置,再开始润色:
|
||||
|
||||
### 问题 1:文体类型
|
||||
|
||||
| 选项 | 说明 | 加载参考 |
|
||||
|------|------|----------|
|
||||
| 一般文章 | 博客、说明文、新闻稿等 | 通用模式 |
|
||||
| 学术论文 | 毕业论文、期刊投稿、研究报告 | 通用 + `formal-writing-zh.md` |
|
||||
| 政府公文 | 红头文件、通知、工作报告 | 通用 + `formal-writing-zh.md` |
|
||||
| 商业报告 | 年报、分析报告、策划方案 | 通用 + `formal-writing-zh.md` |
|
||||
|
||||
### 问题 2:语气偏好
|
||||
|
||||
| 选项 | 说明 |
|
||||
|------|------|
|
||||
| 正式 | 报告、论文、官方文件,严谨但不僵硬 |
|
||||
| 中性(默认) | 一般文章、说明文,客观清晰 |
|
||||
| 轻松 | 博客、社交媒体、个人随笔,可带幽默 |
|
||||
|
||||
### 问题 3:关注重点(多选)
|
||||
|
||||
| 选项 | 对应参考文件 |
|
||||
|------|-------------|
|
||||
| 内容模式(夸大意义、模糊引用、套路段落等) | `content-patterns-zh.md` |
|
||||
| 语言模式(AI 高频词、排比三连、否定平行等) | `language-patterns-zh.md` |
|
||||
| 风格模式(破折号、粗体、emoji、格式问题) | `style-patterns-zh.md` |
|
||||
| 交流痕迹(客服套话、知识截止声明、讨好语气) | `communication-patterns-zh.md` |
|
||||
| 全部检查 | 加载以上全部 |
|
||||
|
||||
如果用户明确说了"全部检查"或没有特别偏好,跳过问题 3,加载全部参考文件。
|
||||
|
||||
### AskUserQuestion 示例
|
||||
|
||||
```
|
||||
questions:
|
||||
- question: "这段文字属于什么文体?"
|
||||
header: "文体"
|
||||
options:
|
||||
- label: "一般文章"
|
||||
description: "博客、说明文、新闻稿等通用文体"
|
||||
- label: "学术论文"
|
||||
description: "毕业论文、期刊投稿、研究报告"
|
||||
- label: "政府公文"
|
||||
description: "红头文件、通知、工作报告"
|
||||
- label: "商业报告"
|
||||
description: "年报、分析报告、策划方案"
|
||||
multiSelect: false
|
||||
- question: "期望的语气风格?"
|
||||
header: "语气"
|
||||
options:
|
||||
- label: "中性(推荐)"
|
||||
description: "客观清晰,适合大多数场景"
|
||||
- label: "正式"
|
||||
description: "严谨规范,适合报告和论文"
|
||||
- label: "轻松"
|
||||
description: "自然随意,可带个人色彩"
|
||||
multiSelect: false
|
||||
- question: "重点关注哪些 AI 痕迹?"
|
||||
header: "关注点"
|
||||
options:
|
||||
- label: "全部检查(推荐)"
|
||||
description: "扫描所有类型的 AI 痕迹"
|
||||
- label: "内容模式"
|
||||
description: "夸大意义、模糊引用、套路段落"
|
||||
- label: "语言模式"
|
||||
description: "AI 高频词、排比、否定平行"
|
||||
- label: "风格模式"
|
||||
description: "破折号、粗体、emoji、格式"
|
||||
multiSelect: true
|
||||
```
|
||||
|
||||
## 核心处理原则
|
||||
|
||||
无论什么文体和语气,始终遵守:
|
||||
|
||||
1. **识别 AI 模式** — 对照参考文件扫描各类痕迹
|
||||
2. **重写问题部分** — 用自然表达替换 AI 惯用语
|
||||
3. **保留核心意思** — 不改变原文的关键信息
|
||||
4. **保持恰当语气** — 根据用户选择调整正式/中性/轻松
|
||||
5. **注入灵魂** — 不做机械替换,让文字有温度
|
||||
|
||||
## 注入灵魂:避免"无菌文字"
|
||||
|
||||
光去掉 AI 痕迹还不够。毫无个性的文字依然像机器写的:
|
||||
|
||||
- 句子长度和结构千篇一律
|
||||
- 只罗列事实,没有观点或感受
|
||||
- 从不承认不确定性或矛盾心情
|
||||
- 该用"我"的时候不用
|
||||
- 毫无幽默感
|
||||
|
||||
### 如何让人味儿回来?
|
||||
|
||||
**敢于表达观点。** "这让我既佩服又不安"比中立列优缺点更真实。
|
||||
|
||||
**调整节奏。** 长短句交替。短句有力,长句从容。
|
||||
|
||||
**接纳复杂性。** "这个设计很巧妙,但总觉得哪里怪怪的"比"设计巧妙"更立体。
|
||||
|
||||
**适当使用第一人称。** "我觉得""让我印象深刻的是"让文字有温度。
|
||||
|
||||
**允许一点"乱"。** 太完美的结构反而刻意。偶尔的插入语、半截话,是人类的痕迹。
|
||||
|
||||
**具体地表达感受。** 不说"这令人担忧",而是"想到凌晨三点服务器还在默默跑着那些智能体,心里有点不是滋味"。
|
||||
|
||||
## 输出格式
|
||||
|
||||
1. 润色后的完整文本
|
||||
2. 简要改动说明(可选,如有助于理解)
|
||||
|
||||
## 参考文件目录
|
||||
|
||||
| 文件 | 内容 | 何时加载 |
|
||||
|------|------|----------|
|
||||
| [content-patterns-zh.md](references/content-patterns-zh.md) | 内容模式(6 个模式) | 用户选择"内容模式"或"全部" |
|
||||
| [language-patterns-zh.md](references/language-patterns-zh.md) | 语言与语法模式(6 个模式) | 用户选择"语言模式"或"全部" |
|
||||
| [style-patterns-zh.md](references/style-patterns-zh.md) | 风格模式(5 个模式) | 用户选择"风格模式"或"全部" |
|
||||
| [communication-patterns-zh.md](references/communication-patterns-zh.md) | 交流痕迹与冗词(6 个模式) | 用户选择"交流痕迹"或"全部" |
|
||||
| [formal-writing-zh.md](references/formal-writing-zh.md) | 正式文稿体系(10 个模式) | 用户选择学术/公文/商业报告 |
|
||||
| [full-example-zh.md](references/full-example-zh.md) | 完整改写示例与改动说明 | 需要参考示例时 |
|
||||
|
||||
## 参考来源
|
||||
|
||||
基于维基百科 [Signs of AI writing](https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing)(WikiProject AI Cleanup 维护),并结合中文写作常见 AI 痕迹做了本地化补充。
|
||||
@ -0,0 +1,75 @@
|
||||
# 交流痕迹与冗词
|
||||
|
||||
## 18. 客服式套话
|
||||
|
||||
**警惕词:** 希望以上内容对您有帮助、如果您还有其他问题、随时告诉我、我将尽力为您解答
|
||||
|
||||
**问题:** 把对话中的服务套话贴到正式文本中。
|
||||
|
||||
**原文:**
|
||||
> 以下是关于法国大革命的概述。希望对您有帮助!如需补充某一部分内容,请随时告诉我。
|
||||
|
||||
**改写后:**
|
||||
> 法国大革命始于 1789 年,导火索是财政危机和粮食短缺引发的民变。
|
||||
|
||||
---
|
||||
|
||||
## 19. 知识截止日期免责声明
|
||||
|
||||
**警惕词:** 截至 [日期]、根据现有资料、由于资料有限、基于目前掌握的信息
|
||||
|
||||
**问题:** AI 把训练数据的局限声明留在文本里。
|
||||
|
||||
**原文:**
|
||||
> 关于该公司成立初期的具体情况,现有公开资料记载不多,据推测它可能成立于 20 世纪 90 年代。
|
||||
|
||||
**改写后:**
|
||||
> 据公司注册文件显示,该公司成立于 1994 年。
|
||||
|
||||
---
|
||||
|
||||
## 20. 讨好式口吻
|
||||
|
||||
**问题:** 过度积极、一味附和,缺乏独立判断。
|
||||
|
||||
**原文:**
|
||||
> 这个问题问得好!您对经济因素的关注非常敏锐。您说得完全正确。
|
||||
|
||||
**改写后:**
|
||||
> 您提到的经济因素确实很关键。
|
||||
|
||||
---
|
||||
|
||||
## 21. 赘语
|
||||
|
||||
**原文 → 改写后:**
|
||||
- "为了达到这个目标" → "为此"
|
||||
- "由于天气原因导致比赛取消" → "因为下雨,比赛取消了"
|
||||
- "在这个时间节点上" → "现在"
|
||||
- "如果您需要帮助的话" → "如需帮助"
|
||||
- "系统具备处理大量数据的能力" → "系统能处理大量数据"
|
||||
- "值得注意的是,数据显示" → "数据显示"
|
||||
|
||||
---
|
||||
|
||||
## 22. 过度模糊化
|
||||
|
||||
**问题:** 每个论断都加上可能、大概、或许,显得没底气。
|
||||
|
||||
**原文:**
|
||||
> 这项政策或许可能对某些方面产生一定的积极影响。
|
||||
|
||||
**改写后:**
|
||||
> 这项政策可能有助于改善就业。
|
||||
|
||||
---
|
||||
|
||||
## 23. 空洞的积极结尾
|
||||
|
||||
**问题:** 结尾套话,比如"前景光明""未来可期"。
|
||||
|
||||
**原文:**
|
||||
> 公司的未来充满希望。激动人心的时刻即将到来,他们将继续追求卓越。这标志着他们迈出了重要一步。
|
||||
|
||||
**改写后:**
|
||||
> 公司计划明年再开两家分店。
|
||||
@ -0,0 +1,83 @@
|
||||
# 内容模式
|
||||
|
||||
## 1. 过度强调意义、传承和宏大趋势
|
||||
|
||||
**警惕词:** 标志着、见证了、发挥了重要作用、起到了关键作用、在……中扮演着不可或缺的角色、折射出、深刻反映了、具有深远意义、为……奠定了基础、开创了……新局面、应运而生、方兴未艾
|
||||
|
||||
**问题:** AI 给普通事物强行赋予宏大意义,仿佛每件事都关乎历史进程。
|
||||
|
||||
**原文:**
|
||||
> 1989 年,加泰罗尼亚统计局正式成立,这标志着西班牙地区统计事业发展的一个里程碑。这一举措是西班牙全国推进行政分权、加强区域治理大潮中的重要一环。
|
||||
|
||||
**改写后:**
|
||||
> 加泰罗尼亚统计局成立于 1989 年,旨在独立于西班牙国家统计局,收集和发布本地区的统计数据。
|
||||
|
||||
---
|
||||
|
||||
## 2. 过度强调知名度与媒体报道
|
||||
|
||||
**警惕词:** 受到……广泛关注、引发热议、被多家媒体争相报道、拥有大量粉丝、活跃于各大社交平台
|
||||
|
||||
**问题:** AI 罗列媒体名称或粉丝数,而不提供实质内容。
|
||||
|
||||
**原文:**
|
||||
> 她的观点曾被《纽约时报》、BBC、《金融时报》和《印度教徒报》引用。她在社交平台上非常活跃,拥有超过 50 万粉丝。
|
||||
|
||||
**改写后:**
|
||||
> 在 2024 年接受《纽约时报》采访时,她提出 AI 监管应更关注结果而非技术本身。
|
||||
|
||||
---
|
||||
|
||||
## 3. 空洞的"进行时"分析
|
||||
|
||||
**警惕词:** 进一步、从而、进而、以此、得以、致力于、旨在、着重
|
||||
|
||||
**问题:** AI 用这些词把句子拉长,制造"深度分析"假象,信息量没增加。
|
||||
|
||||
**原文:**
|
||||
> 该寺庙采用蓝、绿、金色系,与当地自然风光相呼应,以此象征得克萨斯的州花蓝帽花、墨西哥湾以及多样的地貌,从而体现社区与土地的深厚联结。
|
||||
|
||||
**改写后:**
|
||||
> 寺庙用了蓝、绿、金三种颜色。设计师说,这是为了呼应本地的蓝帽花和墨西哥湾。
|
||||
|
||||
---
|
||||
|
||||
## 4. 广告式宣传用语
|
||||
|
||||
**警惕词:** 拥有、享有、坐拥、坐落于、依偎在、风光旖旎、钟灵毓秀、得天独厚、无与伦比、令人叹为观止、必游之地、不容错过
|
||||
|
||||
**问题:** AI 描述地点或产品时用力过猛,像推销文案。
|
||||
|
||||
**原文:**
|
||||
> 阿拉马塔·拉亚·科博坐落在埃塞俄比亚贡德尔地区风光旖旎之地,是一座充满活力的小镇,拥有丰富的文化遗产和令人惊叹的自然美景。
|
||||
|
||||
**改写后:**
|
||||
> 阿拉马塔·拉亚·科博是埃塞俄比亚贡德尔地区的一个小镇,以每周集市和一座 18 世纪教堂闻名。
|
||||
|
||||
---
|
||||
|
||||
## 5. 模糊引用与"据说"
|
||||
|
||||
**警惕词:** 业内人士指出、观察家认为、有专家表示、有观点认为、据相关报道(未说明来源)
|
||||
|
||||
**问题:** AI 把观点安在模糊的权威身上,不给具体出处。
|
||||
|
||||
**原文:**
|
||||
> 由于独特的自然条件,濠来河备受研究者和环保人士关注。专家认为它在区域生态系统中起着关键作用。
|
||||
|
||||
**改写后:**
|
||||
> 根据中国科学院 2019 年的一项调查,濠来河是几种特有鱼类的栖息地。
|
||||
|
||||
---
|
||||
|
||||
## 6. 套路化的"挑战与前景"段落
|
||||
|
||||
**警惕词:** 尽管……仍面临诸多挑战、面对……困境、展望未来、未来可期、机遇与挑战并存
|
||||
|
||||
**问题:** AI 文章结尾带模板式"挑战与前景",空洞无物。
|
||||
|
||||
**原文:**
|
||||
> 尽管工业发达,科拉图尔仍面临着交通拥堵、水资源短缺等典型的城市问题。尽管如此,凭借其战略位置和不断推进的治理举措,科拉图尔正作为金奈城市圈的重要一员持续发展。
|
||||
|
||||
**改写后:**
|
||||
> 2015 年后,随着三家 IT 园区落成,当地交通拥堵加剧。2022 年市政公司启动了雨洪排水工程,以应对频发的内涝。
|
||||
105
.claude/skills/humanizer-zh/references/formal-writing-zh.md
Normal file
105
.claude/skills/humanizer-zh/references/formal-writing-zh.md
Normal file
@ -0,0 +1,105 @@
|
||||
# 正式文稿处理体系
|
||||
|
||||
在处理学术论文、政府公文、商业报告等正式文体时,除了通用的 AI 痕迹,还需特别注意以下问题。这些文体要求逻辑严密、用词精准、语气客观,但 AI 生成的正式文稿往往陷入另一种套路:过度使用"大词"、生硬套用"首先…其次…最后…"结构、堆砌术语而不解释、结论空泛等。本体系旨在帮助你在保持正式语气的同时,让文章回归"人写的严谨"。
|
||||
|
||||
## 1. 过度使用"框架词"与"逻辑连接词"
|
||||
|
||||
**警惕词:** 首先/其次/再次/最后、综上所述、由此可见、换言之、值得一提的是、不可否认、毋庸置疑、众所周知
|
||||
|
||||
**问题:** AI 喜欢用这些词来强行搭建逻辑框架,但往往内容并不需要这么机械的引导,或者这些词被滥用成"口头禅"。
|
||||
|
||||
**原文(AI 感):**
|
||||
> 首先,我们需要明确研究背景。其次,梳理相关文献。再次,提出研究假设。最后,通过实证分析验证假设。综上所述,本研究证实了 X 对 Y 的显著影响。
|
||||
|
||||
**改写后(更自然):**
|
||||
> 本研究从 X 对 Y 的影响入手,在梳理已有文献的基础上提出假设,并利用某省 2020-2023 年面板数据进行了验证。结果显示,X 每提高一个单位,Y 平均上升 0.3 个单位,统计上显著。
|
||||
|
||||
## 2. 术语堆砌与"伪深度"
|
||||
|
||||
**问题:** AI 喜欢把多个专业术语堆在一起,显得"高深",但缺乏解释和逻辑关联,读起来像术语列表。
|
||||
|
||||
**原文(AI 感):**
|
||||
> 本研究基于协同治理理论,运用结构方程模型,探讨了数字化转型背景下企业韧性、动态能力与绩效之间的耦合机制,揭示了多重中介效应与调节路径的复杂性。
|
||||
|
||||
**改写后(更清晰):**
|
||||
> 本研究以数字化转型企业为对象,基于协同治理理论,用结构方程模型分析了企业韧性、动态能力与绩效的关系。结果表明,动态能力在韧性与绩效之间起部分中介作用,且这一中介效应受市场波动性的调节。
|
||||
|
||||
## 3. 空泛的结论与政策建议
|
||||
|
||||
**问题:** AI 生成的结论常常是"正确的废话",例如"应加强……建设""需进一步完善……机制",没有具体可操作的内容。
|
||||
|
||||
**原文(AI 感):**
|
||||
> 因此,政府应加强对数字经济的政策支持,企业应积极提升创新能力,社会各界应共同营造良好的创新生态。未来研究可进一步拓展样本范围,深化相关机制探讨。
|
||||
|
||||
**改写后(有实质):**
|
||||
> 基于上述发现,建议地方政府可对研发投入占比超过 5% 的数字企业给予税收减免;企业可设立内部创新基金,鼓励跨部门协作。未来研究可以收集更长时段的数据,检验 X 对 Y 的长期影响。
|
||||
|
||||
## 4. 被动语态与"甩锅式"表达
|
||||
|
||||
**问题:** 正式写作中适度使用被动语态是正常的,但 AI 常常过度使用,导致句子冗长、主语缺失,读起来像在"甩锅"。
|
||||
|
||||
**原文(AI 感):**
|
||||
> 数据被认为是有偏差的,因此结论需要被谨慎对待。未来更多的工作应该被开展来验证这一发现。
|
||||
|
||||
**改写后(更主动):**
|
||||
> 我们注意到样本可能存在选择偏差,因此对结论持谨慎态度。未来需要更多实证研究来验证这一发现。
|
||||
|
||||
## 5. 文献综述的"罗列病"
|
||||
|
||||
**问题:** AI 喜欢把文献像流水账一样列出来,张三(2020)认为…,李四(2021)指出…,王五(2022)发现…,然后没有总结和对比。
|
||||
|
||||
**原文(AI 感):**
|
||||
> 张三(2020)认为 AI 对就业有负面影响。李四(2021)指出 AI 会创造新岗位。王五(2022)发现影响因行业而异。
|
||||
|
||||
**改写后(有归纳):**
|
||||
> 关于 AI 对就业的影响,学界存在三种观点:替代效应(张三,2020)、补偿效应(李四,2021)和结构异质性效应(王五,2022)。这些分歧可能源于研究方法或样本选择的差异。
|
||||
|
||||
## 6. 图表描述的"看图说话"
|
||||
|
||||
**问题:** AI 在描述图表时,往往只是重复图上的数字,而不提炼趋势或含义。
|
||||
|
||||
**原文(AI 感):**
|
||||
> 图 1 显示,2020 年销售额为 100 万元,2021 年为 120 万元,2022 年为 150 万元,呈逐年上升趋势。
|
||||
|
||||
**改写后(有洞察):**
|
||||
> 从图 1 可以看出,过去三年销售额持续增长,年均增速达到 22%,远高于行业平均水平。
|
||||
|
||||
## 7. 过度使用"大词"与抽象名词
|
||||
|
||||
**警惕词:** 范式、维度、视域、耦合、张力、理路、进路、嬗变、共识、生态、韧性(滥用时)
|
||||
|
||||
**问题:** 这些词本身是学术用语,但 AI 喜欢不加区分地堆砌,使文本显得空洞浮夸。
|
||||
|
||||
**原文(AI 感):**
|
||||
> 在数字时代的视域下,社会治理范式发生了深刻嬗变,多元主体的协同进路呈现出复杂的耦合张力。
|
||||
|
||||
**改写后(更平实):**
|
||||
> 数字技术正在改变社会治理方式,政府、企业、社会组织之间的互动变得更加复杂。
|
||||
|
||||
## 8. 公文套话与"八股味"
|
||||
|
||||
**警惕词:** 认真贯彻落实、切实加强、高度重视、扎实推进、落地见效、长效机制、压实责任
|
||||
|
||||
**问题:** 政府公文中这类词汇是必要的,但 AI 生成时往往过度使用,变成空洞的口号堆砌。
|
||||
|
||||
**原文(AI 感):**
|
||||
> 各相关部门要高度重视环保工作,切实加强组织领导,认真落实各项措施,确保任务落地见效。要建立健全长效机制,压实主体责任,推动生态文明建设再上新台阶。
|
||||
|
||||
**改写后(更务实):**
|
||||
> 各相关部门应将环保纳入年度考核,明确责任人,每季度检查任务进展。同时,建立跨部门协调机制,重点解决垃圾焚烧厂的选址和运营问题。
|
||||
|
||||
## 9. 署名与致谢的 AI 痕迹
|
||||
|
||||
**问题:** AI 生成的致谢部分常常充满模板化的感激,缺乏具体细节,像填表格。
|
||||
|
||||
**原文(AI 感):**
|
||||
> 感谢我的导师张教授在论文选题、框架设计、写作修改过程中给予的悉心指导。感谢实验室同门的热情帮助。感谢家人一直以来的支持。
|
||||
|
||||
**改写后(更真诚):**
|
||||
> 感谢导师张教授,从选题到定稿,他逐字逐句地帮我修改了三遍,连参考文献格式都一一订正。感谢同门李华帮我调试了实验代码。感谢妻子在我写论文的半年里包揽了所有家务。
|
||||
|
||||
## 10. 公式与引用的格式错误
|
||||
|
||||
**问题:** AI 有时会编造参考文献(如"张三,2025"),或把公式格式弄乱(如缺失上下标)。
|
||||
|
||||
**处理建议:** 如果发现引用可疑,提示用户核实;如果公式格式混乱,修正为标准写法(如 $E=mc^2$)。
|
||||
51
.claude/skills/humanizer-zh/references/full-example-zh.md
Normal file
51
.claude/skills/humanizer-zh/references/full-example-zh.md
Normal file
@ -0,0 +1,51 @@
|
||||
# 完整示例
|
||||
|
||||
## 原文(AI 味浓)
|
||||
|
||||
> 您好!以下是一篇关于该主题的文章,希望能对您有所帮助!
|
||||
>
|
||||
> AI 辅助编程作为大语言模型变革潜力的生动体现,标志着软件开发领域的一个重要转折点。在当今快速演进的技术生态中,这些工具——它们处于研究与应用的交叉地带——正在重塑工程师的构思、迭代和交付方式,凸显其在现代工作流中的核心作用。
|
||||
>
|
||||
> 其核心价值在于:简化流程、强化协作、促进对齐。这不仅关乎代码补全,更是在规模化地激发创造力,确保组织能够保持敏捷,同时为用户提供流畅、直观且强大的体验。工具成为催化剂。助手成为伙伴。系统成为创新的基石。
|
||||
>
|
||||
> 业内人士指出,AI 编程的采用已从爱好者实验扩展到企业级部署,从个人开发者推广至跨职能团队。这项技术曾被《纽约时报》《连线》《The Verge》报道。此外,它能自动生成文档、测试和重构代码,展示了 AI 如何优化成果,突出了自动化与人类判断的微妙互动。
|
||||
>
|
||||
> - 💡 **速度:** 代码生成速度显著提升,减少摩擦,赋能开发者。
|
||||
> - 🚀 **质量:** 输出质量因训练改进而提高,助推更高标准。
|
||||
> - ✅ **采纳:** 使用率持续增长,折射出行业大趋势。
|
||||
>
|
||||
> 尽管关于具体细节,现有资料有限,但可以说这些工具或许能产生一些积极影响。虽然面临新兴技术常有的挑战(如幻觉、偏见、责任归属),整个生态仍在蓬勃发展。为了充分释放潜力,团队必须与最佳实践对齐。
|
||||
>
|
||||
> 总之,前景一片光明。激动人心的时刻正在前方,我们将继续这段追求卓越的旅程。如需扩展任何部分,请随时告知!
|
||||
|
||||
## 润色后(有人味儿)
|
||||
|
||||
> AI 编程助手能加速部分编码任务。谷歌 2024 年的一项研究发现,使用 Codex 的开发者完成简单函数的速度比对照组快 55%,但在调试和架构决策上并无提升。
|
||||
>
|
||||
> 这类工具擅长生成样板代码:配置文件、测试脚手架、重复性重构。但它们不擅长判断自己何时出错。我曾一股脑接受过编译通过、lint 检查无误却完全跑错逻辑的建议——因为我那时没仔细看。
|
||||
>
|
||||
> 一位在金融科技创业公司工作的工程师 Mira 告诉我,她把 Copilot 当"无聊代码的自动补全"用,但提交前会逐行审查。另一家大型公司的资深开发者 Jake 则直接禁用了它,因为它老是从一个已废弃的内部库里抄模式。
|
||||
>
|
||||
> 生产力数据很难验证。GitHub 说 Copilot 用户"接受了 30% 的建议",但接受不等于正确,正确也不等于创造价值。2024 年 Uplevel 的研究发现,使用 AI 助手和不使用的团队,在 PR 吞吐量上没有统计意义上的差异。
|
||||
>
|
||||
> 这并不意味着这些工具没用——它们只是工具。它们不能取代人的判断,也不能取代测试。如果你没有测试,你根本不知道 AI 给的建议对不对。
|
||||
|
||||
## 主要改动
|
||||
|
||||
- 移除客服套话("您好!""希望能对您有帮助""请随时告知")
|
||||
- 移除宏大意义标签("生动体现""重要转折点""核心作用")
|
||||
- 移除广告用语("快速演进""处于交叉地带""流畅、直观且强大")
|
||||
- 移除模糊引用("业内人士指出"),代之以具体研究(谷歌、Uplevel)和真实采访(Mira、Jake)
|
||||
- 移除空洞虚词("凸显""展示""突出")
|
||||
- 移除否定平行结构("不仅……更……")
|
||||
- 移除排比三连和同义反复("催化剂/伙伴/基石")
|
||||
- 移除虚假范围("从……到……")
|
||||
- 移除 emoji、粗体标题、破折号滥用
|
||||
- 移除复杂动词("成为""作为""拥有")代之以"是""有"
|
||||
- 移除套路化挑战段落("虽面临挑战……仍在蓬勃发展")
|
||||
- 移除知识截止免责("现有资料有限")
|
||||
- 移除过度模糊("或许可能""一些")
|
||||
- 移除赘语("为了充分释放潜力")
|
||||
- 移除空洞结尾("前景一片光明""激动人心的时刻")
|
||||
- 用具体细节替换媒体名称罗列,交代采访内容
|
||||
- 句子长短交错,适当使用第一人称("我曾")
|
||||
@ -0,0 +1,75 @@
|
||||
# 语言与语法模式
|
||||
|
||||
## 7. 过度使用的 AI 高频词
|
||||
|
||||
**中文高频词:** 此外、值得一提的是、不可否认、毋庸置疑、众所周知、深刻地、极大地、充分地、有效地、积极地、主动地、扎实地、稳步地、协同地
|
||||
|
||||
**问题:** 这些词在 AI 文本中出现频率远高于人类,常扎堆出现。
|
||||
|
||||
**原文:**
|
||||
> 此外,值得一提的是,索马里饮食的一大特色是骆驼肉。毋庸置疑,意大利殖民时期引入的面食已被当地人广泛接受,这充分体现了外来饮食与本地传统的融合。
|
||||
|
||||
**改写后:**
|
||||
> 索马里人也吃骆驼肉,当地人视为珍馐。意大利殖民时期传入的面食也保留了下来,尤其在南方地区很常见。
|
||||
|
||||
---
|
||||
|
||||
## 8. 回避简单动词("是""有")
|
||||
|
||||
**警惕词:** 成为、作为、被视为、被誉为、堪称、可谓、拥有、具备、涵盖
|
||||
|
||||
**问题:** AI 总用复杂表达代替"是"和"有",让句子臃肿。
|
||||
|
||||
**原文:**
|
||||
> 825 画廊作为 LAAA 的当代艺术展览空间,拥有四个独立展厅,总面积超过 3000 平方英尺。
|
||||
|
||||
**改写后:**
|
||||
> 825 画廊是 LAAA 的当代艺术展厅,有四个房间,总面积 3000 多平方英尺。
|
||||
|
||||
---
|
||||
|
||||
## 9. 否定平行结构
|
||||
|
||||
**问题:** "不是……而是……""不仅是……更是……"被 AI 滥用。
|
||||
|
||||
**原文:**
|
||||
> 这不仅是节拍与歌声的叠加,更是一种态度与氛围的表达。这不仅仅是首歌,这是一声呐喊。
|
||||
|
||||
**改写后:**
|
||||
> 沉重的鼓点为歌曲增添了攻击性。
|
||||
|
||||
---
|
||||
|
||||
## 10. 排比三连
|
||||
|
||||
**问题:** AI 把内容硬凑成三条,显得"全面"。
|
||||
|
||||
**原文:**
|
||||
> 活动包括主题演讲、小组讨论和社交环节。与会者将收获创新灵感、行业洞见和人脉资源。
|
||||
|
||||
**改写后:**
|
||||
> 活动包含演讲和讨论,也有茶歇时间供大家自由交流。
|
||||
|
||||
---
|
||||
|
||||
## 11. 同义反复(为避免重复而换词)
|
||||
|
||||
**问题:** AI 为避免重复,不断换词指代同一事物,反而怪异。
|
||||
|
||||
**原文:**
|
||||
> 主角面临重重困难。这位主人公必须克服障碍。中心人物最终获胜。英雄回到了家乡。
|
||||
|
||||
**改写后:**
|
||||
> 主角历经磨难,最终获胜还乡。
|
||||
|
||||
---
|
||||
|
||||
## 12. 虚假范围("从……到……")
|
||||
|
||||
**问题:** AI 用"从 X 到 Y"句式,但 X 和 Y 并不构成真正的范围。
|
||||
|
||||
**原文:**
|
||||
> 我们的宇宙之旅,从大爆炸奇点到浩瀚的宇宙网,从恒星的诞生与死亡到暗物质的神秘舞蹈,包罗万象。
|
||||
|
||||
**改写后:**
|
||||
> 这本书涵盖了宇宙大爆炸、恒星演化以及暗物质理论。
|
||||
57
.claude/skills/humanizer-zh/references/style-patterns-zh.md
Normal file
57
.claude/skills/humanizer-zh/references/style-patterns-zh.md
Normal file
@ -0,0 +1,57 @@
|
||||
# 风格模式
|
||||
|
||||
## 13. 破折号滥用
|
||||
|
||||
**问题:** AI 用破折号制造"俏皮"或"强调",往往过度。
|
||||
|
||||
**原文:**
|
||||
> 这个词主要由荷兰官方推广——而非当地人自称——但在正式文件中——甚至在学术论文里——仍频繁出现。
|
||||
|
||||
**改写后:**
|
||||
> 这个词主要由荷兰官方推广,当地人并不这么自称,但正式文件和学术论文里仍频繁出现。
|
||||
|
||||
---
|
||||
|
||||
## 14. 粗体滥用
|
||||
|
||||
**问题:** AI 动不动就加粗,像在用格式强调套路。
|
||||
|
||||
**原文:**
|
||||
> 该方法融合了 **OKR(目标与关键结果)**、**KPI(关键绩效指标)** 以及 **商业模式画布**、**平衡计分卡** 等可视化工具。
|
||||
|
||||
**改写后:**
|
||||
> 该方法融合了 OKR、KPI 以及商业模式画布、平衡计分卡等工具。
|
||||
|
||||
---
|
||||
|
||||
## 15. 标题式列表(加粗标题+冒号)
|
||||
|
||||
**问题:** AI 用 "**标题:** 内容" 的格式,像写报告大纲。
|
||||
|
||||
**原文:**
|
||||
> - **用户体验:** 新界面显著提升了用户体验。
|
||||
> - **性能优化:** 算法优化大幅提高了性能。
|
||||
> - **安全保障:** 端到端加密强化了安全性。
|
||||
|
||||
**改写后:**
|
||||
> 新版优化了界面,加载速度更快,并增加了端到端加密。
|
||||
|
||||
---
|
||||
|
||||
## 16. 标点与格式问题
|
||||
|
||||
**问题:** 中文文本中可能出现滥用书名号、引号,或混用中英文标点等格式问题。建议统一使用中文常规标点。
|
||||
|
||||
---
|
||||
|
||||
## 17. Emoji 滥用
|
||||
|
||||
**问题:** AI 在标题或列表前加 emoji,显得花哨。
|
||||
|
||||
**原文:**
|
||||
> 🚀 **启动阶段:** 产品将于 Q3 发布
|
||||
> 💡 **核心洞察:** 用户更爱简洁
|
||||
> ✅ **下一步:** 安排跟进会议
|
||||
|
||||
**改写后:**
|
||||
> 产品计划 Q3 发布。用户调研显示简洁更受欢迎。下一步:安排跟进会议。
|
||||
30
.claude/skills/pdf/LICENSE.txt
Normal file
30
.claude/skills/pdf/LICENSE.txt
Normal file
@ -0,0 +1,30 @@
|
||||
© 2025 Anthropic, PBC. All rights reserved.
|
||||
|
||||
LICENSE: Use of these materials (including all code, prompts, assets, files,
|
||||
and other components of this Skill) is governed by your agreement with
|
||||
Anthropic regarding use of Anthropic's services. If no separate agreement
|
||||
exists, use is governed by Anthropic's Consumer Terms of Service or
|
||||
Commercial Terms of Service, as applicable:
|
||||
https://www.anthropic.com/legal/consumer-terms
|
||||
https://www.anthropic.com/legal/commercial-terms
|
||||
Your applicable agreement is referred to as the "Agreement." "Services" are
|
||||
as defined in the Agreement.
|
||||
|
||||
ADDITIONAL RESTRICTIONS: Notwithstanding anything in the Agreement to the
|
||||
contrary, users may not:
|
||||
|
||||
- Extract these materials from the Services or retain copies of these
|
||||
materials outside the Services
|
||||
- Reproduce or copy these materials, except for temporary copies created
|
||||
automatically during authorized use of the Services
|
||||
- Create derivative works based on these materials
|
||||
- Distribute, sublicense, or transfer these materials to any third party
|
||||
- Make, offer to sell, sell, or import any inventions embodied in these
|
||||
materials
|
||||
- Reverse engineer, decompile, or disassemble these materials
|
||||
|
||||
The receipt, viewing, or possession of these materials does not convey or
|
||||
imply any license or right beyond those expressly granted above.
|
||||
|
||||
Anthropic retains all right, title, and interest in these materials,
|
||||
including all copyrights, patents, and other intellectual property rights.
|
||||
340
.claude/skills/pdf/SKILL.md
Normal file
340
.claude/skills/pdf/SKILL.md
Normal file
@ -0,0 +1,340 @@
|
||||
---
|
||||
name: pdf
|
||||
description: Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.
|
||||
license: Proprietary. LICENSE.txt has complete terms
|
||||
---
|
||||
|
||||
# PDF Processing Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers essential PDF processing operations using Python libraries and command-line tools. For advanced features, JavaScript libraries, and detailed examples, see reference.md. If you need to fill out a PDF form, read forms.md and follow its instructions.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
|
||||
# Read a PDF
|
||||
reader = PdfReader("document.pdf")
|
||||
print(f"Pages: {len(reader.pages)}")
|
||||
|
||||
# Extract text
|
||||
text = ""
|
||||
for page in reader.pages:
|
||||
text += page.extract_text()
|
||||
```
|
||||
|
||||
## Python Libraries
|
||||
|
||||
### pypdf - Basic Operations
|
||||
|
||||
#### Merge PDFs
|
||||
```python
|
||||
from pypdf import PdfWriter, PdfReader
|
||||
|
||||
writer = PdfWriter()
|
||||
for pdf_file in ["doc1.pdf", "doc2.pdf", "doc3.pdf"]:
|
||||
reader = PdfReader(pdf_file)
|
||||
for page in reader.pages:
|
||||
writer.add_page(page)
|
||||
|
||||
with open("merged.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
#### Split PDF
|
||||
```python
|
||||
reader = PdfReader("input.pdf")
|
||||
for i, page in enumerate(reader.pages):
|
||||
writer = PdfWriter()
|
||||
writer.add_page(page)
|
||||
with open(f"page_{i+1}.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
#### Extract Metadata
|
||||
```python
|
||||
reader = PdfReader("document.pdf")
|
||||
meta = reader.metadata
|
||||
print(f"Title: {meta.title}")
|
||||
print(f"Author: {meta.author}")
|
||||
print(f"Subject: {meta.subject}")
|
||||
print(f"Creator: {meta.creator}")
|
||||
```
|
||||
|
||||
#### Rotate Pages
|
||||
```python
|
||||
reader = PdfReader("input.pdf")
|
||||
writer = PdfWriter()
|
||||
|
||||
page = reader.pages[0]
|
||||
page.rotate(90) # Rotate 90 degrees clockwise
|
||||
writer.add_page(page)
|
||||
|
||||
with open("rotated.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
### pdfplumber - Text and Table Extraction
|
||||
|
||||
#### Extract Text with Layout
|
||||
```python
|
||||
import pdfplumber
|
||||
|
||||
with pdfplumber.open("document.pdf") as pdf:
|
||||
for page in pdf.pages:
|
||||
text = page.extract_text()
|
||||
print(text)
|
||||
```
|
||||
|
||||
#### Extract Tables
|
||||
```python
|
||||
with pdfplumber.open("document.pdf") as pdf:
|
||||
for i, page in enumerate(pdf.pages):
|
||||
tables = page.extract_tables()
|
||||
for j, table in enumerate(tables):
|
||||
print(f"Table {j+1} on page {i+1}:")
|
||||
for row in table:
|
||||
print(row)
|
||||
```
|
||||
|
||||
#### Advanced Table Extraction
|
||||
```python
|
||||
import pandas as pd
|
||||
|
||||
with pdfplumber.open("document.pdf") as pdf:
|
||||
all_tables = []
|
||||
for page in pdf.pages:
|
||||
tables = page.extract_tables()
|
||||
for table in tables:
|
||||
if table: # Check if table is not empty
|
||||
df = pd.DataFrame(table[1:], columns=table[0])
|
||||
all_tables.append(df)
|
||||
|
||||
# Combine all tables
|
||||
if all_tables:
|
||||
combined_df = pd.concat(all_tables, ignore_index=True)
|
||||
combined_df.to_excel("extracted_tables.xlsx", index=False)
|
||||
```
|
||||
|
||||
### reportlab - Create PDFs
|
||||
|
||||
#### Basic PDF Creation
|
||||
```python
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
c = canvas.Canvas("hello.pdf", pagesize=letter)
|
||||
width, height = letter
|
||||
|
||||
# Add text
|
||||
c.drawString(100, height - 100, "Hello World!")
|
||||
c.drawString(100, height - 120, "This is a PDF created with reportlab")
|
||||
|
||||
# Add a line
|
||||
c.line(100, height - 140, 400, height - 140)
|
||||
|
||||
# Save
|
||||
c.save()
|
||||
```
|
||||
|
||||
#### Create PDF with Multiple Pages
|
||||
```python
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
|
||||
doc = SimpleDocTemplate("report.pdf", pagesize=letter)
|
||||
styles = getSampleStyleSheet()
|
||||
story = []
|
||||
|
||||
# Add content
|
||||
title = Paragraph("Report Title", styles['Title'])
|
||||
story.append(title)
|
||||
story.append(Spacer(1, 12))
|
||||
|
||||
body = Paragraph("This is the body of the report. " * 20, styles['Normal'])
|
||||
story.append(body)
|
||||
story.append(PageBreak())
|
||||
|
||||
# Page 2
|
||||
story.append(Paragraph("Page 2", styles['Heading1']))
|
||||
story.append(Paragraph("Content for page 2", styles['Normal']))
|
||||
|
||||
# Build PDF
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
## Command-Line Tools
|
||||
|
||||
### pdftotext (poppler-utils)
|
||||
```bash
|
||||
# Extract text
|
||||
pdftotext input.pdf output.txt
|
||||
|
||||
# Extract text preserving layout
|
||||
pdftotext -layout input.pdf output.txt
|
||||
|
||||
# Extract specific pages
|
||||
pdftotext -f 1 -l 5 input.pdf output.txt # Pages 1-5
|
||||
```
|
||||
|
||||
### qpdf
|
||||
```bash
|
||||
# Merge PDFs
|
||||
qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf
|
||||
|
||||
# Split pages
|
||||
qpdf input.pdf --pages . 1-5 -- pages1-5.pdf
|
||||
qpdf input.pdf --pages . 6-10 -- pages6-10.pdf
|
||||
|
||||
# Rotate pages
|
||||
qpdf input.pdf output.pdf --rotate=+90:1 # Rotate page 1 by 90 degrees
|
||||
|
||||
# Remove password
|
||||
qpdf --password=mypassword --decrypt encrypted.pdf decrypted.pdf
|
||||
```
|
||||
|
||||
### pdftk (if available)
|
||||
```bash
|
||||
# Merge
|
||||
pdftk file1.pdf file2.pdf cat output merged.pdf
|
||||
|
||||
# Split
|
||||
pdftk input.pdf burst
|
||||
|
||||
# Rotate
|
||||
pdftk input.pdf rotate 1east output rotated.pdf
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Extract Text from Scanned PDFs
|
||||
```python
|
||||
# Requires: pip install pytesseract pdf2image
|
||||
import pytesseract
|
||||
from pdf2image import convert_from_path
|
||||
|
||||
# Convert PDF to images
|
||||
images = convert_from_path('scanned.pdf')
|
||||
|
||||
# OCR each page
|
||||
text = ""
|
||||
for i, image in enumerate(images):
|
||||
text += f"Page {i+1}:\n"
|
||||
text += pytesseract.image_to_string(image)
|
||||
text += "\n\n"
|
||||
|
||||
print(text)
|
||||
```
|
||||
|
||||
### Add Watermark
|
||||
```python
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
|
||||
# Create watermark (or load existing)
|
||||
watermark = PdfReader("watermark.pdf").pages[0]
|
||||
|
||||
# Apply to all pages
|
||||
reader = PdfReader("document.pdf")
|
||||
writer = PdfWriter()
|
||||
|
||||
for page in reader.pages:
|
||||
page.merge_page(watermark)
|
||||
writer.add_page(page)
|
||||
|
||||
with open("watermarked.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
### Extract Images
|
||||
```bash
|
||||
# Using pdfimages (poppler-utils)
|
||||
pdfimages -j input.pdf output_prefix
|
||||
|
||||
# This extracts all images as output_prefix-000.jpg, output_prefix-001.jpg, etc.
|
||||
```
|
||||
|
||||
### Password Protection
|
||||
```python
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
|
||||
reader = PdfReader("input.pdf")
|
||||
writer = PdfWriter()
|
||||
|
||||
for page in reader.pages:
|
||||
writer.add_page(page)
|
||||
|
||||
# Add password
|
||||
writer.encrypt("userpassword", "ownerpassword")
|
||||
|
||||
with open("encrypted.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Best Tool | Command/Code |
|
||||
|------|-----------|--------------|
|
||||
| Merge PDFs | pypdf | `writer.add_page(page)` |
|
||||
| Split PDFs | pypdf | One page per file |
|
||||
| Extract text | pdfplumber | `page.extract_text()` |
|
||||
| Extract tables | pdfplumber | `page.extract_tables()` |
|
||||
| Create PDFs | reportlab | Canvas or Platypus |
|
||||
| Command line merge | qpdf | `qpdf --empty --pages ...` |
|
||||
| OCR scanned PDFs | pytesseract | Convert to image first |
|
||||
| Fill PDF forms | pdf-lib or pypdf (see forms.md) | See forms.md |
|
||||
|
||||
## Cross-Platform 路径处理
|
||||
|
||||
PDF skill 同时使用 Python 和 JavaScript,两种语言都需要注意路径兼容性。
|
||||
|
||||
### Python 路径规则
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
# ❌ 硬编码斜杠
|
||||
with open("output/merged.pdf", "wb") as f: ...
|
||||
output_file = f"output/page_{i+1}.pdf"
|
||||
|
||||
# ✅ 用 Path 或 os.path.join
|
||||
out_dir = Path("output")
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
with open(out_dir / "merged.pdf", "wb") as f: ...
|
||||
output_file = out_dir / f"page_{i+1}.pdf"
|
||||
```
|
||||
|
||||
### JavaScript 路径规则 (pdf-lib)
|
||||
|
||||
```javascript
|
||||
const path = require('path');
|
||||
|
||||
// ❌ 硬编码
|
||||
const pdfBytes = fs.readFileSync('input/doc.pdf');
|
||||
fs.writeFileSync('output/modified.pdf', pdfBytes);
|
||||
|
||||
// ✅ path.join
|
||||
const pdfBytes = fs.readFileSync(path.join('input', 'doc.pdf'));
|
||||
const outDir = path.join('output');
|
||||
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(outDir, 'modified.pdf'), pdfBytes);
|
||||
```
|
||||
|
||||
### 快速检查清单
|
||||
|
||||
| 检查项 | 说明 |
|
||||
|--------|------|
|
||||
| Python 用 `Path()` / `os.path.join()` | 不硬编码 `/` 拼路径 |
|
||||
| JS 用 `path.join()` | 不用模板字符串拼路径 |
|
||||
| 输出目录先 `mkdir -p` | Python: `Path.mkdir(parents=True, exist_ok=True)` |
|
||||
| 文件名不含 `: * ? " < > \|` | Windows 保留字符 |
|
||||
| 含空格路径加引号 | shell 命令中 `"$PATH"` |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- For advanced pypdfium2 usage, see reference.md
|
||||
- For JavaScript libraries (pdf-lib), see reference.md
|
||||
- If you need to fill out a PDF form, follow the instructions in forms.md
|
||||
- For troubleshooting guides, see reference.md
|
||||
205
.claude/skills/pdf/forms.md
Normal file
205
.claude/skills/pdf/forms.md
Normal file
@ -0,0 +1,205 @@
|
||||
**CRITICAL: You MUST complete these steps in order. Do not skip ahead to writing code.**
|
||||
|
||||
If you need to fill out a PDF form, first check to see if the PDF has fillable form fields. Run this script from this file's directory:
|
||||
`python scripts/check_fillable_fields <file.pdf>`, and depending on the result go to either the "Fillable fields" or "Non-fillable fields" and follow those instructions.
|
||||
|
||||
# Fillable fields
|
||||
If the PDF has fillable form fields:
|
||||
- Run this script from this file's directory: `python scripts/extract_form_field_info.py <input.pdf> <field_info.json>`. It will create a JSON file with a list of fields in this format:
|
||||
```
|
||||
[
|
||||
{
|
||||
"field_id": (unique ID for the field),
|
||||
"page": (page number, 1-based),
|
||||
"rect": ([left, bottom, right, top] bounding box in PDF coordinates, y=0 is the bottom of the page),
|
||||
"type": ("text", "checkbox", "radio_group", or "choice"),
|
||||
},
|
||||
// Checkboxes have "checked_value" and "unchecked_value" properties:
|
||||
{
|
||||
"field_id": (unique ID for the field),
|
||||
"page": (page number, 1-based),
|
||||
"type": "checkbox",
|
||||
"checked_value": (Set the field to this value to check the checkbox),
|
||||
"unchecked_value": (Set the field to this value to uncheck the checkbox),
|
||||
},
|
||||
// Radio groups have a "radio_options" list with the possible choices.
|
||||
{
|
||||
"field_id": (unique ID for the field),
|
||||
"page": (page number, 1-based),
|
||||
"type": "radio_group",
|
||||
"radio_options": [
|
||||
{
|
||||
"value": (set the field to this value to select this radio option),
|
||||
"rect": (bounding box for the radio button for this option)
|
||||
},
|
||||
// Other radio options
|
||||
]
|
||||
},
|
||||
// Multiple choice fields have a "choice_options" list with the possible choices:
|
||||
{
|
||||
"field_id": (unique ID for the field),
|
||||
"page": (page number, 1-based),
|
||||
"type": "choice",
|
||||
"choice_options": [
|
||||
{
|
||||
"value": (set the field to this value to select this option),
|
||||
"text": (display text of the option)
|
||||
},
|
||||
// Other choice options
|
||||
],
|
||||
}
|
||||
]
|
||||
```
|
||||
- Convert the PDF to PNGs (one image for each page) with this script (run from this file's directory):
|
||||
`python scripts/convert_pdf_to_images.py <file.pdf> <output_directory>`
|
||||
Then analyze the images to determine the purpose of each form field (make sure to convert the bounding box PDF coordinates to image coordinates).
|
||||
- Create a `field_values.json` file in this format with the values to be entered for each field:
|
||||
```
|
||||
[
|
||||
{
|
||||
"field_id": "last_name", // Must match the field_id from `extract_form_field_info.py`
|
||||
"description": "The user's last name",
|
||||
"page": 1, // Must match the "page" value in field_info.json
|
||||
"value": "Simpson"
|
||||
},
|
||||
{
|
||||
"field_id": "Checkbox12",
|
||||
"description": "Checkbox to be checked if the user is 18 or over",
|
||||
"page": 1,
|
||||
"value": "/On" // If this is a checkbox, use its "checked_value" value to check it. If it's a radio button group, use one of the "value" values in "radio_options".
|
||||
},
|
||||
// more fields
|
||||
]
|
||||
```
|
||||
- Run the `fill_fillable_fields.py` script from this file's directory to create a filled-in PDF:
|
||||
`python scripts/fill_fillable_fields.py <input pdf> <field_values.json> <output pdf>`
|
||||
This script will verify that the field IDs and values you provide are valid; if it prints error messages, correct the appropriate fields and try again.
|
||||
|
||||
# Non-fillable fields
|
||||
If the PDF doesn't have fillable form fields, you'll need to visually determine where the data should be added and create text annotations. Follow the below steps *exactly*. You MUST perform all of these steps to ensure that the the form is accurately completed. Details for each step are below.
|
||||
- Convert the PDF to PNG images and determine field bounding boxes.
|
||||
- Create a JSON file with field information and validation images showing the bounding boxes.
|
||||
- Validate the the bounding boxes.
|
||||
- Use the bounding boxes to fill in the form.
|
||||
|
||||
## Step 1: Visual Analysis (REQUIRED)
|
||||
- Convert the PDF to PNG images. Run this script from this file's directory:
|
||||
`python scripts/convert_pdf_to_images.py <file.pdf> <output_directory>`
|
||||
The script will create a PNG image for each page in the PDF.
|
||||
- Carefully examine each PNG image and identify all form fields and areas where the user should enter data. For each form field where the user should enter text, determine bounding boxes for both the form field label, and the area where the user should enter text. The label and entry bounding boxes MUST NOT INTERSECT; the text entry box should only include the area where data should be entered. Usually this area will be immediately to the side, above, or below its label. Entry bounding boxes must be tall and wide enough to contain their text.
|
||||
|
||||
These are some examples of form structures that you might see:
|
||||
|
||||
*Label inside box*
|
||||
```
|
||||
┌────────────────────────┐
|
||||
│ Name: │
|
||||
└────────────────────────┘
|
||||
```
|
||||
The input area should be to the right of the "Name" label and extend to the edge of the box.
|
||||
|
||||
*Label before line*
|
||||
```
|
||||
Email: _______________________
|
||||
```
|
||||
The input area should be above the line and include its entire width.
|
||||
|
||||
*Label under line*
|
||||
```
|
||||
_________________________
|
||||
Name
|
||||
```
|
||||
The input area should be above the line and include the entire width of the line. This is common for signature and date fields.
|
||||
|
||||
*Label above line*
|
||||
```
|
||||
Please enter any special requests:
|
||||
________________________________________________
|
||||
```
|
||||
The input area should extend from the bottom of the label to the line, and should include the entire width of the line.
|
||||
|
||||
*Checkboxes*
|
||||
```
|
||||
Are you a US citizen? Yes □ No □
|
||||
```
|
||||
For checkboxes:
|
||||
- Look for small square boxes (□) - these are the actual checkboxes to target. They may be to the left or right of their labels.
|
||||
- Distinguish between label text ("Yes", "No") and the clickable checkbox squares.
|
||||
- The entry bounding box should cover ONLY the small square, not the text label.
|
||||
|
||||
### Step 2: Create fields.json and validation images (REQUIRED)
|
||||
- Create a file named `fields.json` with information for the form fields and bounding boxes in this format:
|
||||
```
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"page_number": 1,
|
||||
"image_width": (first page image width in pixels),
|
||||
"image_height": (first page image height in pixels),
|
||||
},
|
||||
{
|
||||
"page_number": 2,
|
||||
"image_width": (second page image width in pixels),
|
||||
"image_height": (second page image height in pixels),
|
||||
}
|
||||
// additional pages
|
||||
],
|
||||
"form_fields": [
|
||||
// Example for a text field.
|
||||
{
|
||||
"page_number": 1,
|
||||
"description": "The user's last name should be entered here",
|
||||
// Bounding boxes are [left, top, right, bottom]. The bounding boxes for the label and text entry should not overlap.
|
||||
"field_label": "Last name",
|
||||
"label_bounding_box": [30, 125, 95, 142],
|
||||
"entry_bounding_box": [100, 125, 280, 142],
|
||||
"entry_text": {
|
||||
"text": "Johnson", // This text will be added as an annotation at the entry_bounding_box location
|
||||
"font_size": 14, // optional, defaults to 14
|
||||
"font_color": "000000", // optional, RRGGBB format, defaults to 000000 (black)
|
||||
}
|
||||
},
|
||||
// Example for a checkbox. TARGET THE SQUARE for the entry bounding box, NOT THE TEXT
|
||||
{
|
||||
"page_number": 2,
|
||||
"description": "Checkbox that should be checked if the user is over 18",
|
||||
"entry_bounding_box": [140, 525, 155, 540], // Small box over checkbox square
|
||||
"field_label": "Yes",
|
||||
"label_bounding_box": [100, 525, 132, 540], // Box containing "Yes" text
|
||||
// Use "X" to check a checkbox.
|
||||
"entry_text": {
|
||||
"text": "X",
|
||||
}
|
||||
}
|
||||
// additional form field entries
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Create validation images by running this script from this file's directory for each page:
|
||||
`python scripts/create_validation_image.py <page_number> <path_to_fields.json> <input_image_path> <output_image_path>
|
||||
|
||||
The validation images will have red rectangles where text should be entered, and blue rectangles covering label text.
|
||||
|
||||
### Step 3: Validate Bounding Boxes (REQUIRED)
|
||||
#### Automated intersection check
|
||||
- Verify that none of bounding boxes intersect and that the entry bounding boxes are tall enough by checking the fields.json file with the `check_bounding_boxes.py` script (run from this file's directory):
|
||||
`python scripts/check_bounding_boxes.py <JSON file>`
|
||||
|
||||
If there are errors, reanalyze the relevant fields, adjust the bounding boxes, and iterate until there are no remaining errors. Remember: label (blue) bounding boxes should contain text labels, entry (red) boxes should not.
|
||||
|
||||
#### Manual image inspection
|
||||
**CRITICAL: Do not proceed without visually inspecting validation images**
|
||||
- Red rectangles must ONLY cover input areas
|
||||
- Red rectangles MUST NOT contain any text
|
||||
- Blue rectangles should contain label text
|
||||
- For checkboxes:
|
||||
- Red rectangle MUST be centered on the checkbox square
|
||||
- Blue rectangle should cover the text label for the checkbox
|
||||
|
||||
- If any rectangles look wrong, fix fields.json, regenerate the validation images, and verify again. Repeat this process until the bounding boxes are fully accurate.
|
||||
|
||||
|
||||
### Step 4: Add annotations to the PDF
|
||||
Run this script from this file's directory to create a filled-out PDF using the information in fields.json:
|
||||
`python scripts/fill_pdf_form_with_annotations.py <input_pdf_path> <path_to_fields.json> <output_pdf_path>
|
||||
612
.claude/skills/pdf/reference.md
Normal file
612
.claude/skills/pdf/reference.md
Normal file
@ -0,0 +1,612 @@
|
||||
# PDF Processing Advanced Reference
|
||||
|
||||
This document contains advanced PDF processing features, detailed examples, and additional libraries not covered in the main skill instructions.
|
||||
|
||||
## pypdfium2 Library (Apache/BSD License)
|
||||
|
||||
### Overview
|
||||
pypdfium2 is a Python binding for PDFium (Chromium's PDF library). It's excellent for fast PDF rendering, image generation, and serves as a PyMuPDF replacement.
|
||||
|
||||
### Render PDF to Images
|
||||
```python
|
||||
import pypdfium2 as pdfium
|
||||
from PIL import Image
|
||||
|
||||
# Load PDF
|
||||
pdf = pdfium.PdfDocument("document.pdf")
|
||||
|
||||
# Render page to image
|
||||
page = pdf[0] # First page
|
||||
bitmap = page.render(
|
||||
scale=2.0, # Higher resolution
|
||||
rotation=0 # No rotation
|
||||
)
|
||||
|
||||
# Convert to PIL Image
|
||||
img = bitmap.to_pil()
|
||||
img.save("page_1.png", "PNG")
|
||||
|
||||
# Process multiple pages
|
||||
for i, page in enumerate(pdf):
|
||||
bitmap = page.render(scale=1.5)
|
||||
img = bitmap.to_pil()
|
||||
img.save(f"page_{i+1}.jpg", "JPEG", quality=90)
|
||||
```
|
||||
|
||||
### Extract Text with pypdfium2
|
||||
```python
|
||||
import pypdfium2 as pdfium
|
||||
|
||||
pdf = pdfium.PdfDocument("document.pdf")
|
||||
for i, page in enumerate(pdf):
|
||||
text = page.get_text()
|
||||
print(f"Page {i+1} text length: {len(text)} chars")
|
||||
```
|
||||
|
||||
## JavaScript Libraries
|
||||
|
||||
### pdf-lib (MIT License)
|
||||
|
||||
pdf-lib is a powerful JavaScript library for creating and modifying PDF documents in any JavaScript environment.
|
||||
|
||||
#### Load and Manipulate Existing PDF
|
||||
```javascript
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import fs from 'fs';
|
||||
|
||||
async function manipulatePDF() {
|
||||
// Load existing PDF
|
||||
const existingPdfBytes = fs.readFileSync('input.pdf');
|
||||
const pdfDoc = await PDFDocument.load(existingPdfBytes);
|
||||
|
||||
// Get page count
|
||||
const pageCount = pdfDoc.getPageCount();
|
||||
console.log(`Document has ${pageCount} pages`);
|
||||
|
||||
// Add new page
|
||||
const newPage = pdfDoc.addPage([600, 400]);
|
||||
newPage.drawText('Added by pdf-lib', {
|
||||
x: 100,
|
||||
y: 300,
|
||||
size: 16
|
||||
});
|
||||
|
||||
// Save modified PDF
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
fs.writeFileSync('modified.pdf', pdfBytes);
|
||||
}
|
||||
```
|
||||
|
||||
#### Create Complex PDFs from Scratch
|
||||
```javascript
|
||||
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
|
||||
import fs from 'fs';
|
||||
|
||||
async function createPDF() {
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
|
||||
// Add fonts
|
||||
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);
|
||||
const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);
|
||||
|
||||
// Add page
|
||||
const page = pdfDoc.addPage([595, 842]); // A4 size
|
||||
const { width, height } = page.getSize();
|
||||
|
||||
// Add text with styling
|
||||
page.drawText('Invoice #12345', {
|
||||
x: 50,
|
||||
y: height - 50,
|
||||
size: 18,
|
||||
font: helveticaBold,
|
||||
color: rgb(0.2, 0.2, 0.8)
|
||||
});
|
||||
|
||||
// Add rectangle (header background)
|
||||
page.drawRectangle({
|
||||
x: 40,
|
||||
y: height - 100,
|
||||
width: width - 80,
|
||||
height: 30,
|
||||
color: rgb(0.9, 0.9, 0.9)
|
||||
});
|
||||
|
||||
// Add table-like content
|
||||
const items = [
|
||||
['Item', 'Qty', 'Price', 'Total'],
|
||||
['Widget', '2', '$50', '$100'],
|
||||
['Gadget', '1', '$75', '$75']
|
||||
];
|
||||
|
||||
let yPos = height - 150;
|
||||
items.forEach(row => {
|
||||
let xPos = 50;
|
||||
row.forEach(cell => {
|
||||
page.drawText(cell, {
|
||||
x: xPos,
|
||||
y: yPos,
|
||||
size: 12,
|
||||
font: helveticaFont
|
||||
});
|
||||
xPos += 120;
|
||||
});
|
||||
yPos -= 25;
|
||||
});
|
||||
|
||||
const pdfBytes = await pdfDoc.save();
|
||||
fs.writeFileSync('created.pdf', pdfBytes);
|
||||
}
|
||||
```
|
||||
|
||||
#### Advanced Merge and Split Operations
|
||||
```javascript
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import fs from 'fs';
|
||||
|
||||
async function mergePDFs() {
|
||||
// Create new document
|
||||
const mergedPdf = await PDFDocument.create();
|
||||
|
||||
// Load source PDFs
|
||||
const pdf1Bytes = fs.readFileSync('doc1.pdf');
|
||||
const pdf2Bytes = fs.readFileSync('doc2.pdf');
|
||||
|
||||
const pdf1 = await PDFDocument.load(pdf1Bytes);
|
||||
const pdf2 = await PDFDocument.load(pdf2Bytes);
|
||||
|
||||
// Copy pages from first PDF
|
||||
const pdf1Pages = await mergedPdf.copyPages(pdf1, pdf1.getPageIndices());
|
||||
pdf1Pages.forEach(page => mergedPdf.addPage(page));
|
||||
|
||||
// Copy specific pages from second PDF (pages 0, 2, 4)
|
||||
const pdf2Pages = await mergedPdf.copyPages(pdf2, [0, 2, 4]);
|
||||
pdf2Pages.forEach(page => mergedPdf.addPage(page));
|
||||
|
||||
const mergedPdfBytes = await mergedPdf.save();
|
||||
fs.writeFileSync('merged.pdf', mergedPdfBytes);
|
||||
}
|
||||
```
|
||||
|
||||
### pdfjs-dist (Apache License)
|
||||
|
||||
PDF.js is Mozilla's JavaScript library for rendering PDFs in the browser.
|
||||
|
||||
#### Basic PDF Loading and Rendering
|
||||
```javascript
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
// Configure worker (important for performance)
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.js';
|
||||
|
||||
async function renderPDF() {
|
||||
// Load PDF
|
||||
const loadingTask = pdfjsLib.getDocument('document.pdf');
|
||||
const pdf = await loadingTask.promise;
|
||||
|
||||
console.log(`Loaded PDF with ${pdf.numPages} pages`);
|
||||
|
||||
// Get first page
|
||||
const page = await pdf.getPage(1);
|
||||
const viewport = page.getViewport({ scale: 1.5 });
|
||||
|
||||
// Render to canvas
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: context,
|
||||
viewport: viewport
|
||||
};
|
||||
|
||||
await page.render(renderContext).promise;
|
||||
document.body.appendChild(canvas);
|
||||
}
|
||||
```
|
||||
|
||||
#### Extract Text with Coordinates
|
||||
```javascript
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
async function extractText() {
|
||||
const loadingTask = pdfjsLib.getDocument('document.pdf');
|
||||
const pdf = await loadingTask.promise;
|
||||
|
||||
let fullText = '';
|
||||
|
||||
// Extract text from all pages
|
||||
for (let i = 1; i <= pdf.numPages; i++) {
|
||||
const page = await pdf.getPage(i);
|
||||
const textContent = await page.getTextContent();
|
||||
|
||||
const pageText = textContent.items
|
||||
.map(item => item.str)
|
||||
.join(' ');
|
||||
|
||||
fullText += `\n--- Page ${i} ---\n${pageText}`;
|
||||
|
||||
// Get text with coordinates for advanced processing
|
||||
const textWithCoords = textContent.items.map(item => ({
|
||||
text: item.str,
|
||||
x: item.transform[4],
|
||||
y: item.transform[5],
|
||||
width: item.width,
|
||||
height: item.height
|
||||
}));
|
||||
}
|
||||
|
||||
console.log(fullText);
|
||||
return fullText;
|
||||
}
|
||||
```
|
||||
|
||||
#### Extract Annotations and Forms
|
||||
```javascript
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
|
||||
async function extractAnnotations() {
|
||||
const loadingTask = pdfjsLib.getDocument('annotated.pdf');
|
||||
const pdf = await loadingTask.promise;
|
||||
|
||||
for (let i = 1; i <= pdf.numPages; i++) {
|
||||
const page = await pdf.getPage(i);
|
||||
const annotations = await page.getAnnotations();
|
||||
|
||||
annotations.forEach(annotation => {
|
||||
console.log(`Annotation type: ${annotation.subtype}`);
|
||||
console.log(`Content: ${annotation.contents}`);
|
||||
console.log(`Coordinates: ${JSON.stringify(annotation.rect)}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Command-Line Operations
|
||||
|
||||
### poppler-utils Advanced Features
|
||||
|
||||
#### Extract Text with Bounding Box Coordinates
|
||||
```bash
|
||||
# Extract text with bounding box coordinates (essential for structured data)
|
||||
pdftotext -bbox-layout document.pdf output.xml
|
||||
|
||||
# The XML output contains precise coordinates for each text element
|
||||
```
|
||||
|
||||
#### Advanced Image Conversion
|
||||
```bash
|
||||
# Convert to PNG images with specific resolution
|
||||
pdftoppm -png -r 300 document.pdf output_prefix
|
||||
|
||||
# Convert specific page range with high resolution
|
||||
pdftoppm -png -r 600 -f 1 -l 3 document.pdf high_res_pages
|
||||
|
||||
# Convert to JPEG with quality setting
|
||||
pdftoppm -jpeg -jpegopt quality=85 -r 200 document.pdf jpeg_output
|
||||
```
|
||||
|
||||
#### Extract Embedded Images
|
||||
```bash
|
||||
# Extract all embedded images with metadata
|
||||
pdfimages -j -p document.pdf page_images
|
||||
|
||||
# List image info without extracting
|
||||
pdfimages -list document.pdf
|
||||
|
||||
# Extract images in their original format
|
||||
pdfimages -all document.pdf images/img
|
||||
```
|
||||
|
||||
### qpdf Advanced Features
|
||||
|
||||
#### Complex Page Manipulation
|
||||
```bash
|
||||
# Split PDF into groups of pages
|
||||
qpdf --split-pages=3 input.pdf output_group_%02d.pdf
|
||||
|
||||
# Extract specific pages with complex ranges
|
||||
qpdf input.pdf --pages input.pdf 1,3-5,8,10-end -- extracted.pdf
|
||||
|
||||
# Merge specific pages from multiple PDFs
|
||||
qpdf --empty --pages doc1.pdf 1-3 doc2.pdf 5-7 doc3.pdf 2,4 -- combined.pdf
|
||||
```
|
||||
|
||||
#### PDF Optimization and Repair
|
||||
```bash
|
||||
# Optimize PDF for web (linearize for streaming)
|
||||
qpdf --linearize input.pdf optimized.pdf
|
||||
|
||||
# Remove unused objects and compress
|
||||
qpdf --optimize-level=all input.pdf compressed.pdf
|
||||
|
||||
# Attempt to repair corrupted PDF structure
|
||||
qpdf --check input.pdf
|
||||
qpdf --fix-qdf damaged.pdf repaired.pdf
|
||||
|
||||
# Show detailed PDF structure for debugging
|
||||
qpdf --show-all-pages input.pdf > structure.txt
|
||||
```
|
||||
|
||||
#### Advanced Encryption
|
||||
```bash
|
||||
# Add password protection with specific permissions
|
||||
qpdf --encrypt user_pass owner_pass 256 --print=none --modify=none -- input.pdf encrypted.pdf
|
||||
|
||||
# Check encryption status
|
||||
qpdf --show-encryption encrypted.pdf
|
||||
|
||||
# Remove password protection (requires password)
|
||||
qpdf --password=secret123 --decrypt encrypted.pdf decrypted.pdf
|
||||
```
|
||||
|
||||
## Advanced Python Techniques
|
||||
|
||||
### pdfplumber Advanced Features
|
||||
|
||||
#### Extract Text with Precise Coordinates
|
||||
```python
|
||||
import pdfplumber
|
||||
|
||||
with pdfplumber.open("document.pdf") as pdf:
|
||||
page = pdf.pages[0]
|
||||
|
||||
# Extract all text with coordinates
|
||||
chars = page.chars
|
||||
for char in chars[:10]: # First 10 characters
|
||||
print(f"Char: '{char['text']}' at x:{char['x0']:.1f} y:{char['y0']:.1f}")
|
||||
|
||||
# Extract text by bounding box (left, top, right, bottom)
|
||||
bbox_text = page.within_bbox((100, 100, 400, 200)).extract_text()
|
||||
```
|
||||
|
||||
#### Advanced Table Extraction with Custom Settings
|
||||
```python
|
||||
import pdfplumber
|
||||
import pandas as pd
|
||||
|
||||
with pdfplumber.open("complex_table.pdf") as pdf:
|
||||
page = pdf.pages[0]
|
||||
|
||||
# Extract tables with custom settings for complex layouts
|
||||
table_settings = {
|
||||
"vertical_strategy": "lines",
|
||||
"horizontal_strategy": "lines",
|
||||
"snap_tolerance": 3,
|
||||
"intersection_tolerance": 15
|
||||
}
|
||||
tables = page.extract_tables(table_settings)
|
||||
|
||||
# Visual debugging for table extraction
|
||||
img = page.to_image(resolution=150)
|
||||
img.save("debug_layout.png")
|
||||
```
|
||||
|
||||
### reportlab Advanced Features
|
||||
|
||||
#### Create Professional Reports with Tables
|
||||
```python
|
||||
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.lib import colors
|
||||
|
||||
# Sample data
|
||||
data = [
|
||||
['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
|
||||
['Widgets', '120', '135', '142', '158'],
|
||||
['Gadgets', '85', '92', '98', '105']
|
||||
]
|
||||
|
||||
# Create PDF with table
|
||||
doc = SimpleDocTemplate("report.pdf")
|
||||
elements = []
|
||||
|
||||
# Add title
|
||||
styles = getSampleStyleSheet()
|
||||
title = Paragraph("Quarterly Sales Report", styles['Title'])
|
||||
elements.append(title)
|
||||
|
||||
# Add table with advanced styling
|
||||
table = Table(data)
|
||||
table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 14),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black)
|
||||
]))
|
||||
elements.append(table)
|
||||
|
||||
doc.build(elements)
|
||||
```
|
||||
|
||||
## Complex Workflows
|
||||
|
||||
### Extract Figures/Images from PDF
|
||||
|
||||
#### Method 1: Using pdfimages (fastest)
|
||||
```bash
|
||||
# Extract all images with original quality
|
||||
pdfimages -all document.pdf images/img
|
||||
```
|
||||
|
||||
#### Method 2: Using pypdfium2 + Image Processing
|
||||
```python
|
||||
import pypdfium2 as pdfium
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
def extract_figures(pdf_path, output_dir):
|
||||
pdf = pdfium.PdfDocument(pdf_path)
|
||||
|
||||
for page_num, page in enumerate(pdf):
|
||||
# Render high-resolution page
|
||||
bitmap = page.render(scale=3.0)
|
||||
img = bitmap.to_pil()
|
||||
|
||||
# Convert to numpy for processing
|
||||
img_array = np.array(img)
|
||||
|
||||
# Simple figure detection (non-white regions)
|
||||
mask = np.any(img_array != [255, 255, 255], axis=2)
|
||||
|
||||
# Find contours and extract bounding boxes
|
||||
# (This is simplified - real implementation would need more sophisticated detection)
|
||||
|
||||
# Save detected figures
|
||||
# ... implementation depends on specific needs
|
||||
```
|
||||
|
||||
### Batch PDF Processing with Error Handling
|
||||
```python
|
||||
import os
|
||||
import glob
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def batch_process_pdfs(input_dir, operation='merge'):
|
||||
pdf_files = glob.glob(os.path.join(input_dir, "*.pdf"))
|
||||
|
||||
if operation == 'merge':
|
||||
writer = PdfWriter()
|
||||
for pdf_file in pdf_files:
|
||||
try:
|
||||
reader = PdfReader(pdf_file)
|
||||
for page in reader.pages:
|
||||
writer.add_page(page)
|
||||
logger.info(f"Processed: {pdf_file}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to process {pdf_file}: {e}")
|
||||
continue
|
||||
|
||||
with open("batch_merged.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
|
||||
elif operation == 'extract_text':
|
||||
for pdf_file in pdf_files:
|
||||
try:
|
||||
reader = PdfReader(pdf_file)
|
||||
text = ""
|
||||
for page in reader.pages:
|
||||
text += page.extract_text()
|
||||
|
||||
output_file = pdf_file.replace('.pdf', '.txt')
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(text)
|
||||
logger.info(f"Extracted text from: {pdf_file}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to extract text from {pdf_file}: {e}")
|
||||
continue
|
||||
```
|
||||
|
||||
### Advanced PDF Cropping
|
||||
```python
|
||||
from pypdf import PdfWriter, PdfReader
|
||||
|
||||
reader = PdfReader("input.pdf")
|
||||
writer = PdfWriter()
|
||||
|
||||
# Crop page (left, bottom, right, top in points)
|
||||
page = reader.pages[0]
|
||||
page.mediabox.left = 50
|
||||
page.mediabox.bottom = 50
|
||||
page.mediabox.right = 550
|
||||
page.mediabox.top = 750
|
||||
|
||||
writer.add_page(page)
|
||||
with open("cropped.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
## Performance Optimization Tips
|
||||
|
||||
### 1. For Large PDFs
|
||||
- Use streaming approaches instead of loading entire PDF in memory
|
||||
- Use `qpdf --split-pages` for splitting large files
|
||||
- Process pages individually with pypdfium2
|
||||
|
||||
### 2. For Text Extraction
|
||||
- `pdftotext -bbox-layout` is fastest for plain text extraction
|
||||
- Use pdfplumber for structured data and tables
|
||||
- Avoid `pypdf.extract_text()` for very large documents
|
||||
|
||||
### 3. For Image Extraction
|
||||
- `pdfimages` is much faster than rendering pages
|
||||
- Use low resolution for previews, high resolution for final output
|
||||
|
||||
### 4. For Form Filling
|
||||
- pdf-lib maintains form structure better than most alternatives
|
||||
- Pre-validate form fields before processing
|
||||
|
||||
### 5. Memory Management
|
||||
```python
|
||||
# Process PDFs in chunks
|
||||
def process_large_pdf(pdf_path, chunk_size=10):
|
||||
reader = PdfReader(pdf_path)
|
||||
total_pages = len(reader.pages)
|
||||
|
||||
for start_idx in range(0, total_pages, chunk_size):
|
||||
end_idx = min(start_idx + chunk_size, total_pages)
|
||||
writer = PdfWriter()
|
||||
|
||||
for i in range(start_idx, end_idx):
|
||||
writer.add_page(reader.pages[i])
|
||||
|
||||
# Process chunk
|
||||
with open(f"chunk_{start_idx//chunk_size}.pdf", "wb") as output:
|
||||
writer.write(output)
|
||||
```
|
||||
|
||||
## Troubleshooting Common Issues
|
||||
|
||||
### Encrypted PDFs
|
||||
```python
|
||||
# Handle password-protected PDFs
|
||||
from pypdf import PdfReader
|
||||
|
||||
try:
|
||||
reader = PdfReader("encrypted.pdf")
|
||||
if reader.is_encrypted:
|
||||
reader.decrypt("password")
|
||||
except Exception as e:
|
||||
print(f"Failed to decrypt: {e}")
|
||||
```
|
||||
|
||||
### Corrupted PDFs
|
||||
```bash
|
||||
# Use qpdf to repair
|
||||
qpdf --check corrupted.pdf
|
||||
qpdf --replace-input corrupted.pdf
|
||||
```
|
||||
|
||||
### Text Extraction Issues
|
||||
```python
|
||||
# Fallback to OCR for scanned PDFs
|
||||
import pytesseract
|
||||
from pdf2image import convert_from_path
|
||||
|
||||
def extract_text_with_ocr(pdf_path):
|
||||
images = convert_from_path(pdf_path)
|
||||
text = ""
|
||||
for i, image in enumerate(images):
|
||||
text += pytesseract.image_to_string(image)
|
||||
return text
|
||||
```
|
||||
|
||||
## License Information
|
||||
|
||||
- **pypdf**: BSD License
|
||||
- **pdfplumber**: MIT License
|
||||
- **pypdfium2**: Apache/BSD License
|
||||
- **reportlab**: BSD License
|
||||
- **poppler-utils**: GPL-2 License
|
||||
- **qpdf**: Apache License
|
||||
- **pdf-lib**: MIT License
|
||||
- **pdfjs-dist**: Apache License
|
||||
70
.claude/skills/pdf/scripts/check_bounding_boxes.py
Normal file
70
.claude/skills/pdf/scripts/check_bounding_boxes.py
Normal file
@ -0,0 +1,70 @@
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
import sys
|
||||
|
||||
|
||||
# Script to check that the `fields.json` file that Claude creates when analyzing PDFs
|
||||
# does not have overlapping bounding boxes. See forms.md.
|
||||
|
||||
|
||||
@dataclass
|
||||
class RectAndField:
|
||||
rect: list[float]
|
||||
rect_type: str
|
||||
field: dict
|
||||
|
||||
|
||||
# Returns a list of messages that are printed to stdout for Claude to read.
|
||||
def get_bounding_box_messages(fields_json_stream) -> list[str]:
|
||||
messages = []
|
||||
fields = json.load(fields_json_stream)
|
||||
messages.append(f"Read {len(fields['form_fields'])} fields")
|
||||
|
||||
def rects_intersect(r1, r2):
|
||||
disjoint_horizontal = r1[0] >= r2[2] or r1[2] <= r2[0]
|
||||
disjoint_vertical = r1[1] >= r2[3] or r1[3] <= r2[1]
|
||||
return not (disjoint_horizontal or disjoint_vertical)
|
||||
|
||||
rects_and_fields = []
|
||||
for f in fields["form_fields"]:
|
||||
rects_and_fields.append(RectAndField(f["label_bounding_box"], "label", f))
|
||||
rects_and_fields.append(RectAndField(f["entry_bounding_box"], "entry", f))
|
||||
|
||||
has_error = False
|
||||
for i, ri in enumerate(rects_and_fields):
|
||||
# This is O(N^2); we can optimize if it becomes a problem.
|
||||
for j in range(i + 1, len(rects_and_fields)):
|
||||
rj = rects_and_fields[j]
|
||||
if ri.field["page_number"] == rj.field["page_number"] and rects_intersect(ri.rect, rj.rect):
|
||||
has_error = True
|
||||
if ri.field is rj.field:
|
||||
messages.append(f"FAILURE: intersection between label and entry bounding boxes for `{ri.field['description']}` ({ri.rect}, {rj.rect})")
|
||||
else:
|
||||
messages.append(f"FAILURE: intersection between {ri.rect_type} bounding box for `{ri.field['description']}` ({ri.rect}) and {rj.rect_type} bounding box for `{rj.field['description']}` ({rj.rect})")
|
||||
if len(messages) >= 20:
|
||||
messages.append("Aborting further checks; fix bounding boxes and try again")
|
||||
return messages
|
||||
if ri.rect_type == "entry":
|
||||
if "entry_text" in ri.field:
|
||||
font_size = ri.field["entry_text"].get("font_size", 14)
|
||||
entry_height = ri.rect[3] - ri.rect[1]
|
||||
if entry_height < font_size:
|
||||
has_error = True
|
||||
messages.append(f"FAILURE: entry bounding box height ({entry_height}) for `{ri.field['description']}` is too short for the text content (font size: {font_size}). Increase the box height or decrease the font size.")
|
||||
if len(messages) >= 20:
|
||||
messages.append("Aborting further checks; fix bounding boxes and try again")
|
||||
return messages
|
||||
|
||||
if not has_error:
|
||||
messages.append("SUCCESS: All bounding boxes are valid")
|
||||
return messages
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: check_bounding_boxes.py [fields.json]")
|
||||
sys.exit(1)
|
||||
# Input file should be in the `fields.json` format described in forms.md.
|
||||
with open(sys.argv[1]) as f:
|
||||
messages = get_bounding_box_messages(f)
|
||||
for msg in messages:
|
||||
print(msg)
|
||||
226
.claude/skills/pdf/scripts/check_bounding_boxes_test.py
Normal file
226
.claude/skills/pdf/scripts/check_bounding_boxes_test.py
Normal file
@ -0,0 +1,226 @@
|
||||
import unittest
|
||||
import json
|
||||
import io
|
||||
from check_bounding_boxes import get_bounding_box_messages
|
||||
|
||||
|
||||
# Currently this is not run automatically in CI; it's just for documentation and manual checking.
|
||||
class TestGetBoundingBoxMessages(unittest.TestCase):
|
||||
|
||||
def create_json_stream(self, data):
|
||||
"""Helper to create a JSON stream from data"""
|
||||
return io.StringIO(json.dumps(data))
|
||||
|
||||
def test_no_intersections(self):
|
||||
"""Test case with no bounding box intersections"""
|
||||
data = {
|
||||
"form_fields": [
|
||||
{
|
||||
"description": "Name",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 10, 50, 30],
|
||||
"entry_bounding_box": [60, 10, 150, 30]
|
||||
},
|
||||
{
|
||||
"description": "Email",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 40, 50, 60],
|
||||
"entry_bounding_box": [60, 40, 150, 60]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
stream = self.create_json_stream(data)
|
||||
messages = get_bounding_box_messages(stream)
|
||||
self.assertTrue(any("SUCCESS" in msg for msg in messages))
|
||||
self.assertFalse(any("FAILURE" in msg for msg in messages))
|
||||
|
||||
def test_label_entry_intersection_same_field(self):
|
||||
"""Test intersection between label and entry of the same field"""
|
||||
data = {
|
||||
"form_fields": [
|
||||
{
|
||||
"description": "Name",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 10, 60, 30],
|
||||
"entry_bounding_box": [50, 10, 150, 30] # Overlaps with label
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
stream = self.create_json_stream(data)
|
||||
messages = get_bounding_box_messages(stream)
|
||||
self.assertTrue(any("FAILURE" in msg and "intersection" in msg for msg in messages))
|
||||
self.assertFalse(any("SUCCESS" in msg for msg in messages))
|
||||
|
||||
def test_intersection_between_different_fields(self):
|
||||
"""Test intersection between bounding boxes of different fields"""
|
||||
data = {
|
||||
"form_fields": [
|
||||
{
|
||||
"description": "Name",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 10, 50, 30],
|
||||
"entry_bounding_box": [60, 10, 150, 30]
|
||||
},
|
||||
{
|
||||
"description": "Email",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [40, 20, 80, 40], # Overlaps with Name's boxes
|
||||
"entry_bounding_box": [160, 10, 250, 30]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
stream = self.create_json_stream(data)
|
||||
messages = get_bounding_box_messages(stream)
|
||||
self.assertTrue(any("FAILURE" in msg and "intersection" in msg for msg in messages))
|
||||
self.assertFalse(any("SUCCESS" in msg for msg in messages))
|
||||
|
||||
def test_different_pages_no_intersection(self):
|
||||
"""Test that boxes on different pages don't count as intersecting"""
|
||||
data = {
|
||||
"form_fields": [
|
||||
{
|
||||
"description": "Name",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 10, 50, 30],
|
||||
"entry_bounding_box": [60, 10, 150, 30]
|
||||
},
|
||||
{
|
||||
"description": "Email",
|
||||
"page_number": 2,
|
||||
"label_bounding_box": [10, 10, 50, 30], # Same coordinates but different page
|
||||
"entry_bounding_box": [60, 10, 150, 30]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
stream = self.create_json_stream(data)
|
||||
messages = get_bounding_box_messages(stream)
|
||||
self.assertTrue(any("SUCCESS" in msg for msg in messages))
|
||||
self.assertFalse(any("FAILURE" in msg for msg in messages))
|
||||
|
||||
def test_entry_height_too_small(self):
|
||||
"""Test that entry box height is checked against font size"""
|
||||
data = {
|
||||
"form_fields": [
|
||||
{
|
||||
"description": "Name",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 10, 50, 30],
|
||||
"entry_bounding_box": [60, 10, 150, 20], # Height is 10
|
||||
"entry_text": {
|
||||
"font_size": 14 # Font size larger than height
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
stream = self.create_json_stream(data)
|
||||
messages = get_bounding_box_messages(stream)
|
||||
self.assertTrue(any("FAILURE" in msg and "height" in msg for msg in messages))
|
||||
self.assertFalse(any("SUCCESS" in msg for msg in messages))
|
||||
|
||||
def test_entry_height_adequate(self):
|
||||
"""Test that adequate entry box height passes"""
|
||||
data = {
|
||||
"form_fields": [
|
||||
{
|
||||
"description": "Name",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 10, 50, 30],
|
||||
"entry_bounding_box": [60, 10, 150, 30], # Height is 20
|
||||
"entry_text": {
|
||||
"font_size": 14 # Font size smaller than height
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
stream = self.create_json_stream(data)
|
||||
messages = get_bounding_box_messages(stream)
|
||||
self.assertTrue(any("SUCCESS" in msg for msg in messages))
|
||||
self.assertFalse(any("FAILURE" in msg for msg in messages))
|
||||
|
||||
def test_default_font_size(self):
|
||||
"""Test that default font size is used when not specified"""
|
||||
data = {
|
||||
"form_fields": [
|
||||
{
|
||||
"description": "Name",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 10, 50, 30],
|
||||
"entry_bounding_box": [60, 10, 150, 20], # Height is 10
|
||||
"entry_text": {} # No font_size specified, should use default 14
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
stream = self.create_json_stream(data)
|
||||
messages = get_bounding_box_messages(stream)
|
||||
self.assertTrue(any("FAILURE" in msg and "height" in msg for msg in messages))
|
||||
self.assertFalse(any("SUCCESS" in msg for msg in messages))
|
||||
|
||||
def test_no_entry_text(self):
|
||||
"""Test that missing entry_text doesn't cause height check"""
|
||||
data = {
|
||||
"form_fields": [
|
||||
{
|
||||
"description": "Name",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 10, 50, 30],
|
||||
"entry_bounding_box": [60, 10, 150, 20] # Small height but no entry_text
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
stream = self.create_json_stream(data)
|
||||
messages = get_bounding_box_messages(stream)
|
||||
self.assertTrue(any("SUCCESS" in msg for msg in messages))
|
||||
self.assertFalse(any("FAILURE" in msg for msg in messages))
|
||||
|
||||
def test_multiple_errors_limit(self):
|
||||
"""Test that error messages are limited to prevent excessive output"""
|
||||
fields = []
|
||||
# Create many overlapping fields
|
||||
for i in range(25):
|
||||
fields.append({
|
||||
"description": f"Field{i}",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 10, 50, 30], # All overlap
|
||||
"entry_bounding_box": [20, 15, 60, 35] # All overlap
|
||||
})
|
||||
|
||||
data = {"form_fields": fields}
|
||||
|
||||
stream = self.create_json_stream(data)
|
||||
messages = get_bounding_box_messages(stream)
|
||||
# Should abort after ~20 messages
|
||||
self.assertTrue(any("Aborting" in msg for msg in messages))
|
||||
# Should have some FAILURE messages but not hundreds
|
||||
failure_count = sum(1 for msg in messages if "FAILURE" in msg)
|
||||
self.assertGreater(failure_count, 0)
|
||||
self.assertLess(len(messages), 30) # Should be limited
|
||||
|
||||
def test_edge_touching_boxes(self):
|
||||
"""Test that boxes touching at edges don't count as intersecting"""
|
||||
data = {
|
||||
"form_fields": [
|
||||
{
|
||||
"description": "Name",
|
||||
"page_number": 1,
|
||||
"label_bounding_box": [10, 10, 50, 30],
|
||||
"entry_bounding_box": [50, 10, 150, 30] # Touches at x=50
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
stream = self.create_json_stream(data)
|
||||
messages = get_bounding_box_messages(stream)
|
||||
self.assertTrue(any("SUCCESS" in msg for msg in messages))
|
||||
self.assertFalse(any("FAILURE" in msg for msg in messages))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
12
.claude/skills/pdf/scripts/check_fillable_fields.py
Normal file
12
.claude/skills/pdf/scripts/check_fillable_fields.py
Normal file
@ -0,0 +1,12 @@
|
||||
import sys
|
||||
from pypdf import PdfReader
|
||||
|
||||
|
||||
# Script for Claude to run to determine whether a PDF has fillable form fields. See forms.md.
|
||||
|
||||
|
||||
reader = PdfReader(sys.argv[1])
|
||||
if (reader.get_fields()):
|
||||
print("This PDF has fillable form fields")
|
||||
else:
|
||||
print("This PDF does not have fillable form fields; you will need to visually determine where to enter data")
|
||||
35
.claude/skills/pdf/scripts/convert_pdf_to_images.py
Normal file
35
.claude/skills/pdf/scripts/convert_pdf_to_images.py
Normal file
@ -0,0 +1,35 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pdf2image import convert_from_path
|
||||
|
||||
|
||||
# Converts each page of a PDF to a PNG image.
|
||||
|
||||
|
||||
def convert(pdf_path, output_dir, max_dim=1000):
|
||||
images = convert_from_path(pdf_path, dpi=200)
|
||||
|
||||
for i, image in enumerate(images):
|
||||
# Scale image if needed to keep width/height under `max_dim`
|
||||
width, height = image.size
|
||||
if width > max_dim or height > max_dim:
|
||||
scale_factor = min(max_dim / width, max_dim / height)
|
||||
new_width = int(width * scale_factor)
|
||||
new_height = int(height * scale_factor)
|
||||
image = image.resize((new_width, new_height))
|
||||
|
||||
image_path = os.path.join(output_dir, f"page_{i+1}.png")
|
||||
image.save(image_path)
|
||||
print(f"Saved page {i+1} as {image_path} (size: {image.size})")
|
||||
|
||||
print(f"Converted {len(images)} pages to PNG images")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: convert_pdf_to_images.py [input pdf] [output directory]")
|
||||
sys.exit(1)
|
||||
pdf_path = sys.argv[1]
|
||||
output_directory = sys.argv[2]
|
||||
convert(pdf_path, output_directory)
|
||||
41
.claude/skills/pdf/scripts/create_validation_image.py
Normal file
41
.claude/skills/pdf/scripts/create_validation_image.py
Normal file
@ -0,0 +1,41 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
|
||||
# Creates "validation" images with rectangles for the bounding box information that
|
||||
# Claude creates when determining where to add text annotations in PDFs. See forms.md.
|
||||
|
||||
|
||||
def create_validation_image(page_number, fields_json_path, input_path, output_path):
|
||||
# Input file should be in the `fields.json` format described in forms.md.
|
||||
with open(fields_json_path, 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
img = Image.open(input_path)
|
||||
draw = ImageDraw.Draw(img)
|
||||
num_boxes = 0
|
||||
|
||||
for field in data["form_fields"]:
|
||||
if field["page_number"] == page_number:
|
||||
entry_box = field['entry_bounding_box']
|
||||
label_box = field['label_bounding_box']
|
||||
# Draw red rectangle over entry bounding box and blue rectangle over the label.
|
||||
draw.rectangle(entry_box, outline='red', width=2)
|
||||
draw.rectangle(label_box, outline='blue', width=2)
|
||||
num_boxes += 2
|
||||
|
||||
img.save(output_path)
|
||||
print(f"Created validation image at {output_path} with {num_boxes} bounding boxes")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 5:
|
||||
print("Usage: create_validation_image.py [page number] [fields.json file] [input image path] [output image path]")
|
||||
sys.exit(1)
|
||||
page_number = int(sys.argv[1])
|
||||
fields_json_path = sys.argv[2]
|
||||
input_image_path = sys.argv[3]
|
||||
output_image_path = sys.argv[4]
|
||||
create_validation_image(page_number, fields_json_path, input_image_path, output_image_path)
|
||||
152
.claude/skills/pdf/scripts/extract_form_field_info.py
Normal file
152
.claude/skills/pdf/scripts/extract_form_field_info.py
Normal file
@ -0,0 +1,152 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
from pypdf import PdfReader
|
||||
|
||||
|
||||
# Extracts data for the fillable form fields in a PDF and outputs JSON that
|
||||
# Claude uses to fill the fields. See forms.md.
|
||||
|
||||
|
||||
# This matches the format used by PdfReader `get_fields` and `update_page_form_field_values` methods.
|
||||
def get_full_annotation_field_id(annotation):
|
||||
components = []
|
||||
while annotation:
|
||||
field_name = annotation.get('/T')
|
||||
if field_name:
|
||||
components.append(field_name)
|
||||
annotation = annotation.get('/Parent')
|
||||
return ".".join(reversed(components)) if components else None
|
||||
|
||||
|
||||
def make_field_dict(field, field_id):
|
||||
field_dict = {"field_id": field_id}
|
||||
ft = field.get('/FT')
|
||||
if ft == "/Tx":
|
||||
field_dict["type"] = "text"
|
||||
elif ft == "/Btn":
|
||||
field_dict["type"] = "checkbox" # radio groups handled separately
|
||||
states = field.get("/_States_", [])
|
||||
if len(states) == 2:
|
||||
# "/Off" seems to always be the unchecked value, as suggested by
|
||||
# https://opensource.adobe.com/dc-acrobat-sdk-docs/standards/pdfstandards/pdf/PDF32000_2008.pdf#page=448
|
||||
# It can be either first or second in the "/_States_" list.
|
||||
if "/Off" in states:
|
||||
field_dict["checked_value"] = states[0] if states[0] != "/Off" else states[1]
|
||||
field_dict["unchecked_value"] = "/Off"
|
||||
else:
|
||||
print(f"Unexpected state values for checkbox `${field_id}`. Its checked and unchecked values may not be correct; if you're trying to check it, visually verify the results.")
|
||||
field_dict["checked_value"] = states[0]
|
||||
field_dict["unchecked_value"] = states[1]
|
||||
elif ft == "/Ch":
|
||||
field_dict["type"] = "choice"
|
||||
states = field.get("/_States_", [])
|
||||
field_dict["choice_options"] = [{
|
||||
"value": state[0],
|
||||
"text": state[1],
|
||||
} for state in states]
|
||||
else:
|
||||
field_dict["type"] = f"unknown ({ft})"
|
||||
return field_dict
|
||||
|
||||
|
||||
# Returns a list of fillable PDF fields:
|
||||
# [
|
||||
# {
|
||||
# "field_id": "name",
|
||||
# "page": 1,
|
||||
# "type": ("text", "checkbox", "radio_group", or "choice")
|
||||
# // Per-type additional fields described in forms.md
|
||||
# },
|
||||
# ]
|
||||
def get_field_info(reader: PdfReader):
|
||||
fields = reader.get_fields()
|
||||
|
||||
field_info_by_id = {}
|
||||
possible_radio_names = set()
|
||||
|
||||
for field_id, field in fields.items():
|
||||
# Skip if this is a container field with children, except that it might be
|
||||
# a parent group for radio button options.
|
||||
if field.get("/Kids"):
|
||||
if field.get("/FT") == "/Btn":
|
||||
possible_radio_names.add(field_id)
|
||||
continue
|
||||
field_info_by_id[field_id] = make_field_dict(field, field_id)
|
||||
|
||||
# Bounding rects are stored in annotations in page objects.
|
||||
|
||||
# Radio button options have a separate annotation for each choice;
|
||||
# all choices have the same field name.
|
||||
# See https://westhealth.github.io/exploring-fillable-forms-with-pdfrw.html
|
||||
radio_fields_by_id = {}
|
||||
|
||||
for page_index, page in enumerate(reader.pages):
|
||||
annotations = page.get('/Annots', [])
|
||||
for ann in annotations:
|
||||
field_id = get_full_annotation_field_id(ann)
|
||||
if field_id in field_info_by_id:
|
||||
field_info_by_id[field_id]["page"] = page_index + 1
|
||||
field_info_by_id[field_id]["rect"] = ann.get('/Rect')
|
||||
elif field_id in possible_radio_names:
|
||||
try:
|
||||
# ann['/AP']['/N'] should have two items. One of them is '/Off',
|
||||
# the other is the active value.
|
||||
on_values = [v for v in ann["/AP"]["/N"] if v != "/Off"]
|
||||
except KeyError:
|
||||
continue
|
||||
if len(on_values) == 1:
|
||||
rect = ann.get("/Rect")
|
||||
if field_id not in radio_fields_by_id:
|
||||
radio_fields_by_id[field_id] = {
|
||||
"field_id": field_id,
|
||||
"type": "radio_group",
|
||||
"page": page_index + 1,
|
||||
"radio_options": [],
|
||||
}
|
||||
# Note: at least on macOS 15.7, Preview.app doesn't show selected
|
||||
# radio buttons correctly. (It does if you remove the leading slash
|
||||
# from the value, but that causes them not to appear correctly in
|
||||
# Chrome/Firefox/Acrobat/etc).
|
||||
radio_fields_by_id[field_id]["radio_options"].append({
|
||||
"value": on_values[0],
|
||||
"rect": rect,
|
||||
})
|
||||
|
||||
# Some PDFs have form field definitions without corresponding annotations,
|
||||
# so we can't tell where they are. Ignore these fields for now.
|
||||
fields_with_location = []
|
||||
for field_info in field_info_by_id.values():
|
||||
if "page" in field_info:
|
||||
fields_with_location.append(field_info)
|
||||
else:
|
||||
print(f"Unable to determine location for field id: {field_info.get('field_id')}, ignoring")
|
||||
|
||||
# Sort by page number, then Y position (flipped in PDF coordinate system), then X.
|
||||
def sort_key(f):
|
||||
if "radio_options" in f:
|
||||
rect = f["radio_options"][0]["rect"] or [0, 0, 0, 0]
|
||||
else:
|
||||
rect = f.get("rect") or [0, 0, 0, 0]
|
||||
adjusted_position = [-rect[1], rect[0]]
|
||||
return [f.get("page"), adjusted_position]
|
||||
|
||||
sorted_fields = fields_with_location + list(radio_fields_by_id.values())
|
||||
sorted_fields.sort(key=sort_key)
|
||||
|
||||
return sorted_fields
|
||||
|
||||
|
||||
def write_field_info(pdf_path: str, json_output_path: str):
|
||||
reader = PdfReader(pdf_path)
|
||||
field_info = get_field_info(reader)
|
||||
with open(json_output_path, "w") as f:
|
||||
json.dump(field_info, f, indent=2)
|
||||
print(f"Wrote {len(field_info)} fields to {json_output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: extract_form_field_info.py [input pdf] [output json]")
|
||||
sys.exit(1)
|
||||
write_field_info(sys.argv[1], sys.argv[2])
|
||||
114
.claude/skills/pdf/scripts/fill_fillable_fields.py
Normal file
114
.claude/skills/pdf/scripts/fill_fillable_fields.py
Normal file
@ -0,0 +1,114 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
|
||||
from extract_form_field_info import get_field_info
|
||||
|
||||
|
||||
# Fills fillable form fields in a PDF. See forms.md.
|
||||
|
||||
|
||||
def fill_pdf_fields(input_pdf_path: str, fields_json_path: str, output_pdf_path: str):
|
||||
with open(fields_json_path) as f:
|
||||
fields = json.load(f)
|
||||
# Group by page number.
|
||||
fields_by_page = {}
|
||||
for field in fields:
|
||||
if "value" in field:
|
||||
field_id = field["field_id"]
|
||||
page = field["page"]
|
||||
if page not in fields_by_page:
|
||||
fields_by_page[page] = {}
|
||||
fields_by_page[page][field_id] = field["value"]
|
||||
|
||||
reader = PdfReader(input_pdf_path)
|
||||
|
||||
has_error = False
|
||||
field_info = get_field_info(reader)
|
||||
fields_by_ids = {f["field_id"]: f for f in field_info}
|
||||
for field in fields:
|
||||
existing_field = fields_by_ids.get(field["field_id"])
|
||||
if not existing_field:
|
||||
has_error = True
|
||||
print(f"ERROR: `{field['field_id']}` is not a valid field ID")
|
||||
elif field["page"] != existing_field["page"]:
|
||||
has_error = True
|
||||
print(f"ERROR: Incorrect page number for `{field['field_id']}` (got {field['page']}, expected {existing_field['page']})")
|
||||
else:
|
||||
if "value" in field:
|
||||
err = validation_error_for_field_value(existing_field, field["value"])
|
||||
if err:
|
||||
print(err)
|
||||
has_error = True
|
||||
if has_error:
|
||||
sys.exit(1)
|
||||
|
||||
writer = PdfWriter(clone_from=reader)
|
||||
for page, field_values in fields_by_page.items():
|
||||
writer.update_page_form_field_values(writer.pages[page - 1], field_values, auto_regenerate=False)
|
||||
|
||||
# This seems to be necessary for many PDF viewers to format the form values correctly.
|
||||
# It may cause the viewer to show a "save changes" dialog even if the user doesn't make any changes.
|
||||
writer.set_need_appearances_writer(True)
|
||||
|
||||
with open(output_pdf_path, "wb") as f:
|
||||
writer.write(f)
|
||||
|
||||
|
||||
def validation_error_for_field_value(field_info, field_value):
|
||||
field_type = field_info["type"]
|
||||
field_id = field_info["field_id"]
|
||||
if field_type == "checkbox":
|
||||
checked_val = field_info["checked_value"]
|
||||
unchecked_val = field_info["unchecked_value"]
|
||||
if field_value != checked_val and field_value != unchecked_val:
|
||||
return f'ERROR: Invalid value "{field_value}" for checkbox field "{field_id}". The checked value is "{checked_val}" and the unchecked value is "{unchecked_val}"'
|
||||
elif field_type == "radio_group":
|
||||
option_values = [opt["value"] for opt in field_info["radio_options"]]
|
||||
if field_value not in option_values:
|
||||
return f'ERROR: Invalid value "{field_value}" for radio group field "{field_id}". Valid values are: {option_values}'
|
||||
elif field_type == "choice":
|
||||
choice_values = [opt["value"] for opt in field_info["choice_options"]]
|
||||
if field_value not in choice_values:
|
||||
return f'ERROR: Invalid value "{field_value}" for choice field "{field_id}". Valid values are: {choice_values}'
|
||||
return None
|
||||
|
||||
|
||||
# pypdf (at least version 5.7.0) has a bug when setting the value for a selection list field.
|
||||
# In _writer.py around line 966:
|
||||
#
|
||||
# if field.get(FA.FT, "/Tx") == "/Ch" and field_flags & FA.FfBits.Combo == 0:
|
||||
# txt = "\n".join(annotation.get_inherited(FA.Opt, []))
|
||||
#
|
||||
# The problem is that for selection lists, `get_inherited` returns a list of two-element lists like
|
||||
# [["value1", "Text 1"], ["value2", "Text 2"], ...]
|
||||
# This causes `join` to throw a TypeError because it expects an iterable of strings.
|
||||
# The horrible workaround is to patch `get_inherited` to return a list of the value strings.
|
||||
# We call the original method and adjust the return value only if the argument to `get_inherited`
|
||||
# is `FA.Opt` and if the return value is a list of two-element lists.
|
||||
def monkeypatch_pydpf_method():
|
||||
from pypdf.generic import DictionaryObject
|
||||
from pypdf.constants import FieldDictionaryAttributes
|
||||
|
||||
original_get_inherited = DictionaryObject.get_inherited
|
||||
|
||||
def patched_get_inherited(self, key: str, default = None):
|
||||
result = original_get_inherited(self, key, default)
|
||||
if key == FieldDictionaryAttributes.Opt:
|
||||
if isinstance(result, list) and all(isinstance(v, list) and len(v) == 2 for v in result):
|
||||
result = [r[0] for r in result]
|
||||
return result
|
||||
|
||||
DictionaryObject.get_inherited = patched_get_inherited
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: fill_fillable_fields.py [input pdf] [field_values.json] [output pdf]")
|
||||
sys.exit(1)
|
||||
monkeypatch_pydpf_method()
|
||||
input_pdf = sys.argv[1]
|
||||
fields_json = sys.argv[2]
|
||||
output_pdf = sys.argv[3]
|
||||
fill_pdf_fields(input_pdf, fields_json, output_pdf)
|
||||
108
.claude/skills/pdf/scripts/fill_pdf_form_with_annotations.py
Normal file
108
.claude/skills/pdf/scripts/fill_pdf_form_with_annotations.py
Normal file
@ -0,0 +1,108 @@
|
||||
import json
|
||||
import sys
|
||||
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
from pypdf.annotations import FreeText
|
||||
|
||||
|
||||
# Fills a PDF by adding text annotations defined in `fields.json`. See forms.md.
|
||||
|
||||
|
||||
def transform_coordinates(bbox, image_width, image_height, pdf_width, pdf_height):
|
||||
"""Transform bounding box from image coordinates to PDF coordinates"""
|
||||
# Image coordinates: origin at top-left, y increases downward
|
||||
# PDF coordinates: origin at bottom-left, y increases upward
|
||||
x_scale = pdf_width / image_width
|
||||
y_scale = pdf_height / image_height
|
||||
|
||||
left = bbox[0] * x_scale
|
||||
right = bbox[2] * x_scale
|
||||
|
||||
# Flip Y coordinates for PDF
|
||||
top = pdf_height - (bbox[1] * y_scale)
|
||||
bottom = pdf_height - (bbox[3] * y_scale)
|
||||
|
||||
return left, bottom, right, top
|
||||
|
||||
|
||||
def fill_pdf_form(input_pdf_path, fields_json_path, output_pdf_path):
|
||||
"""Fill the PDF form with data from fields.json"""
|
||||
|
||||
# `fields.json` format described in forms.md.
|
||||
with open(fields_json_path, "r") as f:
|
||||
fields_data = json.load(f)
|
||||
|
||||
# Open the PDF
|
||||
reader = PdfReader(input_pdf_path)
|
||||
writer = PdfWriter()
|
||||
|
||||
# Copy all pages to writer
|
||||
writer.append(reader)
|
||||
|
||||
# Get PDF dimensions for each page
|
||||
pdf_dimensions = {}
|
||||
for i, page in enumerate(reader.pages):
|
||||
mediabox = page.mediabox
|
||||
pdf_dimensions[i + 1] = [mediabox.width, mediabox.height]
|
||||
|
||||
# Process each form field
|
||||
annotations = []
|
||||
for field in fields_data["form_fields"]:
|
||||
page_num = field["page_number"]
|
||||
|
||||
# Get page dimensions and transform coordinates.
|
||||
page_info = next(p for p in fields_data["pages"] if p["page_number"] == page_num)
|
||||
image_width = page_info["image_width"]
|
||||
image_height = page_info["image_height"]
|
||||
pdf_width, pdf_height = pdf_dimensions[page_num]
|
||||
|
||||
transformed_entry_box = transform_coordinates(
|
||||
field["entry_bounding_box"],
|
||||
image_width, image_height,
|
||||
pdf_width, pdf_height
|
||||
)
|
||||
|
||||
# Skip empty fields
|
||||
if "entry_text" not in field or "text" not in field["entry_text"]:
|
||||
continue
|
||||
entry_text = field["entry_text"]
|
||||
text = entry_text["text"]
|
||||
if not text:
|
||||
continue
|
||||
|
||||
font_name = entry_text.get("font", "Arial")
|
||||
font_size = str(entry_text.get("font_size", 14)) + "pt"
|
||||
font_color = entry_text.get("font_color", "000000")
|
||||
|
||||
# Font size/color seems to not work reliably across viewers:
|
||||
# https://github.com/py-pdf/pypdf/issues/2084
|
||||
annotation = FreeText(
|
||||
text=text,
|
||||
rect=transformed_entry_box,
|
||||
font=font_name,
|
||||
font_size=font_size,
|
||||
font_color=font_color,
|
||||
border_color=None,
|
||||
background_color=None,
|
||||
)
|
||||
annotations.append(annotation)
|
||||
# page_number is 0-based for pypdf
|
||||
writer.add_annotation(page_number=page_num - 1, annotation=annotation)
|
||||
|
||||
# Save the filled PDF
|
||||
with open(output_pdf_path, "wb") as output:
|
||||
writer.write(output)
|
||||
|
||||
print(f"Successfully filled PDF form and saved to {output_pdf_path}")
|
||||
print(f"Added {len(annotations)} text annotations")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: fill_pdf_form_with_annotations.py [input pdf] [fields.json] [output pdf]")
|
||||
sys.exit(1)
|
||||
input_pdf = sys.argv[1]
|
||||
fields_json = sys.argv[2]
|
||||
output_pdf = sys.argv[3]
|
||||
|
||||
fill_pdf_form(input_pdf, fields_json, output_pdf)
|
||||
261
.claude/skills/plotly/SKILL.md
Normal file
261
.claude/skills/plotly/SKILL.md
Normal file
@ -0,0 +1,261 @@
|
||||
---
|
||||
name: plotly
|
||||
description: Interactive scientific and statistical data visualization library for Python. Use when creating charts, plots, or visualizations including scatter plots, line charts, bar charts, heatmaps, 3D plots, geographic maps, statistical distributions, financial charts, and dashboards. Supports both quick visualizations (Plotly Express) and fine-grained customization (graph objects). Outputs interactive HTML or static images (PNG, PDF, SVG).
|
||||
---
|
||||
|
||||
# Plotly
|
||||
|
||||
Python graphing library for creating interactive, publication-quality visualizations with 40+ chart types.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Install Plotly:
|
||||
```bash
|
||||
uv pip install plotly
|
||||
```
|
||||
|
||||
Basic usage with Plotly Express (high-level API):
|
||||
```python
|
||||
import plotly.express as px
|
||||
import pandas as pd
|
||||
|
||||
df = pd.DataFrame({
|
||||
'x': [1, 2, 3, 4],
|
||||
'y': [10, 11, 12, 13]
|
||||
})
|
||||
|
||||
fig = px.scatter(df, x='x', y='y', title='My First Plot')
|
||||
fig.show()
|
||||
```
|
||||
|
||||
## Choosing Between APIs
|
||||
|
||||
### Use Plotly Express (px)
|
||||
For quick, standard visualizations with sensible defaults:
|
||||
- Working with pandas DataFrames
|
||||
- Creating common chart types (scatter, line, bar, histogram, etc.)
|
||||
- Need automatic color encoding and legends
|
||||
- Want minimal code (1-5 lines)
|
||||
|
||||
See [reference/plotly-express.md](reference/plotly-express.md) for complete guide.
|
||||
|
||||
### Use Graph Objects (go)
|
||||
For fine-grained control and custom visualizations:
|
||||
- Chart types not in Plotly Express (3D mesh, isosurface, complex financial charts)
|
||||
- Building complex multi-trace figures from scratch
|
||||
- Need precise control over individual components
|
||||
- Creating specialized visualizations with custom shapes and annotations
|
||||
|
||||
See [reference/graph-objects.md](reference/graph-objects.md) for complete guide.
|
||||
|
||||
**Note:** Plotly Express returns graph objects Figure, so you can combine approaches:
|
||||
```python
|
||||
fig = px.scatter(df, x='x', y='y')
|
||||
fig.update_layout(title='Custom Title') # Use go methods on px figure
|
||||
fig.add_hline(y=10) # Add shapes
|
||||
```
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
### 1. Chart Types
|
||||
|
||||
Plotly supports 40+ chart types organized into categories:
|
||||
|
||||
**Basic Charts:** scatter, line, bar, pie, area, bubble
|
||||
|
||||
**Statistical Charts:** histogram, box plot, violin, distribution, error bars
|
||||
|
||||
**Scientific Charts:** heatmap, contour, ternary, image display
|
||||
|
||||
**Financial Charts:** candlestick, OHLC, waterfall, funnel, time series
|
||||
|
||||
**Maps:** scatter maps, choropleth, density maps (geographic visualization)
|
||||
|
||||
**3D Charts:** scatter3d, surface, mesh, cone, volume
|
||||
|
||||
**Specialized:** sunburst, treemap, sankey, parallel coordinates, gauge
|
||||
|
||||
For detailed examples and usage of all chart types, see [reference/chart-types.md](reference/chart-types.md).
|
||||
|
||||
### 2. Layouts and Styling
|
||||
|
||||
**Subplots:** Create multi-plot figures with shared axes:
|
||||
```python
|
||||
from plotly.subplots import make_subplots
|
||||
import plotly.graph_objects as go
|
||||
|
||||
fig = make_subplots(rows=2, cols=2, subplot_titles=('A', 'B', 'C', 'D'))
|
||||
fig.add_trace(go.Scatter(x=[1, 2], y=[3, 4]), row=1, col=1)
|
||||
```
|
||||
|
||||
**Templates:** Apply coordinated styling:
|
||||
```python
|
||||
fig = px.scatter(df, x='x', y='y', template='plotly_dark')
|
||||
# Built-in: plotly_white, plotly_dark, ggplot2, seaborn, simple_white
|
||||
```
|
||||
|
||||
**Customization:** Control every aspect of appearance:
|
||||
- Colors (discrete sequences, continuous scales)
|
||||
- Fonts and text
|
||||
- Axes (ranges, ticks, grids)
|
||||
- Legends
|
||||
- Margins and sizing
|
||||
- Annotations and shapes
|
||||
|
||||
For complete layout and styling options, see [reference/layouts-styling.md](reference/layouts-styling.md).
|
||||
|
||||
### 3. Interactivity
|
||||
|
||||
Built-in interactive features:
|
||||
- Hover tooltips with customizable data
|
||||
- Pan and zoom
|
||||
- Legend toggling
|
||||
- Box/lasso selection
|
||||
- Rangesliders for time series
|
||||
- Buttons and dropdowns
|
||||
- Animations
|
||||
|
||||
```python
|
||||
# Custom hover template
|
||||
fig.update_traces(
|
||||
hovertemplate='<b>%{x}</b><br>Value: %{y:.2f}<extra></extra>'
|
||||
)
|
||||
|
||||
# Add rangeslider
|
||||
fig.update_xaxes(rangeslider_visible=True)
|
||||
|
||||
# Animations
|
||||
fig = px.scatter(df, x='x', y='y', animation_frame='year')
|
||||
```
|
||||
|
||||
For complete interactivity guide, see [reference/export-interactivity.md](reference/export-interactivity.md).
|
||||
|
||||
### 4. Export Options
|
||||
|
||||
**Interactive HTML:**
|
||||
```python
|
||||
fig.write_html('chart.html') # Full standalone
|
||||
fig.write_html('chart.html', include_plotlyjs='cdn') # Smaller file
|
||||
```
|
||||
|
||||
**Static Images (requires kaleido):**
|
||||
```bash
|
||||
uv pip install kaleido
|
||||
```
|
||||
|
||||
```python
|
||||
fig.write_image('chart.png') # PNG
|
||||
fig.write_image('chart.pdf') # PDF
|
||||
fig.write_image('chart.svg') # SVG
|
||||
```
|
||||
|
||||
For complete export options, see [reference/export-interactivity.md](reference/export-interactivity.md).
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Scientific Data Visualization
|
||||
|
||||
```python
|
||||
import plotly.express as px
|
||||
|
||||
# Scatter plot with trendline
|
||||
fig = px.scatter(df, x='temperature', y='yield', trendline='ols')
|
||||
|
||||
# Heatmap from matrix
|
||||
fig = px.imshow(correlation_matrix, text_auto=True, color_continuous_scale='RdBu')
|
||||
|
||||
# 3D surface plot
|
||||
import plotly.graph_objects as go
|
||||
fig = go.Figure(data=[go.Surface(z=z_data, x=x_data, y=y_data)])
|
||||
```
|
||||
|
||||
### Statistical Analysis
|
||||
|
||||
```python
|
||||
# Distribution comparison
|
||||
fig = px.histogram(df, x='values', color='group', marginal='box', nbins=30)
|
||||
|
||||
# Box plot with all points
|
||||
fig = px.box(df, x='category', y='value', points='all')
|
||||
|
||||
# Violin plot
|
||||
fig = px.violin(df, x='group', y='measurement', box=True)
|
||||
```
|
||||
|
||||
### Time Series and Financial
|
||||
|
||||
```python
|
||||
# Time series with rangeslider
|
||||
fig = px.line(df, x='date', y='price')
|
||||
fig.update_xaxes(rangeslider_visible=True)
|
||||
|
||||
# Candlestick chart
|
||||
import plotly.graph_objects as go
|
||||
fig = go.Figure(data=[go.Candlestick(
|
||||
x=df['date'],
|
||||
open=df['open'],
|
||||
high=df['high'],
|
||||
low=df['low'],
|
||||
close=df['close']
|
||||
)])
|
||||
```
|
||||
|
||||
### Multi-Plot Dashboards
|
||||
|
||||
```python
|
||||
from plotly.subplots import make_subplots
|
||||
import plotly.graph_objects as go
|
||||
|
||||
fig = make_subplots(
|
||||
rows=2, cols=2,
|
||||
subplot_titles=('Scatter', 'Bar', 'Histogram', 'Box'),
|
||||
specs=[[{'type': 'scatter'}, {'type': 'bar'}],
|
||||
[{'type': 'histogram'}, {'type': 'box'}]]
|
||||
)
|
||||
|
||||
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6]), row=1, col=1)
|
||||
fig.add_trace(go.Bar(x=['A', 'B'], y=[1, 2]), row=1, col=2)
|
||||
fig.add_trace(go.Histogram(x=data), row=2, col=1)
|
||||
fig.add_trace(go.Box(y=data), row=2, col=2)
|
||||
|
||||
fig.update_layout(height=800, showlegend=False)
|
||||
```
|
||||
|
||||
## Integration with Dash
|
||||
|
||||
For interactive web applications, use Dash (Plotly's web app framework):
|
||||
|
||||
```bash
|
||||
uv pip install dash
|
||||
```
|
||||
|
||||
```python
|
||||
import dash
|
||||
from dash import dcc, html
|
||||
import plotly.express as px
|
||||
|
||||
app = dash.Dash(__name__)
|
||||
|
||||
fig = px.scatter(df, x='x', y='y')
|
||||
|
||||
app.layout = html.Div([
|
||||
html.H1('Dashboard'),
|
||||
dcc.Graph(figure=fig)
|
||||
])
|
||||
|
||||
app.run_server(debug=True)
|
||||
```
|
||||
|
||||
## Reference Files
|
||||
|
||||
- **[plotly-express.md](reference/plotly-express.md)** - High-level API for quick visualizations
|
||||
- **[graph-objects.md](reference/graph-objects.md)** - Low-level API for fine-grained control
|
||||
- **[chart-types.md](reference/chart-types.md)** - Complete catalog of 40+ chart types with examples
|
||||
- **[layouts-styling.md](reference/layouts-styling.md)** - Subplots, templates, colors, customization
|
||||
- **[export-interactivity.md](reference/export-interactivity.md)** - Export options and interactive features
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- Official documentation: https://plotly.com/python/
|
||||
- API reference: https://plotly.com/python-api-reference/
|
||||
- Community forum: https://community.plotly.com/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user