feat: add Chinese office productivity plugins
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
---
|
||||
name: build-spreadsheets
|
||||
description: 创建、读取、编辑、分析和验证 XLSX、CSV、TSV 表格文件。用户要求制作 Excel、整理 CSV、加入公式和图表、分析工作簿、修复公式或保留模板格式时使用;不用于控制正在 Excel 中打开的实时工作簿。
|
||||
---
|
||||
|
||||
# CJYSA 表格
|
||||
|
||||
## 路由
|
||||
|
||||
- 对 CSV/TSV 做纯数据清洗时可直接用 Python。
|
||||
- 对 XLSX 创建和编辑,优先使用工作区依赖加载器提供的 `@oai/artifact-tool`;不可用时使用其 Python 和 `openpyxl`。
|
||||
- 用户要求控制当前打开的 Excel 窗口时,不使用本技能;需要 Excel/WPS 实时控制能力。
|
||||
|
||||
## 标准流程
|
||||
|
||||
1. 先运行结构检查:
|
||||
|
||||
```powershell
|
||||
& "<bundled-python>" "<skill-dir>\scripts\xlsx_tool.py" inspect "<input.xlsx>"
|
||||
```
|
||||
|
||||
2. 编辑已有工作簿前检查相关单元格的值、公式、数字格式、合并区域、冻结窗格、条件格式和图表。
|
||||
3. 创建工作簿时把输入、计算和输出分区;派生数据使用可审计公式,不把结果硬编码。
|
||||
4. 简单 CSV 转 XLSX 可运行:
|
||||
|
||||
```powershell
|
||||
& "<bundled-python>" "<skill-dir>\scripts\xlsx_tool.py" csv-to-xlsx `
|
||||
"<input.csv>" --output "<output.xlsx>"
|
||||
```
|
||||
|
||||
5. 运行 `formula-scan` 检查明显错误,再读取关键范围核对结果。
|
||||
6. 使用 artifact-tool、Excel、WPS 或可靠渲染器查看所有工作表;不可渲染时明确只完成结构和公式检查。
|
||||
|
||||
## 表格规则
|
||||
|
||||
- 数字、日期、百分比和货币使用真实类型,不写成带符号的文本。
|
||||
- 公式引用保持一致,跨表引用对含空格的工作表名加单引号。
|
||||
- 编辑已有表格时做最小改动,不对整张表盲目自动宽度或重设样式。
|
||||
- 重要假设集中放置并标注;复杂公式拆成辅助行/列。
|
||||
- 不复制 OpenAI 官方 Spreadsheets 插件、内部 API 文档或 App 集成。
|
||||
- 交付前按 [quality-checks.md](references/quality-checks.md) 检查。
|
||||
|
||||
## 输出
|
||||
|
||||
最终只链接用户要求的 XLSX/CSV/TSV,不主动附带 builder、预览图或检查日志。
|
||||
@@ -0,0 +1,7 @@
|
||||
interface:
|
||||
display_name: "CJYSA 表格"
|
||||
short_description: "创建、编辑、分析并验证 Excel 工作簿和数据表"
|
||||
default_prompt: "Use $build-spreadsheets to create or edit an auditable XLSX workbook."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -0,0 +1,9 @@
|
||||
# 表格交付检查
|
||||
|
||||
- 工作表名称、可见性、冻结窗格和筛选范围正确。
|
||||
- 输入、公式和输出区域清晰,关键公式可追溯。
|
||||
- 扫描 `#REF!`、`#DIV/0!`、`#VALUE!`、`#NAME?` 和 `#N/A`。
|
||||
- 日期、数字、百分比和货币使用正确的数据类型与格式。
|
||||
- 合并单元格、条件格式、图表和打印区域未被意外破坏。
|
||||
- 逐表查看预览,确认标题、关键数字和图表未裁切。
|
||||
- 若无法重新计算公式,明确说明结果由 Excel/WPS 打开后计算。
|
||||
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Original XLSX helper for inspection, error scans, and CSV conversion."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from openpyxl import Workbook, load_workbook
|
||||
from openpyxl.styles import Font, PatternFill
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
|
||||
ERROR_TOKENS = ("#REF!", "#DIV/0!", "#VALUE!", "#NAME?", "#N/A", "#NUM!", "#NULL!")
|
||||
|
||||
|
||||
def inspect(path: Path) -> None:
|
||||
workbook = load_workbook(path, data_only=False, read_only=False)
|
||||
sheets = []
|
||||
for sheet in workbook.worksheets:
|
||||
formula_count = 0
|
||||
nonempty = 0
|
||||
for row in sheet.iter_rows():
|
||||
for cell in row:
|
||||
if cell.value is not None:
|
||||
nonempty += 1
|
||||
if isinstance(cell.value, str) and cell.value.startswith("="):
|
||||
formula_count += 1
|
||||
sheets.append(
|
||||
{
|
||||
"name": sheet.title,
|
||||
"state": sheet.sheet_state,
|
||||
"max_row": sheet.max_row,
|
||||
"max_column": sheet.max_column,
|
||||
"nonempty_cells": nonempty,
|
||||
"formulas": formula_count,
|
||||
"merged_ranges": len(sheet.merged_cells.ranges),
|
||||
"charts": len(sheet._charts),
|
||||
"freeze_panes": str(sheet.freeze_panes) if sheet.freeze_panes else None,
|
||||
}
|
||||
)
|
||||
print(json.dumps({"file": str(path.resolve()), "sheets": sheets}, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
def formula_scan(path: Path) -> None:
|
||||
formula_book = load_workbook(path, data_only=False, read_only=False)
|
||||
value_book = load_workbook(path, data_only=True, read_only=False)
|
||||
findings = []
|
||||
for formula_sheet in formula_book.worksheets:
|
||||
value_sheet = value_book[formula_sheet.title]
|
||||
for row in formula_sheet.iter_rows():
|
||||
for cell in row:
|
||||
formula = cell.value
|
||||
cached = value_sheet[cell.coordinate].value
|
||||
if isinstance(formula, str) and formula.startswith("="):
|
||||
formula_upper = formula.upper()
|
||||
if any(token in formula_upper for token in ERROR_TOKENS):
|
||||
findings.append({"sheet": formula_sheet.title, "cell": cell.coordinate, "kind": "formula_text", "value": formula})
|
||||
if isinstance(cached, str) and cached.upper() in ERROR_TOKENS:
|
||||
findings.append({"sheet": formula_sheet.title, "cell": cell.coordinate, "kind": "cached_error", "value": cached})
|
||||
print(json.dumps({"file": str(path.resolve()), "errors": findings, "count": len(findings)}, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
def csv_to_xlsx(source: Path, output: Path, delimiter: str | None) -> None:
|
||||
chosen = delimiter
|
||||
if chosen is None:
|
||||
chosen = "\t" if source.suffix.lower() == ".tsv" else ","
|
||||
workbook = Workbook()
|
||||
sheet = workbook.active
|
||||
sheet.title = "Data"
|
||||
with source.open("r", encoding="utf-8-sig", newline="") as handle:
|
||||
reader = csv.reader(handle, delimiter=chosen)
|
||||
for row_index, row in enumerate(reader, start=1):
|
||||
for column_index, value in enumerate(row, start=1):
|
||||
sheet.cell(row=row_index, column=column_index, value=value)
|
||||
if sheet.max_row >= 1:
|
||||
for cell in sheet[1]:
|
||||
cell.font = Font(bold=True, color="FFFFFF")
|
||||
cell.fill = PatternFill("solid", fgColor="1F4E78")
|
||||
sheet.freeze_panes = "A2"
|
||||
sheet.auto_filter.ref = sheet.dimensions
|
||||
for column in range(1, sheet.max_column + 1):
|
||||
width = max((len(str(sheet.cell(row=row, column=column).value or "")) for row in range(1, min(sheet.max_row, 200) + 1)), default=8)
|
||||
sheet.column_dimensions[get_column_letter(column)].width = min(max(width + 2, 10), 45)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
workbook.save(output)
|
||||
inspect(output)
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser()
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
inspect_cmd = sub.add_parser("inspect")
|
||||
inspect_cmd.add_argument("input", type=Path)
|
||||
scan_cmd = sub.add_parser("formula-scan")
|
||||
scan_cmd.add_argument("input", type=Path)
|
||||
convert_cmd = sub.add_parser("csv-to-xlsx")
|
||||
convert_cmd.add_argument("input", type=Path)
|
||||
convert_cmd.add_argument("--output", required=True, type=Path)
|
||||
convert_cmd.add_argument("--delimiter")
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = build_parser().parse_args()
|
||||
if args.command == "inspect":
|
||||
inspect(args.input)
|
||||
elif args.command == "formula-scan":
|
||||
formula_scan(args.input)
|
||||
elif args.command == "csv-to-xlsx":
|
||||
csv_to_xlsx(args.input, args.output, args.delimiter)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user