Initial commit: merged, deduplicated, and vetted skill collection
Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:
- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
- docx family: kept docx (official, full toolchain) + docx-cn
(GB/T 9704 Chinese official-document constants),
dropped docx_writer (no scripts, name collided with docx)
- humanizer family: kept humanizer-zh (6 zh reference docs),
dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
live credentials
Result: 25 skills, 572 files, ~7.5 MB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
a65adcc2e5
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
# Editor / IDE
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*~
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
# Local secrets
|
||||
.env
|
||||
.env.local
|
||||
*.local
|
||||
|
||||
# Skill-generated artifacts
|
||||
**/output/
|
||||
**/.agent/runs/
|
||||
84
README.md
Normal file
84
README.md
Normal file
@ -0,0 +1,84 @@
|
||||
# Skills
|
||||
|
||||
Claude Code 技能集 — 金融数据、文档处理、数据可视化、联网搜索、写作与元技能。
|
||||
|
||||
## 目录
|
||||
|
||||
### 金融数据与研究
|
||||
|
||||
| 技能 | 说明 |
|
||||
|------|------|
|
||||
| [windpy-sdk](windpy-sdk/) | Wind 金融终端 Python API — 17 个函数、常驻服务、13 份参考文档,覆盖 A 股/债券/基金/期货/期权/外汇/宏观 |
|
||||
| [tushare-data-1.0.5](tushare-data-1.0.5/) | Tushare 财经数据接口 — 股票/基金/期货/数字货币行情 + 公司财务/基金经理基本面 |
|
||||
| [ifind-http-api](ifind-http-api/) | 同花顺 iFinD HTTP API — 18 个端点参考手册,支持股票/基金/债券/期货/期权/宏观数据 |
|
||||
| [gangtise](gangtise/) | 冈底斯港股/A 股研报知识库 + 投研 AI Agent + 经济指标查询 |
|
||||
| [annuity-disclosure-crawler](annuity-disclosure-crawler/) | 企业年金季度管理报告全量爬虫 — 22 家投资管理人官网披露栏目 |
|
||||
| [asset-monitor](asset-monitor/) | 资产异常波动日频监控 — Z-Score 检测偏离 > 2σ 的异常,自动生成 Excel 报告 |
|
||||
| [technical-analyst](technical-analyst/) | 周线 K 线技术分析 — 趋势识别、支撑阻力位、形态、概率场景推演(纯图表) |
|
||||
| [portfolio-analyzer](portfolio-analyzer/) | 投资组合多维分析 — 8 项风险指标、Brinson 三因素归因、6-sheet Excel 报告 |
|
||||
| [financial-report-writing](financial-report-writing/) | A 股上市公司投研报告写作 — 数据获取、分析、Word 输出全流程 |
|
||||
|
||||
### 文档与可视化
|
||||
|
||||
| 技能 | 说明 |
|
||||
|------|------|
|
||||
| [docx](docx/) | Word 文档创建/编辑 — 修订追踪、批注、格式保留、文本提取,含 docx-js + ooxml 工具链 |
|
||||
| [docx-cn](docx-cn/) | 中文公文 Word 文档(GB/T 9704-2012) — 依赖 `docx` skill,提供公文常量与排版规范 |
|
||||
| [xlsx](xlsx/) | Excel 电子表格 — 公式、格式、数据分析、可视化、公式重算 |
|
||||
| [pdf](pdf/) | PDF 处理工具包 — 文本/表格提取、创建、合并/拆分、表单填写 |
|
||||
| [pptx](pptx/) | PowerPoint 创建/编辑/分析 — 布局、母版、图表、演讲备注,含 OOXML schema |
|
||||
| [ppt-station-skill](ppt-station-skill/) | 数据 → 金融报告 PPT — 12 种页面布局、10 主题、ChartJunkCleaner、DeckLinter 合规审计 |
|
||||
| [fund-slides](fund-slides/) | 零依赖 HTML 演示 — 智能内容-可视化映射、ECharts 内联、中文字体支持 |
|
||||
| [plotly](plotly/) | Plotly 交互式可视化 — 散点/折线/热力/3D/地图/金融图表,输出 HTML 或静态图 |
|
||||
|
||||
### 联网搜索与工具箱
|
||||
|
||||
| 技能 | 说明 |
|
||||
|------|------|
|
||||
| [agentrs-search](agentrs-search/) | 京东 agentrs 网关四搜索引擎 — tavily / cloudsway(Bing)/ searchpro(搜狗)/ jdcloud RAG |
|
||||
| [agentrs-map](agentrs-map/) | 高德地图 6 工具 — POI / 地理编码 / 行政区 / IP 定位 / 步行导航 / 坐标转换 |
|
||||
| [agentrs-weather](agentrs-weather/) | 高德天气 — 实况 + 未来预报,支持 adcode 或地址自动解析 |
|
||||
| [zhipu-websearch](zhipu-websearch/) | 智谱 AI Web Search — 双引擎、时间过滤、域名过滤 |
|
||||
|
||||
### 写作与协作
|
||||
|
||||
| 技能 | 说明 |
|
||||
|------|------|
|
||||
| [humanizer-zh](humanizer-zh/) | 中文去 AI 痕迹 — 交互式工作流,33 种模式分 6 篇 reference 按需加载,支持学术/公文/商业报告 |
|
||||
| [ai-pair](ai-pair/) | 多模型协同 — 一个创作(Author),多个审阅(可配 Codex/Gemini/Kimi),覆盖不同 review 维度 |
|
||||
|
||||
### 元技能
|
||||
|
||||
| 技能 | 说明 |
|
||||
|------|------|
|
||||
| [skill-creator](skill-creator/) | 技能创建指南 — 帮助创建和优化 Claude Code skill |
|
||||
| [find-skills](find-skills/) | 技能发现与安装 — 从开源技能生态中搜索和安装 skill |
|
||||
|
||||
## 使用方式
|
||||
|
||||
将技能目录放入项目的 `.claude/skills/` 下,Claude Code 自动识别加载。
|
||||
|
||||
```bash
|
||||
# 克隆整个仓库
|
||||
git clone https://codehub.ablemind.cc/public/skills.git .claude/skills
|
||||
|
||||
# 或只安装单个技能
|
||||
cp -r windpy-sdk <your-project>/.claude/skills/windpy-sdk
|
||||
```
|
||||
|
||||
## 依赖关系
|
||||
|
||||
```
|
||||
asset-monitor ── uses ── WindPy SDK ── ref ── windpy-sdk
|
||||
portfolio-analyzer ── uses ── WindPy SDK ── ref ── windpy-sdk
|
||||
└─ uses ── xlsx(recalc.py 公式验证)
|
||||
docx-cn ── uses ── docx(unpack/pack/validate 工具链)
|
||||
其余 skill 均独立,无交叉依赖
|
||||
```
|
||||
|
||||
需要外部凭证的 skill(首次使用时会自动检测 env / .env,未命中则通过 AskUserQuestion 询问):
|
||||
|
||||
- `windpy-sdk` / `tushare-data-1.0.5` / `ifind-http-api` — 各自数据源账号
|
||||
- `gangtise` — `GANGTISE_TOKEN`
|
||||
- `agentrs-*` — `JD_AGENTRS_TOKEN`
|
||||
- `zhipu-websearch` — `ZHIPU_API_KEY`
|
||||
48
agentrs-map/SKILL.md
Normal file
48
agentrs-map/SKILL.md
Normal file
@ -0,0 +1,48 @@
|
||||
---
|
||||
name: agentrs-map
|
||||
description: >
|
||||
高德地图 / 地理工具箱 —— 通过京东 agentrs 代理网关,用单一 Bearer Token 调 6 个高德(Amap)工具:
|
||||
POI 地点检索、地理编码(地址↔经纬度/adcode)、行政区域查询、IP 定位、步行路径规划、坐标转换。
|
||||
当需要查地点经纬度与 adcode、找附近地点(POI)、做步行导航、转换坐标系、
|
||||
按 IP 定位、查行政区划时使用。触发词:高德、地图、经纬度、坐标、adcode、地理编码、
|
||||
POI、附近、步行路线、导航、坐标转换、IP定位、行政区、省市区。
|
||||
---
|
||||
|
||||
# agentrs-map —— 高德地图 / 地理(6 工具)
|
||||
|
||||
通过 `https://agentrs.jd.com/api/saas/proxy-k/v1/<tool>`,单一 Bearer Token 调 6 个高德工具。
|
||||
|
||||
## 鉴权与调用约定(先读)
|
||||
- 环境变量取密钥,**不硬编码**:`export JD_AGENTRS_TOKEN='你的token'`
|
||||
- **网关只认 JSON body**:文档标 GET/POST 都不影响,query string 会报"缺少必要输入参数"。
|
||||
- HTTP 恒 200,业务看 body:`1001`=token 未授权;`9999 缺少必要输入参数`=参数错;`9999 用户积分不足`=余额不足(重试常能通)。
|
||||
|
||||
## 6 个工具
|
||||
| 工具 | 入参 | 返回要点 |
|
||||
|---|---|---|
|
||||
| `poi` | `{keywords(必填), city(拼音如 beijing)}` | POI 文本检索(名虽 around 但**非按经纬度周边**);`pois[]` 含 rating/人均/营业时间/招牌菜/电话/location |
|
||||
| `geocode` | `{address, city}` | 地址→坐标;`geocodes[]` 含 location/adcode/level/district |
|
||||
| `region` | `{keywords, subdistrict, offset, page}` | 行政区树;`districts[]` 含 adcode/center/level/下级 |
|
||||
| `ip` | `{ip}`(留空取请求 IP,仅国内) | province/city/adcode/rectangle |
|
||||
| `walk` | `{origin:"lon,lat", destination:"lon,lat"}` | `route.paths[]` 含 distance/duration/steps[] 分段导航 |
|
||||
| `coord` | `{locations:"lon,lat", coordsys: gps/baidu/mapbar/autonavi}` | 转成高德坐标 |
|
||||
|
||||
## 用法
|
||||
```bash
|
||||
export JD_AGENTRS_TOKEN='...'
|
||||
python3 scripts/amap.py poi --keywords 故宫 --city beijing
|
||||
python3 scripts/amap.py geocode --address "北京市朝阳区建国路88号"
|
||||
python3 scripts/amap.py region --keywords 北京 --subdistrict 1
|
||||
python3 scripts/amap.py ip --ip 114.247.50.2
|
||||
python3 scripts/amap.py walk --origin 116.397455,39.909187 --destination 116.417854,39.914888
|
||||
python3 scripts/amap.py coord --locations 116.481499,39.990475 --coordsys gps
|
||||
```
|
||||
打印规整 JSON;遇 1001/9999 在 stderr 给可读提示。
|
||||
|
||||
## 组合范式
|
||||
- **"附近有什么 / 某地点信息"**:`poi --keywords X --city Y`。
|
||||
- **"A 到 B 步行怎么走"**:两端各 `geocode`/`poi` 拿经纬度 → `walk`。
|
||||
- **坐标系不一致**(GPS/百度坐标接入高德):先 `coord` 统一到高德。
|
||||
- **查天气**:`geocode` 拿到的 `adcode` 即天气入参 → 见姊妹 skill **agentrs-weather**。
|
||||
|
||||
> 联网搜索见 **agentrs-search**;天气见 **agentrs-weather**。三者共用同一个 JD_AGENTRS_TOKEN。
|
||||
107
agentrs-map/scripts/amap.py
Normal file
107
agentrs-map/scripts/amap.py
Normal file
@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
agentrs-map —— 京东 agentrs 代理网关的「高德地图 / 地理 / 天气」客户端。
|
||||
|
||||
封装 6 个高德(Amap)工具:
|
||||
poi(POI检索) / geocode(地理编码) / region(行政区) / ip(IP定位) /
|
||||
walk(步行路径) / coord(坐标转换)
|
||||
端点:https://agentrs.jd.com/api/saas/proxy-k/v1/<tool>
|
||||
|
||||
鉴权:环境变量 JD_AGENTRS_TOKEN(不硬编码密钥)。
|
||||
约定:网关一律从 JSON body 取参(query string 不生效);HTTP 恒 200,业务看 body。
|
||||
|
||||
注:天气查询见姊妹 skill agentrs-weather(geocode 拿到的 adcode 即天气入参)。
|
||||
|
||||
用法:
|
||||
python3 amap.py poi --keywords 故宫 --city beijing
|
||||
python3 amap.py geocode --address "北京市朝阳区建国路88号"
|
||||
python3 amap.py region --keywords 北京 --subdistrict 1
|
||||
python3 amap.py ip --ip 114.247.50.2
|
||||
python3 amap.py walk --origin 116.397455,39.909187 --destination 116.417854,39.914888
|
||||
python3 amap.py coord --locations 116.481499,39.990475 --coordsys gps
|
||||
"""
|
||||
import os, sys, json, argparse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://agentrs.jd.com/api/saas/proxy-k/v1"
|
||||
|
||||
|
||||
def _token():
|
||||
tok = os.environ.get("JD_AGENTRS_TOKEN")
|
||||
if not tok:
|
||||
sys.exit("错误:未设置 JD_AGENTRS_TOKEN。\n export JD_AGENTRS_TOKEN='你的token'")
|
||||
return tok
|
||||
|
||||
|
||||
def call(tool, body, timeout=30):
|
||||
payload = {k: v for k, v in body.items() if v not in ("", None)}
|
||||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
req = urllib.request.Request(f"{BASE}/{tool}", data=data, method="POST",
|
||||
headers={"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {_token()}"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as r:
|
||||
raw = r.read().decode("utf-8")
|
||||
except urllib.error.URLError as e:
|
||||
return {"_error": f"网络错误: {e}"}
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return {"_raw": raw}
|
||||
|
||||
|
||||
def biz_error(resp):
|
||||
if not isinstance(resp, dict):
|
||||
return None
|
||||
code, msg = resp.get("code"), resp.get("msg") or ""
|
||||
if code in (1001, "1001"):
|
||||
return f"[未授权 1001] 该 token 未开通此工具:{msg}"
|
||||
if code in (9999, "9999") or "积分不足" in msg or "缺少必要输入参数" in msg:
|
||||
return f"[业务失败 {code}] {msg}"
|
||||
return None
|
||||
|
||||
|
||||
# 7 个高德工具 -------------------------------------------------
|
||||
def poi(keywords, city="", **k):
|
||||
return call("search_around_gaode", dict(keywords=keywords, city=city))
|
||||
|
||||
def geocode(address, city="", **k):
|
||||
return call("geocoding", dict(address=address, city=city))
|
||||
|
||||
def region(keywords, subdistrict="", page="", offset="", extensions="", filter="", **k):
|
||||
return call("administrative_region_inquiry", dict(keywords=keywords,
|
||||
subdistrict=subdistrict, page=page, offset=offset,
|
||||
extensions=extensions, filter=filter))
|
||||
|
||||
def ip(ip="", **k):
|
||||
return call("ip_locating", dict(ip=ip))
|
||||
|
||||
def walk(origin, destination, origin_id="", destination_id="", **k):
|
||||
return call("pedestrian_path_planning", dict(origin=origin, destination=destination,
|
||||
origin_id=origin_id, destination_id=destination_id))
|
||||
|
||||
def coord(locations, coordsys="gps", **k):
|
||||
return call("coordinate_transformation", dict(locations=locations, coordsys=coordsys))
|
||||
|
||||
|
||||
TOOLS = {"poi": poi, "geocode": geocode, "region": region, "ip": ip,
|
||||
"walk": walk, "coord": coord}
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="京东 agentrs 高德地图/地理(6 工具)")
|
||||
p.add_argument("tool", choices=list(TOOLS))
|
||||
for opt in ("keywords", "city", "address", "subdistrict", "page", "offset",
|
||||
"extensions", "filter", "ip", "origin", "destination",
|
||||
"origin_id", "destination_id", "locations", "coordsys"):
|
||||
p.add_argument(f"--{opt}", default=None)
|
||||
a = p.parse_args()
|
||||
kw = {k: v for k, v in vars(a).items() if k != "tool" and v is not None}
|
||||
resp = TOOLS[a.tool](**kw)
|
||||
err = biz_error(resp)
|
||||
if err:
|
||||
print(err, file=sys.stderr)
|
||||
print(json.dumps(resp, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
50
agentrs-search/SKILL.md
Normal file
50
agentrs-search/SKILL.md
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
name: agentrs-search
|
||||
description: >
|
||||
联网搜索工具箱 —— 通过京东 agentrs 代理网关,用单一 Bearer Token 调用 4 个搜索引擎:
|
||||
tavily(AI搜索)、cloudsway(必应/Bing,可拿网页全文)、SearchPro(搜狗,实时新闻时效最强)、
|
||||
jdcloud_search(京东云RAG,返回整页正文)。当需要联网检索资讯、新闻、研报、实时信息,
|
||||
或要抓取网页正文做后续分析时使用。触发词:联网搜索、实时搜索、查资讯、查新闻、
|
||||
搜一下、tavily、bing、cloudsway、搜狗、京东搜索、web search、抓网页正文。
|
||||
---
|
||||
|
||||
# agentrs-search —— 联网搜索(4 引擎)
|
||||
|
||||
通过 `https://agentrs.jd.com/api/saas/proxy-k/v1/<tool>`,单一 Bearer Token 调 4 个搜索引擎。
|
||||
|
||||
## 鉴权与调用约定(先读)
|
||||
- 环境变量取密钥,**不硬编码**:`export JD_AGENTRS_TOKEN='你的token'`
|
||||
- **网关只认 JSON body**:文档标 GET/POST 都不影响,query string 会报"缺少必要输入参数"。
|
||||
- HTTP 恒 200,业务看 body:`1001`=token 未授权(充值/补参无用);`9999 缺少必要输入参数`=参数错;`9999 用户积分不足`=余额不足(**重试常能通**)。
|
||||
|
||||
## 4 个引擎怎么选
|
||||
| 引擎 | 底层 | 入参 | 结果路径 | 选用场景 |
|
||||
|---|---|---|---|---|
|
||||
| `tavily` | Tavily | `{query}` | `results[]` | 通用 AI 搜索,默认首选 |
|
||||
| `cloudsway` | Bing/必应 | `{q,count,freshness,enableContent,contentType,mainText,sites,blockWebsites}` | `webPages.value[]` | 中文好;`enableContent=true` 直接拿网页**正文**;`sites/blockWebsites` 站点过滤;`freshness=Day/Week/Month` 时间过滤 |
|
||||
| `searchpro` | 搜狗 | `{Query,Cnt,Site,Industry,...}` | `Response.Pages[]`(**元素是字符串化JSON,需二次 parse**) | **实时新闻/天气,时效性最强** |
|
||||
| `jdcloud` | 京东云 RAG | `{query}` | `data[]`(`page_content` 整页正文 + `recall_distance`) | 要**整页全文**做分析时 |
|
||||
|
||||
## 用法
|
||||
```bash
|
||||
export JD_AGENTRS_TOKEN='...'
|
||||
python3 scripts/search.py tavily "人工智能最新趋势"
|
||||
python3 scripts/search.py cloudsway "中信建投证券" --count 5 --enableContent true --mainText true
|
||||
python3 scripts/search.py searchpro "今天北京天气"
|
||||
python3 scripts/search.py jdcloud "AI agent 发展"
|
||||
```
|
||||
打印规整 JSON;遇 1001/9999 在 stderr 给可读提示。
|
||||
|
||||
## 直接 curl
|
||||
```bash
|
||||
curl -s https://agentrs.jd.com/api/saas/proxy-k/v1/tavily_search \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $JD_AGENTRS_TOKEN" \
|
||||
-d '{"query":"搜索关键词"}'
|
||||
```
|
||||
工具名换 `cloudsway`/`SearchPro`/`jdcloud_search`、body 换对应入参即可。
|
||||
|
||||
## 选用范式
|
||||
- 通用检索 → `tavily`;要最新时效 → `searchpro`;要网页正文做分析 → `jdcloud`(或 `cloudsway --enableContent true`);要限定站点 → `cloudsway --sites baijiahao.baidu.com`。
|
||||
|
||||
> 地图/经纬度/POI/路径 等见姊妹 skill **agentrs-map**;天气见 **agentrs-weather**。三者共用同一个 JD_AGENTRS_TOKEN。
|
||||
103
agentrs-search/scripts/search.py
Normal file
103
agentrs-search/scripts/search.py
Normal file
@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
agentrs-search —— 京东 agentrs 代理网关的「联网搜索」客户端。
|
||||
|
||||
封装 4 个搜索引擎:tavily / cloudsway(Bing) / SearchPro(搜狗) / jdcloud_search(京东云RAG)。
|
||||
端点:https://agentrs.jd.com/api/saas/proxy-k/v1/<tool>
|
||||
|
||||
鉴权:环境变量 JD_AGENTRS_TOKEN(不硬编码密钥)。
|
||||
export JD_AGENTRS_TOKEN='...'
|
||||
|
||||
约定:网关一律从 JSON body 取参(query string 不生效);HTTP 恒 200,业务看 body。
|
||||
1001=工具未授权 | 9999"缺少必要输入参数"=参数错 | 9999"积分不足"=余额不足(可重试)。
|
||||
|
||||
用法:
|
||||
python3 search.py tavily "人工智能最新趋势"
|
||||
python3 search.py cloudsway "中信建投证券" --count 5 --enableContent true --mainText true
|
||||
python3 search.py searchpro "今天北京天气"
|
||||
python3 search.py jdcloud "AI agent 发展"
|
||||
"""
|
||||
import os, sys, json, argparse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://agentrs.jd.com/api/saas/proxy-k/v1"
|
||||
|
||||
|
||||
def _token():
|
||||
tok = os.environ.get("JD_AGENTRS_TOKEN")
|
||||
if not tok:
|
||||
sys.exit("错误:未设置 JD_AGENTRS_TOKEN。\n export JD_AGENTRS_TOKEN='你的token'")
|
||||
return tok
|
||||
|
||||
|
||||
def call(tool, body, timeout=30):
|
||||
payload = {k: v for k, v in body.items() if v not in ("", None)}
|
||||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
req = urllib.request.Request(f"{BASE}/{tool}", data=data, method="POST",
|
||||
headers={"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {_token()}"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as r:
|
||||
raw = r.read().decode("utf-8")
|
||||
except urllib.error.URLError as e:
|
||||
return {"_error": f"网络错误: {e}"}
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return {"_raw": raw}
|
||||
|
||||
|
||||
def biz_error(resp):
|
||||
if not isinstance(resp, dict):
|
||||
return None
|
||||
code, msg = resp.get("code"), resp.get("msg") or ""
|
||||
if code in (1001, "1001"):
|
||||
return f"[未授权 1001] 该 token 未开通此工具:{msg}"
|
||||
if code in (9999, "9999") or "积分不足" in msg or "缺少必要输入参数" in msg:
|
||||
return f"[业务失败 {code}] {msg}"
|
||||
return None
|
||||
|
||||
|
||||
# 4 个搜索引擎 -------------------------------------------------
|
||||
def tavily(query, **k):
|
||||
return call("tavily_search", {"query": query})
|
||||
|
||||
def cloudsway(q, count="", freshness="", offset="", enableContent="",
|
||||
contentType="", mainText="", sites="", blockWebsites="", **k):
|
||||
return call("cloudsway", dict(q=q, count=count, freshness=freshness, offset=offset,
|
||||
enableContent=enableContent, contentType=contentType, mainText=mainText,
|
||||
sites=sites, blockWebsites=blockWebsites))
|
||||
|
||||
def searchpro(query, region="", mode="", site="", cnt="", industry="", **k):
|
||||
return call("SearchPro", dict(Query=query, Region=region, Mode=mode,
|
||||
site=site, Cnt=cnt, Industry=industry))
|
||||
|
||||
def jdcloud(query, **k):
|
||||
return call("jdcloud_search", {"query": query})
|
||||
|
||||
|
||||
TOOLS = {"tavily": tavily, "cloudsway": cloudsway,
|
||||
"searchpro": searchpro, "jdcloud": jdcloud}
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="京东 agentrs 联网搜索(4 引擎)")
|
||||
p.add_argument("engine", choices=list(TOOLS))
|
||||
p.add_argument("query", nargs="?", help="查询词")
|
||||
for opt in ("count", "freshness", "offset", "enableContent", "contentType",
|
||||
"mainText", "sites", "blockWebsites", "region", "mode",
|
||||
"site", "cnt", "industry"):
|
||||
p.add_argument(f"--{opt}", default=None)
|
||||
a = p.parse_args()
|
||||
kw = {k: v for k, v in vars(a).items()
|
||||
if k not in ("engine", "query") and v is not None}
|
||||
if a.query:
|
||||
kw["q" if a.engine == "cloudsway" else "query"] = a.query
|
||||
resp = TOOLS[a.engine](**kw)
|
||||
err = biz_error(resp)
|
||||
if err:
|
||||
print(err, file=sys.stderr)
|
||||
print(json.dumps(resp, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
51
agentrs-weather/SKILL.md
Normal file
51
agentrs-weather/SKILL.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
name: agentrs-weather
|
||||
description: >
|
||||
天气查询工具 —— 通过京东 agentrs 代理网关调用高德天气,返回城市实况(气温/天气/湿度/风力)
|
||||
或未来预报。支持直接传高德 adcode,也支持传城市名/地址自动解析 adcode。当用户问某地天气、
|
||||
气温、今天/明天下不下雨、出门带不带伞、未来几天天气这类问题时使用。
|
||||
触发词:天气、气温、下雨、温度、湿度、风力、今天天气、明天天气、未来天气、weather、带伞。
|
||||
---
|
||||
|
||||
# agentrs-weather —— 高德天气(实况 / 预报)
|
||||
|
||||
通过 `https://agentrs.jd.com/api/saas/proxy-k/v1/weather_inquiry` 查高德天气。
|
||||
|
||||
## 鉴权与调用约定(先读)
|
||||
- 环境变量取密钥,**不硬编码**:`export JD_AGENTRS_TOKEN='你的token'`
|
||||
- **网关只认 JSON body**(query string 不生效);HTTP 恒 200,业务看 body:`1001`=未授权 / `9999 缺少必要输入参数`=参数错 / `9999 用户积分不足`=余额不足(重试常能通)。
|
||||
|
||||
## 关键:入参是 adcode
|
||||
高德天气的 `city` 参数是**高德 adcode**(如北京=110100),**不是经纬度,也不是和风天气的 LocationID**。
|
||||
本 skill 的脚本内置「城市名→adcode」自动解析(借地理编码),所以两种方式都行:
|
||||
- 已知 adcode:`--city 110100`
|
||||
- 只知城市名:`--address 北京` / `--address 上海市黄浦区`(自动解析)
|
||||
|
||||
| 入参 | 说明 |
|
||||
|---|---|
|
||||
| `city` | 高德 adcode(与 `address` 二选一) |
|
||||
| `address` | 城市名/地址,脚本自动解析成 adcode |
|
||||
| `extensions` | `base`=实况(默认) / `all`=预报 |
|
||||
|
||||
返回 `lives[]`(实况)含 weather/temperature/humidity/winddirection/windpower/reporttime;
|
||||
`extensions=all` 时返回 `forecasts[]` 未来数日预报。
|
||||
|
||||
## 用法
|
||||
```bash
|
||||
export JD_AGENTRS_TOKEN='...'
|
||||
python3 scripts/weather.py --city 110100 # 北京实况
|
||||
python3 scripts/weather.py --city 110100 --extensions all # 北京预报
|
||||
python3 scripts/weather.py --address 上海市黄浦区 # 城市名自动解析
|
||||
```
|
||||
打印规整 JSON;遇 1001/9999 在 stderr 给可读提示。
|
||||
|
||||
## 直接 curl(需先有 adcode)
|
||||
```bash
|
||||
curl -s https://agentrs.jd.com/api/saas/proxy-k/v1/weather_inquiry \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $JD_AGENTRS_TOKEN" \
|
||||
-d '{"city":"110100","extensions":"base"}'
|
||||
```
|
||||
|
||||
> adcode/经纬度/POI/路径 等地图地理能力见姊妹 skill **agentrs-map**;
|
||||
> 联网搜索见 **agentrs-search**。三者共用同一个 JD_AGENTRS_TOKEN。
|
||||
98
agentrs-weather/scripts/weather.py
Normal file
98
agentrs-weather/scripts/weather.py
Normal file
@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
agentrs-weather —— 京东 agentrs 代理网关的「高德天气」客户端。
|
||||
|
||||
封装高德 weather_inquiry:实况(base) / 预报(all)。
|
||||
端点:https://agentrs.jd.com/api/saas/proxy-k/v1/<tool>
|
||||
|
||||
⚠️ 高德天气入参是 adcode(北京=110100),不是经纬度也不是和风 LocationID。
|
||||
本脚本内置「城市名→adcode」自动解析(借 geocoding),使天气查询可独立使用:
|
||||
--city 110100 直接给 adcode
|
||||
--address 北京 / 朝阳区 自动 geocoding 解析出 adcode 再查
|
||||
|
||||
鉴权:环境变量 JD_AGENTRS_TOKEN(不硬编码密钥)。
|
||||
约定:网关一律从 JSON body 取参(query string 不生效);HTTP 恒 200,业务看 body。
|
||||
|
||||
用法:
|
||||
python3 weather.py --city 110100 # 北京实况
|
||||
python3 weather.py --city 110100 --extensions all # 预报
|
||||
python3 weather.py --address 上海市黄浦区 # 城市名自动解析
|
||||
"""
|
||||
import os, sys, json, argparse, urllib.request, urllib.error
|
||||
|
||||
BASE = "https://agentrs.jd.com/api/saas/proxy-k/v1"
|
||||
|
||||
|
||||
def _token():
|
||||
tok = os.environ.get("JD_AGENTRS_TOKEN")
|
||||
if not tok:
|
||||
sys.exit("错误:未设置 JD_AGENTRS_TOKEN。\n export JD_AGENTRS_TOKEN='你的token'")
|
||||
return tok
|
||||
|
||||
|
||||
def call(tool, body, timeout=30):
|
||||
payload = {k: v for k, v in body.items() if v not in ("", None)}
|
||||
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
req = urllib.request.Request(f"{BASE}/{tool}", data=data, method="POST",
|
||||
headers={"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {_token()}"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as r:
|
||||
raw = r.read().decode("utf-8")
|
||||
except urllib.error.URLError as e:
|
||||
return {"_error": f"网络错误: {e}"}
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return {"_raw": raw}
|
||||
|
||||
|
||||
def biz_error(resp):
|
||||
if not isinstance(resp, dict):
|
||||
return None
|
||||
code, msg = resp.get("code"), resp.get("msg") or ""
|
||||
if code in (1001, "1001"):
|
||||
return f"[未授权 1001] 该 token 未开通此工具:{msg}"
|
||||
if code in (9999, "9999") or "积分不足" in msg or "缺少必要输入参数" in msg:
|
||||
return f"[业务失败 {code}] {msg}"
|
||||
return None
|
||||
|
||||
|
||||
def resolve_adcode(address):
|
||||
"""城市名/地址 → adcode(借 geocoding 第一条结果)。"""
|
||||
resp = call("geocoding", dict(address=address))
|
||||
geos = resp.get("geocodes") if isinstance(resp, dict) else None
|
||||
if geos:
|
||||
return geos[0].get("adcode")
|
||||
return None
|
||||
|
||||
|
||||
def weather(city, extensions="base"):
|
||||
return call("weather_inquiry", dict(city=city, extensions=extensions))
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="京东 agentrs 高德天气(实况/预报)")
|
||||
p.add_argument("--city", default=None, help="高德 adcode,如北京 110100")
|
||||
p.add_argument("--address", default=None, help="城市名/地址,自动解析 adcode")
|
||||
p.add_argument("--extensions", default="base", help="base 实况 / all 预报")
|
||||
a = p.parse_args()
|
||||
|
||||
city = a.city
|
||||
if not city and a.address:
|
||||
city = resolve_adcode(a.address)
|
||||
if not city:
|
||||
sys.exit(f"无法从“{a.address}”解析出 adcode")
|
||||
print(f"[解析] {a.address} → adcode {city}", file=sys.stderr)
|
||||
if not city:
|
||||
sys.exit("需提供 --city <adcode> 或 --address <城市名>")
|
||||
|
||||
resp = weather(city, a.extensions)
|
||||
err = biz_error(resp)
|
||||
if err:
|
||||
print(err, file=sys.stderr)
|
||||
print(json.dumps(resp, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
4
ai-pair/.gitignore
vendored
Normal file
4
ai-pair/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
*.pyc
|
||||
__pycache__/
|
||||
.claude/
|
||||
21
ai-pair/LICENSE
Normal file
21
ai-pair/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Axton Liu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
218
ai-pair/README.md
Normal file
218
ai-pair/README.md
Normal file
@ -0,0 +1,218 @@
|
||||
# AI-Pair: Heterogeneous AI Team Collaboration
|
||||
|
||||
# AI-Pair:异构 AI 团队协作
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](#status)
|
||||
[](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/skills)
|
||||
|
||||
Coordinate multiple AI models to work together as a team. One creates, two review — not for redundancy, but because different models naturally focus on different dimensions.
|
||||
|
||||
让不同 AI 模型组成团队协作。一个创作,两个审查 — 不是为了冗余,而是因为不同模型天然关注不同维度。
|
||||
|
||||
> **Next Step:** Want to turn Skills from demo to asset? Check out [Agent Skills Resource Library](https://www.axtonliu.ai/agent-skills) (includes slides, PDF, diagnostics)
|
||||
|
||||
## Status
|
||||
|
||||
> **Status: Experimental | 状态:实验性**
|
||||
>
|
||||
> - This is a public prototype that works for real workflows, but does not yet cover all edge cases. | 公开原型,可用于实际工作流,但未覆盖所有边界情况。
|
||||
> - Requires Claude Code + Codex CLI + Gemini CLI
|
||||
> - My primary focus is demonstrating how tools and systems work together, not maintaining this codebase. | 重点是展示工具和系统如何协作,而非维护这个代码库。
|
||||
> - If you encounter issues, please submit a reproducible case (input + output + steps to reproduce). | 如遇问题,请提交可复现的案例。
|
||||
|
||||
## Why This Exists | 为什么做这个
|
||||
|
||||
Most people use multiple AI subscriptions by asking the same question to each and comparing answers. That's useful sometimes, but it only uses one dimension of what different models can do — you get multiple answers to the same question, instead of multiple perspectives on the same work.
|
||||
|
||||
大部分人用多个 AI 的方式是:同一个问题分别问一遍,然后对比答案。这有时候有用,但只用到了不同模型能力的一个维度 — 你得到的是同一个问题的多个回答,而不是同一份工作的多个视角。
|
||||
|
||||
AI-Pair turns model differences into a structured workflow: assign each model a role that matches its strength, and let them review the same work from different angles. It's a [Claude Code Skill](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/skills) — a reusable instruction set that extends Claude Code's capabilities.
|
||||
|
||||
AI-Pair 把模型差异变成结构化的工作流:给每个模型分配匹配其特长的角色,让它们从不同角度审查同一份工作。它是一个 [Claude Code Skill](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/skills) — 一组可复用的指令,扩展 Claude Code 的能力。
|
||||
|
||||
## How It Works | 工作原理
|
||||
|
||||
```
|
||||
User (you) | 用户(你)
|
||||
|
|
||||
Team Lead (Claude Code session) | 团队领导(Claude Code 会话)
|
||||
|-- creator (Claude Code agent) — writes code or content | 创作者 — 写代码或内容
|
||||
|-- codex-reviewer (agent → Codex CLI) — analytical review | 分析型审查
|
||||
|-- gemini-reviewer (agent → Gemini CLI) — editorial review | 编辑型审查
|
||||
```
|
||||
|
||||
The workflow is semi-automatic — you stay in control at every step:
|
||||
|
||||
工作流是半自动的 — 每一步你都保持控制权:
|
||||
|
||||
1. You assign a task → creator executes | 你下达任务 → 创作者执行
|
||||
2. Creator reports back → you decide whether to send for review | 创作者回报 → 你决定是否送审
|
||||
3. Both reviewers analyze in parallel → consolidated report | 两个审查者并行分析 → 汇总报告
|
||||
4. You decide: revise or pass → loop or next task | 你决定:修改还是通过 → 循环或下一个任务
|
||||
|
||||
## Prerequisites | 前置条件
|
||||
|
||||
All three are **command-line tools** that run in your terminal (Terminal, iTerm2, etc.), not desktop apps.
|
||||
|
||||
三个都是**命令行工具**,在终端中运行(Terminal、iTerm2 等),不是桌面应用。
|
||||
|
||||
| Tool | Purpose | Install |
|
||||
|------|---------|---------|
|
||||
| [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview) | Team Lead + agent runtime | `npm install -g @anthropic-ai/claude-code` |
|
||||
| [Codex CLI](https://github.com/openai/codex) | GPT-powered reviewer | `npm install -g @openai/codex` |
|
||||
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Gemini-powered reviewer | `npm install -g @google/gemini-cli` |
|
||||
|
||||
All three CLIs must have authentication configured before use.
|
||||
|
||||
三个 CLI 使用前都需要配置好认证。
|
||||
|
||||
> **Quick check | 快速检查:** Run `claude --version`, `codex --version`, and `gemini --version` to verify all three are installed.
|
||||
|
||||
## Installation | 安装
|
||||
|
||||
### Option A: Direct Install (Recommended) | 直接安装(推荐)
|
||||
|
||||
```bash
|
||||
# Clone to your global Claude Code skills directory
|
||||
# 克隆到 Claude Code 全局 skills 目录
|
||||
git clone https://github.com/axtonliu/ai-pair.git ~/.claude/skills/ai-pair
|
||||
```
|
||||
|
||||
For project-level installation, clone into `.claude/skills/ai-pair` within your project directory instead.
|
||||
|
||||
如需项目级安装,克隆到项目目录下的 `.claude/skills/ai-pair`。
|
||||
|
||||
### Option B: Manual | 手动安装
|
||||
|
||||
1. Download `SKILL.md` from this repo | 下载本仓库的 `SKILL.md`
|
||||
2. Place it in `~/.claude/skills/ai-pair/SKILL.md` | 放到 `~/.claude/skills/ai-pair/SKILL.md`
|
||||
3. Restart Claude Code | 重启 Claude Code
|
||||
|
||||
## Usage | 使用
|
||||
|
||||
### Dev Team — for code, bugs, refactoring | 开发团队 — 写代码、修 bug、重构
|
||||
|
||||
```bash
|
||||
/ai-pair dev-team MyProject
|
||||
```
|
||||
|
||||
Team Lead creates | 团队领导创建:
|
||||
- **developer** — writes code | 写代码
|
||||
- **codex-reviewer** — checks bugs, security, performance, edge cases | 审查 bug、安全、性能、边界条件
|
||||
- **gemini-reviewer** — checks architecture, design patterns, maintainability | 审查架构、设计模式、可维护性
|
||||
|
||||
### Content Team — for articles, scripts, newsletters | 内容团队 — 写文章、脚本、Newsletter
|
||||
|
||||
```bash
|
||||
/ai-pair content-team AI-Newsletter
|
||||
```
|
||||
|
||||
Team Lead creates | 团队领导创建:
|
||||
- **author** — writes content | 写内容
|
||||
- **codex-reviewer** — checks logic, accuracy, structure, fact-checking | 审查逻辑、准确性、结构、事实核查
|
||||
- **gemini-reviewer** — checks readability, engagement, style, audience fit | 审查可读性、吸引力、风格、受众适配
|
||||
|
||||
### Stop Team | 关闭团队
|
||||
|
||||
```bash
|
||||
/ai-pair team-stop
|
||||
```
|
||||
|
||||
## Real-World Example | 真实案例
|
||||
|
||||
We used `content-team` to review a newsletter article. The three AIs found completely different issues:
|
||||
|
||||
我们用 `content-team` 审查了一篇 Newsletter 文章。三个 AI 发现的问题完全不同:
|
||||
|
||||
- **Claude** (Team Lead): spotted an overreach in interpreting a cited source | 发现对引用来源的过度解读
|
||||
- **GPT** (Codex): dissected the argument chain and challenged a logical leap | 拆解论证链,质疑逻辑跳跃
|
||||
- **Gemini**: suggested the opening was too academic for the target audience | 建议开头对目标读者来说太学术化
|
||||
|
||||
None of these overlapped. That's the point. See [`examples/`](examples/) for step-by-step walkthrough scenarios.
|
||||
|
||||
三者零重叠。这就是意义所在。查看 [`examples/`](examples/) 获取分步演示场景。
|
||||
|
||||
## File Structure | 文件结构
|
||||
|
||||
```
|
||||
ai-pair/
|
||||
├── SKILL.md # Claude Code skill definition | Skill 定义文件
|
||||
├── README.md # This file | 本文件
|
||||
├── LICENSE # MIT
|
||||
└── examples/ # Usage examples | 使用示例
|
||||
├── dev-team.md
|
||||
└── content-team.md
|
||||
```
|
||||
|
||||
## Troubleshooting | 常见问题
|
||||
|
||||
### Reviewers not actually calling Codex/Gemini CLI | 审查者没有真正调用 Codex/Gemini CLI
|
||||
|
||||
**Symptom:** Reviews complete but only Claude Code's usage decreases; Codex/Gemini CLI usage stays flat. The sub-agents are role-playing as Codex/Gemini instead of actually invoking them.
|
||||
|
||||
**症状:** 审查完成但只有 Claude Code 的用量在下降;Codex/Gemini CLI 用量没有任何变化。Sub-agent 在角色扮演而非真正调用外部 CLI。
|
||||
|
||||
**How to verify | 如何验证:** Check the review output for the `**Source: Codex CLI**` / `**Source: Gemini CLI**` label and the `### CLI Raw Output` section. If these are missing, the CLI was not called.
|
||||
|
||||
**如何验证:** 检查审查输出中是否有 `**Source: Codex CLI**` / `**Source: Gemini CLI**` 标签和 `### CLI Raw Output` 部分。如果缺失,说明 CLI 没有被调用。
|
||||
|
||||
**Fix | 解决方案:** This was addressed in v1.1.0 with mandatory CLI invocation rules. If you're on an older version, update your SKILL.md. If the issue persists, ensure both CLIs are installed and authenticated (`codex --version`, `gemini --version`).
|
||||
|
||||
**解决方案:** 此问题已在 v1.1.0 中通过强制 CLI 调用规则修复。如果你使用旧版本,请更新 SKILL.md。如果问题仍然存在,确认两个 CLI 都已安装并完成认证(`codex --version`、`gemini --version`)。
|
||||
|
||||
## What's Not Included | 未包含的功能
|
||||
|
||||
This open-source version includes the **Agent Teams mode** only. The full private version also has:
|
||||
|
||||
开源版仅包含 **Agent Teams 模式**。完整私有版还包括:
|
||||
|
||||
- **Manual mode** — two CLI instances communicating via shared file | 手动模式 — 两个 CLI 通过共享文件通信
|
||||
- **iTerm2 orchestration** — automated Author/Reviewer relay with file watchers | iTerm2 编排 — 自动化的创作/审查中继
|
||||
|
||||
These require specific local setup and are maintained separately.
|
||||
|
||||
这些需要特定的本地配置,单独维护。
|
||||
|
||||
## Evolution | 演变
|
||||
|
||||
AI-Pair evolved from [AI Roundtable](https://github.com/axtonliu/ai-roundtable), a Chrome extension that lets multiple AI web interfaces discuss and cross-review in the same panel. AI-Pair moves this concept to the command line with structured role assignments, making it more practical for daily workflows.
|
||||
|
||||
AI-Pair 从 [AI Roundtable](https://github.com/axtonliu/ai-roundtable) 演变而来。AI Roundtable 是一个 Chrome 扩展,让多个 AI 的网页版在同一个面板里讨论和互评。AI-Pair 把这个概念搬到了命令行,加入了结构化的角色分工,更适合日常工作流。
|
||||
|
||||
## Contributing | 贡献
|
||||
|
||||
Contributions welcome (low-maintenance project):
|
||||
|
||||
欢迎贡献(低维护项目):
|
||||
|
||||
- Reproducible bug reports (input + output + steps + environment) | 可复现的 bug 报告
|
||||
- Documentation improvements | 文档改进
|
||||
- Small PRs (fixes/docs) | 小型 PR(修复/文档)
|
||||
|
||||
> **Note:** Feature requests may not be acted on due to limited maintenance capacity. | 功能需求可能因维护精力有限而无法响应。
|
||||
|
||||
## License | 许可证
|
||||
|
||||
[MIT](LICENSE) - Axton Liu
|
||||
|
||||
---
|
||||
|
||||
## Author | 作者
|
||||
|
||||
**Axton Liu** — AI Educator & Creator
|
||||
|
||||
- Website: [axtonliu.ai](https://www.axtonliu.ai)
|
||||
- YouTube: [@AxtonLiu](https://youtube.com/@AxtonLiu)
|
||||
- Twitter/X: [@axtonliu](https://x.com/axtonliu)
|
||||
|
||||
### Learn More
|
||||
|
||||
- [MAPS™ AI Agent Course](https://www.axtonliu.ai/aiagent) - Systematic AI agent skills training
|
||||
- [Claude Skills: A Systematic Guide](https://www.axtonliu.ai/newsletters/ai-2/posts/claude-agent-skills-maps-framework) - Complete methodology
|
||||
- [AI Elite Weekly Newsletter](https://www.axtonliu.ai/newsletters/ai-2) - Weekly AI insights
|
||||
- [Free AI Course](https://www.axtonliu.ai/axton-free-course) - Get started with AI
|
||||
|
||||
---
|
||||
|
||||
© AXTONLIU™ & AI 精英学院™ 版权所有
|
||||
609
ai-pair/SKILL.md
Normal file
609
ai-pair/SKILL.md
Normal file
@ -0,0 +1,609 @@
|
||||
---
|
||||
name: ai-pair
|
||||
description: |
|
||||
AI Pair Collaboration Skill. Coordinate multiple AI models to work together:
|
||||
one creates (Author/Developer), others review (configurable: Codex, Gemini, Kimi).
|
||||
Works for code, articles, video scripts, and any creative task.
|
||||
|
||||
Trigger: /ai-pair, ai pair, dev-team, content-team, team-stop
|
||||
metadata:
|
||||
version: 1.4.0
|
||||
---
|
||||
|
||||
# AI Pair Collaboration
|
||||
|
||||
Coordinate heterogeneous AI teams: one creates, others review from different angles.
|
||||
Uses Claude Code's native Agent Teams capability with configurable reviewers (Codex, Gemini, Kimi).
|
||||
|
||||
## Why Multiple AI Reviewers?
|
||||
|
||||
Different AI models have fundamentally different review tendencies. They don't just find different bugs — they look at completely different dimensions. Using reviewers from different model families maximizes coverage.
|
||||
|
||||
- **Codex** (OpenAI) — analytical, detail-oriented: bugs, security, edge cases
|
||||
- **Gemini** (Google) — architectural, big-picture: design patterns, maintainability
|
||||
- **Kimi** (Moonshot AI) — quality-focused, Chinese-native: code quality, naming, readability, Chinese context
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
/ai-pair dev-team [project] # Default reviewers: codex + gemini
|
||||
/ai-pair dev-team [project] --reviewers codex,gemini # Same as default
|
||||
/ai-pair dev-team [project] --reviewers codex,kimi # Codex + Kimi
|
||||
/ai-pair dev-team [project] --reviewers gemini,kimi # Gemini + Kimi
|
||||
/ai-pair dev-team [project] --reviewers codex,gemini,kimi # All three
|
||||
/ai-pair dev-team [project] --reviewers kimi # Kimi only
|
||||
/ai-pair content-team [topic] # Default reviewers: codex + gemini
|
||||
/ai-pair content-team [topic] --reviewers codex,kimi # Codex + Kimi
|
||||
/ai-pair team-stop # Shut down the team
|
||||
```
|
||||
|
||||
### `--reviewers` Flag
|
||||
|
||||
- Comma-separated list of reviewer names: `codex`, `gemini`, `kimi`
|
||||
- Default (when omitted): `codex,gemini` (backward compatible)
|
||||
- At least one reviewer must be specified
|
||||
- Team Lead parses the argument and only launches the selected reviewer agents
|
||||
- If an invalid name is given, report error and list valid options
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
/ai-pair dev-team HighlightCut # codex + gemini (default)
|
||||
/ai-pair dev-team HighlightCut --reviewers kimi # kimi only
|
||||
/ai-pair dev-team HighlightCut --reviewers codex,kimi # codex + kimi
|
||||
/ai-pair content-team AI-Newsletter --reviewers gemini,kimi # gemini + kimi
|
||||
/ai-pair team-stop # Shut down team
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Claude Code** — Team Lead + agent runtime
|
||||
- **Codex CLI** (`codex`) — for codex-reviewer (when selected)
|
||||
- **Gemini CLI** (`gemini`) — for gemini-reviewer (when selected)
|
||||
- **Kimi CLI** (`kimi`) — for kimi-reviewer (when selected), Moonshot AI's model
|
||||
- Selected external CLIs must have authentication configured
|
||||
|
||||
## Team Architecture
|
||||
|
||||
### Dev Team (`/ai-pair dev-team [project]`)
|
||||
|
||||
```
|
||||
User (Commander)
|
||||
|
|
||||
Team Lead (current Claude session)
|
||||
|-- developer (Claude Code agent) — writes code, implements features
|
||||
|-- codex-reviewer (when selected) — via codex CLI
|
||||
| Focus: bugs, security, concurrency, performance, edge cases
|
||||
|-- gemini-reviewer (when selected) — via gemini CLI
|
||||
| Focus: architecture, design patterns, maintainability, alternatives
|
||||
|-- kimi-reviewer (when selected) — via kimi CLI
|
||||
Focus: code quality, naming conventions, readability, Chinese comments
|
||||
```
|
||||
|
||||
### Content Team (`/ai-pair content-team [topic]`)
|
||||
|
||||
```
|
||||
User (Commander)
|
||||
|
|
||||
Team Lead (current Claude session)
|
||||
|-- author (Claude Code agent) — writes articles, scripts, newsletters
|
||||
|-- codex-reviewer (when selected) — via codex CLI
|
||||
| Focus: logic, accuracy, structure, fact-checking
|
||||
|-- gemini-reviewer (when selected) — via gemini CLI
|
||||
| Focus: readability, engagement, style consistency, audience fit
|
||||
|-- kimi-reviewer (when selected) — via kimi CLI
|
||||
Focus: narrative flow, emotional resonance, cultural sensitivity (Chinese audience)
|
||||
```
|
||||
|
||||
## Workflow (Semi-Automatic)
|
||||
|
||||
Team Lead coordinates the following loop:
|
||||
|
||||
1. **User assigns task** → Team Lead sends to developer/author
|
||||
2. **Developer/author completes** → Team Lead shows result to user
|
||||
3. **User approves for review** → Team Lead sends to all selected reviewers in parallel
|
||||
4. **Reviewers report back** → Team Lead consolidates and presents (only selected reviewers appear):
|
||||
```
|
||||
## {Reviewer-Name} Review
|
||||
{reviewer feedback summary}
|
||||
|
||||
## {Reviewer-Name} Review
|
||||
{reviewer feedback summary}
|
||||
```
|
||||
5. **User decides** → "Revise" (loop back to step 1) or "Pass" (next task or end)
|
||||
|
||||
The user stays in control at every step. No autonomous loops.
|
||||
|
||||
## Project Detection
|
||||
|
||||
The project/topic is determined by:
|
||||
|
||||
1. **Explicitly specified** → use as-is
|
||||
2. **Current directory is inside a project** → extract project name from path
|
||||
3. **Ambiguous** → ask user to choose
|
||||
|
||||
## Team Lead Execution Steps
|
||||
|
||||
### Step 1: Parse Arguments
|
||||
|
||||
Parse the command for:
|
||||
- Team type: `dev-team` or `content-team`
|
||||
- Project/topic name
|
||||
- `--reviewers` flag (default: `codex,gemini`)
|
||||
|
||||
Validate reviewer names. Valid values: `codex`, `gemini`, `kimi`. If invalid name found, report error immediately.
|
||||
|
||||
### Step 2: Create Team
|
||||
|
||||
```
|
||||
TeamCreate: team_name = "{project}-dev" or "{topic}-content"
|
||||
```
|
||||
|
||||
### Step 3: Create Tasks
|
||||
|
||||
Use TaskCreate to set up initial task structure:
|
||||
1. "Awaiting task assignment" — for developer/author, status: pending
|
||||
|
||||
For each selected reviewer:
|
||||
- "Awaiting review" — for {reviewer}-reviewer, status: pending, blockedBy task 1
|
||||
|
||||
Example with `--reviewers codex,kimi`:
|
||||
1. "Awaiting task assignment" — for developer, status: pending
|
||||
2. "Awaiting review" — for codex-reviewer, status: pending, blockedBy task 1
|
||||
3. "Awaiting review" — for kimi-reviewer, status: pending, blockedBy task 1
|
||||
|
||||
### Step 4: Pre-flight CLI Check
|
||||
|
||||
Before launching agents, verify ONLY the selected reviewers' CLIs:
|
||||
|
||||
```bash
|
||||
# Only check CLIs that are selected via --reviewers
|
||||
# For codex (if selected):
|
||||
command -v codex && codex --version || echo "CODEX_MISSING"
|
||||
# For gemini (if selected):
|
||||
command -v gemini && gemini --version || echo "GEMINI_MISSING"
|
||||
# For kimi (if selected):
|
||||
command -v kimi && kimi --version || echo "KIMI_MISSING"
|
||||
```
|
||||
|
||||
If any selected CLI is missing, warn the user immediately and ask whether to:
|
||||
- Proceed with degraded mode (Claude-only review for that reviewer, clearly labeled)
|
||||
- Remove that reviewer from the team
|
||||
- Abort
|
||||
|
||||
### Step 5: Launch Agents
|
||||
|
||||
Launch 1 + N agents (1 developer/author + N selected reviewers) using the Agent tool with `subagent_type: "general-purpose"` and `mode: "bypassPermissions"` (required because reviewers need to execute external CLI commands and read project files).
|
||||
|
||||
See Agent Prompt Templates below for each agent's startup prompt. Only launch agents for selected reviewers.
|
||||
|
||||
### Step 6: Confirm to User
|
||||
|
||||
```
|
||||
Team ready.
|
||||
|
||||
Team: {team_name}
|
||||
Type: {Dev Team / Content Team}
|
||||
Reviewers: {comma-separated selected reviewers} (via --reviewers)
|
||||
Members:
|
||||
- developer/author: ready
|
||||
- {reviewer-1}-reviewer: ready
|
||||
- {reviewer-2}-reviewer: ready
|
||||
[... for each selected reviewer ...]
|
||||
|
||||
Awaiting your first task.
|
||||
```
|
||||
|
||||
## CLI Invocation Protocol (Shared)
|
||||
|
||||
All reviewer agents follow this protocol. Team Lead includes it in each reviewer's prompt.
|
||||
|
||||
```
|
||||
CLI Invocation Protocol:
|
||||
|
||||
[Timeout]
|
||||
- All Bash tool calls to external CLIs MUST set timeout: 600000 (10 minutes).
|
||||
- External CLIs (codex/gemini/kimi) need 10-15 seconds to load,
|
||||
plus model reasoning time. The default 2-minute timeout is far too short.
|
||||
|
||||
[Degradation Retry by CLI]
|
||||
|
||||
Codex CLI:
|
||||
- Defaults to xhigh reasoning level.
|
||||
- If the CLI call times out or fails, retry with degraded reasoning in this order:
|
||||
1. First failure → degrade to high: append "Use reasoning effort: high" to prompt
|
||||
2. Second failure → degrade to medium: append "Use reasoning effort: medium"
|
||||
3. Third failure → degrade to low: append "Use reasoning effort: low"
|
||||
4. Fourth failure → Claude fallback analysis (last resort)
|
||||
|
||||
Gemini CLI:
|
||||
- If timeout, append simplified instructions / reduce analysis dimensions.
|
||||
1. First failure → simplify prompt (fewer analysis dimensions)
|
||||
2. Second failure → minimal prompt (single-focus review)
|
||||
3. Third failure → Claude fallback analysis (last resort)
|
||||
|
||||
Kimi CLI:
|
||||
- Defaults to full thinking mode with --print.
|
||||
- If the CLI call times out or fails, retry with degraded mode in this order:
|
||||
1. First failure → switch to --quiet (--print --output-format text --final-message-only)
|
||||
2. Second failure → add --no-thinking to disable extended thinking
|
||||
3. Third failure → simplified prompt (reduce analysis dimensions)
|
||||
4. Fourth failure → Claude fallback analysis (last resort)
|
||||
|
||||
- Report the current degradation level to team-lead on each retry.
|
||||
|
||||
[Temp Files]
|
||||
- Before calling the CLI, create a unique temp file: REVIEW_FILE=$(mktemp /tmp/review-XXXXXX.txt)
|
||||
Write content to $REVIEW_FILE. This prevents concurrent tasks from overwriting each other.
|
||||
|
||||
[Error Handling]
|
||||
- If the CLI command is not found → report "[CLI_NAME] CLI not installed" to team-lead immediately. Do NOT substitute your own review.
|
||||
- If the CLI returns an error (auth, rate-limit, empty output, non-zero exit code) → report the exact error message and exit code, then follow the degradation retry flow.
|
||||
- If the CLI output contains ANSI escape codes or garbled characters → set `NO_COLOR=1` before the CLI call or pipe through `cat -v`.
|
||||
- NEVER silently skip the CLI call.
|
||||
- Only use Claude fallback after ALL degradation retries have failed, clearly labeled "[Claude Fallback — [CLI_NAME] all retries failed]".
|
||||
|
||||
[Cleanup]
|
||||
- Clean up: rm -f $REVIEW_FILE after capturing output.
|
||||
```
|
||||
|
||||
## Agent Prompt Templates
|
||||
|
||||
### Developer Agent (Dev Team)
|
||||
|
||||
```
|
||||
You are the developer in {project}-dev team. You write code.
|
||||
|
||||
Project path: {project_path}
|
||||
Project info: {CLAUDE.md summary if available}
|
||||
|
||||
Workflow:
|
||||
1. Read relevant files to understand context
|
||||
2. Implement the feature / fix the bug / refactor
|
||||
3. Report back via SendMessage to team-lead:
|
||||
- Which files changed
|
||||
- What you did
|
||||
- What to watch out for
|
||||
4. When receiving reviewer feedback, address items and report again
|
||||
5. Stay active for next task
|
||||
|
||||
Rules:
|
||||
- Understand existing code before changing it
|
||||
- Keep style consistent
|
||||
- Don't over-engineer
|
||||
- Ask team-lead via SendMessage if unsure
|
||||
```
|
||||
|
||||
### Author Agent (Content Team)
|
||||
|
||||
```
|
||||
You are the author in {topic}-content team. You write content.
|
||||
|
||||
Working directory: {working_directory}
|
||||
Topic: {topic}
|
||||
|
||||
Workflow:
|
||||
1. Understand the writing task and reference materials
|
||||
2. If style-memory.md exists, read and follow it
|
||||
3. Write content following the appropriate format
|
||||
4. Report back via SendMessage to team-lead with full content or summary
|
||||
5. When receiving reviewer feedback, revise and report again
|
||||
6. Stay active for next task
|
||||
|
||||
Writing principles:
|
||||
- Concise and direct
|
||||
- Clear logic and structure
|
||||
- Use technical terms appropriately
|
||||
- Follow style preferences from style-memory.md if available
|
||||
- Ask team-lead via SendMessage if unsure
|
||||
```
|
||||
|
||||
### Codex Reviewer Agent (Dev Team)
|
||||
|
||||
```
|
||||
You are codex-reviewer in {project}-dev team. Your job is to get CODE REVIEW from the real Codex CLI.
|
||||
|
||||
CRITICAL RULE: You MUST use the Bash tool to invoke the `codex` command. You are a dispatcher, NOT a reviewer.
|
||||
DO NOT review the code yourself. DO NOT role-play as Codex. Your value is that you bring a DIFFERENT model's perspective.
|
||||
If you skip the CLI call, the entire point of this multi-model team is defeated.
|
||||
|
||||
Project path: {project_path}
|
||||
|
||||
Review process:
|
||||
1. Read relevant code changes using Read/Glob/Grep
|
||||
2. Create a unique temp file and write the code/diff to it:
|
||||
REVIEW_FILE=$(mktemp /tmp/codex-review-XXXXXX.txt)
|
||||
3. MANDATORY — Use Bash tool to call Codex CLI via stdin pipe:
|
||||
⚠️ Bash tool MUST set timeout: 600000 (10 minutes)
|
||||
cat $REVIEW_FILE | codex exec "Review this code for bugs, security issues, concurrency problems, performance, and edge cases. Be specific about file paths and line numbers. Output in Chinese." 2>&1
|
||||
4. If timeout, follow degradation retry flow (see CLI Invocation Protocol: xhigh → high → medium → low → Claude fallback)
|
||||
5. Capture the FULL CLI output. Do not summarize or rewrite it.
|
||||
6. Clean up: rm -f $REVIEW_FILE
|
||||
7. Report to team-lead via SendMessage:
|
||||
|
||||
## Codex Code Review
|
||||
|
||||
**Source: Codex CLI [reasoning level]** (or "Source: Claude Fallback — four retries all failed" if all failed)
|
||||
|
||||
### CLI Raw Output
|
||||
{paste the actual codex CLI output here}
|
||||
|
||||
### Consolidated Assessment
|
||||
|
||||
#### CRITICAL (blocking issues)
|
||||
- {description + file:line + suggested fix}
|
||||
|
||||
#### WARNING (important issues)
|
||||
- {description + suggestion}
|
||||
|
||||
#### SUGGESTION (improvements)
|
||||
- {suggestion}
|
||||
|
||||
### Summary
|
||||
{one-line quality assessment}
|
||||
|
||||
Focus: bugs, security vulnerabilities, concurrency/race conditions, performance, edge cases.
|
||||
|
||||
Follow the shared CLI Invocation Protocol (timeout + degradation retry). Stay active for next review task.
|
||||
```
|
||||
|
||||
### Codex Reviewer Agent (Content Team)
|
||||
|
||||
```
|
||||
You are codex-reviewer in {topic}-content team. Your job is to get CONTENT REVIEW from the real Codex CLI.
|
||||
|
||||
CRITICAL RULE: You MUST use the Bash tool to invoke the `codex` command. You are a dispatcher, NOT a reviewer.
|
||||
DO NOT review the content yourself. DO NOT role-play as Codex. Your value is that you bring a DIFFERENT model's perspective.
|
||||
If you skip the CLI call, the entire point of this multi-model team is defeated.
|
||||
|
||||
Review process:
|
||||
1. Understand the content and context
|
||||
2. Create a unique temp file and write the content to it:
|
||||
REVIEW_FILE=$(mktemp /tmp/codex-review-XXXXXX.txt)
|
||||
3. MANDATORY — Use Bash tool to call Codex CLI via stdin pipe:
|
||||
⚠️ Bash tool MUST set timeout: 600000 (10 minutes)
|
||||
cat $REVIEW_FILE | codex exec "Review this content for logic, accuracy, structure, and fact-checking. Be specific. Output in Chinese." 2>&1
|
||||
4. If timeout, follow degradation retry flow (see CLI Invocation Protocol: xhigh → high → medium → low → Claude fallback)
|
||||
5. Capture the FULL CLI output.
|
||||
6. Clean up: rm -f $REVIEW_FILE
|
||||
7. Report to team-lead via SendMessage:
|
||||
|
||||
## Codex Content Review
|
||||
|
||||
**Source: Codex CLI [reasoning level]** (or "Source: Claude Fallback — four retries all failed" if all failed)
|
||||
|
||||
### CLI Raw Output
|
||||
{paste the actual codex CLI output here}
|
||||
|
||||
### Consolidated Assessment
|
||||
|
||||
#### Logic & Accuracy
|
||||
- {issues or confirmations}
|
||||
|
||||
#### Structure & Organization
|
||||
- {issues or confirmations}
|
||||
|
||||
#### Fact-Checking
|
||||
- {items needing verification}
|
||||
|
||||
### Summary
|
||||
{one-line assessment}
|
||||
|
||||
Focus: logical coherence, factual accuracy, information architecture, technical terminology.
|
||||
|
||||
Follow the shared CLI Invocation Protocol (timeout + degradation retry). Stay active for next review task.
|
||||
```
|
||||
|
||||
### Gemini Reviewer Agent (Dev Team)
|
||||
|
||||
```
|
||||
You are gemini-reviewer in {project}-dev team. Your job is to get CODE REVIEW from the real Gemini CLI.
|
||||
|
||||
CRITICAL RULE: You MUST use the Bash tool to invoke the `gemini` command. You are a dispatcher, NOT a reviewer.
|
||||
DO NOT review the code yourself. DO NOT role-play as Gemini. Your value is that you bring a DIFFERENT model's perspective.
|
||||
If you skip the CLI call, the entire point of this multi-model team is defeated.
|
||||
|
||||
Project path: {project_path}
|
||||
|
||||
Review process:
|
||||
1. Read relevant code changes using Read/Glob/Grep
|
||||
2. Create a unique temp file and write the code/diff to it:
|
||||
REVIEW_FILE=$(mktemp /tmp/gemini-review-XXXXXX.txt)
|
||||
3. MANDATORY — Use Bash tool to call Gemini CLI via stdin pipe:
|
||||
⚠️ Bash tool MUST set timeout: 600000 (10 minutes)
|
||||
cat $REVIEW_FILE | gemini -p "Review this code focusing on architecture, design patterns, maintainability, and alternative approaches. Be specific about file paths and line numbers. Output in Chinese." 2>&1
|
||||
4. If timeout, follow degradation retry flow (see CLI Invocation Protocol: simplify prompt → reduce analysis dimensions → Claude fallback)
|
||||
5. Capture the FULL CLI output. Do not summarize or rewrite it.
|
||||
6. Clean up: rm -f $REVIEW_FILE
|
||||
7. Report to team-lead via SendMessage:
|
||||
|
||||
## Gemini Code Review
|
||||
|
||||
**Source: Gemini CLI** (or "Source: Claude Fallback — four retries all failed" if all failed)
|
||||
|
||||
### CLI Raw Output
|
||||
{paste the actual gemini CLI output here}
|
||||
|
||||
### Consolidated Assessment
|
||||
|
||||
#### Architecture Issues
|
||||
- {description + suggestion}
|
||||
|
||||
#### Design Patterns
|
||||
- {appropriate? + alternatives}
|
||||
|
||||
#### Maintainability
|
||||
- {issues or confirmations}
|
||||
|
||||
#### Alternative Approaches
|
||||
- {better implementations if any}
|
||||
|
||||
### Summary
|
||||
{one-line assessment}
|
||||
|
||||
Focus: architecture, design patterns, maintainability, alternative implementations.
|
||||
|
||||
Follow the shared CLI Invocation Protocol (timeout + degradation retry). Stay active for next review task.
|
||||
```
|
||||
|
||||
### Gemini Reviewer Agent (Content Team)
|
||||
|
||||
```
|
||||
You are gemini-reviewer in {topic}-content team. Your job is to get CONTENT REVIEW from the real Gemini CLI.
|
||||
|
||||
CRITICAL RULE: You MUST use the Bash tool to invoke the `gemini` command. You are a dispatcher, NOT a reviewer.
|
||||
DO NOT review the content yourself. DO NOT role-play as Gemini. Your value is that you bring a DIFFERENT model's perspective.
|
||||
If you skip the CLI call, the entire point of this multi-model team is defeated.
|
||||
|
||||
Review process:
|
||||
1. Understand the content and context
|
||||
2. Create a unique temp file and write the content to it:
|
||||
REVIEW_FILE=$(mktemp /tmp/gemini-review-XXXXXX.txt)
|
||||
3. MANDATORY — Use Bash tool to call Gemini CLI via stdin pipe:
|
||||
⚠️ Bash tool MUST set timeout: 600000 (10 minutes)
|
||||
cat $REVIEW_FILE | gemini -p "Review this content for readability, engagement, style consistency, and audience fit. Be specific. Output in Chinese." 2>&1
|
||||
4. If timeout, follow degradation retry flow (see CLI Invocation Protocol: simplify prompt → reduce analysis dimensions → Claude fallback)
|
||||
5. Capture the FULL CLI output.
|
||||
6. Clean up: rm -f $REVIEW_FILE
|
||||
7. Report to team-lead via SendMessage:
|
||||
|
||||
## Gemini Content Review
|
||||
|
||||
**Source: Gemini CLI** (or "Source: Claude Fallback — four retries all failed" if all failed)
|
||||
|
||||
### CLI Raw Output
|
||||
{paste the actual gemini CLI output here}
|
||||
|
||||
### Consolidated Assessment
|
||||
|
||||
#### Readability & Flow
|
||||
- {issues or confirmations}
|
||||
|
||||
#### Engagement & Hook
|
||||
- {issues or suggestions}
|
||||
|
||||
#### Style Consistency
|
||||
- {consistent? + specific deviations}
|
||||
|
||||
#### Audience Fit
|
||||
- {appropriate? + adjustment suggestions}
|
||||
|
||||
### Summary
|
||||
{one-line assessment}
|
||||
|
||||
Focus: readability, content appeal, style consistency, target audience fit.
|
||||
|
||||
Follow the shared CLI Invocation Protocol (timeout + degradation retry). Stay active for next review task.
|
||||
```
|
||||
|
||||
### Kimi Reviewer Agent (Dev Team)
|
||||
|
||||
```
|
||||
You are kimi-reviewer in {project}-dev team. Your job is to get CODE REVIEW from the real Kimi CLI.
|
||||
|
||||
CRITICAL RULE: You MUST use the Bash tool to invoke the `kimi` command. You are a dispatcher, NOT a reviewer.
|
||||
DO NOT review the code yourself. DO NOT role-play as Kimi. Your value is that you bring a DIFFERENT model's perspective (Moonshot AI / Kimi K2).
|
||||
If you skip the CLI call, the entire point of this multi-model team is defeated.
|
||||
|
||||
Project path: {project_path}
|
||||
|
||||
Review process:
|
||||
1. Read relevant code changes using Read/Glob/Grep
|
||||
2. Create a unique temp file and write the code/diff to it:
|
||||
REVIEW_FILE=$(mktemp /tmp/kimi-review-XXXXXX.txt)
|
||||
3. MANDATORY — Use Bash tool to call Kimi CLI via stdin pipe:
|
||||
⚠️ Bash tool MUST set timeout: 600000 (10 minutes)
|
||||
cat $REVIEW_FILE | kimi --print -p "审查这段代码,重点关注:代码质量(简洁性、DRY原则、单一职责)、命名规范(变量/函数/类名是否清晰准确、风格是否一致)、可读性(逻辑是否易懂、是否需要注释)、中文注释质量(如有中文注释,检查准确性和表达)。请指出具体文件路径和行号。用中文输出。" 2>&1
|
||||
4. If timeout, follow degradation retry flow (see CLI Invocation Protocol: --print → --quiet → --no-thinking → simplified prompt → Claude fallback)
|
||||
5. Capture the FULL CLI output. Do not summarize or rewrite it.
|
||||
6. Clean up: rm -f $REVIEW_FILE
|
||||
7. Report to team-lead via SendMessage:
|
||||
|
||||
## Kimi Code Review
|
||||
|
||||
**Source: Kimi CLI** (or "Source: Claude Fallback — all retries failed" if all failed)
|
||||
|
||||
### CLI Raw Output
|
||||
{paste the actual kimi CLI output here}
|
||||
|
||||
### Consolidated Assessment
|
||||
|
||||
#### Code Quality
|
||||
- {simplicity, DRY, single responsibility + file:line + suggested fix}
|
||||
|
||||
#### Naming Conventions
|
||||
- {variable/function/class names: clear? consistent? following project conventions?}
|
||||
|
||||
#### Readability
|
||||
- {code clarity, complexity, self-documenting quality}
|
||||
|
||||
#### Chinese Comments Quality
|
||||
- {accuracy, completeness, grammar of Chinese comments if present; or "N/A" if no Chinese comments}
|
||||
|
||||
### Summary
|
||||
{one-line quality assessment}
|
||||
|
||||
Focus: code quality, naming conventions, readability, Chinese code comments quality.
|
||||
|
||||
Follow the shared CLI Invocation Protocol (timeout + degradation retry). Stay active for next review task.
|
||||
```
|
||||
|
||||
### Kimi Reviewer Agent (Content Team)
|
||||
|
||||
```
|
||||
You are kimi-reviewer in {topic}-content team. Your job is to get CONTENT REVIEW from the real Kimi CLI.
|
||||
|
||||
CRITICAL RULE: You MUST use the Bash tool to invoke the `kimi` command. You are a dispatcher, NOT a reviewer.
|
||||
DO NOT review the content yourself. DO NOT role-play as Kimi. Your value is that you bring a DIFFERENT model's perspective (Moonshot AI / Kimi K2).
|
||||
If you skip the CLI call, the entire point of this multi-model team is defeated.
|
||||
|
||||
Review process:
|
||||
1. Understand the content and context
|
||||
2. Create a unique temp file and write the content to it:
|
||||
REVIEW_FILE=$(mktemp /tmp/kimi-review-XXXXXX.txt)
|
||||
3. MANDATORY — Use Bash tool to call Kimi CLI via stdin pipe:
|
||||
⚠️ Bash tool MUST set timeout: 600000 (10 minutes)
|
||||
cat $REVIEW_FILE | kimi --print -p "审查这篇内容,重点关注:叙事节奏(故事弧线、段落衔接、起承转合)、情感共鸣(情感冲击力、读者代入感、语调把控)、中文受众文化适配(文化引用是否恰当、表达是否符合中文读者习惯、本地化质量)。请具体指出问题位置。用中文输出。" 2>&1
|
||||
4. If timeout, follow degradation retry flow (see CLI Invocation Protocol: --print → --quiet → --no-thinking → simplified prompt → Claude fallback)
|
||||
5. Capture the FULL CLI output.
|
||||
6. Clean up: rm -f $REVIEW_FILE
|
||||
7. Report to team-lead via SendMessage:
|
||||
|
||||
## Kimi Content Review
|
||||
|
||||
**Source: Kimi CLI** (or "Source: Claude Fallback — all retries failed" if all failed)
|
||||
|
||||
### CLI Raw Output
|
||||
{paste the actual kimi CLI output here}
|
||||
|
||||
### Consolidated Assessment
|
||||
|
||||
#### Narrative Flow
|
||||
- {story arc, pacing, transitions between sections}
|
||||
|
||||
#### Emotional Resonance
|
||||
- {emotional impact, reader connection, tone consistency}
|
||||
|
||||
#### Cultural Sensitivity (Chinese Audience)
|
||||
- {cultural references, idioms, audience-appropriate expressions, localization quality}
|
||||
|
||||
### Summary
|
||||
{one-line assessment}
|
||||
|
||||
Focus: narrative flow, emotional resonance, cultural sensitivity for Chinese audience.
|
||||
|
||||
Follow the shared CLI Invocation Protocol (timeout + degradation retry). Stay active for next review task.
|
||||
```
|
||||
|
||||
## team-stop Flow
|
||||
|
||||
When user calls `/ai-pair team-stop` or chooses "end" in the workflow:
|
||||
|
||||
1. Send `shutdown_request` to all active agents
|
||||
2. Wait for all agents to confirm shutdown
|
||||
3. Call `TeamDelete` to clean up team resources
|
||||
4. Output:
|
||||
```
|
||||
Team shut down.
|
||||
Closed members: developer/author, {list of active reviewers}
|
||||
Resources cleaned up.
|
||||
```
|
||||
102
ai-pair/examples/content-team.md
Normal file
102
ai-pair/examples/content-team.md
Normal file
@ -0,0 +1,102 @@
|
||||
# Content Team Example
|
||||
|
||||
## Scenario: Review a Newsletter Article
|
||||
|
||||
You've drafted a newsletter about AI memory systems and want multi-perspective feedback before publishing.
|
||||
|
||||
### Start the team
|
||||
|
||||
```bash
|
||||
/ai-pair content-team AI-Newsletter
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Team ready.
|
||||
|
||||
Team: AI-Newsletter-content
|
||||
Type: Content Team
|
||||
Members:
|
||||
- author: ready
|
||||
- codex-reviewer: ready
|
||||
- gemini-reviewer: ready
|
||||
|
||||
Awaiting your first task.
|
||||
```
|
||||
|
||||
### Assign a task
|
||||
|
||||
```
|
||||
Review this draft newsletter article about why AI memory
|
||||
(like MEMORY.md in Claude Code) isn't actually "memory" in the
|
||||
way most people think. The article argues that it's closer to
|
||||
a post-it note than a brain.
|
||||
|
||||
[paste your draft or provide file path]
|
||||
```
|
||||
|
||||
The author agent reads the draft and prepares it for review. You approve sending to reviewers.
|
||||
|
||||
### Review results
|
||||
|
||||
```
|
||||
## Codex Review
|
||||
|
||||
### Logic & Accuracy
|
||||
- The claim "LLMs have no persistent state between sessions" is technically
|
||||
correct but oversimplified. Some systems do maintain vector stores that
|
||||
persist. Consider qualifying with "by default" or "natively".
|
||||
- The analogy to post-it notes breaks down: post-it notes don't have
|
||||
retrieval logic. MEMORY.md is more like a briefing document.
|
||||
|
||||
### Fact-Checking
|
||||
- The reference to "context window limits of 200K tokens" should specify
|
||||
this is Claude's limit. GPT-4 Turbo is 128K.
|
||||
|
||||
### Summary
|
||||
Solid argument, but two factual claims need tightening.
|
||||
|
||||
## Gemini Review
|
||||
|
||||
### Readability & Flow
|
||||
- The opening paragraph uses three technical terms before the reader
|
||||
gets the hook. Consider leading with the relatable scenario first.
|
||||
|
||||
### Engagement & Hook
|
||||
- The "post-it note" metaphor is strong but introduced too late (paragraph 4).
|
||||
Move it to the opening for immediate impact.
|
||||
|
||||
### Audience Fit
|
||||
- Tone shifts between casual (paragraphs 1-3) and academic (paragraphs 4-6).
|
||||
The casual tone matches the target audience better — maintain it throughout.
|
||||
|
||||
### Summary
|
||||
Good content, needs structural reorganization for maximum impact.
|
||||
```
|
||||
|
||||
Codex caught **factual precision issues**. Gemini caught **readability and structure issues**. Zero overlap.
|
||||
|
||||
### Iterate
|
||||
|
||||
You tell Team Lead:
|
||||
```
|
||||
Fix the factual claims Codex flagged.
|
||||
Move the post-it metaphor to the opening as Gemini suggested.
|
||||
Keep the casual tone throughout.
|
||||
Don't change the core argument.
|
||||
```
|
||||
|
||||
The author revises. You can send for another round of review or pass.
|
||||
|
||||
### End the session
|
||||
|
||||
```bash
|
||||
/ai-pair team-stop
|
||||
```
|
||||
|
||||
## Tips for Content Team
|
||||
|
||||
1. **Provide context about your audience** — reviewers give better feedback when they know who's reading
|
||||
2. **Don't fix everything** — you decide which feedback matters. Codex tends to over-index on precision; Gemini tends to over-index on accessibility
|
||||
3. **Use iteratively** — first round for big issues, second round for polish
|
||||
4. **Style memory** — if you have a `style-memory.md` file, the author agent will automatically follow your style preferences
|
||||
81
ai-pair/examples/dev-team.md
Normal file
81
ai-pair/examples/dev-team.md
Normal file
@ -0,0 +1,81 @@
|
||||
# Dev Team Example
|
||||
|
||||
## Scenario: Review a Pull Request
|
||||
|
||||
You're working on a project and want multiple AI perspectives on your code changes.
|
||||
|
||||
### Start the team
|
||||
|
||||
```bash
|
||||
/ai-pair dev-team my-web-app
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Team ready.
|
||||
|
||||
Team: my-web-app-dev
|
||||
Type: Dev Team
|
||||
Members:
|
||||
- developer: ready
|
||||
- codex-reviewer: ready
|
||||
- gemini-reviewer: ready
|
||||
|
||||
Awaiting your first task.
|
||||
```
|
||||
|
||||
### Assign a task
|
||||
|
||||
```
|
||||
Implement rate limiting for the /api/login endpoint.
|
||||
Max 5 attempts per IP per 15 minutes.
|
||||
```
|
||||
|
||||
The developer agent reads the codebase, implements the feature, and reports back.
|
||||
|
||||
### Review results
|
||||
|
||||
After you approve sending for review, both reviewers analyze in parallel:
|
||||
|
||||
```
|
||||
## Codex Review
|
||||
|
||||
### WARNING
|
||||
- Rate limit key uses only IP address. Behind a proxy, all users share one IP.
|
||||
Consider combining IP + user-agent or using X-Forwarded-For.
|
||||
- No cleanup mechanism for expired entries in the rate limit store.
|
||||
|
||||
### SUGGESTION
|
||||
- Add a header (X-RateLimit-Remaining) so clients know their status.
|
||||
|
||||
### Summary
|
||||
Functional implementation, but proxy scenario needs attention.
|
||||
|
||||
## Gemini Review
|
||||
|
||||
### Architecture Issues
|
||||
- In-memory store won't work with multiple server instances.
|
||||
Consider Redis or a shared store.
|
||||
|
||||
### Design Patterns
|
||||
- Rate limiter is tightly coupled to the route handler.
|
||||
Extract as middleware for reuse on other endpoints.
|
||||
|
||||
### Summary
|
||||
Works for single-instance deployment. Needs abstraction for production scale.
|
||||
```
|
||||
|
||||
Notice how Codex found a **security edge case** (proxy IP sharing) while Gemini found an **architecture limitation** (single-instance only). Different dimensions, both valuable.
|
||||
|
||||
### Decide
|
||||
|
||||
You can say:
|
||||
- **"Revise"** — feedback gets sent to developer, loop continues
|
||||
- **"Pass"** — move to next task or end team
|
||||
- **"Revise, but only fix the proxy issue and Redis. Skip the middleware refactor for now."** — you control the scope
|
||||
|
||||
### End the session
|
||||
|
||||
```bash
|
||||
/ai-pair team-stop
|
||||
```
|
||||
112
annuity-disclosure-crawler/SKILL.md
Normal file
112
annuity-disclosure-crawler/SKILL.md
Normal file
@ -0,0 +1,112 @@
|
||||
---
|
||||
name: annuity-disclosure-crawler
|
||||
description: 全量爬取中国企业年金基金投资管理人在其官网发布的《企业年金基金投资管理情况》季度管理报告。内置人社部许可的22家投资管理人名录+每家官网披露栏目入口、抓取策略与站点反爬坑位;驱动脚本按季全量下载并做真伪校验与清单记录。当需要采集/批量下载企业年金(或职业年金)季度管理报告/信息披露、定位某投资管理人官网披露页、更新报告库、或做年金管理人季报数据集时使用。触发词:企业年金、投资管理人、季度报告、季报、信息披露、管理情况、年金爬取、全量爬取、年金披露、pension disclosure crawl。
|
||||
---
|
||||
|
||||
# 企业年金投资管理人季报全量爬取
|
||||
|
||||
## 这个技能做什么
|
||||
|
||||
把人社部许可的 **22 家企业年金基金投资管理人**在各自官网公开发布的
|
||||
《企业年金基金投资管理情况》**季度报告**,按季**全量**抓取到本地,做真伪校验并生成清单。
|
||||
名录、每家披露栏目 URL、抓取策略、站点反爬坑位都已固化在 `references/registry.json`,
|
||||
两个脚本读它干活,无需每次重新摸索。
|
||||
|
||||
核心事实(先读懂再动手):
|
||||
|
||||
- **口径**:抓的是投资管理人**自披露**的《企业年金基金投资管理情况》季度信息披露表。它 ≠ 受托人向委托人/人社部报送的完整《企业年金基金管理情况季度报告》全文(后者按规定不公开)。检索时排在前面的“集合型企业年金计划管理情况信息披露表”常是**受托人口径**(如工商银行、各托管行),别混进投资管理人季报。
|
||||
- **不是每家都公开**:约 19 家官网公开可下;博时、招商的季报仅登录客户门户可见;建信养老金官网只发“养老金产品”层报告且被 WAF+加密挡住。详见 registry 里各家 `access`。
|
||||
- **报告形态三种**:PDF(多数)、**DOCX**(泰康资产,链接无扩展名)、**HTML 网页正文**(海富通/南方/长江/中信证券/人保,无 PDF 附件,存网页)。
|
||||
- **数据坑**:人保养老(picc)官网自 2022Q1 起正文区为空,只登记标题+日期,近四年数据未公开。
|
||||
|
||||
## 两层抓取工作流
|
||||
|
||||
先用纯 HTTP(快、无依赖),搞不定的再上无头浏览器。
|
||||
|
||||
### 第 1 步:看名录与策略
|
||||
|
||||
```bash
|
||||
python3 scripts/crawl.py --list
|
||||
```
|
||||
|
||||
列出 22 家的 slug / 简称 / access(public|waf|login) / strategy / 披露入口。
|
||||
|
||||
### 第 2 步:HTTP 全量抓取(默认路径)
|
||||
|
||||
```bash
|
||||
# 全量抓所有可 HTTP 抓取的机构(跳过 waf/login,会明确提示哪几家要换 headless)
|
||||
python3 scripts/crawl.py --out annuity_reports
|
||||
|
||||
# 只抓某几家;--latest N 只留最新 N 期(0=全量,默认全量)
|
||||
python3 scripts/crawl.py --only m04,m09,m17 --latest 4 --out annuity_reports
|
||||
```
|
||||
|
||||
`strategy` 含义(脚本自动按此处理,一般无需关心):
|
||||
`pdf_index` 列表页直给 PDF 直链 · `detail_index` 列表→详情页→再取文件 ·
|
||||
`html_index` 报告本身是 HTML 网页(存网页)· `spa_api` 列表走后端 JSON ·
|
||||
`headless` 需浏览器(脚本会跳过并提示)· `login_blocked` 非公开(跳过并说明)。
|
||||
|
||||
脚本已内置的通用能力:列表翻页(首页 index.html + index_1/2… 自动补全)、
|
||||
GBK/UTF-8 自适应解码、pdfjs `viewer.html?file=` 包装解包、相对链接补全、
|
||||
期次识别(`2026Q1`/`2026年1季度`/中文数字季度)、真伪校验(PDF 看 `%PDF` 魔数、
|
||||
DOCX 看 `PK`、HTML 看大小+关键字)、内容 MD5 去重、同期两类报告防覆盖。
|
||||
|
||||
### 第 3 步:无头浏览器兜底(WAF/SPA 机构)
|
||||
|
||||
当第 2 步对某家提示“0 命中:请改用 crawl_headless.py”,或该家 `access=waf`/`strategy=headless`:
|
||||
|
||||
```bash
|
||||
pip install playwright --break-system-packages && python3 -m playwright install chromium
|
||||
|
||||
# 默认抓所有需浏览器的机构;也可 --only 指定;--headful 显示窗口调试
|
||||
python3 scripts/crawl_headless.py --only m15,m21 --latest 4 --out annuity_reports
|
||||
```
|
||||
|
||||
真实 Chromium 会自动过瑞数(botgate 412)、加速乐(jsl 521)等 JS 挑战,
|
||||
渲染出 SPA 列表后用带 Cookie 的浏览器上下文下载(含跨域文件站)。复用 crawl.py 的
|
||||
识别/校验/命名逻辑,落地到**同一** `--out` 目录,另出 `manifest_headless.*`。
|
||||
|
||||
典型需 headless 的:富国/工银瑞信/嘉实/易方达(SPA 列表)、平安养老/华泰资产(动态列表)、
|
||||
银华/太平养老/中金(反爬 WAF)。
|
||||
|
||||
## 输出结构与清单
|
||||
|
||||
```
|
||||
annuity_reports/
|
||||
├── m04/ m04_2026Q1.pdf m04_2025Q4.pdf ... _disclosure_page.html
|
||||
├── m13/ m13_2026Q1.docx ...
|
||||
├── m22/ m22_2026Q1.html ...
|
||||
├── manifest.csv / manifest.json # crawl.py 清单:机构·期次·状态·字节·本地路径·源URL
|
||||
└── manifest_headless.csv / .json # crawl_headless.py 清单
|
||||
```
|
||||
|
||||
`status` 取值:`ok`(校验通过)/ `not-pdf`/`not-docx`/`suspect`(存为 `*.suspect` 待查)/ `download-fail:*`。
|
||||
交付前用 manifest 核对每家是否抓全、有无 suspect。
|
||||
|
||||
## 全量 vs 增量
|
||||
|
||||
- **全量**:不加 `--latest`(默认 0),脚本翻完列表所有分页,抓每家全部历史季度。
|
||||
首轮建议单跑几家验证:`--only m04,m17,m13`,确认无误再整体跑。
|
||||
- **增量**:定期只补新季度用 `--latest 2`,MD5 去重会跳过已存在的同内容文件(但会重新下载比对,
|
||||
真正省流可结合已存在文件名跳过)。季度披露节奏:一般次季度中下旬发布(如 Q1 报告约 6 月中下旬)。
|
||||
|
||||
## 排障速查
|
||||
|
||||
- **HTTP 0 命中** → 列表 JS 渲染或被 WAF 挡:改 `crawl_headless.py`。
|
||||
- **headless 仍 0 命中** → 可能需登录、或报告藏在需点击的折叠/下一页里:加 `--headful` 肉眼看,
|
||||
必要时在 registry 该家 `list_url` 填精确分页模板或改 `strategy`。
|
||||
- **拿到的 PDF 打不开/很小** → 多半是 WAF 降级的 HTML 错误页;脚本已标 `suspect`。换 headless。
|
||||
- **404 一堆中文名 .pdf** → 详情页里的“显示用文件名”被误当链接;已在 `files_in_page` 收敛,若仍有属噪声可忽略(不影响真链)。
|
||||
- **工银瑞信**:官网迁 icbccs→icbcubs 成 SPA,直链多 404;可靠源是 Wayback 快照
|
||||
`https://web.archive.org/web/{ts}id_/{原PDF直链}`(快照止于 2024Q2)。
|
||||
- **name 冲突** `rtk gain` 无关;与本技能无关的报错先 `--list` 自检 registry 是否可读。
|
||||
|
||||
## 维护 registry(增删改机构或修坑)
|
||||
|
||||
`references/registry.json` 是唯一事实源,字段语义见其 `meta.field_notes`。常见维护:
|
||||
|
||||
- 名单变动:以**人社部社保基金监管司**最新《企业年金基金管理机构名单》为准,增删 `managers` 条目。
|
||||
- 某家官网改版:更新其 `disclosure_url` / `list_url`(分页模板,`{n}` 为页码) / `file_url_pattern`(报告文件或报告页 URL 正则) / `strategy`。
|
||||
- 加新机构:复制一条,填 `slug`(mNN)、`short`、`name`、`category`、`access`、`strategy`、`item_type`、入口与正则,先 `--only 新slug --latest 2` 验证。
|
||||
|
||||
registry 里每家的 `notes` 记录了该站的真实坑位(编码、反爬类型、命名规律、口径提醒),改站点前先读。
|
||||
421
annuity-disclosure-crawler/references/registry.json
Normal file
421
annuity-disclosure-crawler/references/registry.json
Normal file
@ -0,0 +1,421 @@
|
||||
{
|
||||
"meta": {
|
||||
"title": "企业年金基金投资管理人 · 官网季度管理报告披露源登记",
|
||||
"roster_size": 22,
|
||||
"roster_source": "人社部 2021-10《关于企业年金基金管理机构资格延续的通告》中的投资管理人(22家)+ 基金业协会 amac.org.cn 查询栏目交叉核对。以人社部社保基金监管司最新《企业年金基金管理机构名单》为最终依据。",
|
||||
"verified": "2026-07",
|
||||
"report_title_canonical": "企业年金基金投资管理情况(季度)",
|
||||
"field_notes": {
|
||||
"access": "public=官网公开可匿名下载; waf=官网有披露栏目但被反爬WAF/JS挑战拦截,需无头浏览器; login=季报仅登录客户门户可见,不公开",
|
||||
"strategy": "驱动脚本 scripts/crawl.py 的抓取策略:pdf_index=列表页直接给文件直链; detail_index=列表→详情页→再取文件链; html_index=报告本身是HTML网页(无PDF附件),存网页; spa_api=列表走后端JSON接口; headless=需 playwright 渲染(WAF/SPA); login_blocked=跳过",
|
||||
"item_type": "报告落地文件类型:pdf / docx / html",
|
||||
"list_url": "披露列表页;{n} 为翻页占位符(全量抓取时从1递增直到空页)"
|
||||
}
|
||||
},
|
||||
"managers": [
|
||||
{
|
||||
"slug": "m01",
|
||||
"short": "博时",
|
||||
"name": "博时基金管理有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.bosera.com",
|
||||
"access": "login",
|
||||
"strategy": "login_blocked",
|
||||
"item_type": "html",
|
||||
"disclosure_url": "https://www.bosera.com/column/index.do?classid=00020002000600090001",
|
||||
"list_url": null,
|
||||
"page_param": null,
|
||||
"file_url_pattern": null,
|
||||
"notes": "官网养老/年金板块的季度管理报告、动态月报、净值持仓在「年金客户专区」,需账号密码登录 trade.bosera.com,不对公众公开。客服 95105568。"
|
||||
},
|
||||
{
|
||||
"slug": "m02",
|
||||
"short": "富国",
|
||||
"name": "富国基金管理有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.fullgoal.com.cn",
|
||||
"access": "public",
|
||||
"strategy": "detail_index",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://www.fullgoal.com.cn/main/InstiServices/Retirement/RetirementInfoDis/qynjtzgl/index.html",
|
||||
"list_url": "https://www.fullgoal.com.cn/main/InstiServices/Retirement/RetirementInfoDis/qynjtzgl/index.html",
|
||||
"page_param": null,
|
||||
"file_url_pattern": "https?://www\\.fullgoal\\.com\\.cn/(upload/fck/userfiles/file|wbs-file/ws)/[^\"'\\s]+\\.pdf",
|
||||
"example_links": [
|
||||
"https://www.fullgoal.com.cn/wbs-file/ws/20250320/73059d0333984f7cb04df6ac6fe35fec.pdf"
|
||||
],
|
||||
"notes": "Vue SPA 但列表/文章页 SSR,PDF 直链稳定。栏目→机构服务→养老金信息披露→企业年金投资管理。命名早期 fgjjglyxgsqynjjjtzglqk(YYYYnXjd).pdf,后期哈希名。抓取时官网疑似停留在 2024Q4。SPA 渲染不全时用 headless 兜底。\n\n[2026-07-07 全量验证] crawl.py HTTP 全量抓取通过,10期(2023Q4-2026Q1)。⚠️该披露页无翻页控件,就是官网展示的全部——早于2023Q4的报告在当前官网结构下不存在任何入口,历史更早数据对公众不可得(非爬虫限制,是官网只保留近~2年展示)。"
|
||||
},
|
||||
{
|
||||
"slug": "m03",
|
||||
"short": "工银瑞信",
|
||||
"name": "工银瑞信基金管理有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.icbcubs.com.cn",
|
||||
"access": "waf",
|
||||
"strategy": "headless",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://www.icbccs.com.cn/gyrx/yljj/qynj/qynjxxpl/pages/1.html",
|
||||
"list_url": "https://www.icbccs.com.cn/gyrx/yljj/qynj/qynjxxpl/pages/{n}.html",
|
||||
"page_param": "{n}",
|
||||
"file_url_pattern": "https?://www\\.icbc(cs|ubs)\\.com\\.cn/upload/[0-9]+/[0-9]+/[^\"'\\s]+\\.pdf",
|
||||
"example_links": [
|
||||
"https://www.icbccs.com.cn/upload/2427/2023/20240905qynjglqk.pdf"
|
||||
],
|
||||
"notes": "因瑞信被UBS收购,域名 icbccs→icbcubs 迁移为 Vue SPA,旧直链多404。可靠来源为 Wayback:https://web.archive.org/web/{ts}id_/{原PDF直链}(快照止于2024Q2)。新站列表接口 /gyrx-article/app/article/searchPage 需未公开 POST 参数。注意别混入工商银行(受托人)口径的披露表。\n\n[2026-07-07 复核] 确认 icbccs.com.cn 旧路径headless渲染下仍404(\"此次请求暂不能处理\"),域名迁移导致的失效是稳定状态非偶发。当前可靠来源仍只有 Wayback 快照(止于2024Q2);2024Q3至今(含2026Q1)在新站(icbcubs.com.cn) SPA 后台是否已发布不明——新站文章接口需要的POST参数未逆向出来,这段历史目前对我们不可得。"
|
||||
},
|
||||
{
|
||||
"slug": "m04",
|
||||
"short": "国泰",
|
||||
"name": "国泰基金管理有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.gtfund.com",
|
||||
"access": "public",
|
||||
"strategy": "pdf_index",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://www.gtfund.com/Etrade/Report/nianjinreport/",
|
||||
"list_url": "https://www.gtfund.com/Etrade/Report/nianjinreport/",
|
||||
"page_param": null,
|
||||
"file_url_pattern": "https?://st\\.gtfund\\.com/report/[0-9]{4}/[0-9]{2}/[^\"'\\s]+\\.pdf",
|
||||
"example_links": [
|
||||
"https://st.gtfund.com/report/2026/06/企业年金基金信息披露表_企业年金基金投资管理情况2026Q1.pdf"
|
||||
],
|
||||
"notes": "最省事的正面样本:单个列表页即列出从2022至今十余期,PDF 托管 st.gtfund.com/report/YYYY/MM/,文件名含中文与 YYYYQn,需 URL 编码。"
|
||||
},
|
||||
{
|
||||
"slug": "m05",
|
||||
"short": "海富通",
|
||||
"name": "海富通基金管理有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.hftfund.com",
|
||||
"access": "public",
|
||||
"strategy": "html_index",
|
||||
"item_type": "html",
|
||||
"disclosure_url": "https://www.hftfund.com/annuity/info/index.html",
|
||||
"list_url": "https://www.hftfund.com/annuity/info/index{n}.html",
|
||||
"page_param": "{n}",
|
||||
"file_url_pattern": "https?://www\\.hftfund\\.com/contents/[0-9]{4}/[0-9]+/[^\"'\\s]+\\.html",
|
||||
"example_links": [
|
||||
"https://www.hftfund.com/contents/2025/6/13-399abf20de9f46a59c180b96a9eb12f3.html"
|
||||
],
|
||||
"notes": "报告为 HTML 正文页(无PDF附件),存网页存档即可。列表分页 index.html/index2.html/index3.html;列表 AJAX 渲染,静态只拿到部分历史,全量需 headless 枚举分页。detail URL 用随机哈希。\n\n[2026-07-07 全量验证] HTTP 静态分页拿到 2012A-2023Q2 共46期,历史深度全名录最佳。⚠️但2023Q3至今(含2026Q1最新)在静态HTML里不存在——该区间列表改为AJAX渲染,HTTP拿不到,需 crawl_headless.py 专门补最近这一段;即“旧历史全,新数据缺”,两种手段需配合使用才能补全全部期次。"
|
||||
},
|
||||
{
|
||||
"slug": "m06",
|
||||
"short": "华夏",
|
||||
"name": "华夏基金管理有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.chinaamc.com.cn",
|
||||
"access": "public",
|
||||
"strategy": "detail_index",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://www.chinaamc.com.cn/yanglaojijin/nianjin/nianjinyeji/index.shtml",
|
||||
"list_url": "https://www.chinaamc.com.cn/yanglaojijin/nianjin/nianjinyeji/index_{n}.shtml",
|
||||
"page_param": "{n}",
|
||||
"file_url_pattern": "https?://www\\.chinaamc\\.com/(upload/resources/file/[^\"'\\s]+\\.pdf|front/ui/contentcore/resource/download\\?ID=[0-9]+)",
|
||||
"example_links": [
|
||||
"https://www.chinaamc.com/upload/resources/file/2026/06/18/453207.pdf",
|
||||
"https://www.chinaamc.com/front/ui/contentcore/resource/download?ID=433263"
|
||||
],
|
||||
"notes": "养老金频道→年金→年金业绩。列表每条→详情页 /c/YYYY-MM-DD/xxxxxx.shtml,内含PDF直链或 contentcore/resource/download?ID= 下载接口。覆盖2017至今,最新2026Q1。首页 index.shtml,翻页 index_1.shtml…"
|
||||
},
|
||||
{
|
||||
"slug": "m07",
|
||||
"short": "嘉实",
|
||||
"name": "嘉实基金管理有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.jsfund.cn",
|
||||
"access": "public",
|
||||
"strategy": "spa_api",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://www.jsfund.cn/main/pensions/AnnuityBusiness/index.shtml",
|
||||
"list_url": "https://www.jsfund.cn/main/pensions/AnnuityBusiness/index.shtml",
|
||||
"page_param": null,
|
||||
"file_url_pattern": "https?://www\\.jsfund\\.cn/ueditor/jsp/upload/file/[0-9]{8}/[0-9]+\\.pdf",
|
||||
"api": {
|
||||
"endpoint": "https://www.jsfund.cn/servlet/json?random={rand}",
|
||||
"method": "POST",
|
||||
"params": {
|
||||
"funcNo": "741002",
|
||||
"type": "2",
|
||||
"article_id": "{id}",
|
||||
"rows": "1"
|
||||
},
|
||||
"pdf_field": "content"
|
||||
},
|
||||
"example_links": [
|
||||
"https://www.jsfund.cn/ueditor/jsp/upload/file/20260617/1781691526655083303.pdf"
|
||||
],
|
||||
"notes": "Thinkive 框架 SPA。列表详情页 /main/a/{YYYYMMDD}/{articleId}.shtml 正文异步加载:POST /servlet/json funcNo=741002 取 content(内嵌PDF直链),无需登录/签名。PDF 路径 /ueditor/jsp/upload/file/{YYYYMMDD}/{雪花id}.pdf。\n\n[2026-07-07 已知缺陷] crawl.py 只做静态HTML抓取,未真正调用 registry 里记录的 servlet/json(funcNo=741002) API,故全量运行只抓到列表首屏SSR的最新1期(2026Q1),2025Q4及更早的历史期次未被抓取。这是 crawl.py 的功能缺口(spa_api策略未落地为真实API调用),不是网站不可得——需在 crawl.py 里为 spa_api 策略实现 POST /servlet/json 二次取件才能补全历史。"
|
||||
},
|
||||
{
|
||||
"slug": "m08",
|
||||
"short": "南方",
|
||||
"name": "南方基金管理股份有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.nffund.com",
|
||||
"access": "public",
|
||||
"strategy": "html_index",
|
||||
"item_type": "html",
|
||||
"disclosure_url": "https://www.nffund.com/main/newCCZX/qynj/xxpl/index.shtml",
|
||||
"list_url": "https://www.nffund.com/main/newCCZX/qynj/xxpl/index_{n}.shtml",
|
||||
"page_param": "{n}",
|
||||
"file_url_pattern": "https?://www\\.nffund\\.com/main/newCCZX/qynj/xxpl/[0-9]+\\.shtml",
|
||||
"example_links": [
|
||||
"https://www.nffund.com/main/newCCZX/qynj/xxpl/133350.shtml"
|
||||
],
|
||||
"notes": "持仓资讯→企业年金→信息披露。报告为 HTML 正文(GBK/gb18030 编码,抓后 iconv 转 UTF-8)。无PDF。nffund.com 与 southernfund.com 同主体,直连 nffund.com 抓。\n\n[2026-07-07 诊断] HTTP 0命中(原始HTML里根本没有报告文本,纯AJAX注入)。headless 渲染后报告标题确实可见(2024Q3-2026Q1一路可读),但这些条目不是 <a href> 而是无链接的文本行/onclick,故现有 headless 脚本(只扫<a>标签)仍抓不到文件直链。需针对该站定制:抓可见文本+配对同页的隐藏 data-id/onclick 参数,或抓包找底层 AJAX/JSON 接口。"
|
||||
},
|
||||
{
|
||||
"slug": "m09",
|
||||
"short": "易方达",
|
||||
"name": "易方达基金管理有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.efunds.com.cn",
|
||||
"access": "public",
|
||||
"strategy": "detail_index",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://www.efunds.com.cn/Mobile/lm/yljxxpl/qynjtzglqk/",
|
||||
"list_url": "https://www.efunds.com.cn/Mobile/lm/yljxxpl/qynjtzglqk/",
|
||||
"page_param": null,
|
||||
"file_url_pattern": "https?://cdn\\.efunds\\.com\\.cn/owch/data/bulletin/[0-9]{8}/[^\"'\\s]+\\.pdf",
|
||||
"example_links": [
|
||||
"https://cdn.efunds.com.cn/owch/data/bulletin/20260618/易方达基金管理有限公司企业年金基金投资管理情况-2026年1季度.pdf"
|
||||
],
|
||||
"notes": "机构服务→养老金信息披露→企业年金投资管理情况报告。PDF 在 CDN cdn.efunds.com.cn/owch/data/bulletin/{发布日}/,命名固定「易方达基金管理有限公司企业年金基金投资管理情况-YYYY年N季度.pdf」。列表动态,全量枚举用 headless 或按季推日期。PC 入口 /lm/jgfw/xxpl/。"
|
||||
},
|
||||
{
|
||||
"slug": "m10",
|
||||
"short": "银华",
|
||||
"name": "银华基金管理股份有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.yhfund.com.cn",
|
||||
"access": "waf",
|
||||
"strategy": "headless",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "http://www.yhfund.com.cn/main/corporatePension/report/index.shtml",
|
||||
"list_url": "http://www.yhfund.com.cn/main/corporatePension/report/index.shtml",
|
||||
"page_param": null,
|
||||
"file_url_pattern": "https?://www\\.yhfund\\.com\\.cn/upload/[0-9]{8}/[^\"'\\s]+\\.pdf",
|
||||
"example_links": [
|
||||
"https://www.yhfund.com.cn/upload/20240528/202405281716881071862.pdf"
|
||||
],
|
||||
"notes": "报告栏目 /main/corporatePension/report/。所有动态 .shtml 页对数据中心IP被WAF拦(837B「可疑访问」页),列表需 headless/住宅IP。但 /upload/YYYYMMDD/ 下的PDF不受WAF限,拿到直链即可curl。文件名含随机时间戳,不可猜,须从列表页取。\n\n[2026-07-07 复核] 确认 headless(无stealth) 仍被WAF拦:返回 `可疑访问行为被拒绝`(200状态码但内容为拦截页)。说明该WAF会指纹识别headless/自动化浏览器,不是简单等待JS渲染能绕过的,需要更强的反检测(stealth插件/真实指纹)或人工过一次拿到cookie复用。已知可行的是:若能从其它渠道(搜索引擎缓存等)拿到具体PDF直链,/upload/路径本身不拦,可以直接下载——但发现新链接这一步现阶段做不到。"
|
||||
},
|
||||
{
|
||||
"slug": "m11",
|
||||
"short": "招商",
|
||||
"name": "招商基金管理有限公司",
|
||||
"category": "基金公司",
|
||||
"homepage": "https://www.cmfchina.com",
|
||||
"access": "login",
|
||||
"strategy": "login_blocked",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://www.cmfchina.com/web/elderlyCare/enterprisePension/index.html",
|
||||
"list_url": null,
|
||||
"page_param": null,
|
||||
"file_url_pattern": null,
|
||||
"notes": "企业年金页有「信息披露」入口,但跳转客户门户 qy.cmfchina.com/#/pensionInfo,未登录一律 redirect /login,需 fundAccount+计划号+验证码换 token 才能下载季报。公开的只有「养老金产品公告」(分红/变更等),非季度管理报告。"
|
||||
},
|
||||
{
|
||||
"slug": "m12",
|
||||
"short": "华泰资产",
|
||||
"name": "华泰资产管理有限公司",
|
||||
"category": "保险资管",
|
||||
"homepage": "https://www.htam.com.cn",
|
||||
"access": "public",
|
||||
"strategy": "detail_index",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://www.htam.com.cn/product/notice",
|
||||
"list_url": "https://www.htam.com.cn/product/notice",
|
||||
"page_param": null,
|
||||
"file_url_pattern": "https?://www\\.htam\\.com\\.cn/(File|Uploads/Picture)/[^\"'\\s]+\\.pdf",
|
||||
"api": {
|
||||
"endpoint": "https://www.htam.com.cn/api/v1/article/{id}",
|
||||
"method": "GET",
|
||||
"pdf_field": "content"
|
||||
},
|
||||
"example_links": [
|
||||
"https://www.htam.com.cn/File/2026/6/22/202606221619312026年1季度企业年金基金投资管理情况.pdf"
|
||||
],
|
||||
"notes": "产品→公告。PDF 在 /File/YYYY/M/D/ 或 /Uploads/Picture/;详情走 /api/v1/article/{id} 返回含PDF链。文件名含中文季度标识。\n\n[2026-07-07 诊断] 与首次人工爬取(2026-07-06)相比,官网疑似已改版:headless 渲染「产品公告→养老金产品」页面正常200且标题正确,但列表区显示「暂无数据」,此前记录的 /File/...pdf 与 /api/v1/article/{id} 路径当前均取不到任何条目。需重新踩点新的筛选/接口方式,此为站点近期变动导致的临时失效,非永久不可得。"
|
||||
},
|
||||
{
|
||||
"slug": "m13",
|
||||
"short": "泰康资产",
|
||||
"name": "泰康资产管理有限责任公司",
|
||||
"category": "保险资管",
|
||||
"homepage": "https://www.taikangasset.cn",
|
||||
"access": "public",
|
||||
"strategy": "detail_index",
|
||||
"item_type": "docx",
|
||||
"disclosure_url": "https://www.taikangasset.cn/comproduct/ylbusiness/enterprisefunds/enterpriseinfo/list_294_1.html",
|
||||
"list_url": "https://www.taikangasset.cn/comproduct/ylbusiness/enterprisefunds/enterpriseinfo/list_294_{n}.html",
|
||||
"page_param": "{n}",
|
||||
"list_pages_known": 6,
|
||||
"file_url_pattern": "https?://files\\.taikangasset\\.cn/(service/business/website/download/[0-9A-F]{32}|customer/websiteDownload\\.do\\?id=[0-9]+)",
|
||||
"example_links": [
|
||||
"https://files.taikangasset.cn/service/business/website/download/76CBEEB8DCFA465FB436646D8624585B",
|
||||
"https://files.taikangasset.cn/customer/websiteDownload.do?id=154963234050491764318476102"
|
||||
],
|
||||
"notes": "养老金业务→企业年金→信息披露。⚠️报告是 .docx(Word) 不是PDF,下载链接无扩展名,服务端返回Word,按 .docx 命名。列表倒序6页。新报告走 /service/business/website/download/{32位HEX},旧报告 /customer/websiteDownload.do?id={数字}。"
|
||||
},
|
||||
{
|
||||
"slug": "m14",
|
||||
"short": "长江养老",
|
||||
"name": "长江养老保险股份有限公司",
|
||||
"category": "养老保险公司",
|
||||
"homepage": "https://www.cj-pension.com.cn",
|
||||
"access": "public",
|
||||
"strategy": "html_index",
|
||||
"item_type": "html",
|
||||
"disclosure_url": "https://www.cj-pension.com.cn/cjyl/Channel/3505179_1/qynjcpxxpl/jdxxpl",
|
||||
"list_url": "https://www.cj-pension.com.cn/cjyl/Channel/3505179_{n}/qynjcpxxpl/jdxxpl",
|
||||
"page_param": "{n}",
|
||||
"list_pages_known": 11,
|
||||
"file_url_pattern": "https?://www\\.cj-pension\\.com\\.cn/c/[0-9]{4}-[0-9]{2}-[0-9]{2}/[0-9]+\\.shtml",
|
||||
"example_links": [
|
||||
"https://www.cj-pension.com.cn/c/2026-06-18/1884003.shtml"
|
||||
],
|
||||
"notes": "信息披露→企业年金产品信息披露→季度/年度信息披露。每季两类:①企业年金集合计划管理情况 ②企业年金基金投资管理情况。报告为 HTML(.shtml) 正文,无PDF。分11页。别混入 health.cpic.com.cn 的公司年度PDF。"
|
||||
},
|
||||
{
|
||||
"slug": "m15",
|
||||
"short": "平安养老",
|
||||
"name": "平安养老保险股份有限公司",
|
||||
"category": "养老保险公司",
|
||||
"homepage": "https://yl.pingan.com",
|
||||
"access": "public",
|
||||
"strategy": "headless",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://yl.pingan.com/branding/products",
|
||||
"list_url": "https://yl.pingan.com/branding/products",
|
||||
"page_param": null,
|
||||
"file_url_pattern": "https?://yl\\.pingan\\.com/app_upload/file/[0-9]{8}/[^\"'\\s]+\\.pdf",
|
||||
"example_links": [
|
||||
"https://yl.pingan.com/app_upload/file/20251128/企业年金基金投资管理情况2025Q3_1470.pdf"
|
||||
],
|
||||
"notes": "产品及方案→年金→企业年金投资管理业绩披露。PDF 在 /app_upload/file/{YYYYMMDD}/,文件名含 YYYYQn。列表页动态渲染(WebFetch只得标题),全量枚举用 headless;PDF直链本身可匿名curl。yl.pingan.com=pension.pingan.com。"
|
||||
},
|
||||
{
|
||||
"slug": "m16",
|
||||
"short": "太平养老",
|
||||
"name": "太平养老保险股份有限公司",
|
||||
"category": "养老保险公司",
|
||||
"homepage": "http://tppension.cntaiping.com",
|
||||
"access": "waf",
|
||||
"strategy": "headless",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://tppension.cntaiping.com/njjylbz-jhxqynjjhglqk/",
|
||||
"list_url": "https://tppension.cntaiping.com/njjylbz-jhxqynjjhglqk/",
|
||||
"page_param": null,
|
||||
"file_url_pattern": null,
|
||||
"notes": "公开信息披露→企业年金信息披露(/info-qynjxxpl/);报告列表在 /njjylbz-jhxqynjjhglqk/(集合型企业年金计划管理情况,按季)。⚠️全域瑞数(botgate)动态JS反爬:首请求 HTTP 412,需浏览器执行 $_ts JS 挑战算 Cookie。必须 headless(playwright/真浏览器)。个案账户走登录门户 service.tppension.cntaiping.com。\n\n[2026-07-07 复核] 确认 headless(无stealth) 仍卡在瑞数botgate挑战:HTTP 412,body仅39字节。这是主动的JS人机验证(不只是异步渲染延迟),需要专门的挑战破解逻辑(执行页面下发的混淆JS算出正确Cookie),通用playwright耐心等待解决不了。当前仍无法从官网直接爬到任何报告直链。"
|
||||
},
|
||||
{
|
||||
"slug": "m17",
|
||||
"short": "新华养老",
|
||||
"name": "新华养老保险股份有限公司",
|
||||
"category": "养老保险公司",
|
||||
"homepage": "https://www.newchinapension.com",
|
||||
"access": "public",
|
||||
"strategy": "detail_index",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://www.newchinapension.com/xhylbx/_300468/312175/index.html",
|
||||
"list_url": "https://www.newchinapension.com/xhylbx/_300468/312175/index_{n}.html",
|
||||
"page_param": "{n}",
|
||||
"file_url_pattern": "https?://www\\.newchinapension\\.com/xhylbx/articleFileDir/[0-9]{4}-[0-9]{2}/[0-9]+/[^\"'\\s]+\\.pdf",
|
||||
"example_links": [
|
||||
"https://www.newchinapension.com/xhylbx/articleFileDir/2026-06/25/ccedf2a37bcf433f9d21a9e20eedcf0a/新华养老保险股份有限公司_企业年金基金投资管理情况2026Q1.pdf"
|
||||
],
|
||||
"notes": "公开信息披露→企业年金投管业务信息披露,共29条,最新2026Q1。PDF直链含中文文件名(需URL编码),命名「新华养老保险股份有限公司_企业年金基金投资管理情况YYYYQn.pdf」。另有「年度信息披露报告」为不同栏目。"
|
||||
},
|
||||
{
|
||||
"slug": "m18",
|
||||
"short": "国寿养老",
|
||||
"name": "中国人寿养老保险股份有限公司",
|
||||
"category": "养老保险公司",
|
||||
"homepage": "https://www.clpc.com.cn",
|
||||
"access": "public",
|
||||
"strategy": "detail_index",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "https://www.clpc.com.cn/chinalifepension/jgkh/qynj_yljcpxxpl/index.html",
|
||||
"list_url": "https://www.clpc.com.cn/chinalifepension/jgkh/qynj_yljcpxxpl/index_{n}.html",
|
||||
"page_param": "{n}",
|
||||
"file_url_pattern": "https?://www\\.clpc\\.com\\.cn/chinalifepension/articleFileDir/[0-9]{4}-[0-9]{2}/[0-9]+/[^\"'\\s]+\\.pdf",
|
||||
"example_links": [
|
||||
"https://www.clpc.com.cn/chinalifepension/articleFileDir/2026-06/23/543e6a924dbd470f86913e5d570d903d.pdf"
|
||||
],
|
||||
"notes": "机构客户→企业年金/养老金产品信息披露。每季两份:①企业年金基金投资管理情况 ②集合型企业年金计划管理情况。PDF 在 /articleFileDir/YYYY-MM/DD/{hash}.pdf。公司层面「信息披露」(/gkxxpl/)是另一独立栏目。"
|
||||
},
|
||||
{
|
||||
"slug": "m19",
|
||||
"short": "人保养老",
|
||||
"name": "中国人民养老保险有限责任公司",
|
||||
"category": "养老保险公司",
|
||||
"homepage": "https://www.picc-pension.com.cn",
|
||||
"access": "public",
|
||||
"strategy": "html_index",
|
||||
"item_type": "html",
|
||||
"disclosure_url": "https://www.picc-pension.com.cn/html/category/1812/200019-1.htm",
|
||||
"list_url": "https://www.picc-pension.com.cn/html/category/1812/200019-{n}.htm",
|
||||
"page_param": "{n}",
|
||||
"file_url_pattern": "https?://www\\.picc-pension\\.com\\.cn/html/report/[0-9]{4}/[0-9]+-1\\.htm",
|
||||
"example_links": [
|
||||
"https://www.picc-pension.com.cn/html/report/2606/0007-1.htm"
|
||||
],
|
||||
"notes": "信息披露→企业年金信息披露,2018Q3–2026Q1 共62条,每季两类(投资管理情况/集合计划受托)。报告为内联 HTML 表格(<span id=ReportIDtext>),无附件。⚠️注意:仅 2018Q3–2021Q4 正文含完整数据表;2022Q1 起官网正文区为空(只有标题+日期),近四年数据未公开。\n\n[2026-07-07 诊断] 双重问题:①技术层——该页文章列表由站点自有的 `/asset/dyn/a_article/script.js` 挂件异步注入,headless 渲染10秒后 body 仍完全为空(0字节),该挂件在无头环境下未能执行/未能取到数据,尚未查明原因(可能校验referrer/viewport/需真实交互),导致列表拿不到,是当前爬虫的真实缺口;②数据层——即使解决①、能进入具体报告页,此前已确认自2022Q1起正文区`<span id=ReportIDtext>`本身为空(仅标题+日期),即近四年官网自己就没有公开数据,这部分是官网侧的历史数据缺失,与技术能力无关,无法通过改进爬虫解决。"
|
||||
},
|
||||
{
|
||||
"slug": "m20",
|
||||
"short": "建信养老金",
|
||||
"name": "建信养老金管理有限责任公司",
|
||||
"category": "养老金管理公司",
|
||||
"homepage": "http://www.ccbpension.com",
|
||||
"access": "waf",
|
||||
"strategy": "login_blocked",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "http://www.ccbpension.com/xxpl_yyjnbg.html",
|
||||
"list_url": null,
|
||||
"page_param": null,
|
||||
"file_url_pattern": null,
|
||||
"notes": "Vue SPA,信息披露走 /api/modules/disclosure/product/pageQuery,⚠️整站WAF(「访问拦截」页)+ API返回加密密文({jsonData:base64},需 /api/crypto/getKey 解密),无头浏览器也难。且官网披露的是「养老金产品」层报告,非计划层企业年金季报——计划层季报不在官网公开(报送受托人/人社部)。第三方(企查查)缓存可见零星产品年报,非官网、非季度。"
|
||||
},
|
||||
{
|
||||
"slug": "m21",
|
||||
"short": "中金",
|
||||
"name": "中国国际金融股份有限公司",
|
||||
"category": "证券公司",
|
||||
"homepage": "http://www.cicc.com",
|
||||
"access": "waf",
|
||||
"strategy": "headless",
|
||||
"item_type": "pdf",
|
||||
"disclosure_url": "http://www.cicc.com/portal/investor/info/fnancial_cn.xhtml?columnId=718",
|
||||
"list_url": "http://www.cicc.com/portal/investor/info/fnancial_cn.xhtml?columnId=718",
|
||||
"page_param": null,
|
||||
"file_url_pattern": "https?://www\\.cicc\\.com/upload/file/[^\"'\\s]+\\.pdf",
|
||||
"example_links": [
|
||||
"https://www.cicc.com/upload/file/2025/10/27/a9a6eb17-36b4-49ba-ae3f-8ec0f9c86eb5.pdf"
|
||||
],
|
||||
"notes": "投资者关系→信息披露(columnId=718)。⚠️加速乐(jsl)反爬:首请求521+JS挑战,需两级cookie(__jsluid_s + __jsl_clearance_s),第二级为混淆VM挑战,需 node/浏览器执行算出。用 headless 最稳。披露为「养老金产品管理情况报告」(产品层,中金作投管人)。\n\n[2026-07-07 复核] 确认 headless(无stealth) 仍返回 HTTP 521(加速乐jsl挑战页)。此前人工爬取是用 node 手写脚本模拟执行两级混淆VM挑战才拿到 __jsl_clearance_s cookie 换到真实PDF——这是一次性手工破解,未固化进 crawl_headless.py 的通用逻辑,故本次自动化复测未能复现,仍需人工/定制脚本介入。"
|
||||
},
|
||||
{
|
||||
"slug": "m22",
|
||||
"short": "中信证券",
|
||||
"name": "中信证券股份有限公司",
|
||||
"category": "证券公司",
|
||||
"homepage": "http://www.cs.ecitic.com",
|
||||
"access": "public",
|
||||
"strategy": "html_index",
|
||||
"item_type": "html",
|
||||
"disclosure_url": "http://www.cs.ecitic.com/newsite/ywzx/zcgl/qynj/xxdt/",
|
||||
"list_url": "http://www.cs.ecitic.com/newsite/ywzx/zcgl/qynj/xxdt/index_{n}.html",
|
||||
"page_param": "{n}",
|
||||
"file_url_pattern": "https?://www\\.cs\\.ecitic\\.com/newsite/ywzx/zcgl/qynj/xxdt/[0-9]{6}/t[0-9]{8}_[0-9]+\\.html",
|
||||
"example_links": [
|
||||
"http://www.cs.ecitic.com/newsite/ywzx/zcgl/qynj/xxdt/202606/t20260618_1213353.html"
|
||||
],
|
||||
"notes": "业务专区→资产管理→企业年金→信息动态。《中信证券企业年金基金投资管理情况》按季,报告为 HTML 正文(无PDF附件),列表可追溯至2023Q4。www.cs.ecitic.com=www.citics.com。首页 xxdt/,翻页 index_1.html…"
|
||||
}
|
||||
]
|
||||
}
|
||||
372
annuity-disclosure-crawler/scripts/crawl.py
Executable file
372
annuity-disclosure-crawler/scripts/crawl.py
Executable file
@ -0,0 +1,372 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
企业年金投资管理人 · 官网季度管理报告 全量爬取驱动
|
||||
=================================================
|
||||
读取 references/registry.json,按每家机构的 strategy 抓取官网披露栏目下
|
||||
**全部历史季度**的《企业年金基金投资管理情况》报告,落地到 --out 目录,
|
||||
并做真伪校验(魔数/大小)与清单(manifest)记录。
|
||||
|
||||
纯 HTTP 可搞定的策略:pdf_index / detail_index / html_index / spa_api(部分)
|
||||
需浏览器的策略:headless(本脚本跳过并提示改用 crawl_headless.py)
|
||||
不公开:login_blocked(跳过并说明)
|
||||
|
||||
用法:
|
||||
python3 crawl.py --list # 只列出名录与每家策略/入口
|
||||
python3 crawl.py # 全量抓取所有可 HTTP 抓取的机构
|
||||
python3 crawl.py --only m04,m09,m17 # 只抓指定机构(逗号分隔 slug 或简称)
|
||||
python3 crawl.py --latest 3 # 每家只保留最新 N 期(默认0=全量)
|
||||
python3 crawl.py --out /path/to/out # 指定输出目录(默认 ./annuity_reports)
|
||||
python3 crawl.py --max-pages 40 # 列表分页最多翻多少页(默认30)
|
||||
python3 crawl.py --include-waf # 也尝试对 waf 机构做HTTP直取(多半失败,仅留痕)
|
||||
|
||||
依赖: requests (pip install requests --break-system-packages)
|
||||
"""
|
||||
import argparse, json, os, re, sys, time, hashlib
|
||||
from urllib.parse import urljoin, urlparse, unquote
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
sys.exit("需要 requests: pip install requests --break-system-packages")
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
REGISTRY = os.path.join(os.path.dirname(HERE), "references", "registry.json")
|
||||
|
||||
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"
|
||||
|
||||
# 期次识别: 2026Q1 / 2026年1季度 / 2026年第一季度 / 2026-1季度
|
||||
CN_NUM = {"一": "1", "二": "2", "三": "3", "四": "4"}
|
||||
PERIOD_RES = [
|
||||
re.compile(r"(20\d{2})\s*[Qq]\s*([1-4])"),
|
||||
re.compile(r"(20\d{2})\D{0,4}?第?\s*([1-4一二三四])\s*季度"),
|
||||
]
|
||||
|
||||
|
||||
# 报告标题判别词(区分季报条目 vs 栏目里的其它通知)
|
||||
TITLE_KW = re.compile(r"管理情况|投资管理|年金.{0,6}报告")
|
||||
|
||||
|
||||
def parse_period(*texts):
|
||||
for t in texts:
|
||||
if not t:
|
||||
continue
|
||||
t = unquote(t)
|
||||
for rx in PERIOD_RES:
|
||||
m = rx.search(t)
|
||||
if m:
|
||||
q = CN_NUM.get(m.group(2), m.group(2))
|
||||
return f"{m.group(1)}Q{q}"
|
||||
# 年度报告
|
||||
m = re.search(r"(20\d{2})\s*年度", t)
|
||||
if m:
|
||||
return f"{m.group(1)}A"
|
||||
return None
|
||||
|
||||
|
||||
def fetch(url, referer=None, binary=False, timeout=60, session=None):
|
||||
s = session or requests
|
||||
h = {"User-Agent": UA, "Accept-Language": "zh-CN,zh;q=0.9"}
|
||||
if referer:
|
||||
h["Referer"] = referer
|
||||
r = s.get(url, headers=h, timeout=timeout, allow_redirects=True, verify=False)
|
||||
r.raise_for_status()
|
||||
if binary:
|
||||
return r.content, r.headers.get("Content-Type", "")
|
||||
# 文本:尝试正确解码(GBK 站点很多)
|
||||
raw = r.content
|
||||
ct = r.headers.get("Content-Type", "").lower()
|
||||
enc = None
|
||||
m = re.search(r"charset=([\w-]+)", ct)
|
||||
if m:
|
||||
enc = m.group(1)
|
||||
if not enc:
|
||||
head = raw[:2000].decode("ascii", "ignore").lower()
|
||||
m = re.search(r'charset=["\']?([\w-]+)', head)
|
||||
if m:
|
||||
enc = m.group(1)
|
||||
for e in ([enc] if enc else []) + ["utf-8", "gb18030"]:
|
||||
try:
|
||||
return raw.decode(e), r.headers.get("Content-Type", "")
|
||||
except Exception:
|
||||
continue
|
||||
return raw.decode("utf-8", "ignore"), r.headers.get("Content-Type", "")
|
||||
|
||||
|
||||
def abs_links(html, base):
|
||||
"""返回 [(href_abs, anchor_text)]"""
|
||||
out = []
|
||||
for m in re.finditer(r'<a\b[^>]*?href=["\']([^"\']+)["\'][^>]*>(.*?)</a>', html, re.I | re.S):
|
||||
href = m.group(1).strip()
|
||||
if href.startswith(("javascript:", "#", "mailto:")):
|
||||
continue
|
||||
text = re.sub(r"<[^>]+>", "", m.group(2)).strip()
|
||||
out.append((urljoin(base, href), text))
|
||||
return out
|
||||
|
||||
|
||||
def file_from_href(href, base, rx):
|
||||
"""判断一个链接是否指向报告文件,返回可下载的绝对 URL 或 None。
|
||||
处理 pdfjs 包装(viewer.html?file=xxx.pdf)、相对路径、以及 .pdf/.docx 后缀兜底。"""
|
||||
cands = [href]
|
||||
m = re.search(r"[?&]file=([^&]+)", href)
|
||||
if m:
|
||||
cands.append(unquote(m.group(1)))
|
||||
for c in cands:
|
||||
cabs = urljoin(base, c)
|
||||
if rx and (rx.search(c) or rx.search(cabs)):
|
||||
return cabs
|
||||
if c.split("?")[0].lower().endswith((".pdf", ".docx", ".doc", ".xlsx")):
|
||||
return cabs
|
||||
return None
|
||||
|
||||
|
||||
def files_in_page(html, base, rx):
|
||||
"""从一个页面里挖出所有报告文件直链(详情页二跳用)。"""
|
||||
hits = set()
|
||||
for href, _ in abs_links(html, base):
|
||||
f = file_from_href(href, base, rx)
|
||||
if f:
|
||||
hits.add(f)
|
||||
if rx:
|
||||
for m in rx.finditer(html):
|
||||
hits.add(urljoin(base, m.group(0)))
|
||||
# 裸露的 .pdf/.docx 直链(在 JS/iframe src 里)——必须是真路径(以 / 或 http 开头),
|
||||
# 避免把详情页里的“显示用中文文件名”误当成同目录相对链接
|
||||
for m in re.finditer(r'["\']((?:https?:)?/[^"\'\s]+\.(?:pdf|docx))["\']', html, re.I):
|
||||
hits.add(urljoin(base, m.group(1)))
|
||||
return list(hits)
|
||||
|
||||
|
||||
def list_pages(mgr, max_pages):
|
||||
"""生成要翻的列表页 URL(去重、保序)。
|
||||
很多站点第 1 页是 index.html(无编号),第 2 页起才是 index_1/index2…,
|
||||
因此始终把 disclosure_url 作为首页,再拼编号页。"""
|
||||
lu = mgr.get("list_url")
|
||||
disc = mgr.get("disclosure_url")
|
||||
if not lu:
|
||||
return [disc] if disc else []
|
||||
if "{n}" not in lu:
|
||||
return [lu]
|
||||
known = mgr.get("list_pages_known")
|
||||
top = known if known else max_pages
|
||||
seq = [disc] + [lu.replace("{n}", str(i)) for i in range(1, top + 1)]
|
||||
seen, out = set(), []
|
||||
for u in seq:
|
||||
if u and u not in seen:
|
||||
seen.add(u); out.append(u)
|
||||
return out
|
||||
|
||||
|
||||
def verify(path, item_type):
|
||||
try:
|
||||
sz = os.path.getsize(path)
|
||||
except OSError:
|
||||
return False, 0, "missing"
|
||||
if sz < 512:
|
||||
return False, sz, "too-small"
|
||||
with open(path, "rb") as f:
|
||||
head = f.read(8)
|
||||
if item_type == "pdf":
|
||||
return (head[:4] == b"%PDF", sz, "ok" if head[:4] == b"%PDF" else "not-pdf")
|
||||
if item_type == "docx":
|
||||
return (head[:2] == b"PK", sz, "ok" if head[:2] == b"PK" else "not-docx")
|
||||
# html
|
||||
with open(path, "rb") as f:
|
||||
body = f.read(60000)
|
||||
txt = body.decode("utf-8", "ignore") + body.decode("gb18030", "ignore")
|
||||
ok = ("年金" in txt or "<title" in txt.lower()) and b"<html" in body.lower() or sz > 3000
|
||||
return (bool(ok), sz, "ok" if ok else "suspect")
|
||||
|
||||
|
||||
def safe_name(period, url, item_type, slug):
|
||||
ext = {"pdf": ".pdf", "docx": ".docx", "html": ".html"}[item_type]
|
||||
base = period or ("item_" + hashlib.md5(url.encode()).hexdigest()[:8])
|
||||
return f"{slug}_{base}{ext}"
|
||||
|
||||
|
||||
def crawl_manager(mgr, outdir, latest, max_pages, include_waf):
|
||||
slug = mgr["slug"]
|
||||
strat = mgr["strategy"]
|
||||
item_type = mgr["item_type"]
|
||||
res = {"slug": slug, "name": mgr["name"], "strategy": strat, "access": mgr["access"],
|
||||
"disclosure_url": mgr["disclosure_url"], "files": [], "skipped": None}
|
||||
|
||||
if strat == "login_blocked":
|
||||
res["skipped"] = "login/非公开:季报仅登录客户门户可见或未在官网公开"
|
||||
return res
|
||||
if strat in ("headless",) and not include_waf:
|
||||
res["skipped"] = "需无头浏览器(WAF/SPA):改用 crawl_headless.py 或加 --include-waf 试探"
|
||||
return res
|
||||
|
||||
d = os.path.join(outdir, slug)
|
||||
os.makedirs(d, exist_ok=True)
|
||||
sess = requests.Session()
|
||||
pat = mgr.get("file_url_pattern")
|
||||
rx = re.compile(pat) if pat else None
|
||||
|
||||
# 1) 逐列表页收集候选(锚点为主,带标题文本→可解析期次并过滤非报告条目)
|
||||
found = {} # file_url -> period (pdf_index/html_index 的最终文件)
|
||||
detail_q = {} # detail_url -> period (detail_index 需二跳)
|
||||
pages = list_pages(mgr, max_pages) or [mgr["disclosure_url"]]
|
||||
empty_streak = 0
|
||||
for i, pg in enumerate(pages):
|
||||
try:
|
||||
html, _ = fetch(pg, referer=mgr["disclosure_url"], session=sess)
|
||||
except Exception:
|
||||
empty_streak += 1
|
||||
if empty_streak >= 2:
|
||||
break
|
||||
continue
|
||||
if i == 0:
|
||||
with open(os.path.join(d, "_disclosure_page.html"), "w", encoding="utf-8", errors="ignore") as f:
|
||||
f.write(html)
|
||||
new = 0
|
||||
for href, text in abs_links(html, pg):
|
||||
p = parse_period(text, href)
|
||||
titled = bool(text and TITLE_KW.search(text))
|
||||
if not (p or titled):
|
||||
continue # 不像报告条目,跳过(滤掉栏目里的其它通知/导航)
|
||||
direct = file_from_href(href, pg, rx)
|
||||
if direct: # 锚点本身就是文件(或 pdfjs 包装) → 直接收
|
||||
if direct not in found:
|
||||
found[direct] = p; new += 1
|
||||
elif rx and rx.search(href): # 锚点是 html 报告页(html_index) → 收
|
||||
if href not in found:
|
||||
found[href] = p; new += 1
|
||||
elif strat in ("detail_index", "spa_api"): # 是详情页 → 二跳
|
||||
if href not in detail_q:
|
||||
detail_q[href] = (p, text); new += 1
|
||||
# pdf_index 兜底:直链可能不在 <a> 里(onclick 等),用正则再捞一遍(要求文件名自带期次)
|
||||
if strat == "pdf_index" and rx:
|
||||
for m in rx.finditer(html):
|
||||
u = urljoin(pg, m.group(0))
|
||||
pu = parse_period(u)
|
||||
if pu and u not in found:
|
||||
found[u] = pu; new += 1
|
||||
empty_streak = 0 if new else empty_streak + 1
|
||||
if empty_streak >= 3:
|
||||
break
|
||||
time.sleep(0.3)
|
||||
|
||||
# 2) detail_index:进详情页取文件直链
|
||||
for href, (p, text) in detail_q.items():
|
||||
try:
|
||||
html, _ = fetch(href, referer=mgr["disclosure_url"], session=sess)
|
||||
except Exception:
|
||||
continue
|
||||
for u in files_in_page(html, href, rx):
|
||||
if u not in found:
|
||||
found[u] = parse_period(u) or p
|
||||
time.sleep(0.2)
|
||||
|
||||
if not found:
|
||||
res["skipped"] = "HTTP 抓取 0 命中:列表多为 JS 渲染或被拦,请改用 crawl_headless.py"
|
||||
return res
|
||||
|
||||
# 3) 排序、按 latest 截断(保留最新 N 个不同期次的全部报告)、下载
|
||||
items = sorted(found.items(), key=lambda kv: (kv[1] or ""), reverse=True)
|
||||
if latest and latest > 0:
|
||||
periods_kept, kept = [], []
|
||||
for u, p in items:
|
||||
if p is None:
|
||||
continue # 无法定期次的条目在 latest 模式下略过
|
||||
if p not in periods_kept:
|
||||
if len(periods_kept) >= latest:
|
||||
break
|
||||
periods_kept.append(p)
|
||||
kept.append((u, p))
|
||||
items = kept
|
||||
|
||||
seen_md5 = set()
|
||||
for u, period in items:
|
||||
try:
|
||||
content, ct = fetch(u, referer=mgr["disclosure_url"], binary=True, session=sess)
|
||||
except Exception as e:
|
||||
res["files"].append({"url": u, "period": period, "status": f"download-fail:{e}", "local": None, "bytes": 0})
|
||||
continue
|
||||
h = hashlib.md5(content).hexdigest()
|
||||
if h in seen_md5:
|
||||
continue # 同一报告被多个 URL 变体重复收录,按内容去重
|
||||
seen_md5.add(h)
|
||||
# 若声明 pdf 却拿到 html 错误页
|
||||
it = item_type
|
||||
if item_type == "pdf" and content[:4] != b"%PDF" and content[:2] == b"PK":
|
||||
it = "docx"
|
||||
name = safe_name(period, u, it, slug)
|
||||
path = os.path.join(d, name)
|
||||
if os.path.exists(path): # 同期两类报告(投资管理/集合计划)等→加短哈希防覆盖
|
||||
stem, ext = os.path.splitext(name)
|
||||
path = os.path.join(d, f"{stem}_{hashlib.md5(u.encode()).hexdigest()[:6]}{ext}")
|
||||
with open(path, "wb") as f:
|
||||
f.write(content)
|
||||
ok, sz, why = verify(path, it)
|
||||
if not ok:
|
||||
os.rename(path, path + ".suspect")
|
||||
path = path + ".suspect"
|
||||
res["files"].append({"url": u, "period": period, "status": why,
|
||||
"local": os.path.relpath(path), "bytes": sz})
|
||||
time.sleep(0.2)
|
||||
return res
|
||||
|
||||
|
||||
def main():
|
||||
import urllib3
|
||||
urllib3.disable_warnings()
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--registry", default=REGISTRY)
|
||||
ap.add_argument("--out", default="annuity_reports")
|
||||
ap.add_argument("--only", default="")
|
||||
ap.add_argument("--latest", type=int, default=0, help="每家保留最新N期,0=全量")
|
||||
ap.add_argument("--max-pages", type=int, default=30)
|
||||
ap.add_argument("--include-waf", action="store_true")
|
||||
ap.add_argument("--list", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
reg = json.load(open(args.registry, encoding="utf-8"))
|
||||
managers = reg["managers"]
|
||||
|
||||
if args.list:
|
||||
print(f"{'slug':5} {'简称':10} {'access':7} {'strategy':13} 披露入口")
|
||||
for m in managers:
|
||||
print(f"{m['slug']:5} {m['short']:10} {m['access']:7} {m['strategy']:13} {m['disclosure_url']}")
|
||||
return
|
||||
|
||||
only = [x.strip() for x in args.only.split(",") if x.strip()]
|
||||
if only:
|
||||
managers = [m for m in managers if m["slug"] in only or m["short"] in only]
|
||||
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
all_res = []
|
||||
for m in managers:
|
||||
print(f"\n=== {m['slug']} {m['short']} [{m['strategy']}/{m['access']}] ===", flush=True)
|
||||
r = crawl_manager(m, args.out, args.latest, args.max_pages, args.include_waf)
|
||||
if r["skipped"]:
|
||||
print(" ⏭ " + r["skipped"])
|
||||
else:
|
||||
okn = sum(1 for f in r["files"] if f["status"] == "ok")
|
||||
print(f" ✓ {okn}/{len(r['files'])} 个文件有效,落地 {os.path.join(args.out, m['slug'])}/")
|
||||
for f in r["files"][:6]:
|
||||
print(f" [{f['period']}] {f['status']} {f['bytes']}B {f['local']}")
|
||||
all_res.append(r)
|
||||
|
||||
# manifest
|
||||
mani = os.path.join(args.out, "manifest.json")
|
||||
json.dump({"generated": time.strftime("%Y-%m-%d %H:%M"), "results": all_res},
|
||||
open(mani, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||
import csv
|
||||
with open(os.path.join(args.out, "manifest.csv"), "w", newline="", encoding="utf-8-sig") as fp:
|
||||
w = csv.writer(fp)
|
||||
w.writerow(["slug", "机构", "策略", "access", "期次", "状态", "字节", "本地路径", "源URL"])
|
||||
for r in all_res:
|
||||
if r["skipped"]:
|
||||
w.writerow([r["slug"], r["name"], r["strategy"], r["access"], "", "SKIP:" + r["skipped"], "", "", ""])
|
||||
for f in r["files"]:
|
||||
w.writerow([r["slug"], r["name"], r["strategy"], r["access"], f["period"], f["status"], f["bytes"], f["local"], f["url"]])
|
||||
tot = sum(len(r["files"]) for r in all_res)
|
||||
okt = sum(1 for r in all_res for f in r["files"] if f["status"] == "ok")
|
||||
print(f"\n完成:{okt}/{tot} 个有效文件。清单:{mani} 及 manifest.csv")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
218
annuity-disclosure-crawler/scripts/crawl_headless.py
Executable file
218
annuity-disclosure-crawler/scripts/crawl_headless.py
Executable file
@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
企业年金季报爬取 · 无头浏览器兜底
|
||||
================================
|
||||
用于 crawl.py 搞不定的机构:列表页是 SPA(JS 渲染) 或整站有反爬 WAF/JS 挑战
|
||||
(strategy=headless / spa_api,或 access=waf)。用真实 Chromium 渲染页面、
|
||||
自动通过瑞数/加速乐等 JS 挑战、取到报告链接后用带 Cookie 的浏览器上下文下载。
|
||||
|
||||
依赖:
|
||||
pip install playwright --break-system-packages
|
||||
python3 -m playwright install chromium
|
||||
|
||||
用法:
|
||||
python3 crawl_headless.py --only m15,m21 # 指定机构
|
||||
python3 crawl_headless.py # 默认抓所有 headless/spa_api/waf 机构
|
||||
python3 crawl_headless.py --latest 3 --out DIR # 同 crawl.py 语义
|
||||
python3 crawl_headless.py --only m21 --headful # 显示浏览器窗口(调试/过人机)
|
||||
|
||||
与 crawl.py 复用同一 registry.json 与期次识别/校验/命名逻辑。
|
||||
"""
|
||||
import argparse, json, os, sys, time, hashlib, re, csv
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
import crawl # 复用 parse_period / file_from_href / files_in_page / verify / safe_name / TITLE_KW / list_pages
|
||||
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
UA = crawl.UA
|
||||
REGISTRY = crawl.REGISTRY
|
||||
|
||||
|
||||
def harvest_links(page):
|
||||
"""返回页面上所有 <a> 的 (href_abs, text)。"""
|
||||
return page.eval_on_selector_all(
|
||||
"a",
|
||||
"els => els.map(a => [a.href, (a.innerText||a.textContent||'').trim()])",
|
||||
)
|
||||
|
||||
|
||||
def crawl_manager(ctx, mgr, outdir, latest, max_pages):
|
||||
slug = mgr["slug"]
|
||||
strat = mgr["strategy"]
|
||||
item_type = mgr["item_type"]
|
||||
rx = re.compile(mgr["file_url_pattern"]) if mgr.get("file_url_pattern") else None
|
||||
res = {"slug": slug, "name": mgr["name"], "strategy": strat, "access": mgr["access"],
|
||||
"disclosure_url": mgr["disclosure_url"], "files": [], "skipped": None}
|
||||
if strat == "login_blocked":
|
||||
res["skipped"] = "登录墙/非公开,浏览器也无法匿名取得"
|
||||
return res
|
||||
|
||||
d = os.path.join(outdir, slug)
|
||||
os.makedirs(d, exist_ok=True)
|
||||
page = ctx.new_page()
|
||||
found = {} # file_url -> period
|
||||
detail_q = {}
|
||||
|
||||
pages = crawl.list_pages(mgr, max_pages) or [mgr["disclosure_url"]]
|
||||
empty = 0
|
||||
for i, pg in enumerate(pages):
|
||||
try:
|
||||
page.goto(pg, wait_until="networkidle", timeout=45000)
|
||||
except Exception:
|
||||
try:
|
||||
page.goto(pg, wait_until="domcontentloaded", timeout=45000)
|
||||
except Exception:
|
||||
empty += 1
|
||||
if empty >= 2:
|
||||
break
|
||||
continue
|
||||
time.sleep(2.5) # 等 JS 挑战/异步渲染
|
||||
for _ in range(4): # 滚动触发懒加载
|
||||
page.mouse.wheel(0, 20000)
|
||||
time.sleep(0.6)
|
||||
if i == 0:
|
||||
try:
|
||||
open(os.path.join(d, "_disclosure_rendered.html"), "w", encoding="utf-8").write(page.content())
|
||||
except Exception:
|
||||
pass
|
||||
new = 0
|
||||
for href, text in harvest_links(page):
|
||||
if not href:
|
||||
continue
|
||||
p = crawl.parse_period(text, href)
|
||||
titled = bool(text and crawl.TITLE_KW.search(text))
|
||||
if not (p or titled):
|
||||
continue
|
||||
direct = crawl.file_from_href(href, pg, rx)
|
||||
if direct:
|
||||
if direct not in found:
|
||||
found[direct] = p; new += 1
|
||||
elif strat in ("detail_index", "spa_api", "headless"):
|
||||
if href not in detail_q:
|
||||
detail_q[href] = (p, text); new += 1
|
||||
empty = 0 if new else empty + 1
|
||||
if empty >= 3:
|
||||
break
|
||||
|
||||
# 详情页二跳(渲染后取文件链)
|
||||
for href, (p, text) in list(detail_q.items()):
|
||||
try:
|
||||
page.goto(href, wait_until="networkidle", timeout=40000)
|
||||
time.sleep(1.5)
|
||||
except Exception:
|
||||
continue
|
||||
html = page.content()
|
||||
for u in crawl.files_in_page(html, href, rx):
|
||||
if u not in found:
|
||||
found[u] = crawl.parse_period(u) or p
|
||||
|
||||
if not found:
|
||||
res["skipped"] = "渲染后仍 0 命中:可能需登录、或分页/下载入口需交互(试 --headful 观察)"
|
||||
page.close()
|
||||
return res
|
||||
|
||||
items = sorted(found.items(), key=lambda kv: (kv[1] or ""), reverse=True)
|
||||
if latest and latest > 0:
|
||||
periods_kept, kept = [], []
|
||||
for u, pp in items:
|
||||
if pp is None:
|
||||
continue
|
||||
if pp not in periods_kept:
|
||||
if len(periods_kept) >= latest:
|
||||
break
|
||||
periods_kept.append(pp)
|
||||
kept.append((u, pp))
|
||||
items = kept
|
||||
|
||||
seen_md5 = set()
|
||||
for u, period in items:
|
||||
try:
|
||||
r = ctx.request.get(u, timeout=60000) # 带浏览器 Cookie 下载,过 WAF
|
||||
content = r.body()
|
||||
except Exception as e:
|
||||
res["files"].append({"url": u, "period": period, "status": f"download-fail:{e}", "local": None, "bytes": 0})
|
||||
continue
|
||||
h = hashlib.md5(content).hexdigest()
|
||||
if h in seen_md5:
|
||||
continue
|
||||
seen_md5.add(h)
|
||||
it = item_type
|
||||
if item_type == "pdf" and content[:4] != b"%PDF" and content[:2] == b"PK":
|
||||
it = "docx"
|
||||
name = crawl.safe_name(period, u, it, slug)
|
||||
path = os.path.join(d, name)
|
||||
if os.path.exists(path):
|
||||
stem, ext = os.path.splitext(name)
|
||||
path = os.path.join(d, f"{stem}_{hashlib.md5(u.encode()).hexdigest()[:6]}{ext}")
|
||||
open(path, "wb").write(content)
|
||||
ok, sz, why = crawl.verify(path, it)
|
||||
if not ok:
|
||||
os.rename(path, path + ".suspect"); path += ".suspect"
|
||||
res["files"].append({"url": u, "period": period, "status": why, "local": os.path.relpath(path), "bytes": sz})
|
||||
time.sleep(0.2)
|
||||
page.close()
|
||||
return res
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--registry", default=REGISTRY)
|
||||
ap.add_argument("--out", default="annuity_reports")
|
||||
ap.add_argument("--only", default="")
|
||||
ap.add_argument("--latest", type=int, default=0)
|
||||
ap.add_argument("--max-pages", type=int, default=30)
|
||||
ap.add_argument("--headful", action="store_true")
|
||||
args = ap.parse_args()
|
||||
|
||||
reg = json.load(open(args.registry, encoding="utf-8"))
|
||||
managers = reg["managers"]
|
||||
only = [x.strip() for x in args.only.split(",") if x.strip()]
|
||||
if only:
|
||||
managers = [m for m in managers if m["slug"] in only or m["short"] in only]
|
||||
else: # 默认:需浏览器的那批
|
||||
managers = [m for m in managers if m["strategy"] in ("headless", "spa_api") or m["access"] == "waf"]
|
||||
managers = [m for m in managers if m["strategy"] != "login_blocked"]
|
||||
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
all_res = []
|
||||
with sync_playwright() as pw:
|
||||
browser = pw.chromium.launch(headless=not args.headful)
|
||||
ctx = browser.new_context(user_agent=UA, locale="zh-CN", ignore_https_errors=True,
|
||||
extra_http_headers={"Accept-Language": "zh-CN,zh;q=0.9"})
|
||||
for m in managers:
|
||||
print(f"\n=== {m['slug']} {m['short']} [{m['strategy']}/{m['access']}] ===", flush=True)
|
||||
try:
|
||||
r = crawl_manager(ctx, m, args.out, args.latest, args.max_pages)
|
||||
except Exception as e:
|
||||
r = {"slug": m["slug"], "name": m["name"], "strategy": m["strategy"], "access": m["access"],
|
||||
"disclosure_url": m["disclosure_url"], "files": [], "skipped": f"异常:{e}"}
|
||||
if r["skipped"]:
|
||||
print(" ⏭ " + r["skipped"])
|
||||
else:
|
||||
okn = sum(1 for f in r["files"] if f["status"] == "ok")
|
||||
print(f" ✓ {okn}/{len(r['files'])} 个有效,落地 {os.path.join(args.out, m['slug'])}/")
|
||||
for f in r["files"][:6]:
|
||||
print(f" [{f['period']}] {f['status']} {f['bytes']}B {f['local']}")
|
||||
all_res.append(r)
|
||||
browser.close()
|
||||
|
||||
mani = os.path.join(args.out, "manifest_headless.json")
|
||||
json.dump({"generated": time.strftime("%Y-%m-%d %H:%M"), "results": all_res},
|
||||
open(mani, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
|
||||
with open(os.path.join(args.out, "manifest_headless.csv"), "w", newline="", encoding="utf-8-sig") as fp:
|
||||
w = csv.writer(fp)
|
||||
w.writerow(["slug", "机构", "策略", "access", "期次", "状态", "字节", "本地路径", "源URL"])
|
||||
for r in all_res:
|
||||
if r["skipped"]:
|
||||
w.writerow([r["slug"], r["name"], r["strategy"], r["access"], "", "SKIP:" + r["skipped"], "", "", ""])
|
||||
for f in r["files"]:
|
||||
w.writerow([r["slug"], r["name"], r["strategy"], r["access"], f["period"], f["status"], f["bytes"], f["local"], f["url"]])
|
||||
okt = sum(1 for r in all_res for f in r["files"] if f["status"] == "ok")
|
||||
print(f"\n完成:{okt} 个有效文件。清单:{mani}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
192
asset-monitor/SKILL.md
Normal file
192
asset-monitor/SKILL.md
Normal file
@ -0,0 +1,192 @@
|
||||
---
|
||||
name: asset-monitor
|
||||
description: 资产异常波动日频监控与报告生成。当用户需要对股票、指数、商品等资产进行日频异常波动监控,检测偏离历史均值超过2倍标准差的异常,并生成Excel报告时使用此技能。
|
||||
---
|
||||
|
||||
# 资产异常波动日频监控
|
||||
|
||||
## 触发条件
|
||||
|
||||
当用户需要:
|
||||
- 监控多资产类别的日频异常波动
|
||||
- 检测偏离历史均值超过2倍标准差的资产
|
||||
- 生成异常波动报告(Excel/Markdown)
|
||||
- 批量分析申万三级行业、A股指数、商品期货、全球指数等
|
||||
|
||||
## 依赖
|
||||
|
||||
本 skill 依赖 windpy-sdk 获取数据。使用时需要:
|
||||
1. Wind 金融终端已启动
|
||||
2. 参考 windpy-sdk skill 了解数据获取方法
|
||||
|
||||
## 监控脚本
|
||||
|
||||
使用 `scripts/monitor.py` 进行监控:
|
||||
|
||||
```bash
|
||||
# 基础监控
|
||||
python scripts/monitor.py
|
||||
|
||||
# 带报告输出
|
||||
python scripts/monitor.py --notify
|
||||
|
||||
# 自定义参数
|
||||
python scripts/monitor.py --threshold 2.5 --min-days 60
|
||||
```
|
||||
|
||||
## 监控资产范围
|
||||
|
||||
| 资产类别 | 数量 | 说明 |
|
||||
|---------|------|------|
|
||||
| 申万三级行业 | 259个 | 全量三级行业指数 |
|
||||
| A股主要指数 | 9个 | 沪深300、中证500等 |
|
||||
| 中债指数 | 5个 | 中债总指数、国债指数等 |
|
||||
| 主流ETF | 8个 | 沪深300ETF、创业板ETF等 |
|
||||
| 商品期货 | 8个 | 黄金、白银、铜、原油等 |
|
||||
| 全球指数 | 9个 | 标普500、纳指、道指等 |
|
||||
|
||||
**总计**: 298个资产
|
||||
|
||||
## 核心监控逻辑
|
||||
|
||||
### Z-Score 异常检测
|
||||
|
||||
```python
|
||||
# 计算Z值
|
||||
z_score = (今日涨跌幅 - 历史均值) / 历史标准差
|
||||
|
||||
# 异常判定
|
||||
if abs(z_score) > 2.0:
|
||||
标记为异常
|
||||
```
|
||||
|
||||
### 筛选条件
|
||||
|
||||
- 历史数据 > 30个交易日
|
||||
- 历史标准差 > 0
|
||||
- 按 |Z| 绝对值降序排列
|
||||
|
||||
## 输出结果
|
||||
|
||||
### Excel 报告
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| category | 资产类别 |
|
||||
| code | 资产代码 |
|
||||
| name | 资产名称 |
|
||||
| today_return | 今日涨跌幅(%) |
|
||||
| z_score | Z值 |
|
||||
| direction | 大涨/大跌 |
|
||||
|
||||
### 监控输出示例
|
||||
|
||||
```
|
||||
================================================================================
|
||||
📊 资产异常波动监控
|
||||
时间: 2026-02-09 06:22
|
||||
Z值阈值: 2.0
|
||||
================================================================================
|
||||
|
||||
[监控] 申万三级行业 (259个)
|
||||
共 259 个资产
|
||||
⚠️ 印染(申万): +5.30% (Z=+3.33)
|
||||
⚠️ 纺织化学制品(申万): +5.35% (Z=+3.22)
|
||||
发现 5 个异常
|
||||
|
||||
[监控] 商品期货 (8个)
|
||||
⚠️ 沪银近月: -14.02% (Z=-4.39)
|
||||
发现 1 个异常
|
||||
|
||||
[监控] 全球指数 (9个)
|
||||
⚠️ 道琼斯: +2.47% (Z=+2.34)
|
||||
⚠️ VIX波动率: -18.42% (Z=-2.26)
|
||||
发现 2 个异常
|
||||
|
||||
================================================================================
|
||||
✅ 监控完成,共发现 8 个异常
|
||||
================================================================================
|
||||
```
|
||||
|
||||
## 脚本使用方法
|
||||
|
||||
详见 `scripts/monitor.py` 代码注释。
|
||||
|
||||
### 命令行参数
|
||||
|
||||
```bash
|
||||
python scripts/monitor.py --help
|
||||
|
||||
Options:
|
||||
--threshold FLOAT Z-Score阈值,默认2.0
|
||||
--min-days INT 最小交易日,默认30
|
||||
--output DIR 输出目录,默认output
|
||||
--notify 打印文本报告
|
||||
```
|
||||
|
||||
### Python API
|
||||
|
||||
```python
|
||||
# 直接导入脚本中的函数使用
|
||||
import sys
|
||||
sys.path.insert(0, 'scripts')
|
||||
from monitor import run_monitoring, generate_excel_report
|
||||
|
||||
# 运行监控(需先连接Wind)
|
||||
from WindPy import w
|
||||
w.start()
|
||||
anomalies = run_monitoring(threshold_z=2.0)
|
||||
w.stop()
|
||||
|
||||
# 生成报告
|
||||
excel_path = generate_excel_report(anomalies)
|
||||
```
|
||||
|
||||
## 定时任务设置
|
||||
|
||||
```bash
|
||||
# crontab -e
|
||||
# 每日15:30运行
|
||||
30 15 * * * cd /path/to/skill && python scripts/monitor.py --notify
|
||||
```
|
||||
|
||||
## 配置文件
|
||||
|
||||
配置示例见 `references/monitor-config-example.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"monitor": {
|
||||
"threshold_z": 2.0,
|
||||
"min_trading_days": 30
|
||||
},
|
||||
"assets": {
|
||||
"sw3_industry": {"enabled": true},
|
||||
"ashare_index": {"enabled": true},
|
||||
"commodity": {"enabled": true}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 故障排查
|
||||
|
||||
| 问题 | 原因 | 解决方案 |
|
||||
|------|------|---------|
|
||||
| Wind连接失败 | Wind终端未启动 | 启动Wind终端 |
|
||||
| 无数据返回 | 无数据权限 | 联系Wind开通权限 |
|
||||
| 报告为空 | 今日无异常 | 正常现象 |
|
||||
|
||||
## 与其他 Skill 的关系
|
||||
|
||||
```
|
||||
asset-monitor (监控逻辑 + 报告生成)
|
||||
↓ 使用 WindPy 获取数据
|
||||
WindPy SDK (Wind 金融终端 API)
|
||||
```
|
||||
|
||||
**注意**: asset-monitor 直接使用 WindPy,但字段和板块代码可参考 windpy-sdk skill 的文档。
|
||||
|
||||
## 参考
|
||||
|
||||
- windpy-sdk skill - WindPy 函数参考和字段速查
|
||||
- `references/monitor-config-example.json` - 配置示例
|
||||
37
asset-monitor/references/monitor-config-example.json
Normal file
37
asset-monitor/references/monitor-config-example.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"monitor": {
|
||||
"threshold_z": 2.0,
|
||||
"min_trading_days": 30,
|
||||
"lookback_period": "-252TD"
|
||||
},
|
||||
"assets": {
|
||||
"sw3_industry": {
|
||||
"enabled": true,
|
||||
"description": "申万三级行业(259个全量)"
|
||||
},
|
||||
"ashare_index": {
|
||||
"enabled": true,
|
||||
"description": "A股主要指数(9个)"
|
||||
},
|
||||
"bond_index": {
|
||||
"enabled": true,
|
||||
"description": "中债指数(5个)"
|
||||
},
|
||||
"etf": {
|
||||
"enabled": true,
|
||||
"description": "主流ETF(8个)"
|
||||
},
|
||||
"commodity": {
|
||||
"enabled": true,
|
||||
"description": "商品期货(8个主要品种)"
|
||||
},
|
||||
"global_index": {
|
||||
"enabled": true,
|
||||
"description": "全球指数(9个主要指数)"
|
||||
}
|
||||
},
|
||||
"notification": {
|
||||
"excel": true,
|
||||
"markdown": true
|
||||
}
|
||||
}
|
||||
253
asset-monitor/scripts/monitor.py
Normal file
253
asset-monitor/scripts/monitor.py
Normal file
@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
资产异常波动日频监控脚本
|
||||
|
||||
使用方法:
|
||||
python scripts/monitor.py
|
||||
python scripts/monitor.py --notify
|
||||
"""
|
||||
|
||||
from WindPy import w
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from datetime import datetime, timedelta
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 监控资产配置
|
||||
ASSET_CONFIG = {
|
||||
"sw3_industry": {
|
||||
"name": "申万三级行业",
|
||||
"type": "sector",
|
||||
"sectorid": "a39901011i000000", # 259个
|
||||
},
|
||||
"ashare_index": {
|
||||
"name": "A股主要指数",
|
||||
"type": "direct",
|
||||
"codes": [
|
||||
"000300.SH", "000905.SH", "000016.SH", "000852.SH",
|
||||
"000001.SH", "399001.SZ", "399006.SZ", "000688.SH", "883985.WI"
|
||||
],
|
||||
},
|
||||
"bond_index": {
|
||||
"name": "中债指数",
|
||||
"type": "direct",
|
||||
"codes": [
|
||||
"CBA00101.CS", "CBA00301.CS", "CBA00401.CS",
|
||||
"CBA00501.CS", "CBA00601.CS"
|
||||
],
|
||||
},
|
||||
"etf": {
|
||||
"name": "主流ETF",
|
||||
"type": "direct",
|
||||
"codes": [
|
||||
"510300.SH", "510500.SH", "510050.SH", "159915.SZ",
|
||||
"588000.SH", "512480.SH", "515030.SH", "512760.SH"
|
||||
],
|
||||
},
|
||||
"commodity": {
|
||||
"name": "商品期货",
|
||||
"type": "direct",
|
||||
"codes": [
|
||||
"AU00.SHF", "AG00.SHF", "CU00.SHF", "AL00.SHF",
|
||||
"ZN00.SHF", "RB00.SHF", "SC00.INE", "TA00.CZC"
|
||||
],
|
||||
},
|
||||
"global_index": {
|
||||
"name": "全球指数",
|
||||
"type": "direct",
|
||||
"codes": [
|
||||
"SPX.GI", "IXIC.GI", "DJI.GI", "VIX.GI",
|
||||
"HSI.HI", "N225.GI", "KS11.GI", "GDAXI.GI", "FTSE.GI"
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def analyze_asset(code, name, category, threshold_z=2.0, min_days=30):
|
||||
"""分析单个资产的波动"""
|
||||
try:
|
||||
one_year_ago = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d')
|
||||
today_str = datetime.now().strftime('%Y%m%d')
|
||||
|
||||
hist = w.wsd(code, "pct_chg", one_year_ago, today_str, "", usedf=True)
|
||||
|
||||
if hist[0] == 0 and len(hist[1]) > min_days:
|
||||
returns = hist[1]['PCT_CHG'].dropna()
|
||||
if len(returns) > min_days:
|
||||
mean_ret = returns.mean()
|
||||
std_ret = returns.std()
|
||||
today_ret = returns.iloc[-1] if len(returns) > 0 else None
|
||||
|
||||
if today_ret is not None and std_ret > 0:
|
||||
z_score = (today_ret - mean_ret) / std_ret
|
||||
|
||||
if abs(z_score) > threshold_z:
|
||||
return {
|
||||
'category': category,
|
||||
'code': code,
|
||||
'name': name,
|
||||
'today_return': float(today_ret),
|
||||
'z_score': float(z_score),
|
||||
'direction': '大涨' if z_score > 0 else '大跌'
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def monitor_sector(config, threshold_z=2.0):
|
||||
"""监控板块类资产"""
|
||||
print(f"\n[监控] {config['name']}")
|
||||
|
||||
today_str = datetime.now().strftime('%Y%m%d')
|
||||
result = w.wset("sectorconstituent", f"date={today_str};sectorid={config['sectorid']}")
|
||||
|
||||
if result.ErrorCode != 0 or len(result.Data) < 2:
|
||||
print(f" ⚠️ 未获取到数据")
|
||||
return []
|
||||
|
||||
codes = result.Data[1]
|
||||
names = result.Data[2]
|
||||
|
||||
print(f" 共 {len(codes)} 个资产")
|
||||
|
||||
anomalies = []
|
||||
for code, name in zip(codes, names):
|
||||
result = analyze_asset(code, name, config['name'], threshold_z)
|
||||
if result:
|
||||
anomalies.append(result)
|
||||
print(f" ⚠️ {name}: {result['today_return']:+.2f}% (Z={result['z_score']:+.2f})")
|
||||
|
||||
print(f" 发现 {len(anomalies)} 个异常")
|
||||
return anomalies
|
||||
|
||||
|
||||
def monitor_direct(config, threshold_z=2.0):
|
||||
"""监控直接代码类资产"""
|
||||
print(f"\n[监控] {config['name']} ({len(config['codes'])}个)")
|
||||
|
||||
# 获取名称
|
||||
try:
|
||||
result = w.wss(','.join(config['codes']), "sec_name", "", usedf=True)
|
||||
name_map = dict(zip(result[1].index, result[1]['SEC_NAME'])) if result[0] == 0 else {}
|
||||
except:
|
||||
name_map = {code: code for code in config['codes']}
|
||||
|
||||
anomalies = []
|
||||
for code in config['codes']:
|
||||
name = name_map.get(code, code)
|
||||
result = analyze_asset(code, name, config['name'], threshold_z)
|
||||
if result:
|
||||
anomalies.append(result)
|
||||
print(f" ⚠️ {name}: {result['today_return']:+.2f}% (Z={result['z_score']:+.2f})")
|
||||
|
||||
print(f" 发现 {len(anomalies)} 个异常")
|
||||
return anomalies
|
||||
|
||||
|
||||
def run_monitoring(threshold_z=2.0):
|
||||
"""运行完整监控"""
|
||||
today = datetime.now()
|
||||
|
||||
print("="*70)
|
||||
print(f"📊 资产异常波动监控")
|
||||
print(f"时间: {today.strftime('%Y-%m-%d %H:%M')}")
|
||||
print(f"Z值阈值: {threshold_z}")
|
||||
print("="*70)
|
||||
|
||||
all_anomalies = []
|
||||
|
||||
for key, config in ASSET_CONFIG.items():
|
||||
try:
|
||||
if config['type'] == 'sector':
|
||||
anomalies = monitor_sector(config, threshold_z)
|
||||
else:
|
||||
anomalies = monitor_direct(config, threshold_z)
|
||||
all_anomalies.extend(anomalies)
|
||||
except Exception as e:
|
||||
print(f" ❌ {config['name']} 监控失败: {e}")
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print(f"✅ 监控完成,共发现 {len(all_anomalies)} 个异常")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
return all_anomalies
|
||||
|
||||
|
||||
def generate_excel_report(anomalies, output_dir="output"):
|
||||
"""生成 Excel 报告"""
|
||||
if not anomalies:
|
||||
return None
|
||||
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
df = pd.DataFrame(anomalies)
|
||||
df = df.sort_values('z_score', key=abs, ascending=False)
|
||||
|
||||
today_str = datetime.now().strftime('%Y%m%d')
|
||||
excel_path = os.path.join(output_dir, f"asset_anomaly_report_{today_str}.xlsx")
|
||||
df.to_excel(excel_path, index=False, sheet_name='异常波动资产')
|
||||
|
||||
print(f"✅ Excel 报告: {excel_path}")
|
||||
return excel_path
|
||||
|
||||
|
||||
def generate_text_report(anomalies):
|
||||
"""生成文本报告"""
|
||||
if not anomalies:
|
||||
return "📊 资产异常监控\n\n✅ 今日无异常资产。"
|
||||
|
||||
lines = [
|
||||
"📊 资产异常波动报告",
|
||||
f"报告时间: {datetime.now().strftime('%Y-%m-%d')}",
|
||||
"",
|
||||
f"共发现 {len(anomalies)} 个异常资产:",
|
||||
"",
|
||||
]
|
||||
|
||||
for i, item in enumerate(anomalies, 1):
|
||||
emoji = "🚀" if item['z_score'] > 0 else "📉"
|
||||
lines.append(
|
||||
f"{i}. {emoji} {item['name']} ({item['category']})\n"
|
||||
f" 涨跌幅: {item['today_return']:+.2f}% | Z值: {item['z_score']:+.2f}"
|
||||
)
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='资产异常波动监控')
|
||||
parser.add_argument('--threshold', type=float, default=2.0, help='Z-Score阈值')
|
||||
parser.add_argument('--output', type=str, default='output', help='输出目录')
|
||||
parser.add_argument('--notify', action='store_true', help='打印报告')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 连接 Wind
|
||||
print("正在连接 Wind...")
|
||||
w.start()
|
||||
print("✅ Wind 连接成功\n")
|
||||
|
||||
try:
|
||||
# 运行监控
|
||||
anomalies = run_monitoring(threshold_z=args.threshold)
|
||||
|
||||
# 生成报告
|
||||
if anomalies:
|
||||
excel_path = generate_excel_report(anomalies, args.output)
|
||||
|
||||
if args.notify:
|
||||
print("\n" + generate_text_report(anomalies))
|
||||
else:
|
||||
print("✅ 今日无异常资产")
|
||||
|
||||
finally:
|
||||
w.stop()
|
||||
print("\nWind 连接已断开")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
489
docx-cn/SKILL.md
Normal file
489
docx-cn/SKILL.md
Normal file
@ -0,0 +1,489 @@
|
||||
---
|
||||
name: docx-cn
|
||||
description: "基于 docx-js (Node.js) 创建和编辑符合 GB/T 9704-2012 中国公文格式标准的 Word 文档。当用户需要生成公文、研究报告、内部文件等中文 Word 文档时使用。触发词:公文、Word、docx、报告排版、格式规范、GB/T 9704。"
|
||||
---
|
||||
|
||||
# 中文公文 Word 文档生成(docx-js)
|
||||
|
||||
## 概述
|
||||
|
||||
基于官方 docx skill 微调,专为中文公文 / 金融研究报告场景优化。
|
||||
使用 **docx-js**(Node.js)生成文档,配合官方 skill 的 unpack/pack/validate 工具链编辑现有文档。
|
||||
|
||||
## 核心工具链
|
||||
|
||||
| 任务 | 方法 |
|
||||
|------|------|
|
||||
| **新建文档** | `docx-js` — 本文件中的模板 |
|
||||
| **编辑现有文档** | unpack XML → 编辑 → repack(见「编辑现有文档」节) |
|
||||
| **验证** | `python .claude/skills/docx/scripts/office/validate.py output.docx` |
|
||||
| **读取内容** | `pandoc document.docx -o output.md` |
|
||||
|
||||
---
|
||||
|
||||
## GB/T 9704-2012 公文格式常量
|
||||
|
||||
### 单位换算速查
|
||||
|
||||
```
|
||||
1 inch = 1440 DXA = 72 pt = 25.4 mm
|
||||
1 mm = 56.693 DXA
|
||||
1 pt = 20 DXA(spacing/行距用)
|
||||
font size 单位 = half-point(半磅),如 14pt = 28
|
||||
```
|
||||
|
||||
### 页面设置(A4)
|
||||
|
||||
```javascript
|
||||
const PAGE = {
|
||||
WIDTH: 11906, // 210mm
|
||||
HEIGHT: 16838, // 297mm
|
||||
MARGIN_TOP: 2098, // 37mm
|
||||
MARGIN_BOTTOM: 1985, // 35mm
|
||||
MARGIN_LEFT: 1588, // 28mm
|
||||
MARGIN_RIGHT: 1474, // 26mm
|
||||
CONTENT_WIDTH: 8844, // 210mm - 28mm - 26mm
|
||||
};
|
||||
```
|
||||
|
||||
### 字号对照表
|
||||
|
||||
| 中文字号 | pt | half-point (docx-js size) | 用途 |
|
||||
|----------|-----|---------------------------|------|
|
||||
| 二号 | 22 | 44 | 大标题 |
|
||||
| 三号 | 16 | 32 | 一级标题 |
|
||||
| 四号 | 14 | 28 | 正文 / 二级标题 |
|
||||
| 小四 | 12 | 24 | 表格内容 |
|
||||
| 五号 | 10.5 | 21 | 页脚、注释 |
|
||||
|
||||
### 字体规范
|
||||
|
||||
```javascript
|
||||
// 中文字体必须同时设置 eastAsia,否则 Word 渲染时可能回退到宋体
|
||||
const FONT = {
|
||||
HEITI: { ascii: "SimHei", eastAsia: "黑体", hAnsi: "SimHei" }, // 标题
|
||||
FANGSONG: { ascii: "FangSong", eastAsia: "仿宋", hAnsi: "FangSong" }, // 正文
|
||||
KAITI: { ascii: "KaiTi", eastAsia: "楷体", hAnsi: "KaiTi" }, // 二级标题
|
||||
SONGTI: { ascii: "SimSun", eastAsia: "宋体", hAnsi: "SimSun" }, // 备用
|
||||
};
|
||||
```
|
||||
|
||||
### 行距与段落
|
||||
|
||||
```javascript
|
||||
const SPACING = {
|
||||
// 行距(twips = pt × 20),lineRule: "exact" → 固定值
|
||||
BODY: { line: 560, lineRule: "exact" }, // 28pt 固定行距
|
||||
HEADING1: { line: 640, lineRule: "exact" }, // 32pt
|
||||
HEADING2: { line: 560, lineRule: "exact" }, // 28pt
|
||||
TABLE: { line: 440, lineRule: "exact" }, // 22pt
|
||||
|
||||
// 段前段后(twips)
|
||||
H1_BEFORE: 240, H1_AFTER: 120, // 12pt / 6pt
|
||||
H2_BEFORE: 120, H2_AFTER: 60, // 6pt / 3pt
|
||||
};
|
||||
|
||||
const INDENT = {
|
||||
FIRST_LINE: 560, // 首行缩进 2 字符 ≈ 28pt = 560 DXA
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 创建新文档
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
npm install docx # 项目内安装即可,无需全局
|
||||
```
|
||||
|
||||
### 完整模板:公文报告(无封面)
|
||||
|
||||
```javascript
|
||||
const fs = require("fs");
|
||||
const {
|
||||
Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
|
||||
Header, Footer, AlignmentType, HeadingLevel, BorderStyle, WidthType,
|
||||
ShadingType, PageNumber, PageBreak, LevelFormat, TabStopType, TabStopPosition,
|
||||
} = require("docx");
|
||||
|
||||
// ─── GB/T 9704-2012 格式常量 ─────────────────────────────
|
||||
const PAGE = {
|
||||
WIDTH: 11906, HEIGHT: 16838,
|
||||
MARGIN_TOP: 2098, MARGIN_BOTTOM: 1985,
|
||||
MARGIN_LEFT: 1588, MARGIN_RIGHT: 1474,
|
||||
CONTENT_WIDTH: 8844,
|
||||
};
|
||||
|
||||
const FONT = {
|
||||
HEITI: { ascii: "SimHei", eastAsia: "黑体", hAnsi: "SimHei" },
|
||||
FANGSONG: { ascii: "FangSong", eastAsia: "仿宋", hAnsi: "FangSong" },
|
||||
KAITI: { ascii: "KaiTi", eastAsia: "楷体", hAnsi: "KaiTi" },
|
||||
};
|
||||
|
||||
// ─── 样式定义 ─────────────────────────────────────────────
|
||||
const styles = {
|
||||
default: {
|
||||
document: {
|
||||
run: { font: FONT.FANGSONG, size: 28 }, // 默认:仿宋四号
|
||||
paragraph: {
|
||||
spacing: { line: 560, lineRule: "exact" },
|
||||
},
|
||||
},
|
||||
},
|
||||
paragraphStyles: [
|
||||
{
|
||||
id: "Heading1", name: "Heading 1",
|
||||
basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { font: FONT.HEITI, size: 32, bold: true }, // 黑体三号
|
||||
paragraph: {
|
||||
spacing: { line: 640, lineRule: "exact", before: 240, after: 120 },
|
||||
outlineLevel: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "Heading2", name: "Heading 2",
|
||||
basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { font: FONT.KAITI, size: 28, bold: true }, // 楷体四号加粗
|
||||
paragraph: {
|
||||
spacing: { line: 560, lineRule: "exact", before: 120, after: 60 },
|
||||
outlineLevel: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// ─── 列表定义 ─────────────────────────────────────────────
|
||||
const numbering = {
|
||||
config: [
|
||||
{
|
||||
reference: "bullets",
|
||||
levels: [{
|
||||
level: 0, format: LevelFormat.BULLET, text: "\u2022",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
run: { font: FONT.FANGSONG, size: 28 },
|
||||
paragraph: { indent: { left: 1120, hanging: 560 } }, // 缩进 2 字符 + 悬挂
|
||||
},
|
||||
}],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// ─── 辅助函数 ─────────────────────────────────────────────
|
||||
|
||||
/** 创建正文段落(仿宋四号,首行缩进 2 字符) */
|
||||
function bodyParagraph(text, options = {}) {
|
||||
const { bold = false, alignment = AlignmentType.JUSTIFIED } = options;
|
||||
return new Paragraph({
|
||||
alignment,
|
||||
indent: { firstLine: 560 },
|
||||
spacing: { line: 560, lineRule: "exact" },
|
||||
children: [new TextRun({ text, font: FONT.FANGSONG, size: 28, bold })],
|
||||
});
|
||||
}
|
||||
|
||||
/** 创建一级标题(黑体三号) */
|
||||
function heading1(text) {
|
||||
return new Paragraph({
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
children: [new TextRun({ text, font: FONT.HEITI, size: 32, bold: true })],
|
||||
});
|
||||
}
|
||||
|
||||
/** 创建二级标题(楷体四号加粗) */
|
||||
function heading2(text) {
|
||||
return new Paragraph({
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
children: [new TextRun({ text, font: FONT.KAITI, size: 28, bold: true })],
|
||||
});
|
||||
}
|
||||
|
||||
/** 创建三线表 */
|
||||
function threeLineTable(headers, rows) {
|
||||
const noBorder = { style: BorderStyle.NONE, size: 0 };
|
||||
const thickBorder = { style: BorderStyle.SINGLE, size: 12, color: "000000" }; // 1.5pt
|
||||
const thinBorder = { style: BorderStyle.SINGLE, size: 6, color: "000000" }; // 0.75pt
|
||||
|
||||
// 表头行边框:顶粗 + 底细
|
||||
const headerBorders = {
|
||||
top: thickBorder, bottom: thinBorder,
|
||||
left: noBorder, right: noBorder,
|
||||
};
|
||||
// 数据行边框:无顶底左右(靠 insideH 提供行间线)
|
||||
const dataBorders = {
|
||||
top: noBorder, bottom: noBorder,
|
||||
left: noBorder, right: noBorder,
|
||||
};
|
||||
// 最后一行底部:粗线
|
||||
const lastRowBorders = {
|
||||
top: noBorder, bottom: thickBorder,
|
||||
left: noBorder, right: noBorder,
|
||||
};
|
||||
|
||||
const colWidths = headers.map(() => Math.floor(PAGE.CONTENT_WIDTH / headers.length));
|
||||
|
||||
function makeCell(text, isHeader, borders) {
|
||||
return new TableCell({
|
||||
borders,
|
||||
width: { size: colWidths[0], type: WidthType.DXA },
|
||||
shading: isHeader
|
||||
? { fill: "F2F2F2", type: ShadingType.CLEAR }
|
||||
: undefined,
|
||||
margins: { top: 40, bottom: 40, left: 80, right: 80 },
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { line: 440, lineRule: "exact" },
|
||||
children: [new TextRun({
|
||||
text,
|
||||
font: isHeader ? FONT.HEITI : FONT.FANGSONG,
|
||||
size: 24, // 小四 12pt
|
||||
bold: isHeader,
|
||||
})],
|
||||
})],
|
||||
});
|
||||
}
|
||||
|
||||
const tableRows = [
|
||||
// 表头
|
||||
new TableRow({
|
||||
children: headers.map(h => makeCell(h, true, headerBorders)),
|
||||
}),
|
||||
// 数据行
|
||||
...rows.map((row, rowIdx) =>
|
||||
new TableRow({
|
||||
children: row.map(cell =>
|
||||
makeCell(cell, false, rowIdx === rows.length - 1 ? lastRowBorders : dataBorders)
|
||||
),
|
||||
})
|
||||
),
|
||||
];
|
||||
|
||||
return new Table({
|
||||
width: { size: PAGE.CONTENT_WIDTH, type: WidthType.DXA },
|
||||
columnWidths: colWidths,
|
||||
rows: tableRows,
|
||||
});
|
||||
}
|
||||
|
||||
/** 报告标题区(居中,无封面) */
|
||||
function titleBlock(title, subtitle, date, institution) {
|
||||
return [
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { line: 640, lineRule: "exact", after: 0 },
|
||||
children: [new TextRun({ text: title, font: FONT.HEITI, size: 32, bold: true })],
|
||||
}),
|
||||
subtitle ? new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { line: 640, lineRule: "exact", after: 120 },
|
||||
children: [new TextRun({ text: subtitle, font: FONT.HEITI, size: 32 })],
|
||||
}) : null,
|
||||
new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { line: 560, lineRule: "exact", after: 0 },
|
||||
children: [new TextRun({ text: date, font: FONT.FANGSONG, size: 28 })],
|
||||
}),
|
||||
institution ? new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { line: 560, lineRule: "exact", after: 240 },
|
||||
children: [new TextRun({ text: institution, font: FONT.FANGSONG, size: 28 })],
|
||||
}) : null,
|
||||
].filter(Boolean);
|
||||
}
|
||||
|
||||
// ─── 使用示例:构建文档 ──────────────────────────────────
|
||||
|
||||
const doc = new Document({
|
||||
styles,
|
||||
numbering,
|
||||
sections: [{
|
||||
properties: {
|
||||
page: {
|
||||
size: { width: PAGE.WIDTH, height: PAGE.HEIGHT },
|
||||
margin: {
|
||||
top: PAGE.MARGIN_TOP, bottom: PAGE.MARGIN_BOTTOM,
|
||||
left: PAGE.MARGIN_LEFT, right: PAGE.MARGIN_RIGHT,
|
||||
},
|
||||
},
|
||||
},
|
||||
headers: {
|
||||
default: new Header({
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new TextRun({
|
||||
text: "内部资料 注意保密",
|
||||
font: FONT.FANGSONG, size: 21, color: "808080",
|
||||
})],
|
||||
})],
|
||||
}),
|
||||
},
|
||||
footers: {
|
||||
default: new Footer({
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ text: "— ", font: FONT.FANGSONG, size: 21 }),
|
||||
new TextRun({ children: [PageNumber.CURRENT], font: FONT.FANGSONG, size: 21 }),
|
||||
new TextRun({ text: " —", font: FONT.FANGSONG, size: 21 }),
|
||||
],
|
||||
})],
|
||||
}),
|
||||
},
|
||||
children: [
|
||||
// 标题区
|
||||
...titleBlock(
|
||||
"zn04(鲁肆号)年金组合",
|
||||
"权益投资分析与策略展望报告",
|
||||
"估值日:2026年3月10日",
|
||||
"工银瑞信基金管理有限公司"
|
||||
),
|
||||
|
||||
// 正文
|
||||
heading1("一、投资策略与研判框架"),
|
||||
bodyParagraph("本组合采用"多维度全局配置"策略,横跨A股和港股市场..."),
|
||||
|
||||
heading2("1.1 八大核心研判及验证"),
|
||||
threeLineTable(
|
||||
["#", "核心判断", "验证状态", "关键证据"],
|
||||
[
|
||||
["1", "恒生科技有超额收益", "1月验证后逆转", "1月+4.2%→3月-12.45%"],
|
||||
["2", "AI国内参与性价比不高", "已验证", "A股AI概念高位回落"],
|
||||
]
|
||||
),
|
||||
|
||||
heading1("二、组合现状概览"),
|
||||
bodyParagraph("截至估值日,组合单位净值1.4430,权益仓位合计占比约23%。"),
|
||||
],
|
||||
}],
|
||||
});
|
||||
|
||||
// 导出
|
||||
Packer.toBuffer(doc).then(buffer => {
|
||||
fs.writeFileSync("output.docx", buffer);
|
||||
console.log("✅ 已生成: output.docx");
|
||||
});
|
||||
```
|
||||
|
||||
### 运行
|
||||
|
||||
```bash
|
||||
node generate_report.js
|
||||
# 验证(可选,使用官方 docx skill 的验证工具)
|
||||
python .claude/skills/docx/scripts/office/validate.py output.docx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 辅助函数速查
|
||||
|
||||
| 函数 | 用途 | 格式 |
|
||||
|------|------|------|
|
||||
| `titleBlock(title, subtitle, date, institution)` | 报告标题区(无封面) | 黑体三号居中 |
|
||||
| `heading1(text)` | 一级标题 | 黑体三号 16pt |
|
||||
| `heading2(text)` | 二级标题 | 楷体四号加粗 |
|
||||
| `bodyParagraph(text, {bold, alignment})` | 正文段落 | 仿宋四号,首行缩进,28pt行距 |
|
||||
| `threeLineTable(headers, rows)` | 三线表 | 顶底1.5pt,内部0.75pt,无竖线 |
|
||||
|
||||
---
|
||||
|
||||
## 编辑现有文档
|
||||
|
||||
当需要修改已有 .docx 文件时,使用官方 docx skill 的 unpack/pack 工作流:
|
||||
|
||||
```bash
|
||||
# 1. 解包
|
||||
python .claude/skills/docx/scripts/office/unpack.py document.docx unpacked/
|
||||
|
||||
# 2. 编辑 unpacked/word/document.xml(直接用 Edit 工具修改 XML)
|
||||
|
||||
# 3. 重新打包
|
||||
python .claude/skills/docx/scripts/office/pack.py unpacked/ output.docx --original document.docx
|
||||
```
|
||||
|
||||
### 公文格式关键 XML 片段
|
||||
|
||||
#### 页面设置(插入 `<w:sectPr>` 中)
|
||||
|
||||
```xml
|
||||
<w:pgSz w:w="11906" w:h="16838"/>
|
||||
<w:pgMar w:top="2098" w:right="1474" w:bottom="1985" w:left="1588"
|
||||
w:header="851" w:footer="851" w:gutter="0"/>
|
||||
```
|
||||
|
||||
#### 正文段落格式
|
||||
|
||||
```xml
|
||||
<w:pPr>
|
||||
<w:spacing w:line="560" w:lineRule="exact"/>
|
||||
<w:ind w:firstLine="560"/>
|
||||
<w:jc w:val="both"/>
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="FangSong" w:eastAsia="仿宋" w:hAnsi="FangSong"/>
|
||||
<w:sz w:val="28"/>
|
||||
<w:szCs w:val="28"/>
|
||||
</w:rPr>
|
||||
```
|
||||
|
||||
#### 一级标题
|
||||
|
||||
```xml
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Heading1"/>
|
||||
<w:spacing w:line="640" w:lineRule="exact" w:before="240" w:after="120"/>
|
||||
</w:pPr>
|
||||
<w:rPr>
|
||||
<w:rFonts w:ascii="SimHei" w:eastAsia="黑体" w:hAnsi="SimHei"/>
|
||||
<w:b/>
|
||||
<w:sz w:val="32"/>
|
||||
<w:szCs w:val="32"/>
|
||||
</w:rPr>
|
||||
```
|
||||
|
||||
#### 三线表边框(`<w:tblBorders>` 内)
|
||||
|
||||
```xml
|
||||
<w:top w:val="single" w:sz="12" w:space="0" w:color="000000"/>
|
||||
<w:bottom w:val="single" w:sz="12" w:space="0" w:color="000000"/>
|
||||
<w:insideH w:val="single" w:sz="6" w:space="0" w:color="000000"/>
|
||||
<w:left w:val="none" w:sz="0" w:space="0" w:color="auto"/>
|
||||
<w:right w:val="none" w:sz="0" w:space="0" w:color="auto"/>
|
||||
<w:insideV w:val="none" w:sz="0" w:space="0" w:color="auto"/>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 从现有文档提取内容并重建
|
||||
|
||||
当需要对已有文档「重排版」时,推荐提取内容后用 docx-js 重建:
|
||||
|
||||
```bash
|
||||
# 提取为 Markdown
|
||||
pandoc original.docx -o content.md
|
||||
|
||||
# 然后读取 content.md,用上面的模板函数构建新文档
|
||||
```
|
||||
|
||||
这比原地修改格式更可靠,避免了样式冲突和继承问题。
|
||||
|
||||
---
|
||||
|
||||
## 格式检查清单
|
||||
|
||||
- [ ] 纸张:A4(11906 × 16838 DXA)
|
||||
- [ ] 页边距:上37 / 下35 / 左28 / 右26 mm
|
||||
- [ ] 一级标题:黑体三号(16pt),固定行距 32pt
|
||||
- [ ] 二级标题:楷体四号加粗(14pt),固定行距 28pt
|
||||
- [ ] 正文:仿宋四号(14pt),固定行距 28pt,首行缩进 2 字符
|
||||
- [ ] 表格:三线表,表头黑体小四 + 浅灰底,内容仿宋小四
|
||||
- [ ] 页脚:居中 "— 页码 —",仿宋五号
|
||||
- [ ] 字体必须设置 `eastAsia` 属性
|
||||
|
||||
## 依赖
|
||||
|
||||
- **Node.js**: v18+
|
||||
- **docx**: `npm install docx`(docx-js 库)
|
||||
- **pandoc**: 文本提取(可选)
|
||||
- **官方 docx skill**: 提供 validate.py / unpack.py / pack.py
|
||||
287
docx-cn/scripts/gongwen_constants.js
Normal file
287
docx-cn/scripts/gongwen_constants.js
Normal file
@ -0,0 +1,287 @@
|
||||
/**
|
||||
* GB/T 9704-2012 公文格式常量
|
||||
* 供所有公文文档生成脚本共享
|
||||
*/
|
||||
|
||||
const { AlignmentType, BorderStyle, WidthType, ShadingType, LevelFormat,
|
||||
HeadingLevel, Paragraph, TextRun, Table, TableRow, TableCell,
|
||||
Header, Footer, PageNumber } = require("docx");
|
||||
|
||||
// ─── 页面 ─────────────────────────────────────────────────
|
||||
const PAGE = {
|
||||
WIDTH: 11906, // 210mm
|
||||
HEIGHT: 16838, // 297mm
|
||||
MARGIN_TOP: 2098, // 37mm
|
||||
MARGIN_BOTTOM: 1985, // 35mm
|
||||
MARGIN_LEFT: 1588, // 28mm
|
||||
MARGIN_RIGHT: 1474, // 26mm
|
||||
CONTENT_WIDTH: 8844, // 210 - 28 - 26 mm
|
||||
};
|
||||
|
||||
// ─── 字体 ─────────────────────────────────────────────────
|
||||
const FONT = {
|
||||
HEITI: { ascii: "SimHei", eastAsia: "黑体", hAnsi: "SimHei" },
|
||||
FANGSONG: { ascii: "FangSong", eastAsia: "仿宋", hAnsi: "FangSong" },
|
||||
KAITI: { ascii: "KaiTi", eastAsia: "楷体", hAnsi: "KaiTi" },
|
||||
SONGTI: { ascii: "SimSun", eastAsia: "宋体", hAnsi: "SimSun" },
|
||||
};
|
||||
|
||||
// ─── 字号(half-point) ───────────────────────────────────
|
||||
const SIZE = {
|
||||
ER_HAO: 44, // 二号 22pt
|
||||
SAN_HAO: 32, // 三号 16pt
|
||||
SI_HAO: 28, // 四号 14pt
|
||||
XIAO_SI: 24, // 小四 12pt
|
||||
WU_HAO: 21, // 五号 10.5pt
|
||||
};
|
||||
|
||||
// ─── 行距(twips = pt × 20) ─────────────────────────────
|
||||
const LINE = {
|
||||
BODY: { line: 560, lineRule: "exact" }, // 28pt
|
||||
HEADING1: { line: 640, lineRule: "exact" }, // 32pt
|
||||
HEADING2: { line: 560, lineRule: "exact" }, // 28pt
|
||||
TABLE: { line: 440, lineRule: "exact" }, // 22pt
|
||||
TITLE: { line: 640, lineRule: "exact" }, // 32pt
|
||||
COMPACT: { line: 360, lineRule: "exact" }, // 18pt
|
||||
};
|
||||
|
||||
// ─── 缩进(DXA) ─────────────────────────────────────────
|
||||
const INDENT = {
|
||||
FIRST_LINE: 560, // 首行缩进 2 字符
|
||||
BULLET_LEFT: 1120,
|
||||
BULLET_HANG: 560,
|
||||
};
|
||||
|
||||
// ─── 边框 ─────────────────────────────────────────────────
|
||||
const BORDER = {
|
||||
NONE: { style: BorderStyle.NONE, size: 0 },
|
||||
THICK: { style: BorderStyle.SINGLE, size: 12, color: "000000" }, // 1.5pt
|
||||
THIN: { style: BorderStyle.SINGLE, size: 6, color: "000000" }, // 0.75pt
|
||||
};
|
||||
|
||||
// ─── 样式预设 ─────────────────────────────────────────────
|
||||
function getStyles() {
|
||||
return {
|
||||
default: {
|
||||
document: {
|
||||
run: { font: FONT.FANGSONG, size: SIZE.SI_HAO },
|
||||
paragraph: { spacing: LINE.BODY },
|
||||
},
|
||||
},
|
||||
paragraphStyles: [
|
||||
{
|
||||
id: "Heading1", name: "Heading 1",
|
||||
basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { font: FONT.HEITI, size: SIZE.SAN_HAO, bold: true },
|
||||
paragraph: { spacing: { ...LINE.HEADING1, before: 240, after: 120 }, outlineLevel: 0 },
|
||||
},
|
||||
{
|
||||
id: "Heading2", name: "Heading 2",
|
||||
basedOn: "Normal", next: "Normal", quickFormat: true,
|
||||
run: { font: FONT.KAITI, size: SIZE.SI_HAO, bold: true },
|
||||
paragraph: { spacing: { ...LINE.HEADING2, before: 120, after: 60 }, outlineLevel: 1 },
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function getNumbering() {
|
||||
return {
|
||||
config: [{
|
||||
reference: "bullets",
|
||||
levels: [{
|
||||
level: 0, format: LevelFormat.BULLET, text: "\u2022",
|
||||
alignment: AlignmentType.LEFT,
|
||||
style: {
|
||||
run: { font: FONT.FANGSONG, size: SIZE.SI_HAO },
|
||||
paragraph: { indent: { left: INDENT.BULLET_LEFT, hanging: INDENT.BULLET_HANG } },
|
||||
},
|
||||
}],
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
// ─── 辅助函数 ─────────────────────────────────────────────
|
||||
|
||||
/** 报告标题区(无封面,居中) */
|
||||
function titleBlock(title, subtitle, date, institution) {
|
||||
const items = [];
|
||||
items.push(new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { ...LINE.TITLE, after: 0 },
|
||||
children: [new TextRun({ text: title, font: FONT.HEITI, size: SIZE.SAN_HAO, bold: true })],
|
||||
}));
|
||||
if (subtitle) {
|
||||
items.push(new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { ...LINE.TITLE, after: 120 },
|
||||
children: [new TextRun({ text: subtitle, font: FONT.HEITI, size: SIZE.SAN_HAO })],
|
||||
}));
|
||||
}
|
||||
items.push(new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { ...LINE.BODY, after: 0 },
|
||||
children: [new TextRun({ text: date, font: FONT.FANGSONG, size: SIZE.SI_HAO })],
|
||||
}));
|
||||
if (institution) {
|
||||
items.push(new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: { ...LINE.BODY, after: 240 },
|
||||
children: [new TextRun({ text: institution, font: FONT.FANGSONG, size: SIZE.SI_HAO })],
|
||||
}));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
/** 一级标题 */
|
||||
function h1(text) {
|
||||
return new Paragraph({
|
||||
heading: HeadingLevel.HEADING_1,
|
||||
spacing: { ...LINE.HEADING1, before: 240, after: 120 },
|
||||
children: [new TextRun({ text, font: FONT.HEITI, size: SIZE.SAN_HAO, bold: true })],
|
||||
});
|
||||
}
|
||||
|
||||
/** 二级标题 */
|
||||
function h2(text) {
|
||||
return new Paragraph({
|
||||
heading: HeadingLevel.HEADING_2,
|
||||
spacing: { ...LINE.HEADING2, before: 120, after: 60 },
|
||||
children: [new TextRun({ text, font: FONT.KAITI, size: SIZE.SI_HAO, bold: true })],
|
||||
});
|
||||
}
|
||||
|
||||
/** 正文段落 */
|
||||
function body(text, opts = {}) {
|
||||
const { bold = false, align = AlignmentType.JUSTIFIED, indent = true } = opts;
|
||||
return new Paragraph({
|
||||
alignment: align,
|
||||
indent: indent ? { firstLine: INDENT.FIRST_LINE } : undefined,
|
||||
spacing: LINE.BODY,
|
||||
children: [new TextRun({ text, font: FONT.FANGSONG, size: SIZE.SI_HAO, bold })],
|
||||
});
|
||||
}
|
||||
|
||||
/** 多 run 正文段落(支持混排加粗) */
|
||||
function bodyRuns(runs, opts = {}) {
|
||||
const { align = AlignmentType.JUSTIFIED, indent = true } = opts;
|
||||
return new Paragraph({
|
||||
alignment: align,
|
||||
indent: indent ? { firstLine: INDENT.FIRST_LINE } : undefined,
|
||||
spacing: LINE.BODY,
|
||||
children: runs.map(r => {
|
||||
if (typeof r === "string") {
|
||||
return new TextRun({ text: r, font: FONT.FANGSONG, size: SIZE.SI_HAO });
|
||||
}
|
||||
return new TextRun({ font: FONT.FANGSONG, size: SIZE.SI_HAO, ...r });
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 列表项 */
|
||||
function bullet(text) {
|
||||
return new Paragraph({
|
||||
numbering: { reference: "bullets", level: 0 },
|
||||
spacing: LINE.BODY,
|
||||
children: [new TextRun({ text, font: FONT.FANGSONG, size: SIZE.SI_HAO })],
|
||||
});
|
||||
}
|
||||
|
||||
/** 三线表 */
|
||||
function threeLineTable(headers, rows, opts = {}) {
|
||||
const { colWidths } = opts;
|
||||
const widths = colWidths || headers.map(() => Math.floor(PAGE.CONTENT_WIDTH / headers.length));
|
||||
|
||||
function cell(text, isHeader, isLastRow, colIdx) {
|
||||
const borders = {
|
||||
top: isHeader ? BORDER.THICK : BORDER.NONE,
|
||||
bottom: isHeader ? BORDER.THIN : (isLastRow ? BORDER.THICK : BORDER.NONE),
|
||||
left: BORDER.NONE,
|
||||
right: BORDER.NONE,
|
||||
};
|
||||
return new TableCell({
|
||||
borders,
|
||||
width: { size: widths[colIdx], type: WidthType.DXA },
|
||||
shading: isHeader ? { fill: "1F3864", type: ShadingType.CLEAR } : undefined,
|
||||
margins: { top: 40, bottom: 40, left: 80, right: 80 },
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: LINE.TABLE,
|
||||
children: [new TextRun({
|
||||
text: String(text),
|
||||
font: isHeader ? FONT.HEITI : FONT.FANGSONG,
|
||||
size: SIZE.XIAO_SI,
|
||||
bold: isHeader,
|
||||
color: isHeader ? "FFFFFF" : undefined,
|
||||
})],
|
||||
})],
|
||||
});
|
||||
}
|
||||
|
||||
// 表级边框全部 NONE,由 cell 级别控制三线效果
|
||||
const noBorder = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" };
|
||||
return new Table({
|
||||
width: { size: PAGE.CONTENT_WIDTH, type: WidthType.DXA },
|
||||
columnWidths: widths,
|
||||
borders: {
|
||||
top: noBorder, bottom: noBorder,
|
||||
left: noBorder, right: noBorder,
|
||||
insideHorizontal: noBorder, insideVertical: noBorder,
|
||||
},
|
||||
rows: [
|
||||
new TableRow({ children: headers.map((h, i) => cell(h, true, false, i)) }),
|
||||
...rows.map((row, ri) =>
|
||||
new TableRow({
|
||||
children: row.map((c, ci) => cell(c, false, ri === rows.length - 1, ci)),
|
||||
})
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/** 标准页脚(— 页码 —) */
|
||||
function pageFooter() {
|
||||
return new Footer({
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [
|
||||
new TextRun({ text: "— ", font: FONT.FANGSONG, size: SIZE.WU_HAO }),
|
||||
new TextRun({ children: [PageNumber.CURRENT], font: FONT.FANGSONG, size: SIZE.WU_HAO }),
|
||||
new TextRun({ text: " —", font: FONT.FANGSONG, size: SIZE.WU_HAO }),
|
||||
],
|
||||
})],
|
||||
});
|
||||
}
|
||||
|
||||
/** 标准页眉 */
|
||||
function pageHeader(text = "内部资料 注意保密") {
|
||||
return new Header({
|
||||
children: [new Paragraph({
|
||||
alignment: AlignmentType.CENTER,
|
||||
children: [new TextRun({
|
||||
text, font: FONT.FANGSONG, size: SIZE.WU_HAO, color: "808080",
|
||||
})],
|
||||
})],
|
||||
});
|
||||
}
|
||||
|
||||
/** section 属性(页面设置) */
|
||||
function sectionProps(opts = {}) {
|
||||
return {
|
||||
page: {
|
||||
size: { width: PAGE.WIDTH, height: PAGE.HEIGHT },
|
||||
margin: {
|
||||
top: PAGE.MARGIN_TOP, bottom: PAGE.MARGIN_BOTTOM,
|
||||
left: PAGE.MARGIN_LEFT, right: PAGE.MARGIN_RIGHT,
|
||||
},
|
||||
},
|
||||
...opts,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PAGE, FONT, SIZE, LINE, INDENT, BORDER,
|
||||
getStyles, getNumbering, sectionProps,
|
||||
titleBlock, h1, h2, body, bodyRuns, bullet,
|
||||
threeLineTable, pageFooter, pageHeader,
|
||||
};
|
||||
30
docx/LICENSE.txt
Normal file
30
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
docx/SKILL.md
Normal file
198
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
docx/docx-js.md
Normal file
499
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
docx/ooxml.md
Normal file
610
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
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
Normal file
1499
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
Normal file
File diff suppressed because it is too large
Load Diff
146
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd
Normal file
146
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd
Normal file
@ -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>
|
||||
1085
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd
Normal file
1085
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd
Normal file
File diff suppressed because it is too large
Load Diff
11
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd
Normal file
11
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd
Normal file
@ -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
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd
Normal file
3081
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd
Normal file
File diff suppressed because it is too large
Load Diff
23
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
Normal file
23
docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
Normal file
@ -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
docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd
Normal file
1676
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>
|
||||
144
docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd
Normal file
144
docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd
Normal file
@ -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>
|
||||
582
docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd
Normal file
582
docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd
Normal file
@ -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
docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd
Normal file
4439
docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd
Normal file
File diff suppressed because it is too large
Load Diff
570
docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd
Normal file
570
docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd
Normal file
@ -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>
|
||||
509
docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
Normal file
509
docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
Normal file
@ -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
docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd
Normal file
3646
docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd
Normal file
File diff suppressed because it is too large
Load Diff
116
docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd
Normal file
116
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>
|
||||
42
docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd
Normal file
42
docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd
Normal file
@ -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>
|
||||
50
docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd
Normal file
50
docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd
Normal file
@ -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>
|
||||
49
docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd
Normal file
49
docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd
Normal file
@ -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>
|
||||
33
docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd
Normal file
33
docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd
Normal file
@ -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
docx/ooxml/schemas/mce/mc.xsd
Normal file
75
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
docx/ooxml/schemas/microsoft/wml-2010.xsd
Normal file
560
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
docx/ooxml/schemas/microsoft/wml-2012.xsd
Normal file
67
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
docx/ooxml/schemas/microsoft/wml-2018.xsd
Normal file
14
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
docx/ooxml/schemas/microsoft/wml-cex-2018.xsd
Normal file
20
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
docx/ooxml/schemas/microsoft/wml-cid-2016.xsd
Normal file
13
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>
|
||||
4
docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd
Normal file
4
docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd
Normal file
@ -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>
|
||||
8
docx/ooxml/schemas/microsoft/wml-symex-2015.xsd
Normal file
8
docx/ooxml/schemas/microsoft/wml-symex-2015.xsd
Normal file
@ -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
docx/ooxml/scripts/pack.py
Executable file
159
docx/ooxml/scripts/pack.py
Executable 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
docx/ooxml/scripts/unpack.py
Executable file
29
docx/ooxml/scripts/unpack.py
Executable 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
docx/ooxml/scripts/validate.py
Executable file
69
docx/ooxml/scripts/validate.py
Executable 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
docx/ooxml/scripts/validation/__init__.py
Normal file
15
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
docx/ooxml/scripts/validation/base.py
Normal file
951
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
docx/ooxml/scripts/validation/docx.py
Normal file
274
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
docx/ooxml/scripts/validation/pptx.py
Normal file
315
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
docx/ooxml/scripts/validation/redlining.py
Normal file
279
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
docx/scripts/__init__.py
Executable file
1
docx/scripts/__init__.py
Executable file
@ -0,0 +1 @@
|
||||
# Make scripts directory a package for relative imports in tests
|
||||
1276
docx/scripts/document.py
Executable file
1276
docx/scripts/document.py
Executable file
File diff suppressed because it is too large
Load Diff
3
docx/scripts/templates/comments.xml
Normal file
3
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>
|
||||
3
docx/scripts/templates/commentsExtended.xml
Normal file
3
docx/scripts/templates/commentsExtended.xml
Normal file
@ -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>
|
||||
3
docx/scripts/templates/commentsExtensible.xml
Normal file
3
docx/scripts/templates/commentsExtensible.xml
Normal file
@ -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
docx/scripts/templates/commentsIds.xml
Normal file
3
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
docx/scripts/templates/people.xml
Normal file
3
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
docx/scripts/utilities.py
Executable file
374
docx/scripts/utilities.py
Executable 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
|
||||
685
financial-report-writing/SKILL.md
Normal file
685
financial-report-writing/SKILL.md
Normal file
@ -0,0 +1,685 @@
|
||||
# 金融研究报告撰写技能
|
||||
|
||||
## 概述
|
||||
本技能用于协助用户撰写专业的A股上市公司投资研究报告,涵盖数据获取、分析和文档格式化等全流程。
|
||||
|
||||
## 适用场景
|
||||
- 撰写上市公司周报、月报或专项研究报告
|
||||
- 需要使用Wind金融终端获取实时或历史数据
|
||||
- 生成符合中国国家标准的Word格式文档
|
||||
- 技术分析与基本面分析相结合的投资报告
|
||||
|
||||
## 核心能力
|
||||
|
||||
### 1. 数据获取(WindPy SDK)
|
||||
|
||||
#### 连接Wind终端
|
||||
```python
|
||||
from WindPy import w
|
||||
w.start()
|
||||
data = w.wsd("300866.SZ", "close", "2025-11-01", "2026-02-06")
|
||||
w.stop()
|
||||
```
|
||||
|
||||
#### 常用数据函数
|
||||
- `w.wsd()` - 日线序列数据
|
||||
- `w.wsi()` - 分钟线数据
|
||||
- `w.wss()` - 截面数据(最新值)
|
||||
- `wsd` 常用字段:
|
||||
- `close` - 收盘价
|
||||
- `open`/`high`/`low` - 开盘/最高/最低价
|
||||
- `volume`/`amt` - 成交量/成交额
|
||||
- `pct_chg` - 涨跌幅
|
||||
- `ma5`, `ma10`, `ma20`, `ma60` - 均线
|
||||
- `roe` - 净资产收益率
|
||||
- `pe_ttm`, `pb_lf` - 市盈率/市净率
|
||||
|
||||
#### 技术指标计算
|
||||
- 移动平均线:MA5, MA10, MA20, MA60
|
||||
- 波动率:使用标准差计算年化波动率
|
||||
- 支撑位/阻力位:基于高低点分析
|
||||
|
||||
### 2. Word文档格式化(python-docx)
|
||||
|
||||
#### 国家标准 GB/T 9704-2012
|
||||
|
||||
**页面设置**
|
||||
```python
|
||||
from docx.shared import Mm
|
||||
section.top_margin = Mm(37)
|
||||
section.bottom_margin = Mm(35)
|
||||
section.left_margin = Mm(28)
|
||||
section.right_margin = Mm(26)
|
||||
```
|
||||
|
||||
**字体规范**
|
||||
```python
|
||||
def set_font(run, font_name, size=None, bold=None):
|
||||
run.font.name = font_name
|
||||
run.font.size = Pt(size) if size else None
|
||||
if bold is not None:
|
||||
run.font.bold = bold
|
||||
# 设置中文字体
|
||||
run._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
|
||||
|
||||
# 三号标题:16pt
|
||||
# 四号正文:14pt
|
||||
# 小四表格:12pt
|
||||
```
|
||||
|
||||
**字号对照表**
|
||||
- 三号:16pt
|
||||
- 四号:14pt
|
||||
- 小四:12pt
|
||||
- 五号:10.5pt
|
||||
|
||||
**行距设置**
|
||||
```python
|
||||
paragraph.paragraph_format.line_spacing = Pt(28) # 正文
|
||||
paragraph.paragraph_format.line_spacing = Pt(25) # 小标题
|
||||
paragraph.paragraph_format.line_spacing_rule = 2 # 固定值
|
||||
```
|
||||
|
||||
**段落格式**
|
||||
```python
|
||||
# 首行缩进2字符(28pt)
|
||||
paragraph.paragraph_format.first_line_indent = Pt(28)
|
||||
|
||||
# 两端对齐
|
||||
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
||||
|
||||
# 段前段后距
|
||||
paragraph.paragraph_format.space_before = Pt(0)
|
||||
paragraph.paragraph_format.space_after = Pt(0)
|
||||
```
|
||||
|
||||
#### 三线表格式
|
||||
|
||||
```python
|
||||
def set_table_borders(table):
|
||||
tblPr = table._tbl.tblPr
|
||||
tblBorders = OxmlElement('w:tblBorders')
|
||||
# 顶底边框 1.5pt (sz=18)
|
||||
for pos in ['top', 'bottom']:
|
||||
border = OxmlElement(f'w:{pos}')
|
||||
border.set(qn('w:val'), 'single')
|
||||
border.set(qn('w:sz'), '18')
|
||||
border.set(qn('w:color'), 'auto')
|
||||
tblBorders.append(border)
|
||||
# 内部横线 0.75pt (sz=9)
|
||||
for pos in ['insideH']:
|
||||
border = OxmlElement(f'w:{pos}')
|
||||
border.set(qn('w:val'), 'single')
|
||||
border.set(qn('w:sz'), '9')
|
||||
border.set(qn('w:color'), 'auto')
|
||||
tblBorders.append(border)
|
||||
# 无左右和竖线
|
||||
for pos in ['left', 'right', 'insideV']:
|
||||
border = OxmlElement(f'w:{pos}')
|
||||
border.set(qn('w:val'), 'none')
|
||||
tblBorders.append(border)
|
||||
tblPr.append(tblBorders)
|
||||
```
|
||||
|
||||
#### 页脚页码
|
||||
```python
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
footer = section.footer
|
||||
p = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph()
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
r1 = p.add_run('—')
|
||||
set_font(r1, '仿宋', 12)
|
||||
r2 = p.add_run() # 页码占位
|
||||
set_font(r2, '仿宋', 12)
|
||||
r3 = p.add_run(' ')
|
||||
set_font(r3, '仿宋', 12)
|
||||
r4 = p.add_run() # 页码占位
|
||||
set_font(r4, '仿宋', 12)
|
||||
r5 = p.add_run('—')
|
||||
set_font(r5, '仿宋', 12)
|
||||
```
|
||||
|
||||
### 3. 报告结构标准
|
||||
|
||||
#### 完整报告结构(可选封面版)
|
||||
1. **封面页**
|
||||
- 标题:黑体三号(16pt),居中
|
||||
- 报告机构和日期:黑体三号(16pt),居中
|
||||
|
||||
2. **免责声明**(可选)
|
||||
- 标题:黑体三号(16pt),居中
|
||||
- 内容:仿宋四号(14pt),两端对齐
|
||||
|
||||
3. **核心提要**
|
||||
- 标题:黑体三号(16pt),居中
|
||||
- 内容:3-4个要点,仿宋四号(14pt),首行缩进2字符
|
||||
|
||||
4. **一、投资评级**
|
||||
- 标题:黑体三号(16pt)
|
||||
- 评级表格:小四(12pt),三线表
|
||||
- 内容:投资建议、目标价格、有效期、风险等级
|
||||
|
||||
5. **二、核心观点**
|
||||
- 标题:黑体三号(16pt)
|
||||
- 内容:基本面+技术面分析+投资建议
|
||||
|
||||
6. **三、市场表现回顾**
|
||||
- 标题:黑体三号(16pt)
|
||||
- (一)价格走势分析:黑体三号(16pt)
|
||||
- (二)K线数据表:标题黑体小四(12pt)
|
||||
|
||||
#### 简化报告结构(无封面)
|
||||
- 核心提要 → 一、投资评级 → 二、核心观点 → 三、市场表现回顾
|
||||
- 不使用分页符,保持内容连贯
|
||||
|
||||
### 4. 内容撰写要点
|
||||
|
||||
#### 核心提要
|
||||
- 突出关键数据(ROE、涨跌幅等)
|
||||
- 简明扼要,3-4个要点
|
||||
- 包含明确的投资建议
|
||||
|
||||
#### 核心观点
|
||||
- 基本面:盈利能力(ROE)、估值水平(PE/PB)
|
||||
- 技术面:价格趋势、均线、支撑阻力位
|
||||
- 投资建议:具体操作策略
|
||||
|
||||
#### 市场表现回顾
|
||||
- 时间周期:近13周(周线数据)
|
||||
- 价格区间:最高价、最低价、累计涨跌幅
|
||||
- 数据表格:日期、开盘、最高、最低、收盘、涨跌幅、成交量
|
||||
|
||||
### 5. 数据处理技巧
|
||||
|
||||
#### 时间序列对齐
|
||||
```python
|
||||
import pandas as pd
|
||||
df = pd.DataFrame(data, columns=['date', 'open', 'high', 'low', 'close'])
|
||||
df['date'] = pd.to_datetime(df['date'])
|
||||
df.sort_values('date', ascending=True)
|
||||
```
|
||||
|
||||
#### 周线数据汇总
|
||||
```python
|
||||
df_weekly = df.resample('W').agg({
|
||||
'open': 'first',
|
||||
'high': 'max',
|
||||
'low': 'min',
|
||||
'close': 'last',
|
||||
'volume': 'sum'
|
||||
})
|
||||
```
|
||||
|
||||
#### 波动率计算
|
||||
```python
|
||||
import numpy as np
|
||||
returns = df['close'].pct_change().dropna()
|
||||
volatility = returns.std() * np.sqrt(52) * 100 # 年化百分比
|
||||
```
|
||||
|
||||
### 6. 常见问题解决
|
||||
|
||||
#### 字体显示问题
|
||||
- 必须同时设置中文字体:`run._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)`
|
||||
- 不要只设置 `run.font.name`
|
||||
|
||||
#### 表格单元格格式
|
||||
- Cell对象没有 `paragraph_format` 属性
|
||||
- 需要通过 `cell.paragraphs[0].paragraph_format` 访问
|
||||
|
||||
#### Run对象访问
|
||||
- Run对象没有 `runs` 属性
|
||||
- 段落的runs通过 `paragraph.runs` 访问
|
||||
|
||||
#### 行距单位
|
||||
- 使用 `Pt()` 设置固定行距(点)
|
||||
- `line_spacing_rule = 2` 表示固定值
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本流程
|
||||
1. 使用WindPy SDK获取股票数据
|
||||
2. 计算技术指标(MA、波动率等)
|
||||
3. 分析基本面指标(ROE、PE等)
|
||||
4. 整理分析结论
|
||||
5. 使用python-docx生成Word文档
|
||||
6. 应用GB/T 9704-2012格式规范
|
||||
7. 保存文档
|
||||
|
||||
### 快速检查清单(Word文档)
|
||||
- [ ] 页面边距:37/35/28/26mm
|
||||
- [ ] 字体:标题黑体三号、正文仿宋四号、表格小四
|
||||
- [ ] 行距:正文28pt、小标题25pt
|
||||
- [ ] 首行缩进:正文2字符(28pt)
|
||||
- [ ] 三线表:顶底1.5pt、内部0.75pt、无边框
|
||||
- [ ] 数据来源注脚:右对齐,小四(12pt)
|
||||
|
||||
### Excel图表检查清单
|
||||
详见「图表设置检查清单」表格(第7节末尾)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **字体重要性**:中文文本必须设置 `w:eastAsia` 属性,否则可能显示为默认字体
|
||||
2. **表格边框**:使用三线表格式,无左右和竖线
|
||||
3. **段落缩进**:正文段落需要首行缩进,标题不需要
|
||||
4. **分页控制**:根据需要决定是否分页,简化版通常不分页
|
||||
5. **数据时效**:注意数据更新日期,避免使用过期数据
|
||||
6. **投资建议**:提供明确的操作策略,但需提醒风险
|
||||
|
||||
### 7. Excel 图表绘制(openpyxl)
|
||||
|
||||
本技能支持生成包含原生 Excel 图表的 .xlsx 文件,图表可在 Excel 中编辑和交互。
|
||||
|
||||
#### 基础图表创建
|
||||
|
||||
```python
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.chart import LineChart, BarChart, Reference
|
||||
from openpyxl.chart.axis import DateAxis
|
||||
|
||||
# 创建工作簿
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "股价走势"
|
||||
|
||||
# 写入数据
|
||||
ws.append(["日期", "收盘价", "成交量"])
|
||||
for date, close, vol in data:
|
||||
ws.append([date, close, vol])
|
||||
|
||||
# 创建折线图(股价走势)
|
||||
chart = LineChart()
|
||||
chart.title = "股价走势"
|
||||
chart.y_axis.title = "价格(元)"
|
||||
chart.x_axis.title = "日期"
|
||||
|
||||
# 设置数据区域
|
||||
data_ref = Reference(ws, min_col=2, min_row=1, max_row=len(data)+1)
|
||||
cats_ref = Reference(ws, min_col=1, min_row=2, max_row=len(data)+1)
|
||||
chart.add_data(data_ref, titles_from_data=True)
|
||||
chart.set_categories(cats_ref)
|
||||
|
||||
# 设置样式
|
||||
chart.style = 10
|
||||
chart.height = 10 # 高度(厘米)
|
||||
chart.width = 20 # 宽度(厘米)
|
||||
|
||||
# 添加到工作表
|
||||
ws.add_chart(chart, "D2")
|
||||
wb.save("股价走势.xlsx")
|
||||
```
|
||||
|
||||
#### 常用图表类型
|
||||
|
||||
**折线图(LineChart)**
|
||||
- 适用:股价走势、均线趋势
|
||||
- 特点:展示时间序列变化
|
||||
|
||||
```python
|
||||
from openpyxl.chart import LineChart
|
||||
chart = LineChart()
|
||||
chart.add_data(data_ref)
|
||||
chart.set_categories(cats_ref)
|
||||
```
|
||||
|
||||
**柱状图(BarChart)**
|
||||
- 适用:成交量对比、财务数据对比
|
||||
- 特点:横向展示数据大小
|
||||
|
||||
```python
|
||||
from openpyxl.chart import BarChart
|
||||
chart = BarChart()
|
||||
chart.type = "col" # 垂直柱状图
|
||||
chart.add_data(data_ref)
|
||||
```
|
||||
|
||||
**组合图表(股价+成交量)**
|
||||
- 适用:K线+成交量组合展示
|
||||
- 特点:双Y轴,价格+量能
|
||||
|
||||
```python
|
||||
from openpyxl.chart import LineChart, BarChart
|
||||
from openpyxl.chart.series import DataPoint
|
||||
|
||||
# 主图:股价线
|
||||
price_chart = LineChart()
|
||||
price_chart.add_data(price_ref, titles_from_data=True)
|
||||
price_chart.y_axis.title = "价格(元)"
|
||||
|
||||
# 副图:成交量柱状
|
||||
vol_chart = BarChart()
|
||||
vol_chart.add_data(vol_ref, titles_from_data=True)
|
||||
vol_chart.y_axis.axId = 200 # 设置次要Y轴
|
||||
vol_chart.y_axis.title = "成交量(手)"
|
||||
|
||||
# 组合图表
|
||||
price_chart += vol_chart
|
||||
price_chart.y_axis.crosses = "max" # 成交量轴在右侧
|
||||
```
|
||||
|
||||
#### 图表样式设置
|
||||
|
||||
**线条与颜色规范**
|
||||
```python
|
||||
# 线条样式
|
||||
from openpyxl.chart.series import DataPoint
|
||||
from openpyxl.drawing.fill import SolidColorFillProperties
|
||||
from openpyxl.drawing.line import LineProperties
|
||||
|
||||
series = chart.series[0]
|
||||
series.graphicalProperties.line.solidFill = "4472C4" # 线条颜色
|
||||
series.graphicalProperties.line.width = 25000 # 线条粗细(EMUs)
|
||||
|
||||
# 标记点样式
|
||||
series.marker.symbol = "circle"
|
||||
series.marker.size = 5
|
||||
```
|
||||
|
||||
**A股颜色规范(涨跌自动着色)**
|
||||
```python
|
||||
# 柱状图根据涨跌自动设置颜色(红涨绿跌)
|
||||
from openpyxl.drawing.fill import PatternFillProperties, ColorChoice
|
||||
from openpyxl.chart.series import DataPoint
|
||||
|
||||
# 为每个数据点设置颜色
|
||||
for i, val in enumerate(values):
|
||||
pt = DataPoint(idx=i)
|
||||
# A股规范:上涨红色,下跌绿色
|
||||
fill_color = "FF0000" if val >= 0 else "00B050"
|
||||
pt.graphicalProperties = GraphicalProperties(
|
||||
solidFill=fill_color
|
||||
)
|
||||
series.data_points.append(pt)
|
||||
```
|
||||
|
||||
**网格线与图例**
|
||||
```python
|
||||
# 网格线设置(研报风格:仅水平主网格线)
|
||||
chart.x_axis.majorGridlines = None # 隐藏X轴网格
|
||||
chart.y_axis.majorGridlines = None # 或设置为浅灰色虚线
|
||||
|
||||
# 图例位置
|
||||
chart.legend.position = "b" # 底部:b, 右侧:r, 顶部:t, 左侧:l
|
||||
```
|
||||
|
||||
#### 双轴图表(价格+涨跌幅)
|
||||
|
||||
```python
|
||||
from openpyxl.chart import LineChart
|
||||
from openpyxl.chart.axis import DateAxis
|
||||
|
||||
# 主图:股价
|
||||
price_chart = LineChart()
|
||||
price_chart.add_data(price_ref, titles_from_data=True)
|
||||
price_chart.y_axis.title = "价格(元)"
|
||||
|
||||
# 副图:涨跌幅(柱状)
|
||||
pct_chart = LineChart()
|
||||
pct_chart.add_data(pct_ref, titles_from_data=True)
|
||||
pct_chart.y_axis.axId = 200
|
||||
pct_chart.y_axis.title = "涨跌幅(%)"
|
||||
|
||||
# 组合
|
||||
price_chart += pct_chart
|
||||
price_chart.y_axis.crosses = "max"
|
||||
```
|
||||
|
||||
#### 坐标轴精修(研报级设置)
|
||||
|
||||
**数值轴(Y轴)刻度设置**
|
||||
```python
|
||||
# 设置Y轴最大最小值(避免自动刻度导致的空白过多)
|
||||
chart.y_axis.scaling.min = 10 # 根据实际数据调整
|
||||
chart.y_axis.scaling.max = 50
|
||||
|
||||
# 设置主要刻度单位
|
||||
chart.y_axis.majorUnit = 5
|
||||
|
||||
# 对于大数值(如成交量),建议数据预处理转换为"亿"或"万"
|
||||
# 并在轴标题中注明单位
|
||||
chart.y_axis.title = "成交量(亿元)"
|
||||
```
|
||||
|
||||
**日期轴(X轴)标签优化**
|
||||
```python
|
||||
from openpyxl.chart.axis import DateAxis
|
||||
from openpyxl.drawing.text import Paragraph, ParagraphProperties, CharacterProperties
|
||||
|
||||
# 设置X轴为日期轴
|
||||
chart.x_axis = DateAxis()
|
||||
chart.x_axis.title = "日期"
|
||||
|
||||
# 标签间隔(避免数据点过多导致重叠)
|
||||
chart.x_axis.tickLblSkip = 5 # 每5个数据点显示一个标签
|
||||
|
||||
# 标签旋转(防止重叠)
|
||||
from copy import deepcopy
|
||||
from openpyxl.drawing.text import RichText
|
||||
rt = RichText(p=[Paragraph(
|
||||
pPr=ParagraphProperties(defRPr=CharacterProperties(sz=900)),
|
||||
endParaRPr=CharacterProperties(sz=900)
|
||||
)])
|
||||
chart.x_axis.txPr = rt
|
||||
```
|
||||
|
||||
**坐标轴数字格式**
|
||||
```python
|
||||
# Y轴数字格式(保留2位小数)
|
||||
chart.y_axis.numFmt = '0.00'
|
||||
|
||||
# 百分比格式
|
||||
chart.y_axis.numFmt = '0.00%'
|
||||
```
|
||||
|
||||
#### 数据标签与参考线(视觉增强)
|
||||
|
||||
**数据标签设置**
|
||||
```python
|
||||
from openpyxl.chart.label import DataLabelList
|
||||
|
||||
# 显示所有数据点的值
|
||||
chart.dataLabels = DataLabelList()
|
||||
chart.dataLabels.showVal = True
|
||||
|
||||
# 仅显示最后一个点(标注最新价)- 需要手动设置
|
||||
from openpyxl.chart.series import DataPoint
|
||||
last_pt = DataPoint(idx=len(values)-1)
|
||||
last_pt.graphicalProperties = GraphicalProperties(solidFill="FF0000")
|
||||
series.data_points = [last_pt]
|
||||
```
|
||||
|
||||
**参考线(均值/目标价)**
|
||||
```python
|
||||
from openpyxl.chart.series import SeriesLabel
|
||||
|
||||
# 添加均值参考线(作为单独的数据系列)
|
||||
mean_value = sum(values) / len(values)
|
||||
ws.append([""] * (len(headers)-1) + ["均值"])
|
||||
for i in range(len(values)):
|
||||
ws.append([""] * (len(headers)-1) + [mean_value])
|
||||
|
||||
mean_ref = Reference(ws, min_col=len(headers), min_row=len(values)+2, max_row=len(values)*2+1)
|
||||
mean_series = Series(mean_ref, title="均值线")
|
||||
chart.series.append(mean_series)
|
||||
```
|
||||
|
||||
#### 财务数据可视化
|
||||
|
||||
**营收利润趋势图**
|
||||
```python
|
||||
# 数据准备
|
||||
ws.append(["报告期", "营业收入", "净利润"])
|
||||
for period, revenue, profit in financial_data:
|
||||
ws.append([period, revenue/1e8, profit/1e8]) # 转换为亿元
|
||||
|
||||
# 组合图表
|
||||
chart = BarChart()
|
||||
chart.type = "col"
|
||||
chart.grouping = "clustered"
|
||||
chart.title = "营业收入与净利润"
|
||||
chart.y_axis.title = "金额(亿元)"
|
||||
|
||||
data = Reference(ws, min_col=2, max_col=3, min_row=1, max_row=len(financial_data)+1)
|
||||
cats = Reference(ws, min_col=1, min_row=2, max_row=len(financial_data)+1)
|
||||
chart.add_data(data, titles_from_data=True)
|
||||
chart.set_categories(cats)
|
||||
|
||||
# 添加数据标签
|
||||
chart.dataLabels = DataLabelList()
|
||||
chart.dataLabels.showVal = True
|
||||
```
|
||||
|
||||
**ROE 柱状图**
|
||||
```python
|
||||
chart = BarChart()
|
||||
chart.type = "col"
|
||||
data = Reference(ws, min_col=2, min_row=1, max_row=5)
|
||||
cats = Reference(ws, min_col=1, min_row=2, max_row=5)
|
||||
chart.add_data(data, titles_from_data=True)
|
||||
chart.set_categories(cats)
|
||||
chart.title = "年度 ROE 对比"
|
||||
```
|
||||
|
||||
#### Excel 文件与 Word 报告配合
|
||||
|
||||
**方案A:Excel 作为附件**
|
||||
```python
|
||||
# 生成带图表的 Excel 文件
|
||||
wb.save(f"{stock_name}_数据分析.xlsx")
|
||||
|
||||
# 在 Word 中引用
|
||||
paragraph = doc.add_paragraph()
|
||||
run = paragraph.add_run(f"详细数据及图表请参见附件《{stock_name}_数据分析.xlsx》")
|
||||
set_font(run, "仿宋", 12)
|
||||
```
|
||||
|
||||
**方案B:图表图片直接插入 Word(推荐)**
|
||||
```python
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.dates as mdates
|
||||
from docx.shared import Inches
|
||||
|
||||
# 使用 Matplotlib 生成符合 GB/T 9704 字体要求的图表
|
||||
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
|
||||
plt.rcParams['axes.unicode_minus'] = False
|
||||
|
||||
fig, ax = plt.subplots(figsize=(8, 4.5))
|
||||
ax.plot(dates, prices, color='#4472C4', linewidth=1.5)
|
||||
ax.set_title('股价走势', fontsize=12, fontweight='bold')
|
||||
ax.set_xlabel('日期', fontsize=10)
|
||||
ax.set_ylabel('价格(元)', fontsize=10)
|
||||
ax.grid(True, alpha=0.3)
|
||||
|
||||
# 保存为图片
|
||||
fig.savefig('chart.png', dpi=150, bbox_inches='tight')
|
||||
plt.close()
|
||||
|
||||
# 插入 Word
|
||||
doc.add_picture('chart.png', width=Inches(6))
|
||||
```
|
||||
|
||||
**方案C:Word 原生图表(功能有限)**
|
||||
```python
|
||||
from docx.chart import Chart
|
||||
from docx.enum.chart import XL_CHART_TYPE
|
||||
|
||||
# 注意:docx.chart 功能较弱,仅支持基础图表类型
|
||||
# 如需复杂图表,建议使用方案A或B
|
||||
```
|
||||
|
||||
#### 图表最佳实践
|
||||
|
||||
1. **数据准备**:使用 pandas 预处理数据,确保格式正确
|
||||
2. **图表选择**:
|
||||
- 时间序列 → 折线图
|
||||
- 对比分析 → 柱状图
|
||||
- 价格+成交量 → 组合图
|
||||
3. **颜色规范**(A股标准):
|
||||
- 上涨:红色(FF0000)
|
||||
- 下跌:绿色(00B050,推荐)或(00AA00)
|
||||
- 中性:蓝色(4472C4)
|
||||
4. **数据单位**:图表标题或轴标题中注明单位(元、%、亿元等)
|
||||
5. **数据来源**:图表下方注明数据来源,如"数据来源:Wind"
|
||||
|
||||
#### 图表设置检查清单
|
||||
|
||||
| 检查项 | 专业要求 | 常见错误 |
|
||||
|--------|----------|----------|
|
||||
| **坐标轴标题** | 必须包含单位,如"价格(元)"、"比例(%)" | 漏写单位或单位不统一 |
|
||||
| **色彩方案** | 涨红跌绿(A股规范),多序列使用高对比度商务色 | 使用 Excel 默认全家桶配色 |
|
||||
| **网格线** | 仅保留水平主网格线,且设为浅灰色/虚线,或直接隐藏 | 粗黑实线网格,遮盖趋势线 |
|
||||
| **字体统一** | 图表内字体需与 Word 正文匹配(建议微软雅黑或等线,9pt) | 默认小号宋体,缩放后模糊 |
|
||||
| **数据源标注** | 图表下方须注明:数据来源:Wind,XX证券研究所 | 来源不明 |
|
||||
| **标签间隔** | X轴数据点密集时需设置 `tickLblSkip` 防止重叠 | 标签重叠难以辨认 |
|
||||
| **数值精度** | Y轴根据数据类型设置合适的小数位(价格2位,百分比1-2位) | 小数位过多或过少 |
|
||||
|
||||
#### 高度封装工具函数
|
||||
|
||||
工具函数已封装在 `scripts/chart_utils.py` 中,使用时直接导入:
|
||||
|
||||
```python
|
||||
import sys
|
||||
sys.path.insert(0, ".claude/skills/financial-report-writing/scripts")
|
||||
from chart_utils import create_research_chart, create_price_volume_chart
|
||||
|
||||
import pandas as pd
|
||||
|
||||
# 准备数据
|
||||
df = pd.DataFrame({
|
||||
"date": ["2024-01", "2024-02", "2024-03"],
|
||||
"close": [10.5, 11.2, 10.8]
|
||||
})
|
||||
|
||||
# 生成股价走势图
|
||||
wb = create_research_chart(
|
||||
df,
|
||||
chart_type="line",
|
||||
title="股价走势",
|
||||
x_col="date",
|
||||
y_cols="close",
|
||||
y_axis_title="价格(元)",
|
||||
show_last_label_only=True,
|
||||
add_mean_line=True,
|
||||
output_path="股价走势.xlsx"
|
||||
)
|
||||
```
|
||||
|
||||
**可用函数**
|
||||
|
||||
| 函数 | 用途 |
|
||||
|------|------|
|
||||
| `create_research_chart()` | 通用图表(折线/柱状/多序列) |
|
||||
| `create_price_volume_chart()` | 股价+成交量双Y轴组合图 |
|
||||
|
||||
**`create_research_chart()` 主要参数**
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `df` | DataFrame数据源 | 必填 |
|
||||
| `chart_type` | 图表类型:"line"/"bar" | "line" |
|
||||
| `x_col` | X轴列名 | 必填 |
|
||||
| `y_cols` | Y轴列名(支持列表) | 必填 |
|
||||
| `y_axis_title` | Y轴标题(建议带单位) | "" |
|
||||
| `y_format` | 数字格式 | "0.00" |
|
||||
| `show_last_label_only` | 仅显示最后一个标签 | False |
|
||||
| `add_mean_line` | 添加均值参考线 | False |
|
||||
| `color_up/color_down` | 涨跌颜色 | FF0000/00B050 |
|
||||
| `tick_skip` | X轴标签间隔 | 5 |
|
||||
|
||||
完整函数定义和源码见:`scripts/chart_utils.py`
|
||||
|
||||
## 相关工具
|
||||
|
||||
- **WindPy**:Wind金融终端Python API
|
||||
- **python-docx**:Word文档操作库
|
||||
- **openpyxl**:Excel文件操作,支持原生图表
|
||||
- **pandas**:数据处理和分析
|
||||
- **numpy**:数值计算
|
||||
- **GB/T 9704-2012**:党政机关公文格式标准
|
||||
|
||||
## 参考资源
|
||||
|
||||
- WindPy SDK官方文档
|
||||
- python-docx API文档
|
||||
- GB/T 9704-2012国家公文格式标准
|
||||
- 券商研究报告格式规范
|
||||
337
financial-report-writing/references/best_practices.md
Normal file
337
financial-report-writing/references/best_practices.md
Normal file
@ -0,0 +1,337 @@
|
||||
# 金融研究报告最佳实践
|
||||
|
||||
## 目录
|
||||
1. [数据获取最佳实践](#数据获取最佳实践)
|
||||
2. [分析框架](#分析框架)
|
||||
3. [报告撰写规范](#报告撰写规范)
|
||||
4. [常见陷阱](#常见陷阱)
|
||||
5. [质量控制检查清单](#质量控制检查清单)
|
||||
6. [案例分析](#案例分析)
|
||||
|
||||
---
|
||||
|
||||
## 数据获取最佳实践
|
||||
|
||||
### WindPy 连接管理
|
||||
```python
|
||||
# 推荐做法:使用上下文管理器
|
||||
with WindDataFetcher() as fetcher:
|
||||
df = fetcher.get_daily_data(stock_code, start_date, end_date, fields)
|
||||
# 自动处理连接关闭
|
||||
|
||||
# 不推荐:手动管理连接
|
||||
w.start()
|
||||
# ... 代码 ...
|
||||
w.stop() # 容易遗漏
|
||||
```
|
||||
|
||||
### 日期处理
|
||||
- 始终使用标准格式 `YYYY-MM-DD`
|
||||
- 获取周线数据时,使用 `resample('W')` 而非手动计算
|
||||
- 处理交易日历,避免非交易日数据的干扰
|
||||
|
||||
### 数据校验
|
||||
```python
|
||||
def validate_data(df, required_columns):
|
||||
"""数据质量检查"""
|
||||
# 检查空值
|
||||
if df.isnull().any().any():
|
||||
print(f"警告:数据包含空值")
|
||||
df = df.dropna()
|
||||
|
||||
# 检查异常值
|
||||
for col in ['close', 'high', 'low']:
|
||||
if col in df.columns:
|
||||
if (df[col] <= 0).any():
|
||||
raise ValueError(f"{col} 包含非正值")
|
||||
|
||||
# 检查必需字段
|
||||
missing_cols = set(required_columns) - set(df.columns)
|
||||
if missing_cols:
|
||||
raise ValueError(f"缺少必需字段: {missing_cols}")
|
||||
|
||||
return df
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 分析框架
|
||||
|
||||
### 基本面分析维度
|
||||
1. **盈利能力**
|
||||
- ROE(净资产收益率):\>15% 优秀,10-15% 良好
|
||||
- ROA(总资产收益率):\>8% 优秀
|
||||
- 毛利率:行业对比
|
||||
- 净利率:趋势分析
|
||||
|
||||
2. **成长能力**
|
||||
- 营收增长率
|
||||
- 净利润增长率
|
||||
- 复合年均增长率(CAGR)
|
||||
|
||||
3. **运营效率**
|
||||
- 存货周转率
|
||||
- 应收账款周转率
|
||||
- 总资产周转率
|
||||
|
||||
4. **偿债能力**
|
||||
- 资产负债率:<70% 安全
|
||||
- 流动比率:>1.5 较好
|
||||
- 速动比率:>1 较好
|
||||
|
||||
5. **现金流**
|
||||
- 经营现金流净额
|
||||
- 自由现金流
|
||||
- 现金流/净利润 比率
|
||||
|
||||
### 技术面分析维度
|
||||
1. **趋势判断**
|
||||
- 均线系统(MA5/10/20/60)
|
||||
- 高低点连线
|
||||
- 趋势线斜率
|
||||
|
||||
2. **支撑阻力**
|
||||
- 历史高低点
|
||||
- 密集成交区
|
||||
- 均线支撑
|
||||
|
||||
3. **波动特征**
|
||||
- 历史波动率
|
||||
- 振幅统计
|
||||
- 布林带宽度
|
||||
|
||||
### 估值分析框架
|
||||
1. **相对估值**
|
||||
- PE(市盈率):与历史/行业对比
|
||||
- PB(市净率):适用于重资产行业
|
||||
- PS(市销率):适用于成长型企业
|
||||
|
||||
2. **绝对估值**
|
||||
- DCF模型
|
||||
- DDM模型
|
||||
|
||||
---
|
||||
|
||||
## 报告撰写规范
|
||||
|
||||
### 标题层级
|
||||
```
|
||||
一级标题:黑体三号(16pt),如"一、投资评级"
|
||||
二级标题:黑体三号(16pt),如"(一)基本面分析"
|
||||
三级标题:黑体小四(12pt),如"1. 盈利能力"
|
||||
```
|
||||
|
||||
### 正文写作原则
|
||||
1. **数据先行**:先陈述事实数据,后给出分析结论
|
||||
2. **逻辑清晰**:使用"首先...其次...最后..."等逻辑连接词
|
||||
3. **客观中立**:避免使用"必然"、"一定"等绝对化表述
|
||||
4. **风险提示**:每个投资建议都应附带风险提示
|
||||
|
||||
### 表格规范
|
||||
- **表头**:黑体小四(12pt),居中
|
||||
- **数据**:仿宋小四(12pt),居中
|
||||
- **对齐**:日期左对齐,数值右对齐
|
||||
- **单位**:在表头或表注中明确标注
|
||||
|
||||
### 数字格式
|
||||
```python
|
||||
# 价格:保留2位小数
|
||||
price_str = f"{price:.2f}元"
|
||||
|
||||
# 百分比:保留2位小数,带%号
|
||||
pct_str = f"{pct:.2f}%"
|
||||
|
||||
# 大数字:使用万/亿单位
|
||||
amount_wan = f"{amount / 10000:.0f}万元"
|
||||
amount_yi = f"{amount / 100000000:.2f}亿元"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见陷阱
|
||||
|
||||
### 数据陷阱
|
||||
1. **前复权与后复权混淆**
|
||||
- 计算收益率时必须使用前复权数据
|
||||
- 查看历史价格时注意复权方式
|
||||
|
||||
2. **停牌数据处理**
|
||||
- 停牌期间数据可能为空
|
||||
- 成交量为0需要特殊处理
|
||||
|
||||
3. **财报季节效应**
|
||||
- Q4数据通常包含全年调整
|
||||
- 同比计算时注意去年同期特殊情况
|
||||
|
||||
### 分析陷阱
|
||||
1. **幸存者偏差**
|
||||
- 只分析现有公司,忽视已退市公司
|
||||
- 行业对比时使用可比较样本
|
||||
|
||||
2. **过度拟合**
|
||||
- 避免基于历史数据过度优化参数
|
||||
- 使用滚动回测验证策略
|
||||
|
||||
3. **相关性不等于因果性**
|
||||
- 相关系数高不代表因果关系
|
||||
- 考虑第三变量的影响
|
||||
|
||||
### 报告陷阱
|
||||
1. **目标价计算过于乐观**
|
||||
- 考虑多种情景(乐观/中性/悲观)
|
||||
- 明确目标价有效期
|
||||
|
||||
2. **忽视流动性风险**
|
||||
- 小市值股票注意流动性
|
||||
- 考虑交易成本影响
|
||||
|
||||
3. **风险提示流于形式**
|
||||
- 针对具体股票的风险要具体化
|
||||
- 量化风险等级
|
||||
|
||||
---
|
||||
|
||||
## 质量控制检查清单
|
||||
|
||||
### 数据检查
|
||||
- [ ] 数据来源明确标注
|
||||
- [ ] 数据时间范围正确
|
||||
- [ ] 无缺失值或已说明处理方式
|
||||
- [ ] 价格数据使用正确复权方式
|
||||
- [ ] 财务数据使用最新财报
|
||||
|
||||
### 分析检查
|
||||
- [ ] ROE、PE等关键指标计算正确
|
||||
- [ ] 涨跌幅计算准确
|
||||
- [ ] 均线计算无未来函数
|
||||
- [ ] 支撑阻力位基于实际高低点
|
||||
|
||||
### 格式检查
|
||||
- [ ] 页面边距:37/35/28/26mm
|
||||
- [ ] 标题使用黑体三号
|
||||
- [ ] 正文使用仿宋四号
|
||||
- [ ] 表格使用小四
|
||||
- [ ] 三线表格式正确
|
||||
- [ ] 页脚页码格式正确
|
||||
|
||||
### 内容检查
|
||||
- [ ] 核心提要包含4个要点
|
||||
- [ ] 投资评级明确
|
||||
- [ ] 目标价格合理且有依据
|
||||
- [ ] 风险提示具体充分
|
||||
- [ ] 无明显错别字
|
||||
- [ ] 语句通顺无歧义
|
||||
|
||||
---
|
||||
|
||||
## 案例分析
|
||||
|
||||
### 案例1:成长股分析报告
|
||||
**股票特征**:高ROE、高PE、快速增长
|
||||
|
||||
**分析重点**:
|
||||
- 关注营收和利润增长持续性
|
||||
- 分析行业空间和竞争格局
|
||||
- 评估估值合理性(PEG指标)
|
||||
|
||||
**报告结构**:
|
||||
```
|
||||
核心提要
|
||||
├── 高速增长:营收/利润CAGR > 30%
|
||||
├── 行业龙头:市场份额持续提升
|
||||
├── 估值偏高:PE高于行业平均
|
||||
└── 给予"增持"评级
|
||||
|
||||
一、投资评级
|
||||
├── 增持
|
||||
├── 目标价
|
||||
└── 风险提示:增速放缓风险
|
||||
|
||||
二、核心观点
|
||||
├── (一)基本面分析
|
||||
│ ├── 1. 成长性:营收增长35%,净利润增长42%
|
||||
│ ├── 2. 盈利能力:ROE 22%,毛利率58%
|
||||
│ └── 3. 估值:PE 45倍,PEG 1.1
|
||||
├── (二)技术面分析
|
||||
│ ├── 1. 趋势:长期上升通道
|
||||
│ ├── 2. 支撑:MA60支撑有效
|
||||
│ └── 3. 成交量:放量突破
|
||||
└── (三)投资建议
|
||||
└── 关注回调至支撑位买入机会
|
||||
```
|
||||
|
||||
### 案例2:价值股分析报告
|
||||
**股票特征**:低PE、高股息、稳定增长
|
||||
|
||||
**分析重点**:
|
||||
- 关注股息率和分红稳定性
|
||||
- 分析护城河和竞争优势
|
||||
- 评估安全边际
|
||||
|
||||
**核心指标对比**:
|
||||
| 指标 | 该公司 | 行业平均 | 评价 |
|
||||
|------|--------|----------|------|
|
||||
| PE TTM | 8.5 | 15.2 | 低估 |
|
||||
| PB LF | 1.2 | 2.1 | 低估 |
|
||||
| 股息率 | 5.2% | 2.8% | 优秀 |
|
||||
| ROE | 14.5% | 12.3% | 良好 |
|
||||
|
||||
### 案例3:周期股分析报告
|
||||
**股票特征**:业绩波动大、与宏观经济高度相关
|
||||
|
||||
**分析重点**:
|
||||
- 关注行业景气度周期
|
||||
- 分析产能利用率和库存
|
||||
- 判断周期位置(顶部/底部/上升/下降)
|
||||
|
||||
**风险提示模板**:
|
||||
```
|
||||
风险提示:
|
||||
1. 宏观经济下行风险
|
||||
2. 行业产能过剩风险
|
||||
3. 原材料价格波动风险
|
||||
4. 下游需求不及预期风险
|
||||
5. 政策监管变化风险
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 附录:常用 Wind 字段速查
|
||||
|
||||
### 价格字段
|
||||
| 字段 | 说明 | 用途 |
|
||||
|------|------|------|
|
||||
| close | 收盘价 | 技术分析基础 |
|
||||
| open | 开盘价 | 日K线 |
|
||||
| high | 最高价 | 支撑阻力分析 |
|
||||
| low | 最低价 | 支撑阻力分析 |
|
||||
| volume | 成交量 | 量价分析 |
|
||||
| amt | 成交额 | 流动性分析 |
|
||||
| pct_chg | 涨跌幅 | 收益率计算 |
|
||||
|
||||
### 技术指标字段
|
||||
| 字段 | 说明 | 用途 |
|
||||
|------|------|------|
|
||||
| ma5/ma10/ma20/ma60 | 均线 | 趋势判断 |
|
||||
| pe_ttm | 市盈率TTM | 估值分析 |
|
||||
| pb_lf | 市净率LF | 估值分析 |
|
||||
| ps_ttm | 市销率TTM | 估值分析 |
|
||||
| turnover | 换手率 | 活跃度分析 |
|
||||
|
||||
### 财务字段
|
||||
| 字段 | 说明 | 用途 |
|
||||
|------|------|------|
|
||||
| roe_wgt | 加权ROE | 盈利能力 |
|
||||
| roa2 | 总资产报酬率 | 盈利能力 |
|
||||
| grossprofitmargin | 毛利率 | 盈利能力 |
|
||||
| netprofitmargin | 净利率 | 盈利能力 |
|
||||
| debt_to_assets | 资产负债率 | 偿债能力 |
|
||||
| current_ratio | 流动比率 | 偿债能力 |
|
||||
|
||||
---
|
||||
|
||||
## 版本记录
|
||||
|
||||
| 版本 | 日期 | 更新内容 |
|
||||
|------|------|----------|
|
||||
| 1.0 | 2026-02-06 | 初始版本,包含基础最佳实践 |
|
||||
469
financial-report-writing/scripts/analysis_engine.py
Normal file
469
financial-report-writing/scripts/analysis_engine.py
Normal file
@ -0,0 +1,469 @@
|
||||
"""
|
||||
投资分析引擎
|
||||
提供基本面分析、技术面分析和估值分析功能
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class InvestmentRating(Enum):
|
||||
"""投资评级"""
|
||||
BUY = "买入"
|
||||
ACCUMULATE = "增持"
|
||||
HOLD = "持有"
|
||||
REDUCE = "减持"
|
||||
SELL = "卖出"
|
||||
|
||||
|
||||
class RiskLevel(Enum):
|
||||
"""风险等级"""
|
||||
LOW = "低风险"
|
||||
LOW_MEDIUM = "中低风险"
|
||||
MEDIUM = "中等风险"
|
||||
MEDIUM_HIGH = "中高风险"
|
||||
HIGH = "高风险"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TechnicalAnalysis:
|
||||
"""技术分析结果"""
|
||||
trend: str # 趋势判断
|
||||
support_level: float # 支撑位
|
||||
resistance_level: float # 阻力位
|
||||
volatility: float # 波动率
|
||||
ma_status: str # 均线状态
|
||||
technical_summary: str # 技术总结
|
||||
|
||||
|
||||
@dataclass
|
||||
class FundamentalAnalysis:
|
||||
"""基本面分析结果"""
|
||||
roe: float # 净资产收益率
|
||||
roe_comment: str # ROE评价
|
||||
pe_ttm: float # 市盈率TTM
|
||||
pb_lf: float # 市净率LF
|
||||
valuation_comment: str # 估值评价
|
||||
fundamental_summary: str # 基本面总结
|
||||
|
||||
|
||||
@dataclass
|
||||
class InvestmentRecommendation:
|
||||
"""投资建议"""
|
||||
rating: InvestmentRating
|
||||
target_price: float
|
||||
risk_level: RiskLevel
|
||||
recommendation_text: str
|
||||
key_factors: List[str]
|
||||
|
||||
|
||||
class AnalysisEngine:
|
||||
"""
|
||||
投资分析引擎
|
||||
"""
|
||||
|
||||
# ROE评价标准
|
||||
ROE_THRESHOLDS = {
|
||||
'excellent': 20,
|
||||
'good': 15,
|
||||
'average': 10,
|
||||
'poor': 5
|
||||
}
|
||||
|
||||
# PE评价标准(参考值,实际应根据行业调整)
|
||||
PE_THRESHOLDS = {
|
||||
'undervalued': 15,
|
||||
'fair': 25,
|
||||
'overvalued': 35
|
||||
}
|
||||
|
||||
# PB评价标准
|
||||
PB_THRESHOLDS = {
|
||||
'undervalued': 1.5,
|
||||
'fair': 3.0,
|
||||
'overvalued': 5.0
|
||||
}
|
||||
|
||||
def __init__(self, price_df: pd.DataFrame, financial_data: Dict):
|
||||
"""
|
||||
初始化分析引擎
|
||||
|
||||
Args:
|
||||
price_df: 价格数据DataFrame
|
||||
financial_data: 财务数据字典
|
||||
"""
|
||||
self.price_df = price_df.copy()
|
||||
self.financial_data = financial_data
|
||||
|
||||
# 计算涨跌幅和收益率
|
||||
if 'pct_chg' not in self.price_df.columns and 'close' in self.price_df.columns:
|
||||
self.price_df['pct_chg'] = self.price_df['close'].pct_change() * 100
|
||||
|
||||
def analyze_technical(self) -> TechnicalAnalysis:
|
||||
"""
|
||||
技术分析
|
||||
|
||||
Returns:
|
||||
TechnicalAnalysis对象
|
||||
"""
|
||||
df = self.price_df
|
||||
|
||||
# 计算均线
|
||||
if 'ma5' not in df.columns:
|
||||
df['ma5'] = df['close'].rolling(window=5).mean()
|
||||
if 'ma10' not in df.columns:
|
||||
df['ma10'] = df['close'].rolling(window=10).mean()
|
||||
if 'ma20' not in df.columns:
|
||||
df['ma20'] = df['close'].rolling(window=20).mean()
|
||||
|
||||
latest = df.iloc[-1]
|
||||
|
||||
# 趋势判断
|
||||
if latest['close'] > latest['ma5'] > latest['ma10'] > latest['ma20']:
|
||||
trend = "强势上涨"
|
||||
elif latest['close'] > latest['ma5'] > latest['ma10']:
|
||||
trend = "震荡上行"
|
||||
elif latest['close'] < latest['ma5'] < latest['ma10'] < latest['ma20']:
|
||||
trend = "强势下跌"
|
||||
elif latest['close'] < latest['ma5'] < latest['ma10']:
|
||||
trend = "震荡下行"
|
||||
else:
|
||||
trend = "震荡整理"
|
||||
|
||||
# 计算支撑位和阻力位(基于近期高低点)
|
||||
recent_data = df.tail(20)
|
||||
support_level = recent_data['low'].min()
|
||||
resistance_level = recent_data['high'].max()
|
||||
|
||||
# 计算波动率
|
||||
returns = df['close'].pct_change().dropna()
|
||||
volatility = returns.std() * np.sqrt(52) * 100 # 年化波动率
|
||||
|
||||
# 均线状态
|
||||
if latest['ma5'] > latest['ma20']:
|
||||
ma_status = "多头排列"
|
||||
elif latest['ma5'] < latest['ma20']:
|
||||
ma_status = "空头排列"
|
||||
else:
|
||||
ma_status = "均线粘合"
|
||||
|
||||
# 技术总结
|
||||
if trend in ["强势上涨", "震荡上行"] and ma_status == "多头排列":
|
||||
technical_summary = "技术面呈上升趋势,建议关注回调买入机会。"
|
||||
elif trend in ["强势下跌", "震荡下行"]:
|
||||
technical_summary = "技术面呈下降趋势,建议观望或减仓。"
|
||||
else:
|
||||
technical_summary = "技术面呈震荡格局,建议高抛低吸。"
|
||||
|
||||
return TechnicalAnalysis(
|
||||
trend=trend,
|
||||
support_level=round(support_level, 2),
|
||||
resistance_level=round(resistance_level, 2),
|
||||
volatility=round(volatility, 2),
|
||||
ma_status=ma_status,
|
||||
technical_summary=technical_summary
|
||||
)
|
||||
|
||||
def analyze_fundamental(self) -> FundamentalAnalysis:
|
||||
"""
|
||||
基本面分析
|
||||
|
||||
Returns:
|
||||
FundamentalAnalysis对象
|
||||
"""
|
||||
# 获取ROE
|
||||
roe = self.financial_data.get('roe_wgt', 0) or self.financial_data.get('roe', 0)
|
||||
|
||||
# ROE评价
|
||||
if roe >= self.ROE_THRESHOLDS['excellent']:
|
||||
roe_comment = "ROE表现优异,公司盈利能力强劲"
|
||||
elif roe >= self.ROE_THRESHOLDS['good']:
|
||||
roe_comment = "ROE表现良好,盈利能力较强"
|
||||
elif roe >= self.ROE_THRESHOLDS['average']:
|
||||
roe_comment = "ROE处于行业平均水平"
|
||||
else:
|
||||
roe_comment = "ROE偏低,需关注盈利能力改善"
|
||||
|
||||
# 获取PE和PB
|
||||
pe_ttm = self.financial_data.get('pe_ttm', 0) or self.price_df['pe_ttm'].iloc[-1] if 'pe_ttm' in self.price_df.columns else 0
|
||||
pb_lf = self.financial_data.get('pb_lf', 0) or self.price_df['pb_lf'].iloc[-1] if 'pb_lf' in self.price_df.columns else 0
|
||||
|
||||
# 估值评价
|
||||
if pe_ttm <= self.PE_THRESHOLDS['undervalued'] and pb_lf <= self.PB_THRESHOLDS['undervalued']:
|
||||
valuation_comment = "估值处于历史低位,具备较高安全边际"
|
||||
elif pe_ttm <= self.PE_THRESHOLDS['fair']:
|
||||
valuation_comment = "估值处于合理区间"
|
||||
elif pe_ttm <= self.PE_THRESHOLDS['overvalued']:
|
||||
valuation_comment = "估值略高,需关注业绩成长性"
|
||||
else:
|
||||
valuation_comment = "估值偏高,注意追高风险"
|
||||
|
||||
# 基本面总结
|
||||
if roe >= self.ROE_THRESHOLDS['good'] and pe_ttm <= self.PE_THRESHOLDS['fair']:
|
||||
fundamental_summary = "公司基本面健康,盈利能力强,估值合理,具备投资价值。"
|
||||
elif roe >= self.ROE_THRESHOLDS['good']:
|
||||
fundamental_summary = "公司盈利能力较强,但估值偏高,建议等待回调机会。"
|
||||
else:
|
||||
fundamental_summary = "公司基本面一般,建议谨慎观望。"
|
||||
|
||||
return FundamentalAnalysis(
|
||||
roe=round(roe, 2) if roe else 0,
|
||||
roe_comment=roe_comment,
|
||||
pe_ttm=round(pe_ttm, 2) if pe_ttm else 0,
|
||||
pb_lf=round(pb_lf, 2) if pb_lf else 0,
|
||||
valuation_comment=valuation_comment,
|
||||
fundamental_summary=fundamental_summary
|
||||
)
|
||||
|
||||
def generate_recommendation(self,
|
||||
current_price: float,
|
||||
fundamental: FundamentalAnalysis = None,
|
||||
technical: TechnicalAnalysis = None) -> InvestmentRecommendation:
|
||||
"""
|
||||
生成投资建议
|
||||
|
||||
Args:
|
||||
current_price: 当前价格
|
||||
fundamental: 基本面分析结果
|
||||
technical: 技术面分析结果
|
||||
|
||||
Returns:
|
||||
InvestmentRecommendation对象
|
||||
"""
|
||||
if fundamental is None:
|
||||
fundamental = self.analyze_fundamental()
|
||||
if technical is None:
|
||||
technical = self.analyze_technical()
|
||||
|
||||
# 综合评分
|
||||
score = 0
|
||||
|
||||
# 基本面评分
|
||||
if fundamental.roe >= self.ROE_THRESHOLDS['excellent']:
|
||||
score += 3
|
||||
elif fundamental.roe >= self.ROE_THRESHOLDS['good']:
|
||||
score += 2
|
||||
elif fundamental.roe >= self.ROE_THRESHOLDS['average']:
|
||||
score += 1
|
||||
|
||||
# 估值评分
|
||||
if fundamental.pe_ttm <= self.PE_THRESHOLDS['undervalued']:
|
||||
score += 3
|
||||
elif fundamental.pe_ttm <= self.PE_THRESHOLDS['fair']:
|
||||
score += 2
|
||||
elif fundamental.pe_ttm <= self.PE_THRESHOLDS['overvalued']:
|
||||
score += 1
|
||||
|
||||
# 技术面评分
|
||||
if technical.trend in ["强势上涨", "震荡上行"]:
|
||||
score += 2
|
||||
elif technical.trend == "震荡整理":
|
||||
score += 1
|
||||
|
||||
# 风险等级
|
||||
if technical.volatility > 50:
|
||||
risk_level = RiskLevel.HIGH
|
||||
elif technical.volatility > 35:
|
||||
risk_level = RiskLevel.MEDIUM_HIGH
|
||||
elif technical.volatility > 25:
|
||||
risk_level = RiskLevel.MEDIUM
|
||||
elif technical.volatility > 15:
|
||||
risk_level = RiskLevel.LOW_MEDIUM
|
||||
else:
|
||||
risk_level = RiskLevel.LOW
|
||||
|
||||
# 目标价格计算(基于PE和支撑阻力)
|
||||
target_price_pe = current_price * (self.PE_THRESHOLDS['fair'] / max(fundamental.pe_ttm, 1))
|
||||
target_price_resistance = technical.resistance_level * 1.1
|
||||
target_price = round((target_price_pe + target_price_resistance) / 2, 2)
|
||||
|
||||
# 投资建议
|
||||
key_factors = []
|
||||
|
||||
if score >= 6:
|
||||
rating = InvestmentRating.BUY
|
||||
recommendation_text = f"综合基本面和技术面分析,该股投资价值较高。目标价{target_price}元,建议逢低买入。"
|
||||
key_factors.append("盈利能力强劲,ROE表现优异")
|
||||
elif score >= 4:
|
||||
rating = InvestmentRating.ACCUMULATE
|
||||
recommendation_text = f"公司基本面良好,建议关注回调机会逐步建仓。目标价{target_price}元。"
|
||||
key_factors.append("基本面稳健,估值合理")
|
||||
elif score >= 2:
|
||||
rating = InvestmentRating.HOLD
|
||||
recommendation_text = "公司基本面一般,当前估值合理,建议持有观望。"
|
||||
key_factors.append("基本面中性,估值合理")
|
||||
else:
|
||||
rating = InvestmentRating.REDUCE
|
||||
recommendation_text = "公司基本面较弱或估值偏高,建议谨慎或减仓。"
|
||||
key_factors.append("基本面一般或估值偏高")
|
||||
|
||||
if technical.trend in ["强势上涨", "震荡上行"]:
|
||||
key_factors.append("技术面呈上升趋势")
|
||||
elif technical.trend in ["强势下跌", "震荡下行"]:
|
||||
key_factors.append("技术面呈下降趋势")
|
||||
|
||||
if fundamental.pe_ttm <= self.PE_THRESHOLDS['undervalued']:
|
||||
key_factors.append("估值处于历史低位")
|
||||
|
||||
return InvestmentRecommendation(
|
||||
rating=rating,
|
||||
target_price=target_price,
|
||||
risk_level=risk_level,
|
||||
recommendation_text=recommendation_text,
|
||||
key_factors=key_factors
|
||||
)
|
||||
|
||||
def generate_core_summary(self,
|
||||
fundamental: FundamentalAnalysis = None,
|
||||
technical: TechnicalAnalysis = None,
|
||||
recommendation: InvestmentRecommendation = None) -> List[str]:
|
||||
"""
|
||||
生成核心提要要点
|
||||
|
||||
Returns:
|
||||
要点列表
|
||||
"""
|
||||
if fundamental is None:
|
||||
fundamental = self.analyze_fundamental()
|
||||
if technical is None:
|
||||
technical = self.analyze_technical()
|
||||
if recommendation is None:
|
||||
# 获取最新价格
|
||||
latest_price = self.price_df['close'].iloc[-1] if 'close' in self.price_df.columns else 0
|
||||
recommendation = self.generate_recommendation(latest_price, fundamental, technical)
|
||||
|
||||
summary_points = []
|
||||
|
||||
# ROE要点
|
||||
if fundamental.roe > 0:
|
||||
summary_points.append(
|
||||
f"该公司最新净资产收益率(ROE)为{fundamental.roe}%,{fundamental.roe_comment}"
|
||||
)
|
||||
|
||||
# 估值要点
|
||||
if fundamental.pe_ttm > 0:
|
||||
summary_points.append(
|
||||
f"当前市盈率(TTM)为{fundamental.pe_ttm}倍,{fundamental.valuation_comment}"
|
||||
)
|
||||
|
||||
# 技术面要点
|
||||
summary_points.append(
|
||||
f"技术面显示股价呈现{technical.trend}趋势,{technical.technical_summary}"
|
||||
)
|
||||
|
||||
# 投资建议要点
|
||||
summary_points.append(
|
||||
f"给予\"{recommendation.rating.value}\"评级,目标价格{recommendation.target_price}元,风险等级为{recommendation.risk_level.value}"
|
||||
)
|
||||
|
||||
return summary_points
|
||||
|
||||
def get_price_statistics(self) -> Dict:
|
||||
"""
|
||||
获取价格统计数据
|
||||
|
||||
Returns:
|
||||
统计数据字典
|
||||
"""
|
||||
df = self.price_df
|
||||
|
||||
if len(df) == 0:
|
||||
return {}
|
||||
|
||||
return {
|
||||
'latest_price': round(df['close'].iloc[-1], 2),
|
||||
'highest_price': round(df['high'].max(), 2),
|
||||
'lowest_price': round(df['low'].min(), 2),
|
||||
'avg_price': round(df['close'].mean(), 2),
|
||||
'total_change': round(((df['close'].iloc[-1] / df['close'].iloc[0]) - 1) * 100, 2),
|
||||
'max_daily_gain': round(df['pct_chg'].max(), 2) if 'pct_chg' in df.columns else 0,
|
||||
'max_daily_loss': round(df['pct_chg'].min(), 2) if 'pct_chg' in df.columns else 0,
|
||||
'volatility': round(df['close'].pct_change().std() * np.sqrt(52) * 100, 2)
|
||||
}
|
||||
|
||||
|
||||
def perform_comprehensive_analysis(price_df: pd.DataFrame,
|
||||
financial_data: Dict,
|
||||
current_price: float = None) -> Dict:
|
||||
"""
|
||||
执行综合分析并返回所有结果
|
||||
|
||||
Args:
|
||||
price_df: 价格数据
|
||||
financial_data: 财务数据
|
||||
current_price: 当前价格(可选)
|
||||
|
||||
Returns:
|
||||
包含所有分析结果的字典
|
||||
"""
|
||||
engine = AnalysisEngine(price_df, financial_data)
|
||||
|
||||
if current_price is None:
|
||||
current_price = price_df['close'].iloc[-1]
|
||||
|
||||
technical = engine.analyze_technical()
|
||||
fundamental = engine.analyze_fundamental()
|
||||
recommendation = engine.generate_recommendation(current_price, fundamental, technical)
|
||||
summary_points = engine.generate_core_summary(fundamental, technical, recommendation)
|
||||
price_stats = engine.get_price_statistics()
|
||||
|
||||
return {
|
||||
'technical': technical,
|
||||
'fundamental': fundamental,
|
||||
'recommendation': recommendation,
|
||||
'summary_points': summary_points,
|
||||
'price_stats': price_stats
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 测试代码
|
||||
import numpy as np
|
||||
|
||||
# 创建模拟数据
|
||||
dates = pd.date_range(start='2025-01-01', periods=60, freq='W')
|
||||
np.random.seed(42)
|
||||
prices = 30 + np.cumsum(np.random.randn(60) * 0.5)
|
||||
|
||||
price_df = pd.DataFrame({
|
||||
'date': dates,
|
||||
'open': prices * 0.98,
|
||||
'high': prices * 1.03,
|
||||
'low': prices * 0.97,
|
||||
'close': prices,
|
||||
'volume': np.random.randint(1000000, 5000000, 60),
|
||||
'pe_ttm': 25 + np.random.randn(60) * 2,
|
||||
'pb_lf': 3 + np.random.randn(60) * 0.2
|
||||
})
|
||||
|
||||
financial_data = {
|
||||
'roe_wgt': 16.5,
|
||||
'pe_ttm': 25.3,
|
||||
'pb_lf': 3.1,
|
||||
'sec_name': '测试公司'
|
||||
}
|
||||
|
||||
# 执行分析
|
||||
results = perform_comprehensive_analysis(price_df, financial_data)
|
||||
|
||||
print("=== 技术分析 ===")
|
||||
print(f"趋势: {results['technical'].trend}")
|
||||
print(f"支撑位: {results['technical'].support_level}")
|
||||
print(f"阻力位: {results['technical'].resistance_level}")
|
||||
|
||||
print("\n=== 基本面分析 ===")
|
||||
print(f"ROE: {results['fundamental'].roe}%")
|
||||
print(f"PE TTM: {results['fundamental'].pe_ttm}")
|
||||
print(f"PB LF: {results['fundamental'].pb_lf}")
|
||||
|
||||
print("\n=== 投资建议 ===")
|
||||
print(f"评级: {results['recommendation'].rating.value}")
|
||||
print(f"目标价: {results['recommendation'].target_price}元")
|
||||
print(f"风险等级: {results['recommendation'].risk_level.value}")
|
||||
|
||||
print("\n=== 核心提要 ===")
|
||||
for i, point in enumerate(results['summary_points'], 1):
|
||||
print(f"{i}. {point}")
|
||||
311
financial-report-writing/scripts/chart_utils.py
Normal file
311
financial-report-writing/scripts/chart_utils.py
Normal file
@ -0,0 +1,311 @@
|
||||
"""
|
||||
金融研究报告图表工具函数
|
||||
============================
|
||||
|
||||
提供符合研报规范的高度封装图表生成函数。
|
||||
|
||||
Usage:
|
||||
from chart_utils import create_research_chart, create_price_volume_chart
|
||||
|
||||
wb = create_research_chart(
|
||||
df,
|
||||
chart_type="line",
|
||||
title="股价走势",
|
||||
x_col="date",
|
||||
y_cols="close",
|
||||
output_path="report.xlsx"
|
||||
)
|
||||
"""
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.chart import LineChart, BarChart, Reference
|
||||
from openpyxl.chart.axis import DateAxis
|
||||
from openpyxl.chart.label import DataLabelList
|
||||
from openpyxl.chart.series import DataPoint, Series
|
||||
from openpyxl.chart.shapes import GraphicalProperties
|
||||
import pandas as pd
|
||||
from typing import Optional, List, Union
|
||||
|
||||
|
||||
def create_research_chart(
|
||||
df: pd.DataFrame,
|
||||
chart_type: str = "line",
|
||||
title: str = "",
|
||||
x_col: str = None,
|
||||
y_cols: Union[str, List[str]] = None,
|
||||
y_axis_title: str = "",
|
||||
x_axis_title: str = "",
|
||||
y_format: str = "0.00",
|
||||
show_data_labels: bool = False,
|
||||
show_last_label_only: bool = False,
|
||||
add_mean_line: bool = False,
|
||||
color_up: str = "FF0000",
|
||||
color_down: str = "00B050",
|
||||
tick_skip: int = 5,
|
||||
height: int = 10,
|
||||
width: int = 20,
|
||||
output_path: str = None
|
||||
) -> Workbook:
|
||||
"""
|
||||
生成符合研报规范的 Excel 图表
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
df : pd.DataFrame
|
||||
数据源,必须包含 x_col 和 y_cols 指定的列
|
||||
chart_type : str
|
||||
图表类型:"line"(折线图), "bar"(柱状图)
|
||||
title : str
|
||||
图表标题
|
||||
x_col : str
|
||||
X轴数据列名(日期/类别)
|
||||
y_cols : str or List[str]
|
||||
Y轴数据列名,支持多序列
|
||||
y_axis_title : str
|
||||
Y轴标题(建议带单位,如"价格(元)")
|
||||
x_axis_title : str
|
||||
X轴标题
|
||||
y_format : str
|
||||
Y轴数字格式,默认"0.00",百分比用"0.00%"
|
||||
show_data_labels : bool
|
||||
是否显示所有数据标签
|
||||
show_last_label_only : bool
|
||||
是否仅显示最后一个数据点的标签(标注最新值)
|
||||
add_mean_line : bool
|
||||
是否添加均值参考线
|
||||
color_up : str
|
||||
上涨/正值颜色(默认红色 FF0000)
|
||||
color_down : str
|
||||
下跌/负值颜色(默认绿色 00B050)
|
||||
tick_skip : int
|
||||
X轴标签间隔(防止重叠)
|
||||
height : int
|
||||
图表高度(厘米)
|
||||
width : int
|
||||
图表宽度(厘米)
|
||||
output_path : str
|
||||
输出文件路径,None则返回Workbook对象
|
||||
|
||||
Returns:
|
||||
--------
|
||||
Workbook : openpyxl Workbook对象
|
||||
|
||||
Examples:
|
||||
---------
|
||||
>>> # 股价走势图
|
||||
>>> df = pd.DataFrame({
|
||||
... 'date': ['2024-01', '2024-02', '2024-03'],
|
||||
... 'close': [10.5, 11.2, 10.8]
|
||||
... })
|
||||
>>> wb = create_research_chart(
|
||||
... df, chart_type="line",
|
||||
... title="股价走势",
|
||||
... x_col="date", y_cols="close",
|
||||
... y_axis_title="价格(元)",
|
||||
... show_last_label_only=True,
|
||||
... output_path="股价走势.xlsx"
|
||||
... )
|
||||
|
||||
>>> # 营收利润对比图
|
||||
>>> wb = create_research_chart(
|
||||
... df, chart_type="bar",
|
||||
... title="营业收入与净利润",
|
||||
... x_col="period", y_cols=["revenue", "profit"],
|
||||
... y_axis_title="金额(亿元)",
|
||||
... y_format="0.0"
|
||||
... )
|
||||
"""
|
||||
# 标准化 y_cols
|
||||
if isinstance(y_cols, str):
|
||||
y_cols = [y_cols]
|
||||
|
||||
# 创建工作簿
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "数据"
|
||||
|
||||
# 写入表头
|
||||
headers = [x_col] + y_cols
|
||||
ws.append(headers)
|
||||
|
||||
# 写入数据
|
||||
for _, row in df.iterrows():
|
||||
ws.append([row[col] for col in headers])
|
||||
|
||||
# 创建图表
|
||||
if chart_type == "line":
|
||||
chart = LineChart()
|
||||
elif chart_type == "bar":
|
||||
chart = BarChart()
|
||||
chart.type = "col"
|
||||
chart.grouping = "clustered"
|
||||
else:
|
||||
chart = LineChart()
|
||||
|
||||
# 设置标题和轴
|
||||
chart.title = title
|
||||
chart.y_axis.title = y_axis_title
|
||||
chart.x_axis.title = x_axis_title
|
||||
|
||||
# 设置图表尺寸
|
||||
chart.height = height
|
||||
chart.width = width
|
||||
|
||||
# 设置数据区域
|
||||
data_start_row = 1
|
||||
data_end_row = len(df) + 1
|
||||
|
||||
for i, y_col in enumerate(y_cols):
|
||||
col_idx = headers.index(y_col) + 1
|
||||
data_ref = Reference(ws, min_col=col_idx, min_row=data_start_row,
|
||||
max_row=data_end_row)
|
||||
cats_ref = Reference(ws, min_col=1, min_row=2, max_row=data_end_row)
|
||||
|
||||
chart.add_data(data_ref, titles_from_data=True)
|
||||
if i == 0:
|
||||
chart.set_categories(cats_ref)
|
||||
|
||||
# 设置Y轴格式
|
||||
chart.y_axis.numFmt = y_format
|
||||
|
||||
# 设置X轴标签间隔
|
||||
chart.x_axis.tickLblSkip = tick_skip
|
||||
|
||||
# 网格线设置(仅保留水平主网格线)
|
||||
chart.x_axis.majorGridlines = None
|
||||
|
||||
# 数据标签设置
|
||||
if show_data_labels:
|
||||
chart.dataLabels = DataLabelList()
|
||||
chart.dataLabels.showVal = True
|
||||
|
||||
# 仅显示最后一个标签
|
||||
if show_last_label_only and chart.series:
|
||||
series = chart.series[0]
|
||||
last_idx = len(df) - 1
|
||||
pt = DataPoint(idx=last_idx)
|
||||
pt.graphicalProperties = GraphicalProperties(solidFill=color_up)
|
||||
series.data_points = [pt]
|
||||
|
||||
# 柱状图涨跌着色
|
||||
if chart_type == "bar" and len(y_cols) == 1:
|
||||
series = chart.series[0]
|
||||
values = df[y_cols[0]].tolist()
|
||||
for i, val in enumerate(values):
|
||||
pt = DataPoint(idx=i)
|
||||
fill_color = color_up if val >= 0 else color_down
|
||||
pt.graphicalProperties = GraphicalProperties(solidFill=fill_color)
|
||||
series.data_points.append(pt)
|
||||
|
||||
# 添加均值参考线
|
||||
if add_mean_line and chart.series:
|
||||
series = chart.series[0]
|
||||
values = df[y_cols[0]].tolist()
|
||||
mean_val = sum(values) / len(values)
|
||||
|
||||
# 在工作表添加均值列
|
||||
mean_col = len(headers) + 1
|
||||
ws.cell(row=1, column=mean_col, value="均值")
|
||||
for i in range(2, data_end_row + 1):
|
||||
ws.cell(row=i, column=mean_col, value=mean_val)
|
||||
|
||||
mean_ref = Reference(ws, min_col=mean_col, min_row=1, max_row=data_end_row)
|
||||
mean_series = Series(mean_ref, title="均值")
|
||||
chart.series.append(mean_series)
|
||||
|
||||
# 图例位置(多序列时显示在底部)
|
||||
if len(y_cols) > 1 or add_mean_line:
|
||||
chart.legend.position = "b"
|
||||
else:
|
||||
chart.legend = None
|
||||
|
||||
# 添加图表到工作表
|
||||
ws.add_chart(chart, "E2")
|
||||
|
||||
# 添加数据来源注脚
|
||||
ws["A" + str(data_end_row + 2)] = "数据来源:Wind"
|
||||
|
||||
# 保存或返回
|
||||
if output_path:
|
||||
wb.save(output_path)
|
||||
|
||||
return wb
|
||||
|
||||
|
||||
def create_price_volume_chart(
|
||||
df: pd.DataFrame,
|
||||
title: str = "股价与成交量",
|
||||
date_col: str = "date",
|
||||
price_col: str = "close",
|
||||
volume_col: str = "volume",
|
||||
output_path: str = None
|
||||
) -> Workbook:
|
||||
"""
|
||||
生成股价+成交量组合图表(双Y轴)
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
df : pd.DataFrame
|
||||
包含日期、收盘价、成交量的数据
|
||||
title : str
|
||||
图表标题
|
||||
date_col, price_col, volume_col : str
|
||||
各数据列名
|
||||
output_path : str
|
||||
输出路径
|
||||
|
||||
Returns:
|
||||
--------
|
||||
Workbook
|
||||
"""
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "数据"
|
||||
|
||||
# 写入数据
|
||||
ws.append([date_col, price_col, volume_col])
|
||||
for _, row in df.iterrows():
|
||||
ws.append([row[date_col], row[price_col], row[volume_col]])
|
||||
|
||||
# 创建价格折线图(主Y轴)
|
||||
price_chart = LineChart()
|
||||
price_chart.title = title
|
||||
price_chart.y_axis.title = "价格(元)"
|
||||
price_chart.x_axis.title = date_col
|
||||
|
||||
price_ref = Reference(ws, min_col=2, min_row=1, max_row=len(df)+1)
|
||||
cats_ref = Reference(ws, min_col=1, min_row=2, max_row=len(df)+1)
|
||||
|
||||
price_chart.add_data(price_ref, titles_from_data=True)
|
||||
price_chart.set_categories(cats_ref)
|
||||
price_chart.y_axis.numFmt = "0.00"
|
||||
price_chart.x_axis.tickLblSkip = max(1, len(df) // 10)
|
||||
|
||||
# 创建成交量柱状图(次Y轴)
|
||||
vol_chart = BarChart()
|
||||
vol_chart.type = "col"
|
||||
vol_chart.grouping = "clustered"
|
||||
vol_chart.y_axis.axId = 200
|
||||
vol_chart.y_axis.title = "成交量(手)"
|
||||
|
||||
vol_ref = Reference(ws, min_col=3, min_row=1, max_row=len(df)+1)
|
||||
vol_chart.add_data(vol_ref, titles_from_data=True)
|
||||
|
||||
# 组合图表
|
||||
price_chart += vol_chart
|
||||
price_chart.y_axis.crosses = "max"
|
||||
|
||||
# 尺寸设置
|
||||
price_chart.height = 10
|
||||
price_chart.width = 20
|
||||
|
||||
# 隐藏网格线
|
||||
price_chart.x_axis.majorGridlines = None
|
||||
|
||||
ws.add_chart(price_chart, "E2")
|
||||
ws["A" + str(len(df) + 3)] = "数据来源:Wind"
|
||||
|
||||
if output_path:
|
||||
wb.save(output_path)
|
||||
|
||||
return wb
|
||||
265
financial-report-writing/scripts/data_fetcher.py
Normal file
265
financial-report-writing/scripts/data_fetcher.py
Normal file
@ -0,0 +1,265 @@
|
||||
"""
|
||||
Wind 数据获取模块
|
||||
提供统一的接口获取股票历史数据、财务数据和技术指标
|
||||
"""
|
||||
|
||||
from WindPy import w
|
||||
import pandas as pd
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Dict, Optional, Tuple
|
||||
|
||||
|
||||
class WindDataFetcher:
|
||||
"""Wind数据获取器"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化Wind连接"""
|
||||
self._start()
|
||||
|
||||
def _start(self):
|
||||
"""启动Wind连接"""
|
||||
result = w.start()
|
||||
if result.ErrorCode != 0:
|
||||
raise ConnectionError(f"Wind连接失败: {result.Data}")
|
||||
print("Wind连接成功")
|
||||
|
||||
def _stop(self):
|
||||
"""关闭Wind连接"""
|
||||
w.stop()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self._stop()
|
||||
|
||||
@staticmethod
|
||||
def _handle_wsd_result(result, field_names):
|
||||
"""处理wsd返回结果"""
|
||||
if result.ErrorCode != 0:
|
||||
raise ValueError(f"数据获取失败: {result.Data}")
|
||||
|
||||
# 转换为DataFrame
|
||||
data_dict = dict(zip(field_names, result.Data))
|
||||
df = pd.DataFrame(data_dict, columns=field_names)
|
||||
df['date'] = result.Times
|
||||
return df
|
||||
|
||||
def get_daily_data(self,
|
||||
stock_code: str,
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
fields: List[str]) -> pd.DataFrame:
|
||||
"""
|
||||
获取日线数据
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码,如 '300866.SZ'
|
||||
start_date: 开始日期,格式 'YYYY-MM-DD'
|
||||
end_date: 结束日期,格式 'YYYY-MM-DD'
|
||||
fields: 数据字段列表
|
||||
|
||||
Returns:
|
||||
DataFrame包含日期和指定字段的数据
|
||||
"""
|
||||
result = w.wsd(stock_code, fields, start_date, end_date)
|
||||
return self._handle_wsd_result(result, fields)
|
||||
|
||||
def get_stock_basic(self, stock_code: str) -> Dict:
|
||||
"""
|
||||
获取股票基本信息
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码
|
||||
|
||||
Returns:
|
||||
包含股票名称、行业、上市日期等信息的字典
|
||||
"""
|
||||
fields = ['sec_name', 'industry', 'ipo_date', 'list_date', 'trade_status']
|
||||
result = w.wss(stock_code, fields)
|
||||
|
||||
if result.ErrorCode != 0:
|
||||
raise ValueError(f"基本信息获取失败: {result.Data}")
|
||||
|
||||
return dict(zip(fields, result.Data[0]))
|
||||
|
||||
def get_technical_indicators(self,
|
||||
stock_code: str,
|
||||
start_date: str,
|
||||
end_date: str) -> pd.DataFrame:
|
||||
"""
|
||||
获取技术指标数据
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码
|
||||
start_date: 开始日期
|
||||
end_date: 结束日期
|
||||
|
||||
Returns:
|
||||
DataFrame包含MA、成交量等技术指标
|
||||
"""
|
||||
fields = [
|
||||
'close', 'open', 'high', 'low', 'volume', 'amt', 'pct_chg',
|
||||
'ma5', 'ma10', 'ma20', 'ma60',
|
||||
'pe_ttm', 'pb_lf', 'ps_ttm', 'pcf_ncf_ttm'
|
||||
]
|
||||
return self.get_daily_data(stock_code, start_date, end_date, fields)
|
||||
|
||||
def get_financial_data(self,
|
||||
stock_code: str,
|
||||
report_date: str = '') -> Dict:
|
||||
"""
|
||||
获取财务数据
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码
|
||||
report_date: 报告期,格式 'YYYYMMDD',默认最新
|
||||
|
||||
Returns:
|
||||
包含ROE、营收、净利润等财务指标的字典
|
||||
"""
|
||||
fields = [
|
||||
'roe_wgt', 'roa2', 'net_profit_to_profit', 'total_revenue_ps',
|
||||
'profit_to_gr', 'ebit_ps', 'assets_to_eqt', 'debt_to_assets',
|
||||
'current_ratio', 'quick_ratio', 'op_income_to_revenue'
|
||||
]
|
||||
|
||||
result = w.wss(stock_code, fields, 'rptDate={}'.format(report_date))
|
||||
|
||||
if result.ErrorCode != 0:
|
||||
raise ValueError(f"财务数据获取失败: {result.Data}")
|
||||
|
||||
return dict(zip(fields, result.Data[0]))
|
||||
|
||||
def get_weekly_data(self,
|
||||
stock_code: str,
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
fields: List[str] = None) -> pd.DataFrame:
|
||||
"""
|
||||
获取并汇总周线数据
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码
|
||||
start_date: 开始日期
|
||||
end_date: 结束日期
|
||||
fields: 数据字段,默认为基本OHLCV
|
||||
|
||||
Returns:
|
||||
DataFrame包含周线数据
|
||||
"""
|
||||
if fields is None:
|
||||
fields = ['close', 'open', 'high', 'low', 'volume', 'amt', 'pct_chg']
|
||||
|
||||
# 获取日线数据
|
||||
df_daily = self.get_daily_data(stock_code, start_date, end_date, fields)
|
||||
|
||||
# 转换为周线数据
|
||||
df_daily.set_index('date', inplace=True)
|
||||
df_weekly = df_daily.resample('W').agg({
|
||||
'open': 'first',
|
||||
'high': 'max',
|
||||
'low': 'min',
|
||||
'close': 'last',
|
||||
'volume': 'sum',
|
||||
'amt': 'sum',
|
||||
'pct_chg': 'sum' # 周涨跌幅近似为日涨跌幅之和
|
||||
}).dropna()
|
||||
|
||||
df_weekly.reset_index(inplace=True)
|
||||
return df_weekly
|
||||
|
||||
def get_stock_list_by_sector(self, sector_name: str, limit: int = 10) -> List[str]:
|
||||
"""
|
||||
获取某行业的龙头股票列表
|
||||
|
||||
Args:
|
||||
sector_name: 行业名称
|
||||
limit: 返回数量
|
||||
|
||||
Returns:
|
||||
股票代码列表
|
||||
"""
|
||||
# 使用Wind的行业板块数据
|
||||
fields = ['sec_name', 'ipo_date']
|
||||
# 这里简化处理,实际需要根据Wind板块接口调整
|
||||
print(f"注意: 行业股票列表获取需要根据实际Wind板块接口实现")
|
||||
return []
|
||||
|
||||
def calculate_volatility(self, df: pd.DataFrame, annualized: bool = True) -> float:
|
||||
"""
|
||||
计算波动率
|
||||
|
||||
Args:
|
||||
df: 包含收盘价的DataFrame
|
||||
annualized: 是否年化
|
||||
|
||||
Returns:
|
||||
波动率(百分比)
|
||||
"""
|
||||
returns = df['close'].pct_change().dropna()
|
||||
volatility = returns.std()
|
||||
|
||||
if annualized:
|
||||
# 日数据年化系数:sqrt(252),周数据年化系数:sqrt(52)
|
||||
n = len(df)
|
||||
if n > 250: # 日线数据
|
||||
volatility *= 252 ** 0.5
|
||||
else: # 周线数据
|
||||
volatility *= 52 ** 0.5
|
||||
|
||||
return volatility * 100 # 转换为百分比
|
||||
|
||||
|
||||
# 便捷函数
|
||||
def fetch_stock_report_data(stock_code: str,
|
||||
days_back: int = 90,
|
||||
weekly: bool = True) -> Tuple[pd.DataFrame, Dict]:
|
||||
"""
|
||||
获取股票报告所需的数据
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码
|
||||
days_back: 获取多少天的数据
|
||||
weekly: 是否为周线数据
|
||||
|
||||
Returns:
|
||||
(price_df, financial_dict) 价格数据和财务数据
|
||||
"""
|
||||
end_date = datetime.now().strftime('%Y-%m-%d')
|
||||
start_date = (datetime.now() - timedelta(days=days_back)).strftime('%Y-%m-%d')
|
||||
|
||||
with WindDataFetcher() as fetcher:
|
||||
# 获取价格数据
|
||||
if weekly:
|
||||
price_df = fetcher.get_weekly_data(stock_code, start_date, end_date)
|
||||
else:
|
||||
price_df = fetcher.get_technical_indicators(stock_code, start_date, end_date)
|
||||
|
||||
# 获取财务数据
|
||||
financial_data = fetcher.get_financial_data(stock_code)
|
||||
|
||||
# 获取基本信息
|
||||
basic_info = fetcher.get_stock_basic(stock_code)
|
||||
|
||||
# 合并财务信息
|
||||
financial_data.update(basic_info)
|
||||
|
||||
return price_df, financial_data
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 测试代码
|
||||
with WindDataFetcher() as fetcher:
|
||||
# 示例:获取某股票的数据
|
||||
code = '300866.SZ'
|
||||
start = '2025-01-01'
|
||||
end = '2026-02-06'
|
||||
|
||||
print(f"获取 {code} 的日线数据...")
|
||||
df = fetcher.get_technical_indicators(code, start, end)
|
||||
print(df.tail())
|
||||
|
||||
print(f"\n获取 {code} 的基本信息...")
|
||||
info = fetcher.get_stock_basic(code)
|
||||
print(info)
|
||||
377
financial-report-writing/scripts/document_formatter.py
Normal file
377
financial-report-writing/scripts/document_formatter.py
Normal file
@ -0,0 +1,377 @@
|
||||
"""
|
||||
Word文档格式化模块
|
||||
按照GB/T 9704-2012标准格式化金融研究报告
|
||||
"""
|
||||
|
||||
from docx import Document
|
||||
from docx.shared import Pt, Mm, RGBColor
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
|
||||
from docx.oxml.ns import qn
|
||||
from docx.oxml import OxmlElement
|
||||
|
||||
|
||||
class GBT9704Formatter:
|
||||
"""
|
||||
GB/T 9704-2012党政机关公文格式化器
|
||||
"""
|
||||
|
||||
# 字体映射(号数 -> 磅值)
|
||||
FONT_SIZES = {
|
||||
'三号': 16,
|
||||
'四号': 14,
|
||||
'小四': 12,
|
||||
'五号': 10.5
|
||||
}
|
||||
|
||||
def __init__(self, doc: Document):
|
||||
"""
|
||||
初始化格式化器
|
||||
|
||||
Args:
|
||||
doc: Document对象
|
||||
"""
|
||||
self.doc = doc
|
||||
self._setup_page()
|
||||
|
||||
def _setup_page(self):
|
||||
"""设置页面格式"""
|
||||
for section in self.doc.sections:
|
||||
# 页面边距(单位:毫米)
|
||||
section.top_margin = Mm(37)
|
||||
section.bottom_margin = Mm(35)
|
||||
section.left_margin = Mm(28)
|
||||
section.right_margin = Mm(26)
|
||||
|
||||
def set_font(self, run, font_name: str, size: int = None, bold: bool = None, color: RGBColor = None):
|
||||
"""
|
||||
设置字体
|
||||
|
||||
Args:
|
||||
run: Run对象
|
||||
font_name: 字体名称(中文字体)
|
||||
size: 字号(磅值),如 16, 14, 12
|
||||
bold: 是否加粗
|
||||
color: 字体颜色
|
||||
"""
|
||||
run.font.name = font_name
|
||||
run._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
|
||||
|
||||
if size is not None:
|
||||
run.font.size = Pt(size)
|
||||
if bold is not None:
|
||||
run.font.bold = bold
|
||||
if color is not None:
|
||||
run.font.color.rgb = color
|
||||
|
||||
def add_heading(self, text: str, level: int = 1, font_name: str = '黑体', size: int = 16):
|
||||
"""
|
||||
添加标题
|
||||
|
||||
Args:
|
||||
text: 标题文本
|
||||
level: 标题级别(1-3)
|
||||
font_name: 字体名称
|
||||
size: 字号
|
||||
|
||||
Returns:
|
||||
Paragraph对象
|
||||
"""
|
||||
if level == 1:
|
||||
heading = self.doc.add_heading(text, level=1)
|
||||
run = heading.runs[0]
|
||||
self.set_font(run, font_name, size, bold=True)
|
||||
heading.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
||||
heading.paragraph_format.space_before = Pt(0)
|
||||
heading.paragraph_format.space_after = Pt(0)
|
||||
return heading
|
||||
else:
|
||||
# 二级、三级标题使用"一、"、"(一)"等格式
|
||||
prefix_map = {1: '一、', 2: '(一)', 3: '1.'}
|
||||
prefix = prefix_map.get(level, '')
|
||||
|
||||
paragraph = self.doc.add_paragraph(f"{prefix}{text}")
|
||||
run = paragraph.runs[0]
|
||||
self.set_font(run, font_name, size, bold=True)
|
||||
|
||||
# 段落格式
|
||||
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
||||
paragraph.paragraph_format.space_before = Pt(0)
|
||||
paragraph.paragraph_format.space_after = Pt(0)
|
||||
paragraph.paragraph_format.line_spacing = Pt(25)
|
||||
paragraph.paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY
|
||||
|
||||
return paragraph
|
||||
|
||||
def add_paragraph(self, text: str,
|
||||
font_name: str = '仿宋',
|
||||
size: int = 14,
|
||||
first_line_indent: bool = True,
|
||||
alignment: str = 'justify'):
|
||||
"""
|
||||
添加正文段落
|
||||
|
||||
Args:
|
||||
text: 段落文本
|
||||
font_name: 字体名称
|
||||
size: 字号(磅值)
|
||||
first_line_indent: 是否首行缩进
|
||||
alignment: 对齐方式('left', 'center', 'right', 'justify')
|
||||
|
||||
Returns:
|
||||
Paragraph对象
|
||||
"""
|
||||
paragraph = self.doc.add_paragraph(text)
|
||||
run = paragraph.runs[0]
|
||||
self.set_font(run, font_name, size)
|
||||
|
||||
# 段落格式
|
||||
alignment_map = {
|
||||
'left': WD_ALIGN_PARAGRAPH.LEFT,
|
||||
'center': WD_ALIGN_PARAGRAPH.CENTER,
|
||||
'right': WD_ALIGN_PARAGRAPH.RIGHT,
|
||||
'justify': WD_ALIGN_PARAGRAPH.JUSTIFY
|
||||
}
|
||||
paragraph.paragraph_format.alignment = alignment_map.get(alignment, WD_ALIGN_PARAGRAPH.JUSTIFY)
|
||||
|
||||
if first_line_indent:
|
||||
# 首行缩进2字符(28pt)
|
||||
paragraph.paragraph_format.first_line_indent = Pt(28)
|
||||
|
||||
# 行距固定值28pt
|
||||
paragraph.paragraph_format.line_spacing = Pt(28)
|
||||
paragraph.paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY
|
||||
|
||||
# 段前段后距为0
|
||||
paragraph.paragraph_format.space_before = Pt(0)
|
||||
paragraph.paragraph_format.space_after = Pt(0)
|
||||
|
||||
return paragraph
|
||||
|
||||
def add_bullet_list(self, items: List[str], font_name: str = '仿宋', size: int = 14):
|
||||
"""
|
||||
添加项目符号列表
|
||||
|
||||
Args:
|
||||
items: 列表项
|
||||
font_name: 字体名称
|
||||
size: 字号
|
||||
"""
|
||||
for item in items:
|
||||
paragraph = self.doc.add_paragraph(item, style='List Bullet')
|
||||
run = paragraph.runs[0]
|
||||
self.set_font(run, font_name, size)
|
||||
paragraph.paragraph_format.line_spacing = Pt(28)
|
||||
paragraph.paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY
|
||||
paragraph.paragraph_format.space_before = Pt(0)
|
||||
paragraph.paragraph_format.space_after = Pt(0)
|
||||
|
||||
def add_three_line_table(self, data: List[List[str]],
|
||||
headers: List[str],
|
||||
font_name: str = '仿宋',
|
||||
size: int = 12):
|
||||
"""
|
||||
添加三线表
|
||||
|
||||
Args:
|
||||
data: 表格数据(二维列表)
|
||||
headers: 表头
|
||||
font_name: 字体名称
|
||||
size: 字号
|
||||
|
||||
Returns:
|
||||
Table对象
|
||||
"""
|
||||
# 创建表格
|
||||
table = self.doc.add_table(rows=len(data) + 1, cols=len(headers))
|
||||
self._set_three_line_table_borders(table)
|
||||
|
||||
# 设置表头
|
||||
header_cells = table.rows[0].cells
|
||||
for i, header in enumerate(headers):
|
||||
cell = header_cells[i]
|
||||
self._set_cell_font(cell, header, font_name, size, bold=True)
|
||||
cell.paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
# 设置数据行
|
||||
for row_idx, row_data in enumerate(data, start=1):
|
||||
cells = table.rows[row_idx].cells
|
||||
for i, cell_data in enumerate(row_data):
|
||||
cell = cells[i]
|
||||
self._set_cell_font(cell, str(cell_data), font_name, size)
|
||||
cell.paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
return table
|
||||
|
||||
def _set_three_line_table_borders(self, table):
|
||||
"""设置三线表边框"""
|
||||
tblPr = table._tbl.tblPr
|
||||
tblBorders = OxmlElement('w:tblBorders')
|
||||
|
||||
# 顶底边框 1.5pt (sz=18)
|
||||
for pos in ['top', 'bottom']:
|
||||
border = OxmlElement(f'w:{pos}')
|
||||
border.set(qn('w:val'), 'single')
|
||||
border.set(qn('w:sz'), '18')
|
||||
border.set(qn('w:color'), 'auto')
|
||||
tblBorders.append(border)
|
||||
|
||||
# 内部横线 0.75pt (sz=9)
|
||||
for pos in ['insideH']:
|
||||
border = OxmlElement(f'w:{pos}')
|
||||
border.set(qn('w:val'), 'single')
|
||||
border.set(qn('w:sz'), '9')
|
||||
border.set(qn('w:color'), 'auto')
|
||||
tblBorders.append(border)
|
||||
|
||||
# 无左右和竖线
|
||||
for pos in ['left', 'right', 'insideV']:
|
||||
border = OxmlElement(f'w:{pos}')
|
||||
border.set(qn('w:val'), 'none')
|
||||
tblBorders.append(border)
|
||||
|
||||
tblPr.append(tblBorders)
|
||||
|
||||
def _set_cell_font(self, cell, text: str, font_name: str, size: int, bold: bool = False):
|
||||
"""设置单元格字体"""
|
||||
cell.text = ''
|
||||
run = cell.paragraphs[0].add_run(text)
|
||||
self.set_font(run, font_name, size, bold=bold)
|
||||
|
||||
def add_page_number_footer(self):
|
||||
"""添加页码页脚"""
|
||||
for section in self.doc.sections:
|
||||
footer = section.footer
|
||||
|
||||
# 添加页码段落
|
||||
p = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph()
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
# 页码格式:— 1 —
|
||||
self.add_footer_run(p, '— ', font_name='仿宋', size=12)
|
||||
|
||||
# 添加页码字段
|
||||
self.add_page_number_field(p)
|
||||
|
||||
self.add_footer_run(p, ' ', font_name='仿宋', size=12)
|
||||
self.add_footer_run(p, '—', font_name='仿宋', size=12)
|
||||
|
||||
def add_footer_run(self, paragraph, text: str, font_name: str, size: int):
|
||||
"""添加页脚run"""
|
||||
run = paragraph.add_run(text)
|
||||
self.set_font(run, font_name, size)
|
||||
|
||||
def add_page_number_field(self, paragraph):
|
||||
"""添加页码字段"""
|
||||
fldChar1 = OxmlElement('w:fldChar')
|
||||
fldChar1.set(qn('w:fldCharType'), 'begin')
|
||||
|
||||
instrText = OxmlElement('w:instrText')
|
||||
instrText.set(qn('xml:space'), 'preserve')
|
||||
instrText.text = "PAGE"
|
||||
|
||||
fldChar2 = OxmlElement('w:fldChar')
|
||||
fldChar2.set(qn('w:fldCharType'), 'end')
|
||||
|
||||
run = paragraph.add_run()
|
||||
run._r.append(fldChar1)
|
||||
run._r.append(instrText)
|
||||
run._r.append(fldChar2)
|
||||
|
||||
self.set_font(run, '仿宋', 12)
|
||||
|
||||
def add_cover_page(self, title: str, organization: str, date_str: str):
|
||||
"""
|
||||
添加封面页
|
||||
|
||||
Args:
|
||||
title: 报告标题
|
||||
organization: 机构名称
|
||||
date_str: 日期
|
||||
"""
|
||||
# 标题
|
||||
paragraph = self.doc.add_paragraph()
|
||||
run = paragraph.add_run(title)
|
||||
self.set_font(run, '黑体', 22, bold=True)
|
||||
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
paragraph.paragraph_format.space_before = Pt(100)
|
||||
paragraph.paragraph_format.space_after = Pt(50)
|
||||
|
||||
# 机构名称
|
||||
paragraph = self.doc.add_paragraph()
|
||||
run = paragraph.add_run(organization)
|
||||
self.set_font(run, '黑体', 16)
|
||||
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
paragraph.paragraph_format.space_after = Pt(20)
|
||||
|
||||
# 日期
|
||||
paragraph = self.doc.add_paragraph()
|
||||
run = paragraph.add_run(date_str)
|
||||
self.set_font(run, '黑体', 16)
|
||||
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
# 添加分页符
|
||||
self.doc.add_page_break()
|
||||
|
||||
def add_data_source_footnote(self, text: str = "数据来源:Wind资讯"):
|
||||
"""
|
||||
添加数据来源注脚
|
||||
|
||||
Args:
|
||||
text: 注脚文本
|
||||
"""
|
||||
paragraph = self.doc.add_paragraph()
|
||||
run = paragraph.add_run(text)
|
||||
self.set_font(run, '仿宋', 12)
|
||||
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||||
|
||||
|
||||
# 便捷函数
|
||||
def create_formatted_document() -> Tuple[Document, GBT9704Formatter]:
|
||||
"""
|
||||
创建格式化的Word文档
|
||||
|
||||
Returns:
|
||||
(Document, GBT9704Formatter) 文档对象和格式化器
|
||||
"""
|
||||
doc = Document()
|
||||
formatter = GBT9704Formatter(doc)
|
||||
return doc, formatter
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 测试代码
|
||||
doc, formatter = create_formatted_document()
|
||||
|
||||
# 添加封面
|
||||
formatter.add_cover_page(
|
||||
'某某股票投资研究报告',
|
||||
'某某证券研究所',
|
||||
'2026年2月6日'
|
||||
)
|
||||
|
||||
# 添加核心提要
|
||||
formatter.add_heading('核心提要', level=1)
|
||||
formatter.add_bullet_list([
|
||||
'该公司2025年三季度净资产收益率(ROE)为15.2%,盈利能力较强',
|
||||
'当前市盈率(TTM)为25.3倍,略高于行业平均水平',
|
||||
'技术面显示股价呈现震荡上行趋势,建议关注回调机会',
|
||||
'给予"买入"评级,目标价格35元'
|
||||
])
|
||||
|
||||
# 添加投资评级表格
|
||||
formatter.add_heading('一、投资评级', level=1)
|
||||
data = [
|
||||
['买入', '35.00元', '2026年12月31日', '中风险']
|
||||
]
|
||||
formatter.add_three_line_table(
|
||||
data,
|
||||
['投资建议', '目标价格', '有效期', '风险等级']
|
||||
)
|
||||
|
||||
# 添加数据来源
|
||||
formatter.add_data_source_footnote()
|
||||
|
||||
# 保存文档
|
||||
output_path = 'test_report.docx'
|
||||
doc.save(output_path)
|
||||
print(f"测试报告已保存到: {output_path}")
|
||||
313
financial-report-writing/scripts/generate_report.py
Normal file
313
financial-report-writing/scripts/generate_report.py
Normal file
@ -0,0 +1,313 @@
|
||||
"""
|
||||
金融研究报告生成器
|
||||
整合数据获取、分析和文档生成功能,一键生成完整报告
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# 添加脚本目录到路径
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
from docx import Document
|
||||
from docx.shared import Pt
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
|
||||
from data_fetcher import WindDataFetcher, fetch_stock_report_data
|
||||
from document_formatter import create_formatted_document, GBT9704Formatter
|
||||
from analysis_engine import perform_comprehensive_analysis
|
||||
|
||||
|
||||
class ReportGenerator:
|
||||
"""
|
||||
金融研究报告生成器
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.doc = None
|
||||
self.formatter = None
|
||||
self.analysis_results = None
|
||||
|
||||
def generate_report(self,
|
||||
stock_code: str,
|
||||
stock_name: str = None,
|
||||
organization: str = "某某证券研究所",
|
||||
author: str = "",
|
||||
include_cover: bool = True,
|
||||
output_path: str = None) -> str:
|
||||
"""
|
||||
生成完整的投资研究报告
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码,如 '300866.SZ'
|
||||
stock_name: 股票名称(如不提供则自动获取)
|
||||
organization: 研究机构名称
|
||||
author: 分析师姓名
|
||||
include_cover: 是否包含封面
|
||||
output_path: 输出文件路径
|
||||
|
||||
Returns:
|
||||
生成的文件路径
|
||||
"""
|
||||
print(f"开始生成 {stock_code} 的投资研究报告...")
|
||||
|
||||
# 1. 获取数据
|
||||
print("正在获取股票数据...")
|
||||
try:
|
||||
price_df, financial_data = fetch_stock_report_data(stock_code, days_back=90, weekly=True)
|
||||
except Exception as e:
|
||||
print(f"数据获取失败: {e}")
|
||||
raise
|
||||
|
||||
# 如果未提供股票名称,从财务数据获取
|
||||
if stock_name is None:
|
||||
stock_name = financial_data.get('sec_name', stock_code)
|
||||
|
||||
print(f"获取到 {len(price_df)} 条周线数据")
|
||||
|
||||
# 2. 执行分析
|
||||
print("正在执行投资分析...")
|
||||
current_price = price_df['close'].iloc[-1]
|
||||
self.analysis_results = perform_comprehensive_analysis(
|
||||
price_df, financial_data, current_price
|
||||
)
|
||||
|
||||
# 3. 创建文档
|
||||
print("正在生成Word文档...")
|
||||
self.doc, self.formatter = create_formatted_document()
|
||||
|
||||
# 4. 添加封面(可选)
|
||||
if include_cover:
|
||||
self._add_cover_page(stock_name, organization)
|
||||
|
||||
# 5. 添加免责声明
|
||||
self._add_disclaimer()
|
||||
|
||||
# 6. 添加核心提要
|
||||
self._add_core_summary(stock_name)
|
||||
|
||||
# 7. 添加投资评级
|
||||
self._add_investment_rating(stock_name, current_price)
|
||||
|
||||
# 8. 添加核心观点
|
||||
self._add_core_views()
|
||||
|
||||
# 9. 添加市场表现回顾
|
||||
self._add_market_review(price_df)
|
||||
|
||||
# 10. 添加页脚
|
||||
self.formatter.add_page_number_footer()
|
||||
|
||||
# 11. 保存文档
|
||||
if output_path is None:
|
||||
date_str = datetime.now().strftime('%Y%m%d')
|
||||
output_path = f"{stock_name}_{stock_code.replace('.', '_')}_研究报告_{date_str}.docx"
|
||||
|
||||
self.doc.save(output_path)
|
||||
print(f"报告已保存: {output_path}")
|
||||
|
||||
return output_path
|
||||
|
||||
def _add_cover_page(self, stock_name: str, organization: str):
|
||||
"""添加封面页"""
|
||||
date_str = datetime.now().strftime('%Y年%m月%d日')
|
||||
self.formatter.add_cover_page(
|
||||
title=f"{stock_name}投资研究报告",
|
||||
organization=organization,
|
||||
date_str=date_str
|
||||
)
|
||||
|
||||
def _add_disclaimer(self):
|
||||
"""添加免责声明"""
|
||||
self.formatter.add_heading('免责声明', level=1)
|
||||
self.formatter.add_paragraph(
|
||||
"本报告所载资料的来源及观点皆为公开信息,但并不能保证其准确性和完整性。本报告仅供参考,"
|
||||
"不构成任何投资建议或承诺,投资者应审慎决策,独立判断,自行承担投资风险。"
|
||||
)
|
||||
self.formatter.add_paragraph(
|
||||
"本报告版权归本公司所有,未经书面许可,任何机构和个人不得以任何形式翻版、复制、刊登、"
|
||||
"发表或引用。如征得本公司同意进行引用、刊发的,需在允许的范围内使用,并注明出处为"
|
||||
"'某某证券研究所',且不得对本报告进行任何有悖原意的引用、删节和修改。"
|
||||
)
|
||||
|
||||
def _add_core_summary(self, stock_name: str):
|
||||
"""添加核心提要"""
|
||||
self.formatter.add_heading('核心提要', level=1)
|
||||
|
||||
summary_points = self.analysis_results['summary_points']
|
||||
for i, point in enumerate(summary_points, 1):
|
||||
# 添加项目符号
|
||||
paragraph = self.doc.add_paragraph()
|
||||
run = paragraph.add_run(f"{i}. {point}")
|
||||
self.formatter.set_font(run, '仿宋', 14)
|
||||
paragraph.paragraph_format.line_spacing = Pt(28)
|
||||
paragraph.paragraph_format.line_spacing_rule = 2
|
||||
paragraph.paragraph_format.space_before = Pt(0)
|
||||
paragraph.paragraph_format.space_after = Pt(0)
|
||||
|
||||
def _add_investment_rating(self, stock_name: str, current_price: float):
|
||||
"""添加投资评级"""
|
||||
self.formatter.add_heading('一、投资评级', level=1)
|
||||
|
||||
# 表格数据
|
||||
recommendation = self.analysis_results['recommendation']
|
||||
|
||||
data = [[
|
||||
recommendation.rating.value,
|
||||
f"{recommendation.target_price:.2f}元",
|
||||
(datetime.now() + timedelta(days=365)).strftime('%Y年%m月%d日'),
|
||||
recommendation.risk_level.value
|
||||
]]
|
||||
|
||||
self.formatter.add_three_line_table(
|
||||
data,
|
||||
['投资建议', '目标价格', '有效期', '风险等级'],
|
||||
font_name='仿宋',
|
||||
size=12
|
||||
)
|
||||
|
||||
# 添加说明文字
|
||||
self.formatter.add_paragraph(
|
||||
f"当前股价:{current_price:.2f}元。"
|
||||
f"目标价较当前价格{'上涨' if recommendation.target_price > current_price else '下跌'}"
|
||||
f"{abs((recommendation.target_price / current_price - 1) * 100):.2f}%。"
|
||||
)
|
||||
|
||||
def _add_core_views(self):
|
||||
"""添加核心观点"""
|
||||
self.formatter.add_heading('二、核心观点', level=1)
|
||||
|
||||
# 基本面分析
|
||||
fundamental = self.analysis_results['fundamental']
|
||||
self.formatter.add_heading('(一)基本面分析', level=2)
|
||||
self.formatter.add_paragraph(
|
||||
f"1. 盈利能力:公司最新净资产收益率(ROE)为{fundamental.roe}%,"
|
||||
f"{fundamental.roe_comment}。"
|
||||
)
|
||||
self.formatter.add_paragraph(
|
||||
f"2. 估值水平:当前市盈率(TTM)为{fundamental.pe_ttm}倍,"
|
||||
f"市净率(LF)为{fundamental.pb_lf}倍。{fundamental.valuation_comment}。"
|
||||
)
|
||||
self.formatter.add_paragraph(f"3. 结论:{fundamental.fundamental_summary}")
|
||||
|
||||
# 技术面分析
|
||||
technical = self.analysis_results['technical']
|
||||
self.formatter.add_heading('(二)技术面分析', level=2)
|
||||
self.formatter.add_paragraph(
|
||||
f"1. 价格趋势:近期股价呈现{technical.trend}态势,"
|
||||
f"{technical.ma_status}。"
|
||||
)
|
||||
self.formatter.add_paragraph(
|
||||
f"2. 支撑与阻力:近期支撑位{technical.support_level}元,"
|
||||
f"阻力位{technical.resistance_level}元。"
|
||||
)
|
||||
self.formatter.add_paragraph(
|
||||
f"3. 波动率:近期年化波动率为{technical.volatility}%,"
|
||||
f"{'属于高波动品种' if technical.volatility > 40 else '波动适中' if technical.volatility > 25 else '波动较低'}。"
|
||||
)
|
||||
|
||||
# 投资建议
|
||||
recommendation = self.analysis_results['recommendation']
|
||||
self.formatter.add_heading('(三)投资建议', level=2)
|
||||
self.formatter.add_paragraph(recommendation.recommendation_text)
|
||||
|
||||
self.formatter.add_paragraph("主要投资逻辑:")
|
||||
for factor in recommendation.key_factors:
|
||||
paragraph = self.doc.add_paragraph()
|
||||
run = paragraph.add_run(f"• {factor}")
|
||||
self.formatter.set_font(run, '仿宋', 14)
|
||||
paragraph.paragraph_format.line_spacing = Pt(28)
|
||||
paragraph.paragraph_format.line_spacing_rule = 2
|
||||
paragraph.paragraph_format.first_line_indent = Pt(28)
|
||||
|
||||
def _add_market_review(self, price_df):
|
||||
"""添加市场表现回顾"""
|
||||
self.formatter.add_heading('三、市场表现回顾', level=1)
|
||||
|
||||
# 价格走势分析
|
||||
self.formatter.add_heading('(一)价格走势分析', level=2)
|
||||
|
||||
price_stats = self.analysis_results['price_stats']
|
||||
|
||||
self.formatter.add_paragraph(
|
||||
f"近期股价最高达到{price_stats['highest_price']}元,"
|
||||
f"最低下探至{price_stats['lowest_price']}元,"
|
||||
f"区间累计涨跌幅为{price_stats['total_change']:.2f}%。"
|
||||
f"最新收盘价为{price_stats['latest_price']}元,"
|
||||
f"区间平均价格为{price_stats['avg_price']}元。"
|
||||
)
|
||||
|
||||
# K线数据表
|
||||
self.formatter.add_heading('(二)K线数据', level=2)
|
||||
|
||||
# 准备表格数据(最近13周)
|
||||
recent_data = price_df.tail(13).copy()
|
||||
table_data = []
|
||||
|
||||
for _, row in recent_data.iterrows():
|
||||
date_str = row['date'].strftime('%Y-%m-%d') if hasattr(row['date'], 'strftime') else str(row['date'])[:10]
|
||||
table_data.append([
|
||||
date_str,
|
||||
f"{row['open']:.2f}",
|
||||
f"{row['high']:.2f}",
|
||||
f"{row['low']:.2f}",
|
||||
f"{row['close']:.2f}",
|
||||
f"{row.get('pct_chg', 0):.2f}%",
|
||||
f"{row.get('volume', 0) / 10000:.0f}"
|
||||
])
|
||||
|
||||
self.formatter.add_three_line_table(
|
||||
table_data,
|
||||
['日期', '开盘价', '最高价', '最低价', '收盘价', '涨跌幅', '成交量(万股)'],
|
||||
font_name='仿宋',
|
||||
size=12
|
||||
)
|
||||
|
||||
# 数据来源
|
||||
self.formatter.add_data_source_footnote()
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
parser = argparse.ArgumentParser(description='生成金融投资研究报告')
|
||||
parser.add_argument('stock_code', help='股票代码,如 300866.SZ 或 000001')
|
||||
parser.add_argument('--name', '-n', help='股票名称')
|
||||
parser.add_argument('--org', '-o', default='某某证券研究所', help='研究机构名称')
|
||||
parser.add_argument('--author', '-a', default='', help='分析师姓名')
|
||||
parser.add_argument('--no-cover', action='store_true', help='不包含封面')
|
||||
parser.add_argument('--output', '-p', help='输出文件路径')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# 标准化股票代码
|
||||
stock_code = args.stock_code
|
||||
if '.' not in stock_code:
|
||||
if stock_code.startswith('6'):
|
||||
stock_code = f"{stock_code}.SH"
|
||||
else:
|
||||
stock_code = f"{stock_code}.SZ"
|
||||
|
||||
generator = ReportGenerator()
|
||||
|
||||
try:
|
||||
output_path = generator.generate_report(
|
||||
stock_code=stock_code,
|
||||
stock_name=args.name,
|
||||
organization=args.org,
|
||||
author=args.author,
|
||||
include_cover=not args.no_cover,
|
||||
output_path=args.output
|
||||
)
|
||||
print(f"\n报告生成成功: {output_path}")
|
||||
except Exception as e:
|
||||
print(f"\n报告生成失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
133
find-skills/SKILL.md
Normal file
133
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
|
||||
```
|
||||
22
fund-slides/LICENSE
Normal file
22
fund-slides/LICENSE
Normal file
@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Zara Zhang (original author)
|
||||
Copyright (c) 2026 hanlinlibham (fork with financial enhancements)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
120
fund-slides/README.md
Normal file
120
fund-slides/README.md
Normal file
@ -0,0 +1,120 @@
|
||||
# Fund Slides
|
||||
|
||||
> Forked from [frontend-slides](https://github.com/zarazhangrui/frontend-slides) by [@zarazhangrui](https://github.com/zarazhangrui) (Zara Zhang).
|
||||
> Enhanced by [@hanlinlibham](https://github.com/hanlinlibham) with financial chart integration and offline optimizations.
|
||||
|
||||
A Claude Code skill for creating animation-rich HTML presentations from any content — with first-class support for financial data visualization.
|
||||
|
||||
## What This Does
|
||||
|
||||
**Fund Slides** helps non-designers create beautiful web presentations without knowing CSS or JavaScript. It uses a "show, don't tell" approach: instead of asking you to describe your aesthetic preferences in words, it generates visual previews and lets you pick what you like.
|
||||
|
||||
Here is a deck about the original skill, made through the skill:
|
||||
|
||||
https://github.com/user-attachments/assets/ef57333e-f879-432a-afb9-180388982478
|
||||
|
||||
### Key Features
|
||||
|
||||
- **Zero Dependencies** — Single HTML files with inline CSS/JS. No npm, no build tools, no frameworks.
|
||||
- **Offline-Ready** — ECharts library (~1MB) is inlined directly into the HTML. No CDN, no network required.
|
||||
- **Visual Style Discovery** — Can't articulate design preferences? No problem. Pick from generated visual previews.
|
||||
- **PPT Conversion** — Convert existing PowerPoint files to web, preserving all images and content.
|
||||
- **Anti-AI-Slop** — Curated distinctive styles that avoid generic AI aesthetics.
|
||||
- **Production Quality** — Accessible, responsive, well-commented code you can customize.
|
||||
|
||||
### What's New in This Fork
|
||||
|
||||
Compared to the original `frontend-slides`:
|
||||
|
||||
- **ECharts Inline Integration** — ECharts v5 library pre-bundled in `references/echarts.min.js`, inlined at generation time for fully offline HTML output. The original used CDN `<script src="...">` which requires network access.
|
||||
- **Financial Chart Library** — Added specialized chart references for financial use cases:
|
||||
- `charts-trend.md` — Line/area/bar charts for time series
|
||||
- `charts-composition.md` — Pie/rose/treemap/sunburst for composition analysis
|
||||
- `charts-matrix.md` — Heatmap/radar/scatter for multi-dimensional comparison
|
||||
- `charts-flow.md` — Waterfall/candlestick/funnel/sankey for flow analysis
|
||||
- **A-Share Color Convention** — Red-up/green-down (China stock market standard) built into the base template
|
||||
- **Financial Layouts** — KPI cards, data tables, timelines, comparison cards optimized for investment presentations
|
||||
- **Content Mapping System** — Intelligent content-to-visual mapping that automatically selects optimal chart types based on data characteristics
|
||||
- **Safe Base Template** — Defensive HTML template with built-in viewport fitting, CJK typography, responsive breakpoints, and Chinese number formatting (`formatCN`)
|
||||
- **15 Visual Presets** — Expanded from 12 to 15 curated styles including `Research Formal`, `Data Dashboard`, and `Roadshow Elegance`
|
||||
- **Self-Check Checklist** — Phase 3.5 automated quality gate ensuring zero external dependencies, proper `clamp()` usage, and A-share color compliance
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
git clone https://github.com/hanlinlibham/skills.git ~/.claude/skills
|
||||
```
|
||||
|
||||
Then use it by typing `/fund-slides` in Claude Code.
|
||||
|
||||
## Usage
|
||||
|
||||
### Create a New Presentation
|
||||
|
||||
```
|
||||
/fund-slides
|
||||
|
||||
> "Create a fund analysis presentation for XX Fund"
|
||||
```
|
||||
|
||||
The skill will:
|
||||
1. Ask about your content, length, chart needs, and editing preference
|
||||
2. Analyze content and map each block to optimal visual form
|
||||
3. Generate 3 visual style previews for you to compare
|
||||
4. Create the full presentation with charts inlined
|
||||
5. Open it in your browser — works offline
|
||||
|
||||
### Convert a PowerPoint
|
||||
|
||||
```
|
||||
/fund-slides
|
||||
|
||||
> "Convert my presentation.pptx to a web slideshow"
|
||||
```
|
||||
|
||||
## Included Styles
|
||||
|
||||
### Dark Themes
|
||||
- **Bold Signal** — Confident, high-impact, vibrant card on dark
|
||||
- **Electric Studio** — Clean, professional, split-panel
|
||||
- **Creative Voltage** — Energetic, retro-modern, electric blue + neon
|
||||
- **Dark Botanical** — Elegant, sophisticated, warm accents
|
||||
|
||||
### Light Themes
|
||||
- **Notebook Tabs** — Editorial, organized, paper with colorful tabs
|
||||
- **Pastel Geometry** — Friendly, approachable, vertical pills
|
||||
- **Split Pastel** — Playful, modern, two-color vertical split
|
||||
- **Vintage Editorial** — Witty, personality-driven, geometric shapes
|
||||
|
||||
### Financial / Specialty
|
||||
- **Research Formal** — Institutional, trustworthy, data-heavy layouts
|
||||
- **Data Dashboard** — Clean metrics, KPI cards, chart-forward
|
||||
- **Roadshow Elegance** — Premium, investor-facing, bold confidence
|
||||
- **Swiss Modern** — Minimal, Bauhaus-inspired, geometric
|
||||
- **Paper & Ink** — Literary, drop caps, pull quotes
|
||||
- **Neon Cyber** — Futuristic, particle backgrounds, neon glow
|
||||
- **Terminal Green** — Developer-focused, hacker aesthetic
|
||||
|
||||
## Architecture
|
||||
|
||||
| File | Purpose | Loaded When |
|
||||
|------|---------|-------------|
|
||||
| `SKILL.md` | Core workflow and rules | Always (skill invocation) |
|
||||
| `content-mapping.md` | Content type to visual form mapping | Phase 1 (content analysis) |
|
||||
| `STYLE_PRESETS.md` | 15 curated visual presets | Phase 2 (style selection) |
|
||||
| `references/safe-base-template.html` | Defensive base HTML template | Phase 3 (generation) |
|
||||
| `references/echarts.min.js` | ECharts v5 library (~1MB) | Phase 3 (inline into HTML) |
|
||||
| `references/charts-*.md` | Chart type configurations | Phase 3 (per chart type) |
|
||||
| `references/financial-layouts.md` | KPI/table/timeline layouts | Phase 3 (financial slides) |
|
||||
| `references/animation-patterns.md` | CSS/JS animation reference | Phase 3 (generation) |
|
||||
| `references/html-template.md` | HTML structure and JS features | Phase 3 (generation) |
|
||||
| `scripts/extract-pptx.py` | PPT content extraction | Phase 4 (conversion) |
|
||||
|
||||
## Credits
|
||||
|
||||
- **Original author**: [Zara Zhang](https://github.com/zarazhangrui) — created [frontend-slides](https://github.com/zarazhangrui/frontend-slides) with Claude Code
|
||||
- **Fork maintainer**: [hanlinlibham](https://github.com/hanlinlibham) — financial chart integration, offline ECharts inlining, A-share conventions, and expanded presets
|
||||
|
||||
## License
|
||||
|
||||
MIT — See [LICENSE](LICENSE) for details.
|
||||
307
fund-slides/SKILL.md
Normal file
307
fund-slides/SKILL.md
Normal file
@ -0,0 +1,307 @@
|
||||
---
|
||||
name: fund-slides
|
||||
description: Create animation-rich HTML presentations from any content. Provides intelligent content-to-visual mapping -- given information of any type, selects the optimal slide layout, chart, and animation. Zero-dependency single HTML files with Chinese font support and ECharts inline integration. Use when the user wants to build a presentation, convert a PPT/PPTX to web, or create slides.
|
||||
---
|
||||
|
||||
# Frontend Slides
|
||||
|
||||
将任意内容转化为零依赖、动画丰富的 HTML 演示文稿。
|
||||
|
||||
本技能不关心内容从哪来、用什么分析框架 -- 它只做一件事:**给定一块信息,选择最佳的视觉呈现方式。**
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Content-Agnostic** -- 技能不决定"展示什么",只决定"怎么展示"。研究逻辑由用户或上游技能负责。
|
||||
2. **Zero Dependencies** -- 单 HTML 文件,CSS/JS 全部内联。需要图表时,ECharts 库直接内嵌在 HTML 的 `<script>` 标签中(通过 WebFetch 下载 echarts.min.js 后内联),确保离线可用。
|
||||
3. **Show, Don't Tell** -- 生成可视化预览让用户选择风格,而非要求用户用语言描述偏好。
|
||||
4. **Viewport Fitting (NON-NEGOTIABLE)** -- 每张 slide 精确适配 100vh。禁止滚动。内容超限时拆分。
|
||||
5. **No Emoji** -- 禁止任何 emoji。用 CSS 形状、SVG 图标或文字替代。
|
||||
6. **Chinese Font Required** -- 每份演示文稿必须包含中文字体回退链。
|
||||
|
||||
## Design Aesthetics
|
||||
|
||||
杜绝"AI 味"。每份演示文稿都应有定制感。
|
||||
|
||||
- 字体:选择有辨识度的字体,避免 Arial/Inter/Roboto。所有字号用 `clamp()`。
|
||||
- 配色:主色 + 锐利强调色,避免均匀分布。CSS 变量保持一致。
|
||||
- 动画:聚焦高影响力时刻 -- 入场交错显现优于零散微交互。
|
||||
- 背景:渐变/图案/纹理营造层次,而非纯色。
|
||||
|
||||
## Viewport Fitting Rules
|
||||
|
||||
适用于每张 slide 的不变量:
|
||||
|
||||
- `.slide`: `height: 100vh; height: 100dvh; overflow: hidden;`
|
||||
- 所有字号: `clamp(min, preferred, max)` -- 禁止固定 px/rem
|
||||
- 所有间距: `clamp()` 或视口单位
|
||||
- 图片: `max-height: min(50vh, 400px)`
|
||||
- 断点: 700px, 600px, 500px (height), 600px (width)
|
||||
- `prefers-reduced-motion` 支持
|
||||
- CSS 函数取反用 `calc(-1 * clamp(...))` -- 禁止 `-clamp()`
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Detect Mode
|
||||
|
||||
- **Mode A: 新建** -- 用户提供内容(文本/数据/图片),从零创建。进入 Phase 1。
|
||||
- **Mode B: PPT 转换** -- 转换 .pptx 文件。进入 Phase 4。
|
||||
- **Mode C: 增强** -- 改进现有 HTML 演示文稿。遵循 Mode C 规则。
|
||||
|
||||
### Mode C: 修改规则
|
||||
|
||||
1. 添加内容前统计现有元素,对照密度限制
|
||||
2. 任何修改后验证: `overflow: hidden`、`clamp()` 字号、图片 max-height
|
||||
3. 将要溢出时主动拆分并告知用户
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Content Discovery
|
||||
|
||||
**目标:理解用户有什么内容,而非告诉用户应该有什么内容。**
|
||||
|
||||
通过一次 AskUserQuestion 收集信息:
|
||||
|
||||
**问题 1 -- 内容来源** (header: "内容"):
|
||||
你的内容是什么形式?选项:
|
||||
- "内容已就绪" -- 有完整的文字/数据/图表素材
|
||||
- "有粗略笔记" -- 有要点但未整理
|
||||
- "仅有主题" -- 只有主题,需要协助组织结构
|
||||
|
||||
**问题 2 -- 长度** (header: "长度"):
|
||||
大约多少页?选项:简短 5-10 / 中等 10-20 / 较长 20+
|
||||
|
||||
**问题 3 -- 图表需求** (header: "图表"):
|
||||
是否需要数据图表?选项:
|
||||
- "ECharts (推荐)" -- 折线/柱状/饼图/K线/瀑布图,内嵌到 HTML 中,离线可用
|
||||
- "简单 SVG" -- 更轻量,适合简单图表
|
||||
- "不需要"
|
||||
|
||||
**问题 4 -- 在线编辑** (header: "编辑"):
|
||||
是否需要浏览器内编辑文字?选项:是(推荐) / 否
|
||||
|
||||
然后请用户提供内容。
|
||||
|
||||
### Step 1.2: Content Analysis
|
||||
|
||||
**用户提供内容后,读取 [content-mapping.md](content-mapping.md) 进行内容分析:**
|
||||
|
||||
1. **识别** -- 将用户内容拆解为独立信息块
|
||||
2. **分类** -- 对每块内容标注信息类型(数值指标 / 时序趋势 / 结构构成 / 对比关系 / 叙事论点 / 时间序列 / 风险评估)
|
||||
3. **映射** -- 为每块内容匹配最佳视觉形式(KPI卡片 / 图表 / 表格 / 分栏 / 列表 / 时间线 / 对比卡片)
|
||||
4. **排序** -- 组织 slide 顺序(概览 -> 核心内容 -> 结论)
|
||||
5. **呈现给用户确认** -- 展示映射结果表格
|
||||
|
||||
示例输出:
|
||||
|
||||
```
|
||||
Slide 大纲:
|
||||
| # | 内容 | 信息类型 | 视觉形式 |
|
||||
|---|------|---------|---------|
|
||||
| 1 | 标题与主题 | -- | 标题页 |
|
||||
| 2 | 6 个核心财务指标 | 少量关键指标 | KPI 卡片网格 |
|
||||
| 3 | 2019-2024 营收趋势 | 时间序列 | 柱线混合图 |
|
||||
| 4 | 收入构成 + 解读 | 构成 + 论点 | 图文分栏 (饼图+要点) |
|
||||
| 5 | 三条核心优势 | 论点+支撑 | 标题+要点列表 |
|
||||
| ... | ... | ... | ... |
|
||||
```
|
||||
|
||||
通过 AskUserQuestion 确认 (header: "大纲"): "这个 slide 大纲是否合适?" 选项:合适 / 调整
|
||||
|
||||
### Step 1.4: 状态暂存(防止跨 Phase 遗忘)
|
||||
|
||||
确认大纲后,**必须**将分析结果写入 `.slide-plan.md`,格式如下:
|
||||
|
||||
```markdown
|
||||
# Slide Plan
|
||||
- 预设: [所选预设名称]
|
||||
- 图表方案: [ECharts / SVG / 无]
|
||||
- 编辑模式: [是 / 否]
|
||||
- 需读取的 references: [列出 Phase 3 需要的文件]
|
||||
|
||||
| # | 内容摘要 | 类型 | 视觉形式 | 需要的 CSS class |
|
||||
|---|---------|------|---------|----------------|
|
||||
| 1 | ... | ... | ... | ... |
|
||||
```
|
||||
|
||||
Phase 3 生成前**必须先读取此文件**,确保不会遗忘 Phase 1 的分析结果。
|
||||
|
||||
### Step 1.3: Image Evaluation (如有)
|
||||
|
||||
如果用户提供了图片:扫描 -> 查看 -> 评估可用性 -> 融入大纲。
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Style Discovery
|
||||
|
||||
**"看效果选风格"环节。**
|
||||
|
||||
### Step 2.0: 风格路径
|
||||
|
||||
询问 (header: "风格"):
|
||||
- "给我看几个选项"(推荐)-- 基于感受生成 3 个预览
|
||||
- "我知道我想要什么" -- 直接从预设列表选择
|
||||
|
||||
### Step 2.1: 感受选择
|
||||
|
||||
询问 (header: "感受", multiSelect: true, max 2):
|
||||
- 专业/可信 -- 机构感、值得信赖
|
||||
- 自信/有力 -- 创新、大胆
|
||||
- 沉稳/专注 -- 清晰、深思熟虑
|
||||
- 高端/精致 -- 优雅、令人难忘
|
||||
|
||||
### Step 2.2: 生成 3 个风格预览
|
||||
|
||||
读取 [STYLE_PRESETS.md](STYLE_PRESETS.md) 获取 15 个可用预设。根据感受从中选择 3 个差异化预设生成单页预览。
|
||||
|
||||
| 感受 | 建议预设(按适配度排序) |
|
||||
|------|------------------------|
|
||||
| 专业/可信 | Research Formal, Swiss Modern, Notebook Tabs, Data Dashboard |
|
||||
| 自信/有力 | Roadshow Elegance, Bold Signal, Electric Studio |
|
||||
| 沉稳/专注 | Paper & Ink, Data Dashboard, Swiss Modern |
|
||||
| 高端/精致 | Roadshow Elegance, Dark Botanical, Vintage Editorial |
|
||||
|
||||
保存预览到 `.claude-design/slide-previews/`,自动打开。
|
||||
|
||||
### Step 2.3: 用户选择
|
||||
|
||||
询问 (header: "风格选择"): 哪个风格?选项:A / B / C / 混合
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Generate Presentation
|
||||
|
||||
### Step 3.0: 读取状态 + 防护模板
|
||||
|
||||
1. **读取 `.slide-plan.md`** -- 恢复 Phase 1 的分析结果(类型、视觉形式、需要的文件)
|
||||
2. **复制 [references/safe-base-template.html](references/safe-base-template.html) 作为起点** -- 此模板已内置 viewport-base.css、中文字体、A 股配色、formatCN、SlidePresentation 控制器。在此基础上添加内容,**不要删除模板中的任何 CSS 变量或 JS 函数**。
|
||||
|
||||
### Step 3.1: 按需读取参考文件
|
||||
|
||||
根据 `.slide-plan.md` 中列出的 references 读取:
|
||||
|
||||
1. **[STYLE_PRESETS.md](STYLE_PRESETS.md)** -- 获取所选风格的配色、字体、签名元素,覆盖模板中的 SLOT: THEME 变量
|
||||
2. **(按需)** [references/charts-base.md](references/charts-base.md) + 对应的 charts-trend/composition/matrix/flow -- 按 `.slide-plan.md` 列出的图表类型读取
|
||||
3. **(按需)** [references/financial-layouts.md](references/financial-layouts.md) -- 如有 KPI/表格/时间线/对比等布局
|
||||
4. **(按需)** [references/animation-patterns.md](references/animation-patterns.md) -- 高级动画效果
|
||||
5. **(按需)** [references/html-template.md](references/html-template.md) -- 编辑功能等 JS 参考
|
||||
|
||||
### Step 3.1.5: ECharts 内联(如需图表)
|
||||
|
||||
如果 `.slide-plan.md` 中图表方案为 ECharts,**必须**将 ECharts 库内嵌到 HTML 中:
|
||||
|
||||
1. 读取本地文件 [references/echarts.min.js](references/echarts.min.js)(~1MB,已预存在 skill 目录中)
|
||||
2. 将完整内容包裹在 `<script>/* ECharts v5 */...内容...</script>` 中
|
||||
3. 放入模板的 `<!-- SLOT: ECHARTS INLINE -->` 位置
|
||||
4. **禁止使用 `<script src="...">` 外部引用** -- 最终 HTML 必须离线可用
|
||||
|
||||
### Step 3.2: 生成 slide 内容
|
||||
|
||||
在 safe-base-template.html 的 `<!-- SLOT: SLIDES -->` 位置添加 slide。每张 slide 的视觉形式严格按 `.slide-plan.md` 执行。
|
||||
|
||||
**slide 结构由 Phase 1 的 content-mapping 结果驱动,而非固定模板。每份演示文稿的页面组合都应该不同。**
|
||||
|
||||
---
|
||||
|
||||
## Phase 3.5: 代码完整性自检(生成后必须执行)
|
||||
|
||||
输出最终 HTML 前,逐项检查以下清单。任何一项失败,修复后再继续。
|
||||
|
||||
- [ ] `<html lang="zh-CN">` 已设置
|
||||
- [ ] safe-base-template 的核心 CSS 完整保留(搜索 `scroll-snap-type` 确认存在)
|
||||
- [ ] 所有 `font-size:` 使用 `clamp()`(搜索 `font-size:`,确认无固定 px/rem 值)
|
||||
- [ ] `formatCN` 函数已内嵌(如有数字数据)
|
||||
- [ ] 无 emoji 字符(涨跌用 `.trend-up` / `.trend-down` CSS 类)
|
||||
- [ ] 每个 `.slide` 继承了 `overflow: hidden`(来自 safe-base-template)
|
||||
- [ ] 中文字体已加载(Google Fonts link 包含 `Noto+Sans+SC` 或 `Noto+Serif+SC`)
|
||||
- [ ] `--color-positive: #dc2626` 和 `--color-negative: #16a34a` 存在(A 股默认)
|
||||
- [ ] **零外部依赖**:无 `<script src="...">` 外部引用(如有 ECharts,确认是内联 `<script>` 而非 CDN)
|
||||
|
||||
**如有任何项失败,修复后再继续。不要跳过自检。**
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: PPT Conversion
|
||||
|
||||
1. 运行 `python scripts/extract-pptx.py <input.pptx> <output_dir>`
|
||||
2. 展示提取结果,与用户确认
|
||||
3. 进入 Phase 2 选择风格
|
||||
4. 生成 HTML,保留原始内容和图片
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Delivery
|
||||
|
||||
1. 删除 `.claude-design/slide-previews/`
|
||||
2. `open [filename].html`
|
||||
3. 告知:文件位置、风格、页数、导航方式、自定义方法
|
||||
4. **如需通过飞书发送给用户**,使用 message 工具:
|
||||
|
||||
```javascript
|
||||
message({
|
||||
action: "send",
|
||||
channel: "feishu",
|
||||
path: "/home/core/.openclaw/workspace/output/presentation.html",
|
||||
filename: "presentation.html",
|
||||
caption: "基金分析演示文稿"
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fallback: 降级模式
|
||||
|
||||
当内容超过 15 页、或包含 3 种以上图表类型、或模型多次生成失败时,切换到简化路径:
|
||||
|
||||
**布局限制为 3 种:**
|
||||
- KPI 卡片网格(指标页)
|
||||
- 图文分栏(解读页)
|
||||
- 标题 + 要点列表(论述页)
|
||||
|
||||
**风格固定为 Data Dashboard:** 最简洁的预设,最低出错概率。
|
||||
|
||||
**功能限制:**
|
||||
- 不使用 inline editing
|
||||
- 动画只用 fade-in(`.reveal` 类)
|
||||
- 图表只用柱状图和折线图(不用旭日图/桑基图等复杂类型)
|
||||
- 不使用自定义导航(sidebar/nav-dots)
|
||||
|
||||
**分步生成:** 先生成前 3 页预览,确认正确后再生成剩余页面。避免一次生成全部后发现基础层有错。
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
```
|
||||
fund-slides/
|
||||
SKILL.md ← 入口(始终加载)
|
||||
content-mapping.md ← 信息类型→视觉形式映射 + 强制检查清单
|
||||
STYLE_PRESETS.md ← 15 个视觉预设
|
||||
references/
|
||||
safe-base-template.html ← 防护性基础模板(Phase 3 起点,内置全部安全层)
|
||||
echarts.min.js ← ECharts v5 库(~1MB,生成时内联到 HTML)
|
||||
viewport-base.css ← 响应式 CSS + CJK + 全局 A 股配色 + 趋势箭头
|
||||
animation-patterns.md ← 动画模式
|
||||
charts-base.md ← ECharts 基础设施 + 速查表 + SVG
|
||||
charts-trend.md ← 折线/面积/柱状图
|
||||
charts-composition.md ← 饼图/玫瑰/Treemap/旭日图
|
||||
charts-matrix.md ← 热力图/雷达/散点图
|
||||
charts-flow.md ← 瀑布/K线/漏斗/桑基图
|
||||
financial-layouts.md ← KPI/表格/时间线/对比布局
|
||||
html-template.md ← HTML 结构 + JS 功能
|
||||
scripts/
|
||||
extract-pptx.py ← PPT 内容提取(Phase 4)
|
||||
```
|
||||
|
||||
| File | When to Read |
|
||||
|------|-------------|
|
||||
| content-mapping.md | **MANDATORY** Phase 1.2(强制检查清单) |
|
||||
| STYLE_PRESETS.md | **MANDATORY** Phase 2 + Phase 3.1 |
|
||||
| references/safe-base-template.html | **MANDATORY** Phase 3.0(复制为起点) |
|
||||
| references/charts-base.md | Phase 3.1 -- 含图表时(基础设施 + 速查表) |
|
||||
| references/charts-trend.md | Phase 3.1 -- 含折线/面积/柱状图时 |
|
||||
| references/charts-composition.md | Phase 3.1 -- 含饼图/玫瑰/Treemap/旭日图时 |
|
||||
| references/charts-matrix.md | Phase 3.1 -- 含热力图/雷达/散点图时 |
|
||||
| references/charts-flow.md | Phase 3.1 -- 含瀑布/K线/漏斗/桑基图时 |
|
||||
| references/financial-layouts.md | Phase 3.1 -- 含 KPI/表格/时间线/对比时 |
|
||||
| references/animation-patterns.md | Phase 3.1 -- 高级动画时 |
|
||||
| references/html-template.md | Phase 3.1 -- 编辑功能等 JS 参考 |
|
||||
559
fund-slides/STYLE_PRESETS.md
Normal file
559
fund-slides/STYLE_PRESETS.md
Normal file
@ -0,0 +1,559 @@
|
||||
# Style Presets Reference
|
||||
|
||||
Curated visual styles for Frontend Slides. Each preset is inspired by real design references — no generic "AI slop" aesthetics. **Abstract shapes only — no illustrations.**
|
||||
|
||||
**Viewport CSS:** For mandatory base styles, see [references/viewport-base.css](references/viewport-base.css). Include in every presentation.
|
||||
|
||||
---
|
||||
|
||||
## Chinese Font Strategy
|
||||
|
||||
Every preset must include Chinese font fallback. Load via Google Fonts alongside Latin fonts.
|
||||
|
||||
**Standard Chinese Font Stack:**
|
||||
|
||||
| Category | Font | Weight | Use Case |
|
||||
|----------|------|--------|----------|
|
||||
| Sans | Noto Sans SC | 300-700 | Body text, UI, data labels |
|
||||
| Serif | Noto Serif SC | 400-700 | Display headings, editorial |
|
||||
| Literary | LXGW WenKai | 400-700 | Drop caps, quotes, literary feel |
|
||||
|
||||
**Loading template:**
|
||||
```html
|
||||
<!-- Add Chinese font families to the Google Fonts link -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Noto+Serif+SC:wght@400;600;700&family=LXGW+WenKai:wght@400;700&display=swap" rel="stylesheet">
|
||||
```
|
||||
|
||||
**Font-family pairing rules:**
|
||||
- Sans-serif preset display/body: append `, 'Noto Sans SC', sans-serif`
|
||||
- Serif preset display: append `, 'Noto Serif SC', serif`
|
||||
- Serif preset body: append `, 'Noto Sans SC', sans-serif`
|
||||
- Mono preset: append `, 'Noto Sans SC', sans-serif` as final fallback
|
||||
- Literary/editorial preset: can use `'LXGW WenKai'` for display, `'Noto Sans SC'` for body
|
||||
|
||||
Example:
|
||||
```css
|
||||
:root {
|
||||
--font-display: 'Archivo Black', 'Noto Sans SC', sans-serif;
|
||||
--font-body: 'Space Grotesk', 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dark Themes
|
||||
|
||||
### 1. Bold Signal
|
||||
|
||||
**Vibe:** Confident, bold, modern, high-impact
|
||||
|
||||
**Layout:** Colored card on dark gradient. Number top-left, navigation top-right, title bottom-left.
|
||||
|
||||
**Typography:**
|
||||
- Display: `Archivo Black`, `Noto Sans SC` (900)
|
||||
- Body: `Space Grotesk`, `Noto Sans SC` (400/500)
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-primary: #1a1a1a;
|
||||
--bg-gradient: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 50%, #1a1a1a 100%);
|
||||
--card-bg: #FF5722;
|
||||
--text-primary: #ffffff;
|
||||
--text-on-card: #1a1a1a;
|
||||
--font-display: 'Archivo Black', 'Noto Sans SC', sans-serif;
|
||||
--font-body: 'Space Grotesk', 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Bold colored card as focal point (orange, coral, or vibrant accent)
|
||||
- Large section numbers (01, 02, etc.)
|
||||
- Navigation breadcrumbs with active/inactive opacity states
|
||||
- Grid-based layout for precise alignment
|
||||
|
||||
---
|
||||
|
||||
### 2. Electric Studio
|
||||
|
||||
**Vibe:** Bold, clean, professional, high contrast
|
||||
|
||||
**Layout:** Split panel—white top, blue bottom. Brand marks in corners.
|
||||
|
||||
**Typography:**
|
||||
- Display: `Manrope`, `Noto Sans SC` (800)
|
||||
- Body: `Manrope`, `Noto Sans SC` (400/500)
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-dark: #0a0a0a;
|
||||
--bg-white: #ffffff;
|
||||
--accent-blue: #4361ee;
|
||||
--text-dark: #0a0a0a;
|
||||
--text-light: #ffffff;
|
||||
--font-display: 'Manrope', 'Noto Sans SC', sans-serif;
|
||||
--font-body: 'Manrope', 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Two-panel vertical split
|
||||
- Accent bar on panel edge
|
||||
- Quote typography as hero element
|
||||
- Minimal, confident spacing
|
||||
|
||||
---
|
||||
|
||||
### 3. Creative Voltage
|
||||
|
||||
**Vibe:** Bold, creative, energetic, retro-modern
|
||||
|
||||
**Layout:** Split panels—electric blue left, dark right. Script accents.
|
||||
|
||||
**Typography:**
|
||||
- Display: `Syne`, `Noto Sans SC` (700/800)
|
||||
- Mono: `Space Mono`, `Noto Sans SC` (400/700)
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-primary: #0066ff;
|
||||
--bg-dark: #1a1a2e;
|
||||
--accent-neon: #d4ff00;
|
||||
--text-light: #ffffff;
|
||||
--font-display: 'Syne', 'Noto Sans SC', sans-serif;
|
||||
--font-body: 'Space Mono', 'Noto Sans SC', monospace;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Electric blue + neon yellow contrast
|
||||
- Halftone texture patterns
|
||||
- Neon badges/callouts
|
||||
- Script typography for creative flair
|
||||
|
||||
---
|
||||
|
||||
### 4. Dark Botanical
|
||||
|
||||
**Vibe:** Elegant, sophisticated, artistic, premium
|
||||
|
||||
**Layout:** Centered content on dark. Abstract soft shapes in corner.
|
||||
|
||||
**Typography:**
|
||||
- Display: `Cormorant`, `Noto Serif SC` (400/600) — elegant serif
|
||||
- Body: `IBM Plex Sans`, `Noto Sans SC` (300/400)
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-primary: #0f0f0f;
|
||||
--text-primary: #e8e4df;
|
||||
--text-secondary: #9a9590;
|
||||
--accent-warm: #d4a574;
|
||||
--accent-pink: #e8b4b8;
|
||||
--accent-gold: #c9b896;
|
||||
--font-display: 'Cormorant', 'Noto Serif SC', serif;
|
||||
--font-body: 'IBM Plex Sans', 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Abstract soft gradient circles (blurred, overlapping)
|
||||
- Warm color accents (pink, gold, terracotta)
|
||||
- Thin vertical accent lines
|
||||
- Italic signature typography
|
||||
- **No illustrations—only abstract CSS shapes**
|
||||
|
||||
---
|
||||
|
||||
## Light Themes
|
||||
|
||||
### 5. Notebook Tabs
|
||||
|
||||
**Vibe:** Editorial, organized, elegant, tactile
|
||||
|
||||
**Layout:** Cream paper card on dark background. Colorful tabs on right edge.
|
||||
|
||||
**Typography:**
|
||||
- Display: `Bodoni Moda`, `Noto Serif SC` (400/700) — classic editorial
|
||||
- Body: `DM Sans`, `Noto Sans SC` (400/500)
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-outer: #2d2d2d;
|
||||
--bg-page: #f8f6f1;
|
||||
--text-primary: #1a1a1a;
|
||||
--tab-1: #98d4bb; /* Mint */
|
||||
--tab-2: #c7b8ea; /* Lavender */
|
||||
--tab-3: #f4b8c5; /* Pink */
|
||||
--tab-4: #a8d8ea; /* Sky */
|
||||
--tab-5: #ffe6a7; /* Cream */
|
||||
--font-display: 'Bodoni Moda', 'Noto Serif SC', serif;
|
||||
--font-body: 'DM Sans', 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Paper container with subtle shadow
|
||||
- Colorful section tabs on right edge (vertical text)
|
||||
- Binder hole decorations on left
|
||||
- Tab text must scale with viewport: `font-size: clamp(0.5rem, 1vh, 0.7rem)`
|
||||
|
||||
---
|
||||
|
||||
### 6. Pastel Geometry
|
||||
|
||||
**Vibe:** Friendly, organized, modern, approachable
|
||||
|
||||
**Layout:** White card on pastel background. Vertical pills on right edge.
|
||||
|
||||
**Typography:**
|
||||
- Display: `Plus Jakarta Sans`, `Noto Sans SC` (700/800)
|
||||
- Body: `Plus Jakarta Sans`, `Noto Sans SC` (400/500)
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-primary: #c8d9e6;
|
||||
--card-bg: #faf9f7;
|
||||
--pill-pink: #f0b4d4;
|
||||
--pill-mint: #a8d4c4;
|
||||
--pill-sage: #5a7c6a;
|
||||
--pill-lavender: #9b8dc4;
|
||||
--pill-violet: #7c6aad;
|
||||
--font-display: 'Plus Jakarta Sans', 'Noto Sans SC', sans-serif;
|
||||
--font-body: 'Plus Jakarta Sans', 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Rounded card with soft shadow
|
||||
- **Vertical pills on right edge** with varying heights (like tabs)
|
||||
- Consistent pill width, heights: short → medium → tall → medium → short
|
||||
- Download/action icon in corner
|
||||
|
||||
---
|
||||
|
||||
### 7. Split Pastel
|
||||
|
||||
**Vibe:** Playful, modern, friendly, creative
|
||||
|
||||
**Layout:** Two-color vertical split (peach left, lavender right).
|
||||
|
||||
**Typography:**
|
||||
- Display: `Outfit`, `Noto Sans SC` (700/800)
|
||||
- Body: `Outfit`, `Noto Sans SC` (400/500)
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-peach: #f5e6dc;
|
||||
--bg-lavender: #e4dff0;
|
||||
--text-dark: #1a1a1a;
|
||||
--badge-mint: #c8f0d8;
|
||||
--badge-yellow: #f0f0c8;
|
||||
--badge-pink: #f0d4e0;
|
||||
--font-display: 'Outfit', 'Noto Sans SC', sans-serif;
|
||||
--font-body: 'Outfit', 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Split background colors
|
||||
- Playful badge pills with icons
|
||||
- Grid pattern overlay on right panel
|
||||
- Rounded CTA buttons
|
||||
|
||||
---
|
||||
|
||||
### 8. Vintage Editorial
|
||||
|
||||
**Vibe:** Witty, confident, editorial, personality-driven
|
||||
|
||||
**Layout:** Centered content on cream. Abstract geometric shapes as accent.
|
||||
|
||||
**Typography:**
|
||||
- Display: `Fraunces`, `Noto Serif SC` (700/900) — distinctive serif
|
||||
- Body: `Work Sans`, `Noto Sans SC` (400/500)
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-cream: #f5f3ee;
|
||||
--text-primary: #1a1a1a;
|
||||
--text-secondary: #555;
|
||||
--accent-warm: #e8d4c0;
|
||||
--font-display: 'Fraunces', 'Noto Serif SC', serif;
|
||||
--font-body: 'Work Sans', 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Abstract geometric shapes (circle outline + line + dot)
|
||||
- Bold bordered CTA boxes
|
||||
- Witty, conversational copy style
|
||||
- **No illustrations—only geometric CSS shapes**
|
||||
|
||||
---
|
||||
|
||||
## Specialty Themes
|
||||
|
||||
### 9. Neon Cyber
|
||||
|
||||
**Vibe:** Futuristic, techy, confident
|
||||
|
||||
**Typography:** `Clash Display` + `Satoshi` (Fontshare), CN fallback: `Noto Sans SC`
|
||||
|
||||
**Colors:** Deep navy (#0a0f1c), cyan accent (#00ffcc), magenta (#ff00aa)
|
||||
|
||||
**Font vars:**
|
||||
```css
|
||||
:root {
|
||||
--font-display: 'Clash Display', 'Noto Sans SC', sans-serif;
|
||||
--font-body: 'Satoshi', 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature:** Particle backgrounds, neon glow, grid patterns
|
||||
|
||||
---
|
||||
|
||||
### 10. Terminal Green
|
||||
|
||||
**Vibe:** Developer-focused, hacker aesthetic
|
||||
|
||||
**Typography:** `JetBrains Mono` (monospace only), CN fallback: `Noto Sans SC`
|
||||
|
||||
**Colors:** GitHub dark (#0d1117), terminal green (#39d353)
|
||||
|
||||
**Font vars:**
|
||||
```css
|
||||
:root {
|
||||
--font-display: 'JetBrains Mono', 'Noto Sans SC', monospace;
|
||||
--font-body: 'JetBrains Mono', 'Noto Sans SC', monospace;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature:** Scan lines, blinking cursor, code syntax styling
|
||||
|
||||
---
|
||||
|
||||
### 11. Swiss Modern
|
||||
|
||||
**Vibe:** Clean, precise, Bauhaus-inspired
|
||||
|
||||
**Typography:** `Archivo` (800) + `Nunito` (400), CN fallback: `Noto Sans SC`
|
||||
|
||||
**Colors:** Pure white, pure black, red accent (#ff3300)
|
||||
|
||||
**Font vars:**
|
||||
```css
|
||||
:root {
|
||||
--font-display: 'Archivo', 'Noto Sans SC', sans-serif;
|
||||
--font-body: 'Nunito', 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature:** Visible grid, asymmetric layouts, geometric shapes
|
||||
|
||||
---
|
||||
|
||||
### 12. Paper & Ink
|
||||
|
||||
**Vibe:** Editorial, literary, thoughtful
|
||||
|
||||
**Typography:** `Cormorant Garamond` + `Source Serif 4`, CN: `LXGW WenKai` (display) + `Noto Serif SC` (body)
|
||||
|
||||
**Colors:** Warm cream (#faf9f7), charcoal (#1a1a1a), crimson accent (#c41e3a)
|
||||
|
||||
**Font vars:**
|
||||
```css
|
||||
:root {
|
||||
--font-display: 'Cormorant Garamond', 'LXGW WenKai', serif;
|
||||
--font-body: 'Source Serif 4', 'Noto Serif SC', serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature:** Drop caps, pull quotes, elegant horizontal rules
|
||||
|
||||
---
|
||||
|
||||
## Font Pairing Quick Reference
|
||||
|
||||
| Preset | Display Font | Body Font | CN Fallback | Source |
|
||||
|--------|--------------|-----------|-------------|--------|
|
||||
| Bold Signal | Archivo Black | Space Grotesk | Noto Sans SC | Google |
|
||||
| Electric Studio | Manrope | Manrope | Noto Sans SC | Google |
|
||||
| Creative Voltage | Syne | Space Mono | Noto Sans SC | Google |
|
||||
| Dark Botanical | Cormorant | IBM Plex Sans | Noto Serif SC / Noto Sans SC | Google |
|
||||
| Notebook Tabs | Bodoni Moda | DM Sans | Noto Serif SC / Noto Sans SC | Google |
|
||||
| Pastel Geometry | Plus Jakarta Sans | Plus Jakarta Sans | Noto Sans SC | Google |
|
||||
| Split Pastel | Outfit | Outfit | Noto Sans SC | Google |
|
||||
| Vintage Editorial | Fraunces | Work Sans | Noto Serif SC / Noto Sans SC | Google |
|
||||
| Neon Cyber | Clash Display | Satoshi | Noto Sans SC | Fontshare |
|
||||
| Terminal Green | JetBrains Mono | JetBrains Mono | Noto Sans SC | JetBrains |
|
||||
| Swiss Modern | Archivo | Nunito | Noto Sans SC | Google |
|
||||
| Paper & Ink | Cormorant Garamond | Source Serif 4 | LXGW WenKai / Noto Serif SC | Google |
|
||||
| **Research Formal** | Noto Serif SC | Noto Sans SC | (native) | Google |
|
||||
| **Roadshow Elegance** | Playfair Display | Noto Sans SC | Noto Serif SC | Google |
|
||||
| **Data Dashboard** | Noto Sans SC | Noto Sans SC | (native) | Google |
|
||||
|
||||
---
|
||||
|
||||
## Financial Themes (金融专用)
|
||||
|
||||
### 13. Research Formal (研报正式)
|
||||
|
||||
**Vibe:** Institutional, authoritative, data-driven. Suitable for research reports, analyst presentations, internal review meetings.
|
||||
|
||||
**Layout:** Clean white background with navy sidebar navigation. Emphasis on tables and structured data. Section numbers in sidebar.
|
||||
|
||||
**Typography:**
|
||||
- Display: `Noto Serif SC` (600/700) — authoritative serif for Chinese headings
|
||||
- Body: `Noto Sans SC` (400/500) — clean sans for data and body text
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f8f9fa;
|
||||
--text-primary: #1a1a2e;
|
||||
--text-secondary: #5a5a7a;
|
||||
--accent: #1a365d;
|
||||
--accent-secondary: #2563eb;
|
||||
--accent-gold: #c5963a;
|
||||
--border-color: rgba(26, 54, 93, 0.12);
|
||||
--color-positive: #dc2626;
|
||||
--color-negative: #16a34a;
|
||||
--font-display: 'Noto Serif SC', serif;
|
||||
--font-body: 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Navy blue sidebar with section navigation and page numbers
|
||||
- Horizontal rule separators between sections (2px solid navy)
|
||||
- Tables with navy header row and alternating gray rows
|
||||
- Source citations in small text at slide bottom
|
||||
- Company/team logo placement: top-right corner
|
||||
- Section numbering: large navy numerals (01, 02, 03)
|
||||
|
||||
**Financial Layouts:** Optimized for KPI Dashboard, Data Table, Chart + Text Split. See [references/financial-layouts.md](references/financial-layouts.md).
|
||||
|
||||
---
|
||||
|
||||
### 14. Roadshow Elegance (路演风范)
|
||||
|
||||
**Vibe:** Premium, confident, investor-grade. For IPO roadshows, investor meetings, board presentations.
|
||||
|
||||
**Layout:** Dark gradient background with centered content. Gold accent highlights key metrics. Generous whitespace for gravitas.
|
||||
|
||||
**Typography:**
|
||||
- Display: `Playfair Display`, `Noto Serif SC` (700/800) — premium serif
|
||||
- Body: `Noto Sans SC` (300/400) — light weight for elegance
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-primary: #0c1929;
|
||||
--bg-gradient: linear-gradient(145deg, #0c1929 0%, #162d50 40%, #0c1929 100%);
|
||||
--text-primary: #f0ece4;
|
||||
--text-secondary: #8a9bb5;
|
||||
--accent: #d4a853;
|
||||
--accent-secondary: #e8c882;
|
||||
--border-color: rgba(212, 168, 83, 0.2);
|
||||
--color-positive: #ef4444;
|
||||
--color-negative: #22c55e;
|
||||
--card-bg: rgba(255, 255, 255, 0.04);
|
||||
--font-display: 'Playfair Display', 'Noto Serif SC', serif;
|
||||
--font-body: 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Subtle radial gradient glow behind key content
|
||||
- Gold accent lines (1px) as decorative separators
|
||||
- Large KPI values in gold with light glow effect
|
||||
- Minimal text, maximum impact
|
||||
- Abstract geometric shapes: thin gold circles, lines
|
||||
- Progress indicators and slide numbers in muted gold
|
||||
|
||||
**Best for:** Title slides with company valuation, KPI highlights with trend arrows (CSS `::after` triangles, not emoji), full-width chart slides with dark chart themes.
|
||||
|
||||
---
|
||||
|
||||
### 15. Data Dashboard (数据简报)
|
||||
|
||||
**Vibe:** Efficient, analytical, information-dense. For daily/weekly market updates, portfolio reviews, data-driven briefings.
|
||||
|
||||
**Layout:** Grid-based with tight spacing. Header bar with date and context. Content area maximizes data display. 2-3 column grids for KPI cards.
|
||||
|
||||
**Typography:**
|
||||
- Display: `Noto Sans SC` (700) — clean, functional
|
||||
- Body: `Noto Sans SC` (400) — consistent sans throughout
|
||||
|
||||
**Colors:**
|
||||
```css
|
||||
:root {
|
||||
--bg-primary: #f5f5f5;
|
||||
--bg-card: #ffffff;
|
||||
--text-primary: #1f2937;
|
||||
--text-secondary: #6b7280;
|
||||
--accent: #2563eb;
|
||||
--accent-secondary: #60a5fa;
|
||||
--border-color: #e5e7eb;
|
||||
--color-positive: #dc2626;
|
||||
--color-negative: #16a34a;
|
||||
--font-display: 'Noto Sans SC', sans-serif;
|
||||
--font-body: 'Noto Sans SC', sans-serif;
|
||||
}
|
||||
```
|
||||
|
||||
**Signature Elements:**
|
||||
- Top status bar: date + report title + page count
|
||||
- Card-based layout with thin borders (1px solid #e5e7eb)
|
||||
- Compact KPI cards with mini inline sparklines (SVG)
|
||||
- Data tables with fixed header and zebra striping
|
||||
- Trend indicators: CSS triangles + percentage (never emoji)
|
||||
- Minimal decoration, maximum data real estate
|
||||
- Color coding: blue = neutral/info, red = up (A-share), green = down
|
||||
|
||||
**Best for:** Multi-metric overview pages, dense comparison tables, chart grids (2x2), status/alert slides.
|
||||
|
||||
---
|
||||
|
||||
## DO NOT USE (Generic AI Patterns)
|
||||
|
||||
**Fonts:** Inter, Roboto, Arial, system fonts as display
|
||||
|
||||
**Colors:** `#6366f1` (generic indigo), purple gradients on white
|
||||
|
||||
**Layouts:** Everything centered, generic hero sections, identical card grids
|
||||
|
||||
**Decorations:** Realistic illustrations, gratuitous glassmorphism, drop shadows without purpose
|
||||
|
||||
---
|
||||
|
||||
## CSS Gotchas
|
||||
|
||||
### Negating CSS Functions
|
||||
|
||||
**WRONG — silently ignored by browsers (no console error):**
|
||||
```css
|
||||
right: -clamp(28px, 3.5vw, 44px); /* Browser ignores this */
|
||||
margin-left: -min(10vw, 100px); /* Browser ignores this */
|
||||
```
|
||||
|
||||
**CORRECT — wrap in `calc()`:**
|
||||
```css
|
||||
right: calc(-1 * clamp(28px, 3.5vw, 44px)); /* Works */
|
||||
margin-left: calc(-1 * min(10vw, 100px)); /* Works */
|
||||
```
|
||||
|
||||
CSS does not allow a leading `-` before function names. The browser silently discards the entire declaration — no error, the element just appears in the wrong position. **Always use `calc(-1 * ...)` to negate CSS function values.**
|
||||
|
||||
211
fund-slides/content-mapping.md
Normal file
211
fund-slides/content-mapping.md
Normal file
@ -0,0 +1,211 @@
|
||||
# Content Mapping Reference
|
||||
|
||||
信息类型到视觉形式的映射。这是本技能的核心知识 -- 给定任意内容,选择最佳的 slide 形态。
|
||||
|
||||
生成演示文稿时,按以下流程使用本表:
|
||||
1. 识别用户提供的每块内容属于哪种信息类型
|
||||
2. 查表确定对应的视觉形式
|
||||
3. 按内容密度限制拆分 slide
|
||||
4. 选择匹配的 CSS 布局和组件
|
||||
|
||||
---
|
||||
|
||||
## 核心映射表
|
||||
|
||||
### 数值与指标类
|
||||
|
||||
| 信息类型 | 典型内容 | 推荐视觉形式 | 最大密度 | 何时拆分 |
|
||||
|----------|---------|-------------|---------|---------|
|
||||
| 少量关键指标 (3-6 个) | 营收、利润率、用户数、ROE | **KPI 卡片网格** -- 大数字 + 标签 + 趋势标记 | 6 张卡片 | >6 个指标时分页 |
|
||||
| 单个核心数字 + 解释 | "市占率 35%" + 为什么重要 | **Hero 数字** -- 超大数字居中,下方 2-3 行解释 | 1 个数字 + 3 行文字 | 不拆分 |
|
||||
| 指标 + 同比/环比变化 | 本季收入 vs 上季 | **KPI 卡片 + CSS 三角趋势箭头** | 同 KPI 网格 | 同上 |
|
||||
| 评分/打分 (多维度) | 五维能力评分、护城河评级 | **评分条 (progress bar)** -- 名称 + 分数 + 填充条 | 6 个维度 | >6 时分页 |
|
||||
|
||||
### 时序与趋势类
|
||||
|
||||
| 信息类型 | 典型内容 | 推荐视觉形式 | 备注 |
|
||||
|----------|---------|-------------|------|
|
||||
| 单指标时间序列 (3-8 期) | 年度营收 2019-2024 | **柱状图** (ECharts/SVG) | 直观看绝对值和趋势 |
|
||||
| 多指标时间序列 (2-3 条线) | 收入 + 利润 + 毛利率 | **柱线混合图** -- 柱状(绝对值) + 折线(比率) | 双 Y 轴,左绝对值右百分比 |
|
||||
| 增长率/变化率序列 | YoY 增速变化 | **折线图** 或 **面积图** | 强调趋势方向 |
|
||||
| 长期趋势 (>8 期) | 10 年股价、月度数据 | **面积折线图** | 数据点密集时隐藏标签 |
|
||||
|
||||
### 结构与构成类
|
||||
|
||||
| 信息类型 | 典型内容 | 推荐视觉形式 | 备注 |
|
||||
|----------|---------|-------------|------|
|
||||
| 整体拆分为部分 (3-6 项) | 收入结构、成本构成 | **环形饼图** (donut) | >6 项时合并长尾为"其他" |
|
||||
| 部分之间有层级 | 业务线 > 子业务 > 产品 | **矩形树图** (treemap) | 层级不超过 2 层 |
|
||||
| 加减关系 (从 A 到 B 的拆解) | 营收 - 成本 - 费用 = 利润 | **瀑布图** (waterfall) | 正负值用不同颜色 |
|
||||
| 占比对比 (多主体同一维度) | 各公司市占率 | **堆叠柱状图** 或 **分组柱状图** | 主体 <=5 个 |
|
||||
|
||||
### 对比与关系类
|
||||
|
||||
| 信息类型 | 典型内容 | 推荐视觉形式 | 最大密度 |
|
||||
|----------|---------|-------------|---------|
|
||||
| 多主体同维度对比 (3-5 项) | 可比公司估值、产品对比 | **对比卡片网格** -- 每卡片 4-5 行指标 | 4 张卡片 x 5 行 |
|
||||
| 两个主体深度对比 | A 方案 vs B 方案 | **双列对照** -- 左右分栏,逐行对比 | 5-6 行对比项 |
|
||||
| 优劣势分析 | 优势/劣势/机会/威胁 | **2x2 网格卡片** -- 每格 3-4 条 | 4 格 x 4 条 |
|
||||
| 多维度矩阵 (>5 行 x 4 列) | 财务报表、详细数据 | **数据表格** -- 表头 + 交替行 + 高亮列 | 8 行 x 6 列 |
|
||||
|
||||
### 叙事与论点类
|
||||
|
||||
| 信息类型 | 典型内容 | 推荐视觉形式 | 最大密度 |
|
||||
|----------|---------|-------------|---------|
|
||||
| 核心论点 + 支撑要点 | "我们认为 X,因为 A/B/C" | **标题 + 要点列表** -- 每条 1-2 行 | 1 标题 + 5 条 |
|
||||
| 论点 + 数据佐证 | "增速领先" + 图表 | **图文分栏** -- 左文字右图表(或反过来) | 3-4 条文字 + 1 图表 |
|
||||
| 因果链/推导过程 | A 导致 B,B 导致 C | **步骤流程** -- 水平箭头连接的卡片 | 3-5 步 |
|
||||
| 单条核心引用/结论 | "护城河评级: 宽" | **引用页** -- 大字居中 + 出处 | 1 句 (3 行内) |
|
||||
| 多条并列结论 | 投资建议的 3-5 个关注点 | **结论列表** -- 左边框线 + 加粗主句 + 说明 | 4-5 条 |
|
||||
|
||||
### 时间与过程类
|
||||
|
||||
| 信息类型 | 典型内容 | 推荐视觉形式 | 最大密度 |
|
||||
|----------|---------|-------------|---------|
|
||||
| 历史事件序列 | 公司发展史、政策演变 | **纵向时间线** -- 年份 + 节点 + 描述 | 4-5 个节点 |
|
||||
| 未来规划/路线图 | Q1 做 A, Q2 做 B | **横向时间线** -- 阶段 + 里程碑 | 4-6 个阶段 |
|
||||
| 流程步骤 | 投资决策流程、审批流程 | **编号步骤** -- 序号圆 + 标题 + 描述 | 4-5 步 |
|
||||
|
||||
### 风险与评估类
|
||||
|
||||
| 信息类型 | 典型内容 | 推荐视觉形式 | 备注 |
|
||||
|----------|---------|-------------|------|
|
||||
| 风险清单 (含严重性) | 5 个风险 + 影响 + 概率 | **风险表格** -- 3-4 列,含严重性标签 | 5-6 行 |
|
||||
| 评级/评分总结 | 总分 + 分项 | **评分仪表盘** -- 总分居中 + 分项条 | 总分 + 5-6 分项 |
|
||||
| 正面/负面因素对比 | 利好 vs 利空 | **双列对照** -- 绿色左列/红色右列 | 每列 3-4 条 |
|
||||
|
||||
---
|
||||
|
||||
## 视觉形式速查
|
||||
|
||||
按使用频率排序。每种形式对应的完整 HTML/CSS 实现在 [references/financial-layouts.md](references/financial-layouts.md) 和 [references/html-template.md](references/html-template.md) 中。
|
||||
|
||||
### CSS 布局类
|
||||
|
||||
| 视觉形式 | CSS class / 组件 | 适用场景 | 参考文件 |
|
||||
|----------|-----------------|---------|---------|
|
||||
| KPI 卡片网格 | `.kpi-grid > .kpi-card` | 指标概览 | financial-layouts.md #1 |
|
||||
| 标题 + 要点列表 | `h2 + .insight-list` | 论点阐述 | html-template.md |
|
||||
| 数据表格 | `.data-table` | 多维数据 | financial-layouts.md #2 |
|
||||
| 图文分栏 | `.split-layout` | 数据 + 解读 | financial-layouts.md #5 |
|
||||
| 对比卡片 | `.comparison-grid > .comp-card` | 多主体对比 | financial-layouts.md #4 |
|
||||
| 时间线 | `.timeline > .timeline-item` | 事件序列 | financial-layouts.md #3 |
|
||||
| 评分条 | `.moat-item > .moat-bar` | 多维评分 | (inline) |
|
||||
| Hero 数字 | 自定义大字 + 说明 | 单核心指标强调 | (inline) |
|
||||
| 引用页 | `blockquote` 居中 | 核心结论/引用 | html-template.md |
|
||||
| 双列对照 | 2-col grid | A vs B | (inline) |
|
||||
| 步骤流程 | numbered steps | 过程/流程 | (inline) |
|
||||
|
||||
### ECharts 图表类
|
||||
|
||||
| 图表类型 | 适用信息 | 备注 |
|
||||
|----------|---------|------|
|
||||
| 面积图 | 单指标趋势,强调规模感 | 渐变填充增加视觉重量 |
|
||||
| 堆叠面积图 | 多组分构成随时间的变化 | 同时看总量和各部分 |
|
||||
| 柱线混合 + 双Y轴 | 绝对值 + 增长率/比率 | 左轴绝对值,右轴百分比 |
|
||||
| 柱状图 | 离散值对比 | 分组/堆叠/横向三种变体 |
|
||||
| 堆叠柱状图 | 构成 + 趋势 | 看总量变化和结构变化 |
|
||||
| 分组柱状图 | 多主体同维度对比 | 2-4 个主体 |
|
||||
| 横向条形图 | 排名 / Top N | 长标签更易阅读 |
|
||||
| 折线图 | 连续趋势 | 多条线 <=3 条 |
|
||||
| 环形饼图 | 3-6 项占比 | padAngle + borderRadius 更精致 |
|
||||
| 玫瑰图 | 差异大的占比 | 面积缩放强调差距 |
|
||||
| 嵌套环形图 | 两级分类构成 | 内环大类,外环细分 |
|
||||
| 半环仪表盘 | 单一完成度/占比 | 直观展示达标率 |
|
||||
| Treemap | 面积=数值的层级构成 | 支持 2 层,面积直觉强 |
|
||||
| 旭日图 | 3 层以上层级 | 从内到外逐层细化 |
|
||||
| 热力图 | NxM 矩阵数值 | 颜色深浅表示强度 |
|
||||
| 相关性热力图 | 相关系数方阵 | 对称矩阵,-1 到 +1 色阶 |
|
||||
| 雷达图 | 多维综合评分 | 2 个主体对比最佳 |
|
||||
| 散点图 | 两变量相关性 | 气泡大小可编码第三维 |
|
||||
| 瀑布图 | 加减拆解 | 正/负值不同颜色 |
|
||||
| K 线图 | 股价走势 | A 股红涨绿跌 |
|
||||
| 漏斗图 | 逐步递减/转化 | 筛选、转化流程 |
|
||||
| 桑基图 | 流向/流转关系 | 来源到去向的价值流 |
|
||||
| Inline SVG | 简单趋势 (零依赖) | 无需 ECharts CDN |
|
||||
|
||||
所有 ECharts 图表的完整配置样例见 [references/charts-base.md](references/charts-base.md)。
|
||||
|
||||
---
|
||||
|
||||
## 组合策略
|
||||
|
||||
一页 slide 可以组合多种视觉元素,但必须遵守密度限制:
|
||||
|
||||
**允许的组合:**
|
||||
- KPI 网格 (3-4 卡片) + 1 行小字注释
|
||||
- 图表 + 标题 + 数据来源注释
|
||||
- 图文分栏: 左侧 3-4 条要点 + 右侧 1 个图表
|
||||
- 表格 + 标题 + 1 行脚注
|
||||
- 标题 + 2 个并排小图表 (高度减半)
|
||||
|
||||
**禁止的组合:**
|
||||
- 图表 + 表格 (两者都需要大量空间)
|
||||
- KPI 网格 (6 卡片) + 要点列表 (空间不够)
|
||||
- 两个全尺寸图表并排
|
||||
- 表格 + 要点列表
|
||||
|
||||
**遇到复杂内容时:** 拆分为多张 slide,用一致的标题前缀保持连贯性(如"收入分析 (1/2)"、"收入分析 (2/2)")。
|
||||
|
||||
---
|
||||
|
||||
## Slide 生成检查清单(逐项强制执行)
|
||||
|
||||
对每块用户内容,按顺序完成以下 4 步。每步必须有明确结论,不得跳过。
|
||||
|
||||
### Step 1: 分类(单选,必须命中一个)
|
||||
|
||||
| IF 内容是... | THEN 类型 = |
|
||||
|-------------|------------|
|
||||
| 3-6 个数值指标(营收、利润率、用户数等) | `数值指标` |
|
||||
| 带年份/季度的数值序列 | `时序趋势` |
|
||||
| 整体拆分为部分(收入结构、成本构成) | `结构构成` |
|
||||
| 多个主体的同维度对比 | `对比关系` |
|
||||
| 观点/论点/结论 + 文字支撑 | `叙事论点` |
|
||||
| 按时间排列的事件/里程碑 | `时间过程` |
|
||||
| 风险清单/评分/评级 | `风险评估` |
|
||||
|
||||
### Step 2: 密度检查(必须回答 Yes/No)
|
||||
|
||||
对照上方映射表的"最大密度"列:
|
||||
- IF 内容量 <= 密度限制 THEN 继续
|
||||
- IF 内容量 > 密度限制 THEN **必须拆分为多张 slide**,每张只保留一个聚焦点
|
||||
|
||||
**禁止行为:** 不得通过缩小字号、减少间距来容纳超限内容。
|
||||
|
||||
### Step 3: 选择视觉形式(直接查表)
|
||||
|
||||
| IF 类型 = | THEN 视觉形式 = |
|
||||
|-----------|----------------|
|
||||
| `数值指标` 且 <=6 个 | `.kpi-grid` KPI 卡片网格 |
|
||||
| `数值指标` 且 =1 个核心数字 | Hero 数字(超大居中) |
|
||||
| `时序趋势` 且单指标 | 面积图 或 柱状图 |
|
||||
| `时序趋势` 且多指标 | 柱线混合图(双 Y 轴) |
|
||||
| `结构构成` 且 3-6 项 | 环形饼图 |
|
||||
| `结构构成` 且有层级 | Treemap 或 旭日图 |
|
||||
| `结构构成` 且加减拆解 | 瀑布图 |
|
||||
| `对比关系` 且 3-4 个主体 | `.comparison-grid` 对比卡片 |
|
||||
| `对比关系` 且 >5 行数据 | `.data-table` 数据表格 |
|
||||
| `叙事论点` 且纯文字 | 标题 + `.insight-list` 要点列表 |
|
||||
| `叙事论点` 且有数据佐证 | `.split-layout` 图文分栏 |
|
||||
| `时间过程` | `.timeline` 时间线 |
|
||||
| `风险评估` 且清单型 | `.data-table` 风险表格 |
|
||||
| `风险评估` 且多维打分 | 评分条 (progress bar) |
|
||||
|
||||
### Step 4: 位置排序
|
||||
|
||||
- 开头 1-2 页: 标题页 + 概览/KPI
|
||||
- 中间: 核心论证(图表、表格、分栏)
|
||||
- 结尾 1-2 页: 结论/建议
|
||||
|
||||
---
|
||||
|
||||
## 禁止行为清单(看到即纠正)
|
||||
|
||||
- 超过 6 个 KPI 卡片不分页
|
||||
- 图表 + 表格放在同一页
|
||||
- 两个全尺寸图表并排
|
||||
- 使用固定 px/rem 字号(必须 clamp())
|
||||
- 使用 emoji 代替 CSS 趋势箭头
|
||||
- 内容溢出时缩小字号而非拆分 slide
|
||||
- 遗漏中文字体回退链
|
||||
204
fund-slides/references/animation-patterns.md
Normal file
204
fund-slides/references/animation-patterns.md
Normal file
@ -0,0 +1,204 @@
|
||||
# Animation Patterns Reference
|
||||
|
||||
Use this reference when generating presentations. Match animations to the intended feeling.
|
||||
|
||||
## Effect-to-Feeling Guide
|
||||
|
||||
| Feeling | Animations | Visual Cues |
|
||||
|---------|-----------|-------------|
|
||||
| **Dramatic / Cinematic** | Slow fade-ins (1-1.5s), large scale transitions (0.9 to 1), parallax scrolling | Dark backgrounds, spotlight effects, full-bleed images |
|
||||
| **Techy / Futuristic** | Neon glow (box-shadow), glitch/scramble text, grid reveals | Particle systems (canvas), grid patterns, monospace accents, cyan/magenta/electric blue |
|
||||
| **Playful / Friendly** | Bouncy easing (spring physics), floating/bobbing | Rounded corners, pastel/bright colors, hand-drawn elements |
|
||||
| **Professional / Corporate** | Subtle fast animations (200-300ms), clean slides | Navy/slate/charcoal, precise spacing, data visualization focus |
|
||||
| **Calm / Minimal** | Very slow subtle motion, gentle fades | High whitespace, muted palette, serif typography, generous padding |
|
||||
| **Editorial / Magazine** | Staggered text reveals, image-text interplay | Strong type hierarchy, pull quotes, grid-breaking layouts, serif headlines + sans body |
|
||||
|
||||
## Entrance Animations
|
||||
|
||||
```css
|
||||
/* Fade + Slide Up (most versatile) */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.6s var(--ease-out-expo),
|
||||
transform 0.6s var(--ease-out-expo);
|
||||
}
|
||||
.visible .reveal {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Scale In */
|
||||
.reveal-scale {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
transition: opacity 0.6s, transform 0.6s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
/* Slide from Left */
|
||||
.reveal-left {
|
||||
opacity: 0;
|
||||
transform: translateX(-50px);
|
||||
transition: opacity 0.6s, transform 0.6s var(--ease-out-expo);
|
||||
}
|
||||
|
||||
/* Blur In */
|
||||
.reveal-blur {
|
||||
opacity: 0;
|
||||
filter: blur(10px);
|
||||
transition: opacity 0.8s, filter 0.8s var(--ease-out-expo);
|
||||
}
|
||||
```
|
||||
|
||||
## Background Effects
|
||||
|
||||
```css
|
||||
/* Gradient Mesh — layered radial gradients for depth */
|
||||
.gradient-bg {
|
||||
background:
|
||||
radial-gradient(ellipse at 20% 80%, rgba(120, 0, 255, 0.3) 0%, transparent 50%),
|
||||
radial-gradient(ellipse at 80% 20%, rgba(0, 255, 200, 0.2) 0%, transparent 50%),
|
||||
var(--bg-primary);
|
||||
}
|
||||
|
||||
/* Noise Texture — inline SVG for grain */
|
||||
.noise-bg {
|
||||
background-image: url("data:image/svg+xml,..."); /* Inline SVG noise */
|
||||
}
|
||||
|
||||
/* Grid Pattern — subtle structural lines */
|
||||
.grid-bg {
|
||||
background-image:
|
||||
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
}
|
||||
```
|
||||
|
||||
## Interactive Effects
|
||||
|
||||
```javascript
|
||||
/* 3D Tilt on Hover — adds depth to cards/panels */
|
||||
class TiltEffect {
|
||||
constructor(element) {
|
||||
this.element = element;
|
||||
this.element.style.transformStyle = 'preserve-3d';
|
||||
this.element.style.perspective = '1000px';
|
||||
|
||||
this.element.addEventListener('mousemove', (e) => {
|
||||
const rect = this.element.getBoundingClientRect();
|
||||
const x = (e.clientX - rect.left) / rect.width - 0.5;
|
||||
const y = (e.clientY - rect.top) / rect.height - 0.5;
|
||||
this.element.style.transform = `rotateY(${x * 10}deg) rotateX(${-y * 10}deg)`;
|
||||
});
|
||||
|
||||
this.element.addEventListener('mouseleave', () => {
|
||||
this.element.style.transform = 'rotateY(0) rotateX(0)';
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Financial Data Animations
|
||||
|
||||
金融演示专用动画效果。数据展示优先清晰和准确,动画起辅助作用。
|
||||
|
||||
### KPI 数字滚动
|
||||
|
||||
```javascript
|
||||
/* 数字从 0 滚动到目标值,配合 formatCN 格式化 */
|
||||
class CountUp {
|
||||
constructor(el, target, duration = 1200) {
|
||||
this.el = el;
|
||||
this.target = target;
|
||||
this.duration = duration;
|
||||
}
|
||||
start() {
|
||||
const t0 = performance.now();
|
||||
const tick = (now) => {
|
||||
const p = Math.min((now - t0) / this.duration, 1);
|
||||
const eased = 1 - Math.pow(1 - p, 3);
|
||||
this.el.textContent = typeof formatCN === 'function'
|
||||
? formatCN(this.target * eased)
|
||||
: Math.round(this.target * eased).toLocaleString();
|
||||
if (p < 1) requestAnimationFrame(tick);
|
||||
};
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 表格逐行显现
|
||||
|
||||
```css
|
||||
.data-table tbody tr {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
transition: opacity 0.4s var(--ease-out-expo),
|
||||
transform 0.4s var(--ease-out-expo);
|
||||
}
|
||||
.slide.visible .data-table tbody tr { opacity: 1; transform: none; }
|
||||
.slide.visible .data-table tbody tr:nth-child(1) { transition-delay: 0.1s; }
|
||||
.slide.visible .data-table tbody tr:nth-child(2) { transition-delay: 0.15s; }
|
||||
.slide.visible .data-table tbody tr:nth-child(3) { transition-delay: 0.2s; }
|
||||
.slide.visible .data-table tbody tr:nth-child(4) { transition-delay: 0.25s; }
|
||||
.slide.visible .data-table tbody tr:nth-child(5) { transition-delay: 0.3s; }
|
||||
.slide.visible .data-table tbody tr:nth-child(6) { transition-delay: 0.35s; }
|
||||
```
|
||||
|
||||
### KPI 卡片依次进入
|
||||
|
||||
```css
|
||||
.kpi-grid .kpi-card {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.5s var(--ease-out-expo),
|
||||
transform 0.5s var(--ease-out-expo);
|
||||
}
|
||||
.slide.visible .kpi-grid .kpi-card { opacity: 1; transform: none; }
|
||||
.slide.visible .kpi-grid .kpi-card:nth-child(1) { transition-delay: 0.1s; }
|
||||
.slide.visible .kpi-grid .kpi-card:nth-child(2) { transition-delay: 0.2s; }
|
||||
.slide.visible .kpi-grid .kpi-card:nth-child(3) { transition-delay: 0.3s; }
|
||||
.slide.visible .kpi-grid .kpi-card:nth-child(4) { transition-delay: 0.4s; }
|
||||
```
|
||||
|
||||
### 图表容器淡入
|
||||
|
||||
```css
|
||||
.chart-container {
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s ease;
|
||||
}
|
||||
.slide.visible .chart-container {
|
||||
opacity: 1;
|
||||
}
|
||||
```
|
||||
|
||||
### 趋势指标闪烁强调
|
||||
|
||||
```css
|
||||
@keyframes highlight-pulse {
|
||||
0%, 100% { background-color: transparent; }
|
||||
50% { background-color: rgba(var(--accent-rgb, 37, 99, 235), 0.1); }
|
||||
}
|
||||
.kpi-trend.positive, .kpi-trend.negative {
|
||||
animation: highlight-pulse 2s ease 1;
|
||||
animation-delay: 1.5s;
|
||||
border-radius: 2px;
|
||||
padding: 0 0.2em;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Fix |
|
||||
|---------|-----|
|
||||
| Fonts not loading | Check Fontshare/Google Fonts URL; ensure font names match in CSS |
|
||||
| Chinese fonts slow | Noto Sans/Serif SC are 2-4MB; use `display=swap` and subset `&subset=chinese-simplified` |
|
||||
| Animations not triggering | Verify Intersection Observer is running; check `.visible` class is being added |
|
||||
| Scroll snap not working | Ensure `scroll-snap-type: y mandatory` on html; each slide needs `scroll-snap-align: start` |
|
||||
| ECharts not rendering | Charts must init after slide is visible (container needs non-zero dimensions) |
|
||||
| Mobile issues | Disable heavy effects at 768px breakpoint; test touch events; reduce particle count |
|
||||
| Performance issues | Use `will-change` sparingly; prefer `transform`/`opacity` animations; throttle scroll handlers |
|
||||
226
fund-slides/references/charts-base.md
Normal file
226
fund-slides/references/charts-base.md
Normal file
@ -0,0 +1,226 @@
|
||||
# Charts Base -- 基础设施与工具
|
||||
|
||||
ECharts 集成基础:内联引入、容器规范、初始化模式、格式化工具、配色适配。
|
||||
|
||||
按需加载具体图表配置:
|
||||
- [charts-trend.md](charts-trend.md) -- 折线/面积/柱状等趋势图
|
||||
- [charts-composition.md](charts-composition.md) -- 饼图/玫瑰/Treemap/旭日图等构成图
|
||||
- [charts-matrix.md](charts-matrix.md) -- 热力图/雷达/散点等矩阵图
|
||||
- [charts-flow.md](charts-flow.md) -- 瀑布/K线/漏斗/桑基等流向图
|
||||
|
||||
---
|
||||
|
||||
## ECharts 内联引入(零外部依赖)
|
||||
|
||||
**禁止使用 CDN `<script src="...">`。** ECharts 必须内嵌到 HTML 中确保离线可用。
|
||||
|
||||
生成流程:
|
||||
1. 读取本地文件 [echarts.min.js](echarts.min.js)(~1MB,已预存在 references 目录)
|
||||
2. 将完整内容包裹在 `<script>/* ECharts v5 */...内容...</script>` 中
|
||||
3. 放置位置:模板的 `<!-- SLOT: ECHARTS INLINE -->` 处(在 SLOT: SLIDES 之后、base JS 之前)
|
||||
|
||||
```html
|
||||
<!-- 正确:内联(读取 references/echarts.min.js 内容) -->
|
||||
<script>/* ECharts v5 - inline */
|
||||
...(references/echarts.min.js 的完整内容)...
|
||||
</script>
|
||||
|
||||
<!-- 错误:CDN 引用(禁止) -->
|
||||
<!-- <script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script> -->
|
||||
```
|
||||
|
||||
## 容器规范
|
||||
|
||||
```html
|
||||
<div class="chart-container" id="chart1"></div>
|
||||
```
|
||||
|
||||
```css
|
||||
.chart-container {
|
||||
width: min(90vw, 800px);
|
||||
height: min(50vh, 400px);
|
||||
margin: 0 auto;
|
||||
}
|
||||
@media (max-height: 700px) { .chart-container { height: min(40vh, 300px); } }
|
||||
@media (max-height: 500px) { .chart-container { height: min(35vh, 250px); } }
|
||||
```
|
||||
|
||||
## 初始化模式
|
||||
|
||||
图表必须在 slide 可见时初始化,否则容器尺寸为 0:
|
||||
|
||||
```javascript
|
||||
const chartObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const charts = entry.target.querySelectorAll('.chart-container[data-chart]');
|
||||
charts.forEach(el => {
|
||||
if (!el._echartInstance) {
|
||||
const chart = echarts.init(el);
|
||||
chart.setOption(JSON.parse(el.dataset.chart));
|
||||
el._echartInstance = chart;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.3 });
|
||||
```
|
||||
|
||||
## 窗口 resize
|
||||
|
||||
```javascript
|
||||
window.addEventListener('resize', () => {
|
||||
document.querySelectorAll('.chart-container').forEach(el => {
|
||||
if (el._echartInstance) el._echartInstance.resize();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 中文数字格式化工具
|
||||
|
||||
每个包含数据的演示必须内嵌:
|
||||
|
||||
```javascript
|
||||
function formatCN(num, decimals = 1) {
|
||||
if (num === null || num === undefined) return '--';
|
||||
const abs = Math.abs(num);
|
||||
const sign = num < 0 ? '-' : '';
|
||||
if (abs >= 1e8) return sign + (abs / 1e8).toFixed(decimals).replace(/\.0$/, '') + '\u4ebf';
|
||||
if (abs >= 1e4) return sign + (abs / 1e4).toFixed(decimals).replace(/\.0$/, '') + '\u4e07';
|
||||
return sign + abs.toLocaleString('zh-CN');
|
||||
}
|
||||
|
||||
function formatPct(num, decimals = 2) {
|
||||
if (num === null || num === undefined) return '--';
|
||||
return (num * 100).toFixed(decimals) + '%';
|
||||
}
|
||||
|
||||
function formatDateCN(dateStr) {
|
||||
const d = new Date(dateStr);
|
||||
return d.getFullYear() + '\u5e74' + (d.getMonth() + 1) + '\u6708' + d.getDate() + '\u65e5';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配色适配
|
||||
|
||||
```javascript
|
||||
function getThemeColors() {
|
||||
const s = getComputedStyle(document.documentElement);
|
||||
return {
|
||||
primary: s.getPropertyValue('--accent').trim() || '#2563eb',
|
||||
secondary: s.getPropertyValue('--accent-secondary').trim() || '#60a5fa',
|
||||
negative: s.getPropertyValue('--color-negative').trim() || '#dc2626',
|
||||
positive: s.getPropertyValue('--color-positive').trim() || '#16a34a',
|
||||
text: s.getPropertyValue('--text-primary').trim() || '#333',
|
||||
textSec: s.getPropertyValue('--text-secondary').trim() || '#666',
|
||||
bg: s.getPropertyValue('--bg-primary').trim() || '#fff'
|
||||
};
|
||||
}
|
||||
|
||||
function initThemedChart(el, option) {
|
||||
const colors = getThemeColors();
|
||||
const chart = echarts.init(el, null, { renderer: 'canvas' });
|
||||
option.textStyle = option.textStyle || {};
|
||||
option.textStyle.fontFamily = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--font-body').trim() || 'Noto Sans SC, sans-serif';
|
||||
option.textStyle.color = colors.text;
|
||||
chart.setOption(option);
|
||||
return chart;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 图表类型速查
|
||||
|
||||
| 要表达什么 | 推荐图表 | 次选 | 配置文件 |
|
||||
|-----------|---------|------|---------|
|
||||
| 单指标趋势 | 面积图 | 折线图 | charts-trend |
|
||||
| 多指标趋势 | 柱线混合图 | 堆叠面积图 | charts-trend |
|
||||
| 增长率趋势 | 双Y轴折线 | 面积图 | charts-trend |
|
||||
| 构成占比 (3-6项) | 环形饼图 | 玫瑰图 | charts-composition |
|
||||
| 差异大的占比 | 玫瑰图 | Treemap | charts-composition |
|
||||
| 两级分类构成 | 嵌套环形图 | Treemap | charts-composition |
|
||||
| 三级以上层级 | 旭日图 | Treemap | charts-composition |
|
||||
| 面积=数值的层级 | Treemap | 旭日图 | charts-composition |
|
||||
| 单一达标率 | 半环仪表盘 | KPI卡片 | charts-composition |
|
||||
| 排名 / Top N | 横向条形图 | 柱状图 | charts-flow |
|
||||
| 多主体多指标对比 | 分组柱状图 | 雷达图 | charts-trend |
|
||||
| 构成+时间趋势 | 堆叠柱状图 | 堆叠面积图 | charts-trend |
|
||||
| 多维综合评分 | 雷达图 | CSS评分条 | charts-matrix |
|
||||
| 两变量相关性 | 散点图 | 热力图 | charts-matrix |
|
||||
| NxM 矩阵数据 | 热力图 | 表格 | charts-matrix |
|
||||
| 相关系数矩阵 | 相关性热力图 | 表格 | charts-matrix |
|
||||
| 逐步递减/转化 | 漏斗图 | 瀑布图 | charts-flow |
|
||||
| 加减拆解 | 瀑布图 | 堆叠柱 | charts-flow |
|
||||
| 流向/流转 | 桑基图 | 嵌套环形 | charts-flow |
|
||||
| 股价走势 | K线图 | 面积折线 | charts-flow |
|
||||
|
||||
---
|
||||
|
||||
## Inline SVG (零依赖)
|
||||
|
||||
不引入 ECharts 时,用 SVG 实现简单图表。
|
||||
|
||||
### 简单柱状图
|
||||
|
||||
```html
|
||||
<svg class="chart-svg" viewBox="0 0 400 200" preserveAspectRatio="xMidYMid meet">
|
||||
<style>
|
||||
.bar { transition: height 0.6s var(--ease-out-expo), y 0.6s var(--ease-out-expo); }
|
||||
.bar-label { font-family: var(--font-body); font-size: 11px; fill: var(--text-secondary); text-anchor: middle; }
|
||||
.bar-value { font-family: var(--font-body); font-size: 10px; fill: var(--text-primary); text-anchor: middle; }
|
||||
</style>
|
||||
<line x1="50" y1="10" x2="50" y2="170" stroke="var(--text-secondary)" stroke-width="0.5"/>
|
||||
<rect class="bar" x="70" y="50" width="40" height="120" fill="var(--accent)" rx="2"/>
|
||||
<rect class="bar" x="130" y="80" width="40" height="90" fill="var(--accent)" rx="2" opacity="0.7"/>
|
||||
<rect class="bar" x="190" y="30" width="40" height="140" fill="var(--accent)" rx="2"/>
|
||||
<rect class="bar" x="250" y="60" width="40" height="110" fill="var(--accent)" rx="2" opacity="0.7"/>
|
||||
<text class="bar-label" x="90" y="190">Q1</text>
|
||||
<text class="bar-label" x="150" y="190">Q2</text>
|
||||
<text class="bar-label" x="210" y="190">Q3</text>
|
||||
<text class="bar-label" x="270" y="190">Q4</text>
|
||||
<text class="bar-value" x="90" y="45">1.2亿</text>
|
||||
<text class="bar-value" x="150" y="75">0.9亿</text>
|
||||
<text class="bar-value" x="210" y="25">1.4亿</text>
|
||||
<text class="bar-value" x="270" y="55">1.1亿</text>
|
||||
</svg>
|
||||
```
|
||||
|
||||
### 简单折线图
|
||||
|
||||
```html
|
||||
<svg class="chart-svg" viewBox="0 0 400 200" preserveAspectRatio="xMidYMid meet">
|
||||
<defs>
|
||||
<linearGradient id="lineGrad" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="var(--accent)" stop-opacity="0.3"/>
|
||||
<stop offset="100%" stop-color="var(--accent)" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path d="M50,140 L120,100 L190,110 L260,60 L330,40 L330,170 L50,170 Z" fill="url(#lineGrad)"/>
|
||||
<polyline points="50,140 120,100 190,110 260,60 330,40"
|
||||
fill="none" stroke="var(--accent)" stroke-width="2"
|
||||
stroke-dasharray="500" stroke-dashoffset="500">
|
||||
<animate attributeName="stroke-dashoffset" to="0" dur="1.5s" fill="freeze"
|
||||
begin="0.3s" calcMode="spline" keySplines="0.16 1 0.3 1"/>
|
||||
</polyline>
|
||||
<circle cx="50" cy="140" r="3" fill="var(--accent)"/>
|
||||
<circle cx="120" cy="100" r="3" fill="var(--accent)"/>
|
||||
<circle cx="190" cy="110" r="3" fill="var(--accent)"/>
|
||||
<circle cx="260" cy="60" r="3" fill="var(--accent)"/>
|
||||
<circle cx="330" cy="40" r="3" fill="var(--accent)"/>
|
||||
</svg>
|
||||
```
|
||||
|
||||
```css
|
||||
.chart-svg {
|
||||
width: min(90vw, 600px);
|
||||
height: min(40vh, 300px);
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
```
|
||||
236
fund-slides/references/charts-composition.md
Normal file
236
fund-slides/references/charts-composition.md
Normal file
@ -0,0 +1,236 @@
|
||||
# Charts -- Composition (构成类)
|
||||
|
||||
饼图、环形图、Treemap、旭日图等。基础设施见 [charts-base.md](charts-base.md)。
|
||||
|
||||
---
|
||||
|
||||
## 环形饼图(3-6 项占比)
|
||||
|
||||
```javascript
|
||||
const pieOption = {
|
||||
color: ['#1a365d', '#2563eb', '#60a5fa', '#93c5fd', '#c5963a'],
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
|
||||
legend: { orient: 'vertical', right: '5%', top: 'center' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['40%', '50%'],
|
||||
padAngle: 2,
|
||||
itemStyle: { borderRadius: 4 },
|
||||
label: { formatter: '{b}\n{d}%', fontSize: 11 },
|
||||
emphasis: {
|
||||
label: { fontSize: 14, fontWeight: 'bold' },
|
||||
itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.2)' }
|
||||
},
|
||||
data: [
|
||||
{ value: 4500, name: '业务A' },
|
||||
{ value: 3200, name: '业务B' },
|
||||
{ value: 2100, name: '业务C' },
|
||||
{ value: 1500, name: '其他' }
|
||||
]
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 玫瑰图 / 南丁格尔图
|
||||
|
||||
各项数值差异大时,用半径缩放强调差异。
|
||||
|
||||
```javascript
|
||||
const roseOption = {
|
||||
color: ['#1a365d', '#2563eb', '#3b82f6', '#60a5fa', '#93c5fd', '#c5963a'],
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
|
||||
legend: { bottom: '3%', left: 'center' },
|
||||
series: [{
|
||||
type: 'pie',
|
||||
radius: ['15%', '70%'],
|
||||
center: ['50%', '45%'],
|
||||
roseType: 'area', /* 'radius' = 半径缩放, 'area' = 面积缩放 */
|
||||
itemStyle: { borderRadius: 5 },
|
||||
label: { formatter: '{b}\n{c}亿', fontSize: 11 },
|
||||
data: [
|
||||
{ value: 1843, name: '游戏' },
|
||||
{ value: 1200, name: '社交网络' },
|
||||
{ value: 1240, name: '网络广告' },
|
||||
{ value: 2100, name: '金融科技' },
|
||||
{ value: 69, name: '其他' }
|
||||
]
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 嵌套环形图(两级分类)
|
||||
|
||||
内环大类,外环细分。
|
||||
|
||||
```javascript
|
||||
const nestedPieOption = {
|
||||
tooltip: { trigger: 'item', formatter: '{a}<br/>{b}: {c} ({d}%)' },
|
||||
series: [
|
||||
{
|
||||
name: '大类',
|
||||
type: 'pie',
|
||||
radius: ['0%', '35%'],
|
||||
label: { position: 'inner', fontSize: 10, color: '#fff' },
|
||||
data: [
|
||||
{ value: 3043, name: '增值服务' },
|
||||
{ value: 2100, name: '金融科技' },
|
||||
{ value: 1240, name: '广告' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '细分',
|
||||
type: 'pie',
|
||||
radius: ['45%', '70%'],
|
||||
label: { formatter: '{b}: {d}%', fontSize: 10 },
|
||||
data: [
|
||||
{ value: 1843, name: '游戏' },
|
||||
{ value: 1200, name: '社交网络' },
|
||||
{ value: 1100, name: '支付' },
|
||||
{ value: 1000, name: '企业服务' },
|
||||
{ value: 740, name: '媒体广告' },
|
||||
{ value: 500, name: '社交广告' }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 半环仪表盘(单指标达标率)
|
||||
|
||||
```javascript
|
||||
const gaugeOption = {
|
||||
series: [{
|
||||
type: 'gauge',
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
radius: '90%',
|
||||
center: ['50%', '65%'],
|
||||
min: 0, max: 100,
|
||||
pointer: { show: false },
|
||||
progress: { show: true, overlap: false, roundCap: true, width: 18, itemStyle: { color: '#2563eb' } },
|
||||
axisLine: { lineStyle: { width: 18, color: [[1, '#e5e7eb']] } },
|
||||
axisTick: { show: false },
|
||||
splitLine: { show: false },
|
||||
axisLabel: { show: false },
|
||||
detail: {
|
||||
fontSize: 28, fontFamily: 'Noto Sans SC', fontWeight: 700,
|
||||
formatter: '{value}%', offsetCenter: [0, '-15%'], color: '#1a1a2e'
|
||||
},
|
||||
title: { fontSize: 13, offsetCenter: [0, '15%'], color: '#5a5a7a' },
|
||||
data: [{ value: 73.5, name: '目标达成率' }]
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Treemap 矩形树图
|
||||
|
||||
面积 = 数值大小。适合两层层级构成。
|
||||
|
||||
```javascript
|
||||
const treemapOption = {
|
||||
tooltip: {
|
||||
formatter: p => {
|
||||
const path = p.treePathInfo.map(n => n.name).filter(n => n).join(' > ');
|
||||
return path + '<br/>金额: ' + formatCN(p.value);
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: 'treemap',
|
||||
roam: false,
|
||||
width: '92%', height: '85%', top: '8%',
|
||||
breadcrumb: { show: true, bottom: 0 },
|
||||
label: { show: true, formatter: '{b}\n{c}亿', fontSize: 12, fontFamily: 'Noto Sans SC' },
|
||||
upperLabel: { show: true, height: 24, fontSize: 12, fontWeight: 600, color: '#fff' },
|
||||
levels: [
|
||||
{ itemStyle: { borderColor: '#fff', borderWidth: 3, gapWidth: 3 }, upperLabel: { show: true } },
|
||||
{ itemStyle: { borderColor: 'rgba(255,255,255,0.5)', borderWidth: 1, gapWidth: 1 }, colorSaturation: [0.3, 0.6] }
|
||||
],
|
||||
data: [
|
||||
{
|
||||
name: '增值服务', value: 3043,
|
||||
children: [
|
||||
{ name: '本土游戏', value: 1200, itemStyle: { color: '#1a365d' } },
|
||||
{ name: '海外游戏', value: 643, itemStyle: { color: '#2563eb' } },
|
||||
{ name: '社交网络', value: 1200, itemStyle: { color: '#3b82f6' } }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '金融科技', value: 2100,
|
||||
children: [
|
||||
{ name: '支付', value: 1100, itemStyle: { color: '#c5963a' } },
|
||||
{ name: '云服务', value: 600, itemStyle: { color: '#d4a853' } },
|
||||
{ name: '其他', value: 400, itemStyle: { color: '#e8c882' } }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '网络广告', value: 1240,
|
||||
children: [
|
||||
{ name: '社交广告', value: 740, itemStyle: { color: '#16a34a' } },
|
||||
{ name: '媒体广告', value: 500, itemStyle: { color: '#22c55e' } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 旭日图 Sunburst(多层级环形)
|
||||
|
||||
从内到外逐层细化。适合 3 层以上层级。
|
||||
|
||||
```javascript
|
||||
const sunburstOption = {
|
||||
tooltip: {
|
||||
formatter: p => {
|
||||
const path = p.treePathInfo.map(n => n.name).filter(n => n).join(' > ');
|
||||
return path + '<br/>金额: ' + formatCN(p.value);
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: 'sunburst',
|
||||
radius: ['12%', '85%'],
|
||||
sort: 'desc',
|
||||
emphasis: { focus: 'ancestor' },
|
||||
label: { fontSize: 10, fontFamily: 'Noto Sans SC', rotate: 'radial', minAngle: 10 },
|
||||
levels: [
|
||||
{},
|
||||
{ r0: '12%', r: '40%', label: { fontSize: 12, fontWeight: 600 }, itemStyle: { borderWidth: 2, borderColor: '#fff' } },
|
||||
{ r0: '40%', r: '65%', label: { fontSize: 10 }, itemStyle: { borderWidth: 1.5, borderColor: '#fff' } },
|
||||
{ r0: '65%', r: '85%', label: { fontSize: 9, align: 'right' }, itemStyle: { borderWidth: 1, borderColor: 'rgba(255,255,255,0.6)' } }
|
||||
],
|
||||
data: [
|
||||
{
|
||||
name: '增值服务', itemStyle: { color: '#1a365d' },
|
||||
children: [
|
||||
{ name: '游戏', value: 1843, children: [
|
||||
{ name: '王者荣耀', value: 500 }, { name: '和平精英', value: 350 },
|
||||
{ name: 'LoL/Valorant', value: 400 }, { name: '其他', value: 593 }
|
||||
]},
|
||||
{ name: '社交网络', value: 1200, children: [
|
||||
{ name: '会员订阅', value: 650 }, { name: '虚拟道具', value: 550 }
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '金融科技', itemStyle: { color: '#c5963a' },
|
||||
children: [ { name: '支付', value: 1100 }, { name: '云服务', value: 600 }, { name: '其他', value: 400 } ]
|
||||
},
|
||||
{
|
||||
name: '广告', itemStyle: { color: '#16a34a' },
|
||||
children: [ { name: '社交广告', value: 740 }, { name: '媒体广告', value: 500 } ]
|
||||
}
|
||||
]
|
||||
}]
|
||||
};
|
||||
```
|
||||
233
fund-slides/references/charts-flow.md
Normal file
233
fund-slides/references/charts-flow.md
Normal file
@ -0,0 +1,233 @@
|
||||
# Charts -- Flow (流向与特殊类型)
|
||||
|
||||
瀑布图、K 线、横向条形、漏斗图、桑基图。基础设施见 [charts-base.md](charts-base.md)。
|
||||
|
||||
---
|
||||
|
||||
## 瀑布图(加减拆解)
|
||||
|
||||
从总量逐步拆解到净值。正/负值不同颜色。
|
||||
|
||||
```javascript
|
||||
const waterfallOption = {
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
grid: { left: '10%', right: '5%', top: '10%', bottom: '12%' },
|
||||
xAxis: { type: 'category', data: ['营业收入', '营业成本', '毛利', '销售费用', '管理费用', '财务费用', '营业利润'] },
|
||||
yAxis: { type: 'value', axisLabel: { formatter: v => formatCN(v) } },
|
||||
series: [
|
||||
{
|
||||
name: '辅助',
|
||||
type: 'bar', stack: 'total',
|
||||
itemStyle: { borderColor: 'transparent', color: 'transparent' },
|
||||
data: [0, 0, 0, 0, 0, 0, 0] /* 计算后的偏移量 */
|
||||
},
|
||||
{
|
||||
name: '数值',
|
||||
type: 'bar', stack: 'total', barWidth: '40%',
|
||||
label: { show: true, position: 'top', formatter: p => formatCN(p.value) },
|
||||
data: [
|
||||
{ value: 10000, itemStyle: { color: '#2563eb' } },
|
||||
{ value: -6000, itemStyle: { color: '#dc2626' } },
|
||||
{ value: 4000, itemStyle: { color: '#2563eb' } },
|
||||
{ value: -800, itemStyle: { color: '#dc2626' } },
|
||||
{ value: -600, itemStyle: { color: '#dc2626' } },
|
||||
{ value: -200, itemStyle: { color: '#dc2626' } },
|
||||
{ value: 2400, itemStyle: { color: '#16a34a' } }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
**瀑布图辅助层计算逻辑:**
|
||||
|
||||
```javascript
|
||||
// 自动计算 transparent 辅助柱的高度
|
||||
function buildWaterfall(items) {
|
||||
// items: [{ label, value, type: 'start'|'add'|'sub'|'total' }]
|
||||
let running = 0;
|
||||
const categories = [], assist = [], values = [];
|
||||
items.forEach(item => {
|
||||
categories.push(item.label);
|
||||
if (item.type === 'start' || item.type === 'total') {
|
||||
assist.push(0);
|
||||
values.push({ value: item.value, itemStyle: { color: item.type === 'total' ? '#16a34a' : '#2563eb' } });
|
||||
running = item.value;
|
||||
} else {
|
||||
const v = item.type === 'sub' ? -Math.abs(item.value) : Math.abs(item.value);
|
||||
assist.push(Math.max(0, running + Math.min(0, v)));
|
||||
values.push({ value: v, itemStyle: { color: v >= 0 ? '#2563eb' : '#dc2626' } });
|
||||
running += v;
|
||||
}
|
||||
});
|
||||
return { categories, assist, values };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## K 线图(股价走势)
|
||||
|
||||
A 股惯例:红涨绿跌。
|
||||
|
||||
```javascript
|
||||
const candlestickOption = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { left: '10%', right: '5%', top: '10%', bottom: '15%' },
|
||||
xAxis: { type: 'category', data: ['03-01', '03-04', '03-05', '03-06', '03-07', '03-08', '03-11', '03-12'] },
|
||||
yAxis: { type: 'value', scale: true },
|
||||
series: [{
|
||||
type: 'candlestick',
|
||||
itemStyle: {
|
||||
color: '#dc2626', /* 阳线填充 */
|
||||
color0: '#16a34a', /* 阴线填充 */
|
||||
borderColor: '#dc2626',
|
||||
borderColor0: '#16a34a'
|
||||
},
|
||||
data: [
|
||||
/* [open, close, low, high] */
|
||||
[540, 548, 536, 552],
|
||||
[548, 545, 540, 550],
|
||||
[545, 555, 543, 558],
|
||||
[555, 550, 546, 556],
|
||||
[550, 560, 548, 562],
|
||||
[560, 558, 554, 563],
|
||||
[558, 565, 555, 568],
|
||||
[565, 550, 548, 566]
|
||||
]
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
K 线 + 成交量组合:
|
||||
|
||||
```javascript
|
||||
const klineWithVolOption = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: [
|
||||
{ left: '10%', right: '5%', top: '8%', height: '55%' }, /* K 线区域 */
|
||||
{ left: '10%', right: '5%', top: '72%', height: '18%' } /* 成交量区域 */
|
||||
],
|
||||
xAxis: [
|
||||
{ type: 'category', data: /* dates */, gridIndex: 0 },
|
||||
{ type: 'category', data: /* dates */, gridIndex: 1 }
|
||||
],
|
||||
yAxis: [
|
||||
{ type: 'value', scale: true, gridIndex: 0 },
|
||||
{ type: 'value', gridIndex: 1, axisLabel: { formatter: v => formatCN(v) } }
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'candlestick',
|
||||
xAxisIndex: 0, yAxisIndex: 0,
|
||||
itemStyle: { color: '#dc2626', color0: '#16a34a', borderColor: '#dc2626', borderColor0: '#16a34a' },
|
||||
data: /* [[open,close,low,high], ...] */
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
xAxisIndex: 1, yAxisIndex: 1,
|
||||
barWidth: '60%',
|
||||
data: /* volume array, with itemStyle per bar based on up/down */
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 横向条形图(排名 / Top N)
|
||||
|
||||
横向更易阅读长标签。inverse 从上到下递减。
|
||||
|
||||
```javascript
|
||||
const horizontalBarOption = {
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
grid: { left: '22%', right: '10%', top: '5%', bottom: '5%' },
|
||||
xAxis: { type: 'value', axisLabel: { formatter: v => formatCN(v) } },
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
inverse: true,
|
||||
data: ['腾讯', 'Sony', '微软', '任天堂', 'EA', '动视暴雪'],
|
||||
axisLabel: { fontSize: 12 }
|
||||
},
|
||||
series: [{
|
||||
type: 'bar',
|
||||
barWidth: '55%',
|
||||
data: [
|
||||
{ value: 1843, itemStyle: { color: '#1a365d' } },
|
||||
{ value: 1250, itemStyle: { color: '#93c5fd' } },
|
||||
{ value: 1180, itemStyle: { color: '#93c5fd' } },
|
||||
{ value: 920, itemStyle: { color: '#93c5fd' } },
|
||||
{ value: 580, itemStyle: { color: '#93c5fd' } },
|
||||
{ value: 520, itemStyle: { color: '#93c5fd' } }
|
||||
],
|
||||
label: { show: true, position: 'right', formatter: p => formatCN(p.value), fontSize: 11 },
|
||||
itemStyle: { borderRadius: [0, 3, 3, 0] }
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 漏斗图(转化/筛选流程)
|
||||
|
||||
逐步递减的转化过程。
|
||||
|
||||
```javascript
|
||||
const funnelOption = {
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
|
||||
series: [{
|
||||
type: 'funnel',
|
||||
left: '15%', right: '15%', top: '8%', bottom: '8%',
|
||||
width: '70%',
|
||||
minSize: '15%', maxSize: '100%',
|
||||
sort: 'descending',
|
||||
gap: 4,
|
||||
label: { show: true, position: 'inside', formatter: '{b}\n{c}', fontSize: 12, fontFamily: 'Noto Sans SC' },
|
||||
itemStyle: { borderWidth: 0 },
|
||||
data: [
|
||||
{ value: 5000, name: '初筛股票池', itemStyle: { color: '#93c5fd' } },
|
||||
{ value: 2800, name: '基本面过滤', itemStyle: { color: '#60a5fa' } },
|
||||
{ value: 1200, name: '估值合理', itemStyle: { color: '#3b82f6' } },
|
||||
{ value: 450, name: '技术面确认', itemStyle: { color: '#2563eb' } },
|
||||
{ value: 120, name: '最终标的', itemStyle: { color: '#1a365d' } }
|
||||
]
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 桑基图 Sankey(价值流向)
|
||||
|
||||
从来源到去向的流转关系。
|
||||
|
||||
```javascript
|
||||
const sankeyOption = {
|
||||
tooltip: { trigger: 'item', formatter: '{b}: {c}亿' },
|
||||
series: [{
|
||||
type: 'sankey',
|
||||
layout: 'none',
|
||||
top: '5%', bottom: '5%', left: '5%', right: '15%',
|
||||
nodeWidth: 20,
|
||||
nodeGap: 12,
|
||||
label: { fontSize: 11, fontFamily: 'Noto Sans SC' },
|
||||
lineStyle: { color: 'gradient', curveness: 0.5, opacity: 0.4 },
|
||||
emphasis: { lineStyle: { opacity: 0.7 } },
|
||||
data: [
|
||||
{ name: '游戏' }, { name: '社交' }, { name: '广告' }, { name: '金融科技' },
|
||||
{ name: '营业收入' }, { name: '营业成本' }, { name: '毛利' }, { name: '费用' }, { name: '净利润' }
|
||||
],
|
||||
links: [
|
||||
{ source: '游戏', target: '营业收入', value: 1843 },
|
||||
{ source: '社交', target: '营业收入', value: 1200 },
|
||||
{ source: '广告', target: '营业收入', value: 1240 },
|
||||
{ source: '金融科技', target: '营业收入', value: 2100 },
|
||||
{ source: '营业收入', target: '营业成本', value: 3073 },
|
||||
{ source: '营业收入', target: '毛利', value: 3536 },
|
||||
{ source: '毛利', target: '费用', value: 1515 },
|
||||
{ source: '毛利', target: '净利润', value: 2021 }
|
||||
]
|
||||
}]
|
||||
};
|
||||
```
|
||||
146
fund-slides/references/charts-matrix.md
Normal file
146
fund-slides/references/charts-matrix.md
Normal file
@ -0,0 +1,146 @@
|
||||
# Charts -- Matrix (矩阵与分布类)
|
||||
|
||||
热力图、相关性矩阵、雷达图、散点图。基础设施见 [charts-base.md](charts-base.md)。
|
||||
|
||||
---
|
||||
|
||||
## 热力图(NxM 矩阵数据)
|
||||
|
||||
两个维度交叉的数值强度,颜色深浅表示大小。
|
||||
|
||||
```javascript
|
||||
const heatmapOption = {
|
||||
tooltip: { position: 'top', formatter: p => p.data[1] + ' x ' + p.data[0] + ': ' + p.data[2] },
|
||||
grid: { left: '12%', right: '8%', top: '8%', bottom: '15%' },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: ['Q1', 'Q2', 'Q3', 'Q4'],
|
||||
splitArea: { show: true },
|
||||
axisLabel: { fontSize: 11 }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['游戏', '社交', '广告', '金融科技', '云服务'],
|
||||
splitArea: { show: true },
|
||||
axisLabel: { fontSize: 11 }
|
||||
},
|
||||
visualMap: {
|
||||
min: 0, max: 600,
|
||||
calculable: true,
|
||||
orient: 'horizontal',
|
||||
left: 'center', bottom: '0%',
|
||||
inRange: { color: ['#f0f4ff', '#93c5fd', '#2563eb', '#1a365d'] },
|
||||
textStyle: { fontSize: 10 }
|
||||
},
|
||||
series: [{
|
||||
type: 'heatmap',
|
||||
data: [
|
||||
['Q1', '游戏', 480], ['Q2', '游戏', 520], ['Q3', '游戏', 460], ['Q4', '游戏', 580],
|
||||
['Q1', '社交', 290], ['Q2', '社交', 310], ['Q3', '社交', 300], ['Q4', '社交', 320],
|
||||
['Q1', '广告', 250], ['Q2', '广告', 340], ['Q3', '广告', 310], ['Q4', '广告', 380],
|
||||
['Q1', '金融科技', 490], ['Q2', '金融科技', 530], ['Q3', '金融科技', 510], ['Q4', '金融科技', 560],
|
||||
['Q1', '云服务', 130], ['Q2', '云服务', 150], ['Q3', '云服务', 155], ['Q4', '云服务', 170]
|
||||
],
|
||||
label: { show: true, fontSize: 10, color: '#333', formatter: p => p.data[2] },
|
||||
emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.3)' } }
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 相关性热力图(方阵)
|
||||
|
||||
对称矩阵,-1 到 +1 色阶。适用于多资产/多因子相关系数。
|
||||
|
||||
```javascript
|
||||
const labels = ['贵州茅台', '五粮液', '泸州老窖', '山西汾酒', '洋河股份'];
|
||||
const corrData = [
|
||||
[0,0,1.0],[1,0,0.85],[2,0,0.82],[3,0,0.71],[4,0,0.68],
|
||||
[0,1,0.85],[1,1,1.0],[2,1,0.91],[3,1,0.78],[4,1,0.74],
|
||||
[0,2,0.82],[1,2,0.91],[2,2,1.0],[3,2,0.80],[4,2,0.72],
|
||||
[0,3,0.71],[1,3,0.78],[2,3,0.80],[3,3,1.0],[4,3,0.65],
|
||||
[0,4,0.68],[1,4,0.74],[2,4,0.72],[3,4,0.65],[4,4,1.0]
|
||||
];
|
||||
|
||||
const corrMatrixOption = {
|
||||
tooltip: { formatter: p => labels[p.data[0]] + ' vs ' + labels[p.data[1]] + ': ' + p.data[2].toFixed(2) },
|
||||
grid: { left: '15%', right: '12%', top: '15%', bottom: '5%' },
|
||||
xAxis: { type: 'category', data: labels, axisLabel: { rotate: 30, fontSize: 10 } },
|
||||
yAxis: { type: 'category', data: labels, axisLabel: { fontSize: 10 } },
|
||||
visualMap: {
|
||||
min: -1, max: 1,
|
||||
inRange: { color: ['#dc2626', '#fef2f2', '#ffffff', '#eff6ff', '#2563eb'] },
|
||||
orient: 'horizontal', left: 'center', bottom: '0%',
|
||||
text: ['+1.0', '-1.0']
|
||||
},
|
||||
series: [{
|
||||
type: 'heatmap',
|
||||
data: corrData,
|
||||
label: { show: true, fontSize: 10, formatter: p => p.data[2].toFixed(2) }
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 雷达图(多维评分对比)
|
||||
|
||||
多个维度的综合评分。2 个主体对比最佳。
|
||||
|
||||
```javascript
|
||||
const radarOption = {
|
||||
tooltip: {},
|
||||
legend: { top: '2%' },
|
||||
radar: {
|
||||
indicator: [
|
||||
{ name: '网络效应', max: 10 },
|
||||
{ name: '转换成本', max: 10 },
|
||||
{ name: '品牌价值', max: 10 },
|
||||
{ name: '规模经济', max: 10 },
|
||||
{ name: '无形资产', max: 10 },
|
||||
{ name: '成本优势', max: 10 }
|
||||
],
|
||||
radius: '65%',
|
||||
axisName: { fontSize: 11, fontFamily: 'Noto Sans SC' },
|
||||
splitArea: { areaStyle: { color: ['rgba(37,99,235,0.02)', 'rgba(37,99,235,0.05)'] } }
|
||||
},
|
||||
series: [{
|
||||
type: 'radar',
|
||||
data: [
|
||||
{ name: '腾讯', value: [9.5, 9.0, 7.0, 8.0, 8.5, 5.0], areaStyle: { opacity: 0.25 }, lineStyle: { width: 2 } },
|
||||
{ name: '阿里巴巴', value: [7.0, 7.5, 6.5, 8.5, 6.0, 6.0], areaStyle: { opacity: 0.15 }, lineStyle: { width: 2 } }
|
||||
]
|
||||
}]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 散点图 / 气泡图
|
||||
|
||||
两变量相关性,气泡大小编码第三维度。
|
||||
|
||||
```javascript
|
||||
const scatterOption = {
|
||||
tooltip: {
|
||||
formatter: p => p.data[3] + '<br/>PE: ' + p.data[0] + 'x<br/>ROE: ' + p.data[1] + '%<br/>市值: ' + formatCN(p.data[2])
|
||||
},
|
||||
grid: { left: '10%', right: '8%', top: '8%', bottom: '12%' },
|
||||
xAxis: { name: 'PE (TTM)', nameLocation: 'center', nameGap: 30, scale: true },
|
||||
yAxis: { name: 'ROE (%)', nameLocation: 'center', nameGap: 35, scale: true },
|
||||
series: [{
|
||||
type: 'scatter',
|
||||
symbolSize: p => Math.sqrt(p[2]) / 800,
|
||||
data: [
|
||||
[22.8, 22.5, 53000e8, '腾讯'],
|
||||
[16.2, 12.8, 22000e8, '阿里巴巴'],
|
||||
[28.5, 35.2, 16000e8, 'Meta'],
|
||||
[35.0, 42.0, 33000e8, '微软'],
|
||||
[25.0, 30.0, 25000e8, '字节(估)']
|
||||
],
|
||||
label: { show: true, formatter: p => p.data[3], position: 'top', fontSize: 10 },
|
||||
emphasis: { itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.3)' } }
|
||||
}]
|
||||
};
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user