cuxml 2.1.1 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -86
- package/package.json +1 -1
- package/src/checker.js +16 -1
- package/src/cli.js +17 -24
- package/src/converter.js +14 -0
- package/src/templates.js +6 -13
- package/test/Agents.md +0 -76
- package/test/agent_new.md +0 -286
- package/test/ribbon.js +0 -174
- package/test/ribbon.xml +0 -16
- package/test/template.js +0 -0
- package/test/test.js +0 -325
package/README.md
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
> CU: `'Can Use'` or `'customUI'`, whatever……
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/cuxml)
|
|
6
|
-
[](LICENSE)
|
|
7
6
|
|
|
8
7
|
**CUXML** 是一个专为 WPS Office JS 插件开发者设计的工具,用于处理 RibbonUI XML 文件。它可以显著简化开发流程,让你更专注于业务逻辑。
|
|
9
8
|
|
|
10
9
|
## ✨ 特性
|
|
11
10
|
|
|
12
11
|
- 🔄 **XML 到 JavaScript 转换** - 自动将 RibbonUI XML 的回调函数配置转换为 JavaScript 代码
|
|
13
|
-
- 🔍 **XML 合规性检查** - 全面检查 XML 文件是否符合 CustomUI
|
|
12
|
+
- 🔍 **XML 合规性检查** - 全面检查 XML 文件是否符合 CustomUI 规范,详细显示错误和警告
|
|
14
13
|
- 👀 **文件监视** - 监视 XML 文件变化,自动重新转换
|
|
15
14
|
- 📦 **模块化 API** - 提供完整的编程接口,支持同步和异步调用
|
|
16
15
|
- 🔌 **MCP 支持** - 可作为 MCP 插件集成到 AI 工具链中
|
|
16
|
+
- ⚡ **转换前自动检查** - 转换前自动运行检查,提高开发效率
|
|
17
17
|
|
|
18
18
|
## 📦 安装
|
|
19
19
|
|
|
@@ -34,22 +34,54 @@ cuxml -i ./ribbon.xml # 输出到 ./ribbon_temp.js
|
|
|
34
34
|
# 指定输出路径
|
|
35
35
|
cuxml -i ./ribbon.xml -o ./ribbon.js
|
|
36
36
|
|
|
37
|
-
#
|
|
38
|
-
cuxml -i ./ribbon.xml -w
|
|
37
|
+
# 监视模式(文件变化时自动转换,默认覆盖输出文件)
|
|
38
|
+
cuxml -i ./ribbon.xml -w
|
|
39
|
+
|
|
40
|
+
# 非 watch 模式下覆盖已存在的输出文件
|
|
41
|
+
cuxml -i ./ribbon.xml -o ./ribbon.js --overwrite
|
|
39
42
|
|
|
40
43
|
# 使用默认输入路径(当前目录下的 ribbon.xml)
|
|
41
44
|
cuxml # 需要在当前目录有 ribbon.xml 文件
|
|
45
|
+
|
|
46
|
+
# 注意:转换前会自动进行 XML 检查,有错误时会阻止转换
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**选项说明:**
|
|
50
|
+
|
|
51
|
+
| 选项 | 说明 |
|
|
52
|
+
|------|------|
|
|
53
|
+
| `-i, --input` | 输入的 XML 文件路径 |
|
|
54
|
+
| `-o, --output` | 输出的 JS 文件路径 |
|
|
55
|
+
| `-w, --watch` | 监视文件变化并自动转换(自动覆盖) |
|
|
56
|
+
| `--overwrite` | 覆盖输出文件(非 watch 模式使用) |
|
|
57
|
+
|
|
58
|
+
#### convert 子命令
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# 基本转换
|
|
62
|
+
cuxml convert ./ribbon.xml
|
|
63
|
+
|
|
64
|
+
# 指定输入输出
|
|
65
|
+
cuxml convert ./ribbon.xml ./ribbon.js
|
|
66
|
+
|
|
67
|
+
# 监视模式
|
|
68
|
+
cuxml convert ./ribbon.xml ./ribbon.js -w
|
|
69
|
+
|
|
70
|
+
# 非 watch 模式下覆盖文件
|
|
71
|
+
cuxml convert ./ribbon.xml ./ribbon.js --overwrite
|
|
42
72
|
```
|
|
43
73
|
|
|
44
74
|
#### 检查 XML 文件
|
|
45
75
|
|
|
46
76
|
```bash
|
|
47
77
|
cuxml check ./ribbon.xml
|
|
48
|
-
|
|
49
|
-
# 显示详细信息
|
|
50
|
-
cuxml check ./ribbon.xml -v
|
|
51
78
|
```
|
|
52
79
|
|
|
80
|
+
检查命令会自动显示详细的错误和警告信息,包括:
|
|
81
|
+
- 错误类型和位置
|
|
82
|
+
- 警告详情和该元素支持的所有属性(以表格形式显示)
|
|
83
|
+
- 支持的属性会按 4 列格式化输出,便于快速查找
|
|
84
|
+
|
|
53
85
|
#### 查看帮助
|
|
54
86
|
|
|
55
87
|
```bash
|
|
@@ -109,10 +141,6 @@ npm install @modelcontextprotocol/sdk
|
|
|
109
141
|
}
|
|
110
142
|
```
|
|
111
143
|
|
|
112
|
-
## 📖 详细文档
|
|
113
|
-
|
|
114
|
-
完整的使用文档和技术细节请参考:[CUXML Agent 文档](./test/agent_new.md)
|
|
115
|
-
|
|
116
144
|
## 🔧 功能说明
|
|
117
145
|
|
|
118
146
|
### XML 到 JavaScript 转换
|
|
@@ -156,43 +184,9 @@ function handleClick(ctrl) {
|
|
|
156
184
|
- ✅ 属性值类型验证
|
|
157
185
|
- ✅ 元素不支持的属性检测
|
|
158
186
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
```
|
|
162
|
-
cuxml/
|
|
163
|
-
├── src/
|
|
164
|
-
│ ├── api.js # API 模块
|
|
165
|
-
│ ├── checker.js # XML 检查器
|
|
166
|
-
│ ├── cli.js # CLI 工具
|
|
167
|
-
│ ├── converter.js # 转换器
|
|
168
|
-
│ ├── mcp.js # MCP 插件
|
|
169
|
-
│ ├── parser.js # XML 解析器
|
|
170
|
-
│ └── templates.js # 模板生成器
|
|
171
|
-
├── lib/
|
|
172
|
-
│ ├── simpleTypes.json # Simple Types 配置
|
|
173
|
-
│ ├── clashAttributes.json # 冲突属性配置
|
|
174
|
-
│ ├── StDelegate.json # 回调函数属性配置
|
|
175
|
-
│ └── cbfTypes.json # Callback 函数类型配置
|
|
176
|
-
├── test/ # 测试文件和示例
|
|
177
|
-
├── index.js # 主入口
|
|
178
|
-
└── package.json
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## 🎯 最佳实践
|
|
182
|
-
|
|
183
|
-
### 1. 文件组织
|
|
184
|
-
|
|
185
|
-
```
|
|
186
|
-
project/
|
|
187
|
-
├── src/
|
|
188
|
-
│ └── ribbon.xml # RibbonUI 配置文件
|
|
189
|
-
├── gen/
|
|
190
|
-
│ └── ribbon_callbacks.js # 自动生成的回调函数
|
|
191
|
-
├── main.js # 主逻辑文件
|
|
192
|
-
└── package.json
|
|
193
|
-
```
|
|
187
|
+
检查命令会以表格形式显示该元素支持的所有属性,便于快速查找正确的属性名。
|
|
194
188
|
|
|
195
|
-
###
|
|
189
|
+
### 开发工作流
|
|
196
190
|
|
|
197
191
|
```bash
|
|
198
192
|
# 开发阶段:使用监视模式,实时更新
|
|
@@ -202,52 +196,19 @@ cuxml -i src/ribbon.xml -o gen/ribbon_callbacks.js -w
|
|
|
202
196
|
cuxml check src/ribbon.xml && cuxml -i src/ribbon.xml -o gen/ribbon_callbacks.js
|
|
203
197
|
```
|
|
204
198
|
|
|
205
|
-
### 3. 错误处理
|
|
206
|
-
|
|
207
|
-
```javascript
|
|
208
|
-
const cuxml = require('cuxml');
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
cuxml.check('./ribbon.xml');
|
|
212
|
-
cuxml.convert('./ribbon.xml', './ribbon.js');
|
|
213
|
-
console.log('✅ 转换成功!');
|
|
214
|
-
} catch (error) {
|
|
215
|
-
console.error('❌ 转换失败:', error.message);
|
|
216
|
-
// 处理错误
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
199
|
## 🔗 相关资源
|
|
221
200
|
|
|
222
201
|
- [Microsoft CustomUI 规范](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-customui/574eeee8-7a03-406a-b95f-f9e51e53dd9d)
|
|
223
202
|
- [Simple Types 参考](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-customui/869c8c9a-45f8-4119-b068-f61e76d04322)
|
|
224
203
|
- [WPS Office 开发文档](https://wps.cn/developer/)
|
|
225
|
-
- [CUXML Agent 文档](./test/agent_new.md) - 完整的使用文档
|
|
226
|
-
|
|
227
|
-
## 🤝 贡献
|
|
228
|
-
|
|
229
|
-
欢迎贡献!请随时提交 Issue 或 Pull Request。
|
|
230
|
-
|
|
231
|
-
## 📄 许可证
|
|
232
|
-
|
|
233
|
-
[ISC](LICENSE)
|
|
234
204
|
|
|
235
205
|
## 📊 版本历史
|
|
236
206
|
|
|
237
|
-
### v2.
|
|
238
|
-
|
|
239
|
-
**重大更新:完全重构**
|
|
240
|
-
|
|
241
|
-
- ✨ 全新的模块化架构
|
|
242
|
-
- ✨ 完整的 API 模块(支持同步和异步)
|
|
243
|
-
- ✨ 统一的 CLI 命令(`cuxml`)
|
|
244
|
-
- ✨ MCP 协议支持
|
|
245
|
-
- ✨ 完整的 JSDoc 文档
|
|
246
|
-
- ✨ 改进的错误处理和日志输出
|
|
247
|
-
- 🔧 优化项目结构
|
|
248
|
-
- 🐛 修复多个已知问题
|
|
207
|
+
### v2.1.3 (2026-04-20)
|
|
249
208
|
|
|
250
|
-
|
|
209
|
+
**功能增强**
|
|
251
210
|
|
|
252
|
-
-
|
|
253
|
-
-
|
|
211
|
+
- ✨ 改进检查命令输出 - 支持的属性以表格形式显示,提高可读性
|
|
212
|
+
- ✨ 转换前自动检查 - 转换前自动运行 XML 检查,有错误时阻止转换
|
|
213
|
+
- ✨ 详细的警告信息 - 未知属性和不支持的属性会显示该元素支持的所有属性
|
|
214
|
+
- 📝 更新 README 文档
|
package/package.json
CHANGED
package/src/checker.js
CHANGED
|
@@ -228,7 +228,22 @@ class XMLChecker {
|
|
|
228
228
|
.filter(r => r.warnings.length > 0)
|
|
229
229
|
.forEach(r => {
|
|
230
230
|
output += ` 路径: ${r.xmlpath}\n`;
|
|
231
|
-
r.warnings.forEach(w =>
|
|
231
|
+
r.warnings.forEach(w => {
|
|
232
|
+
output += ` - ${w}\n`;
|
|
233
|
+
// 如果是未知属性或不支持的属性,列出该元素支持的所有属性
|
|
234
|
+
if ((w.startsWith('未知属性') || w.includes('不支持的属性')) && r.nodeElement && r.nodeElement.name) {
|
|
235
|
+
const elementName = r.nodeElement.name;
|
|
236
|
+
const supportedAttrs = elementsAttributes[elementName];
|
|
237
|
+
if (supportedAttrs && supportedAttrs.length > 0) {
|
|
238
|
+
// 先对数组进行排序
|
|
239
|
+
const sortedAttrs = [...supportedAttrs].sort();
|
|
240
|
+
output += ` 支持的属性:\n`;
|
|
241
|
+
sortedAttrs.forEach(attr => {
|
|
242
|
+
output += ` - ${attr}\n`;
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
232
247
|
});
|
|
233
248
|
}
|
|
234
249
|
|
package/src/cli.js
CHANGED
|
@@ -33,15 +33,20 @@ program
|
|
|
33
33
|
.option('-i, --input <file>', '输入的 XML 文件路径', './ribbon.xml')
|
|
34
34
|
.option('-o, --output <file>', '输出的 JS 文件路径')
|
|
35
35
|
.option('-w, --watch', '监视文件变化并自动转换')
|
|
36
|
+
.option('--overwrite', '覆盖输出文件(非 watch 模式)')
|
|
36
37
|
.action((options) => {
|
|
37
38
|
const outputPath = options.output || getDefaultOutputPath(options.input);
|
|
38
39
|
try {
|
|
40
|
+
const watchOptions = options.watch ? { overwrite: true } : { overwrite: options.overwrite };
|
|
41
|
+
|
|
39
42
|
if (options.watch) {
|
|
40
43
|
console.log('🚀 启动监视模式...\n');
|
|
41
|
-
CUXMLAPI.watch(options.input, outputPath);
|
|
44
|
+
CUXMLAPI.watch(options.input, outputPath, watchOptions);
|
|
42
45
|
} else {
|
|
43
46
|
console.log('🔄 开始转换...\n');
|
|
44
|
-
const result = CUXMLAPI.convertSync(options.input, outputPath
|
|
47
|
+
const result = CUXMLAPI.convertSync(options.input, outputPath, {
|
|
48
|
+
overwrite: options.overwrite
|
|
49
|
+
});
|
|
45
50
|
|
|
46
51
|
if (result.success) {
|
|
47
52
|
console.log(`✅ ${result.message}`);
|
|
@@ -62,29 +67,15 @@ program
|
|
|
62
67
|
.command('check')
|
|
63
68
|
.description('检查 XML 文件是否符合 CustomUI 规范')
|
|
64
69
|
.argument('<xmlFile>', '要检查的 XML 文件路径')
|
|
65
|
-
.
|
|
66
|
-
.action((xmlFile, options) => {
|
|
70
|
+
.action((xmlFile) => {
|
|
67
71
|
try {
|
|
68
72
|
console.log('🔍 开始检查...\n');
|
|
69
|
-
|
|
73
|
+
|
|
70
74
|
const result = XMLChecker.checkWithSummary(xmlFile);
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
console.log(`总节点数: ${result.totalNodes}`);
|
|
76
|
-
console.log(`通过: ${result.passed}`);
|
|
77
|
-
console.log(`错误: ${result.errors}`);
|
|
78
|
-
console.log(`警告: ${result.warnings}`);
|
|
79
|
-
|
|
80
|
-
if (result.errors === 0 && result.warnings === 0) {
|
|
81
|
-
console.log('\n✅ 检查通过!');
|
|
82
|
-
} else if (result.errors === 0) {
|
|
83
|
-
console.log('\n⚠️ 检查通过,但有警告');
|
|
84
|
-
} else {
|
|
85
|
-
console.log('\n❌ 检查失败,发现错误');
|
|
86
|
-
process.exit(1);
|
|
87
|
-
}
|
|
75
|
+
console.log(XMLChecker.formatSummary(result));
|
|
76
|
+
|
|
77
|
+
if (result.errors > 0) {
|
|
78
|
+
process.exit(1);
|
|
88
79
|
}
|
|
89
80
|
} catch (error) {
|
|
90
81
|
console.error(`❌ 错误: ${error.message}`);
|
|
@@ -99,12 +90,14 @@ program
|
|
|
99
90
|
.argument('<inputFile>', '输入的 XML 文件路径')
|
|
100
91
|
.argument('[outputFile]', '输出的 JS 文件路径', './ribbon_temp.js')
|
|
101
92
|
.option('-w, --watch', '监视文件变化并自动转换')
|
|
102
|
-
.option('--overwrite', '
|
|
93
|
+
.option('--overwrite', '覆盖输出文件(非 watch 模式)')
|
|
103
94
|
.action((inputFile, outputFile, options) => {
|
|
104
95
|
try {
|
|
96
|
+
const watchOptions = options.watch ? { overwrite: true } : { overwrite: options.overwrite };
|
|
97
|
+
|
|
105
98
|
if (options.watch) {
|
|
106
99
|
console.log('🚀 启动监视模式...\n');
|
|
107
|
-
CUXMLAPI.watch(inputFile, outputFile,
|
|
100
|
+
CUXMLAPI.watch(inputFile, outputFile, watchOptions);
|
|
108
101
|
} else {
|
|
109
102
|
console.log('🔄 开始转换...\n');
|
|
110
103
|
const result = CUXMLAPI.convertSync(inputFile, outputFile, {
|
package/src/converter.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const lodash = require('lodash');
|
|
4
4
|
const XMLParser = require('./parser');
|
|
5
|
+
const XMLChecker = require('./checker');
|
|
5
6
|
const { generateCallbackTemplate, processFunctionName } = require('./templates');
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -40,6 +41,19 @@ class XMLConverter {
|
|
|
40
41
|
throw new Error(`输入文件不存在: ${inputPath}`);
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
// 先进行 XML 检查
|
|
45
|
+
const checkSummary = XMLChecker.checkWithSummary(inputPath);
|
|
46
|
+
|
|
47
|
+
// 显示检查结果
|
|
48
|
+
if (!options.silent) {
|
|
49
|
+
if (checkSummary.errors > 0) {
|
|
50
|
+
console.log(XMLChecker.formatSummary(checkSummary));
|
|
51
|
+
throw new Error('XML 检查失败,存在错误');
|
|
52
|
+
} else if (checkSummary.warnings > 0) {
|
|
53
|
+
console.log(XMLChecker.formatSummary(checkSummary));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
// 加载配置
|
|
44
58
|
const clashAttributes = require('../lib/clashAttributes.json').crashs;
|
|
45
59
|
const stDelegate = require('../lib/StDelegate.json');
|
package/src/templates.js
CHANGED
|
@@ -12,18 +12,11 @@ function generateCallbackTemplate(functionName, controls = []) {
|
|
|
12
12
|
// 如果控件数量为 0 或 1,生成简化版本的函数
|
|
13
13
|
if (controls.length <= 1) {
|
|
14
14
|
return `/**
|
|
15
|
-
* @param {RibbonUI} control
|
|
16
|
-
* @note
|
|
17
|
-
* - 这是控件的回调函数,不应该在其他 JS 文件中调用这个函数
|
|
18
|
-
* - 尽量立即完成,避免写长耗时代码。
|
|
15
|
+
* @param {RibbonUI} control 控件对象
|
|
19
16
|
*
|
|
20
|
-
*
|
|
17
|
+
* ${new Date().toLocaleString()}
|
|
21
18
|
*/
|
|
22
19
|
function ${functionName}(control) {
|
|
23
|
-
|
|
24
|
-
// code here
|
|
25
|
-
|
|
26
|
-
// 避免 UI 报错,总是返回 true
|
|
27
20
|
return true;
|
|
28
21
|
}
|
|
29
22
|
`;
|
|
@@ -31,16 +24,16 @@ function ${functionName}(control) {
|
|
|
31
24
|
|
|
32
25
|
// 多个控件使用时,生成 switch case 结构
|
|
33
26
|
const cases = controls.map(control => {
|
|
34
|
-
return ` // xmlPath: ${control.xmlPath}\n case "${control.id}":\n
|
|
27
|
+
return ` // xmlPath: ${control.xmlPath}\n case "${control.id}":\n\n break;`;
|
|
35
28
|
});
|
|
36
29
|
|
|
37
30
|
return `/**
|
|
38
|
-
* @param {RibbonUI} control
|
|
31
|
+
* @param {RibbonUI} control 控件对象
|
|
39
32
|
* @note
|
|
40
|
-
* -
|
|
33
|
+
* - 这是控件的回调函数
|
|
41
34
|
* - 尽量立即完成,避免写长耗时代码。
|
|
42
35
|
*
|
|
43
|
-
*
|
|
36
|
+
* ${new Date().toLocaleString()}
|
|
44
37
|
*/
|
|
45
38
|
function ${functionName}(control) {
|
|
46
39
|
|
package/test/Agents.md
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# CUXML Agent
|
|
2
|
-
|
|
3
|
-
## 1. 简介
|
|
4
|
-
|
|
5
|
-
CUXML 是一个处理 RibbonUI XML 文件的工具。
|
|
6
|
-
|
|
7
|
-
> [ribbon](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-customui/574eeee8-7a03-406a-b95f-f9e51e53dd9d)
|
|
8
|
-
|
|
9
|
-
## CUXML 的功能包括:
|
|
10
|
-
|
|
11
|
-
- 将 RibbonUI 的 XML 文件转换为 callback function。
|
|
12
|
-
- 检查 RibbonUI 的 XML 文件是否合法:
|
|
13
|
-
- 检查 XML 文件是否符合规范。
|
|
14
|
-
- 检查 XML 文件是否有重复的 ID。
|
|
15
|
-
- 检查 XML 文件是否有冲突的属性。比如:label 和 getLabel. 参考:[冲突的属性](../clashAttributes.json)
|
|
16
|
-
- 检查 XML tag 的嵌套合规性。比如:button 不能嵌套在 button 中。
|
|
17
|
-
- 等等,可补充
|
|
18
|
-
- 监视 RibbonUI 文件的变化,并自动转换为 callback function 到指定文件。
|
|
19
|
-
|
|
20
|
-
### callback function 的处理
|
|
21
|
-
|
|
22
|
-
- 将 XML 文件转换为 callback function 的代码,放在一个单独的文件中,比如:`ribbon_temp.js`。
|
|
23
|
-
- 如果callback function 的名称带有"."连接符,则截取"."之后的字符串作为callback function 的名称。比如:`onClick.button1` 转换为 `button1`,最终得到 `function button1(ctrol){}`。
|
|
24
|
-
|
|
25
|
-
### 参考的源文件
|
|
26
|
-
|
|
27
|
-
- xml: [xml](./ribbon.xml)
|
|
28
|
-
- js: [js](./ribbon.js)
|
|
29
|
-
- 转换模版: [template](./template.js)
|
|
30
|
-
|
|
31
|
-
#### 哪些属性可以被转换为 callback function
|
|
32
|
-
|
|
33
|
-
可以参考 [常见属性的callback function](../StDelegate.json)
|
|
34
|
-
|
|
35
|
-
## 2. 使用方法
|
|
36
|
-
|
|
37
|
-
### 命令行
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
cuxml -h
|
|
41
|
-
```
|
|
42
|
-
> -h 显示帮助信息
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
cuxml -i ./ribbon.xml -o ./ribbon.js [-w]
|
|
46
|
-
```
|
|
47
|
-
> -i 指定输入文件,默认是当前目录下的 ribbon.xml。
|
|
48
|
-
>
|
|
49
|
-
> -o 指定输出文件,默认是当前目录下的 ribbon_temp.js。
|
|
50
|
-
>
|
|
51
|
-
> -w 监视文件的变化,并自动转换.
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
cuxml -c ./ribbon.xml
|
|
55
|
-
```
|
|
56
|
-
> -c ,check, 检查 XML 文件是否合法。
|
|
57
|
-
|
|
58
|
-
### API
|
|
59
|
-
|
|
60
|
-
```js
|
|
61
|
-
const cuxml = require('cuxml');
|
|
62
|
-
// convert
|
|
63
|
-
cuxml.convert('./ribbon.xml', './ribbon.js');
|
|
64
|
-
// check
|
|
65
|
-
cuxml.check(xmlString|xmlFile);
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### MCP
|
|
69
|
-
|
|
70
|
-
将其开发为一个 MCP 插件,可供其他工具调用。
|
|
71
|
-
|
|
72
|
-
MCP 提供的 API 包括:
|
|
73
|
-
|
|
74
|
-
- `convert(inputFile, outputFile)`: 将 XML 文件转换为 JavaScript 文件。
|
|
75
|
-
- `check(xmlString|xmlFile)`: 检查 XML 字符串是否合法。
|
|
76
|
-
|
package/test/agent_new.md
DELETED
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
# CUXML Agent
|
|
2
|
-
|
|
3
|
-
## 📌 简介
|
|
4
|
-
|
|
5
|
-
CUXML 是一个专为 WPS Office JS 插件开发者设计的工具,用于处理 RibbonUI XML 文件。它可以显著简化开发流程,让你更专注于业务逻辑而不是重复的代码编写。
|
|
6
|
-
|
|
7
|
-
> 📖 参考文档:[Microsoft CustomUI 规范](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-customui/574eeee8-7a03-406a-b95f-f9e51e53dd9d)
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## ✨ 核心功能
|
|
12
|
-
|
|
13
|
-
### 1. 🔄 XML 到 JavaScript 的转换
|
|
14
|
-
|
|
15
|
-
将 RibbonUI XML 文件中的回调函数配置自动转换为 JavaScript 函数代码,大幅减少手工编码工作。
|
|
16
|
-
|
|
17
|
-
**特性:**
|
|
18
|
-
- 智能解析 XML 中的回调函数属性
|
|
19
|
-
- 生成标准化的 JavaScript 函数模板
|
|
20
|
-
- 支持自动函数名处理(带 `.` 连接符的属性名会被截取)
|
|
21
|
-
- 支持监视文件变化,自动重新转换
|
|
22
|
-
|
|
23
|
-
### 2. 🔍 XML 合规性检查
|
|
24
|
-
|
|
25
|
-
全面检查 XML 文件是否符合 CustomUI 规范,提前发现潜在问题。
|
|
26
|
-
|
|
27
|
-
**检查项目:**
|
|
28
|
-
- ✅ XML 语法规范性
|
|
29
|
-
- ✅ ID 唯一性检查(防止重复 ID)
|
|
30
|
-
- ✅ 属性冲突检测(如 `label` 和 `getLabel` 不能同时存在)
|
|
31
|
-
- ✅ 标签嵌套合规性(如 `button` 不能嵌套在 `button` 中)
|
|
32
|
-
- ✅ 属性值类型验证(基于 Simple Types 规范)
|
|
33
|
-
- ✅ 其他自定义规则验证
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 🚀 快速开始
|
|
38
|
-
|
|
39
|
-
### 安装
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npm install -g cuxml
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### 命令行使用
|
|
46
|
-
|
|
47
|
-
#### 查看帮助信息
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
cuxml -h
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
#### 转换 XML 文件
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
# 基本用法
|
|
57
|
-
cuxml -i ./ribbon.xml -o ./ribbon.js
|
|
58
|
-
|
|
59
|
-
# 使用默认路径(当前目录下的 ribbon.xml -> ribbon_temp.js)
|
|
60
|
-
cuxml
|
|
61
|
-
|
|
62
|
-
# 监视模式(文件变化时自动转换)
|
|
63
|
-
cuxml -i ./ribbon.xml -o ./ribbon.js -w
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
**参数说明:**
|
|
67
|
-
- `-i, --input <file>`: 指定输入的 XML 文件路径,默认为 `./ribbon.xml`
|
|
68
|
-
- `-o, --output <file>`: 指定输出的 JS 文件路径,默认为 `./ribbon_temp.js`
|
|
69
|
-
- `-w, --watch`: 启用监视模式,自动检测文件变化并重新转换
|
|
70
|
-
- `-h, --help`: 显示帮助信息
|
|
71
|
-
|
|
72
|
-
#### 检查 XML 文件
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
cuxml -c ./ribbon.xml
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**参数说明:**
|
|
79
|
-
- `-c, --check <file>`: 检查指定的 XML 文件是否符合规范
|
|
80
|
-
|
|
81
|
-
### API 使用
|
|
82
|
-
|
|
83
|
-
```javascript
|
|
84
|
-
const cuxml = require('cuxml');
|
|
85
|
-
|
|
86
|
-
// 转换 XML 到 JavaScript
|
|
87
|
-
cuxml.convert('./ribbon.xml', './ribbon.js');
|
|
88
|
-
|
|
89
|
-
// 检查 XML 文件(支持文件路径或 XML 字符串)
|
|
90
|
-
cuxml.check('./ribbon.xml');
|
|
91
|
-
// 或
|
|
92
|
-
cuxml.check('<customUI xmlns="...">...</customUI>');
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
---
|
|
96
|
-
|
|
97
|
-
## 📦 MCP 插件
|
|
98
|
-
|
|
99
|
-
CUXML 还可以作为一个 MCP (Model Context Protocol) 插件使用,方便集成到 AI 工具链中。
|
|
100
|
-
|
|
101
|
-
### 可用的 MCP API
|
|
102
|
-
|
|
103
|
-
```javascript
|
|
104
|
-
// 转换 XML 文件为 JavaScript
|
|
105
|
-
mcp.convert(inputFile, outputFile);
|
|
106
|
-
|
|
107
|
-
// 检查 XML 文件或字符串是否合法
|
|
108
|
-
mcp.check(xmlString | xmlFile);
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
**使用场景:**
|
|
112
|
-
- AI 辅助生成 RibbonUI XML 后自动转换
|
|
113
|
-
- 自动化构建流程中的代码生成
|
|
114
|
-
- CI/CD 流程中的 XML 验证
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## 🔧 技术细节
|
|
119
|
-
|
|
120
|
-
### Callback Function 转换规则
|
|
121
|
-
|
|
122
|
-
1. **函数名处理**
|
|
123
|
-
- 如果属性名包含 `.` 分隔符,自动提取最后一部分作为函数名
|
|
124
|
-
- 例如:`onClick.button1` → `function button1(control) {}`
|
|
125
|
-
|
|
126
|
-
2. **函数签名**
|
|
127
|
-
- 所有生成的回调函数都遵循统一的签名
|
|
128
|
-
- 函数接收一个 `control` 参数,表示触发事件的控件对象
|
|
129
|
-
|
|
130
|
-
3. **输出文件**
|
|
131
|
-
- 生成的代码会写入到指定的 JS 文件中
|
|
132
|
-
- 默认输出为当前目录的 `ribbon_temp.js`
|
|
133
|
-
|
|
134
|
-
### 支持的回调函数属性
|
|
135
|
-
|
|
136
|
-
所有支持转换为 callback function 的属性都定义在配置文件中,详细信息请参考:
|
|
137
|
-
|
|
138
|
-
📄 **参考文件:** [StDelegate.json](../StDelegate.json)
|
|
139
|
-
|
|
140
|
-
常见的回调函数属性包括:
|
|
141
|
-
- `onAction` - 按钮点击事件
|
|
142
|
-
- `getLabel` - 动态获取标签文本
|
|
143
|
-
- `getImage` - 动态获取图标
|
|
144
|
-
- `getEnabled` - 动态获取启用状态
|
|
145
|
-
- `getVisible` - 动态获取可见性
|
|
146
|
-
- 等等...
|
|
147
|
-
|
|
148
|
-
### XML 检查规则
|
|
149
|
-
|
|
150
|
-
#### 属性冲突检测
|
|
151
|
-
|
|
152
|
-
参考配置文件中的冲突属性定义:
|
|
153
|
-
|
|
154
|
-
📄 **参考文件:** [clashAttributes.json](../clashAttributes.json)
|
|
155
|
-
|
|
156
|
-
**常见冲突示例:**
|
|
157
|
-
- `label` vs `getLabel`
|
|
158
|
-
- `image` vs `getImage`
|
|
159
|
-
- `screentip` vs `getScreentip`
|
|
160
|
-
- `supertip` vs `getSupertip`
|
|
161
|
-
- `enabled` vs `getEnabled`
|
|
162
|
-
- `visible` vs `getVisible`
|
|
163
|
-
|
|
164
|
-
#### 标签嵌套规则
|
|
165
|
-
|
|
166
|
-
严格遵循 CustomUI 的标签嵌套规范:
|
|
167
|
-
- `button` 不能包含其他按钮
|
|
168
|
-
- `tab` 必须包含在 `ribbon` 或 `ribbon.tabs` 中
|
|
169
|
-
- `group` 必须包含在 `tab` 中
|
|
170
|
-
- 等等...
|
|
171
|
-
|
|
172
|
-
#### Simple Types 支持
|
|
173
|
-
|
|
174
|
-
支持 http://schemas.microsoft.com/office/2006/01/customui 命名空间下的所有 Simple Types。
|
|
175
|
-
|
|
176
|
-
📄 **参考文件:** [simpleTypes.json](../simpleTypes.json)
|
|
177
|
-
|
|
178
|
-
支持版本:
|
|
179
|
-
- CustomUI 2007
|
|
180
|
-
- CustomUI 2010
|
|
181
|
-
- 其他兼容版本
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
## 📁 示例文件
|
|
186
|
-
|
|
187
|
-
项目提供了完整的示例文件,供参考和学习:
|
|
188
|
-
|
|
189
|
-
### 示例文件列表
|
|
190
|
-
|
|
191
|
-
- **XML 示例:** [ribbon.xml](./ribbon.xml) - 完整的 RibbonUI XML 配置示例
|
|
192
|
-
- **JS 输出示例:** [ribbon.js](./ribbon.js) - 转换后的 JavaScript 函数代码
|
|
193
|
-
- **转换模板:** [template.js](./template.js) - 回调函数生成模板
|
|
194
|
-
|
|
195
|
-
### 快速体验
|
|
196
|
-
|
|
197
|
-
1. 查看 `ribbon.xml` 了解 RibbonUI 的 XML 结构
|
|
198
|
-
2. 运行 `cuxml -i ./test/ribbon.xml -o ./output.js` 进行转换
|
|
199
|
-
3. 对比 `ribbon.js` 和生成的 `output.js` 理解转换规则
|
|
200
|
-
4. 运行 `cuxml -c ./test/ribbon.xml` 进行 XML 检查
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## 🎯 最佳实践
|
|
205
|
-
|
|
206
|
-
### 1. 文件组织
|
|
207
|
-
|
|
208
|
-
```
|
|
209
|
-
project/
|
|
210
|
-
├── src/
|
|
211
|
-
│ └── ribbon.xml # RibbonUI 配置文件
|
|
212
|
-
├── gen/
|
|
213
|
-
│ └── ribbon_callbacks.js # 自动生成的回调函数
|
|
214
|
-
├── main.js # 主逻辑文件
|
|
215
|
-
└── package.json
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### 2. 开发工作流
|
|
219
|
-
|
|
220
|
-
```bash
|
|
221
|
-
# 开发阶段:使用监视模式,实时更新
|
|
222
|
-
cuxml -i src/ribbon.xml -o gen/ribbon_callbacks.js -w
|
|
223
|
-
|
|
224
|
-
# 构建阶段:生成并验证
|
|
225
|
-
cuxml -c src/ribbon.xml && cuxml -i src/ribbon.xml -o gen/ribbon_callbacks.js
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### 3. 错误处理
|
|
229
|
-
|
|
230
|
-
```javascript
|
|
231
|
-
const cuxml = require('cuxml');
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
cuxml.check('./ribbon.xml');
|
|
235
|
-
cuxml.convert('./ribbon.xml', './ribbon.js');
|
|
236
|
-
console.log('✅ 转换成功!');
|
|
237
|
-
} catch (error) {
|
|
238
|
-
console.error('❌ 转换失败:', error.message);
|
|
239
|
-
// 处理错误
|
|
240
|
-
}
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### 4. 自定义扩展
|
|
244
|
-
|
|
245
|
-
虽然 CUXML 提供了自动生成的模板,但复杂的业务逻辑仍需手动编写:
|
|
246
|
-
|
|
247
|
-
```javascript
|
|
248
|
-
// 自动生成的基础模板
|
|
249
|
-
function button1(control) {
|
|
250
|
-
// TODO: 实现按钮逻辑
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// 手动添加的复杂逻辑
|
|
254
|
-
function button1(control) {
|
|
255
|
-
const app = control.context;
|
|
256
|
-
const selection = app.Selection;
|
|
257
|
-
|
|
258
|
-
// 复杂的业务逻辑
|
|
259
|
-
if (selection && selection.Type === 1) { // 文本选中
|
|
260
|
-
// 处理文本...
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## 🔗 相关资源
|
|
268
|
-
|
|
269
|
-
- [Microsoft CustomUI 规范](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-customui/574eeee8-7a03-406a-b95f-f9e51e53dd9d)
|
|
270
|
-
- [Simple Types 参考](https://learn.microsoft.com/en-us/openspecs/office_standards/ms-customui/869c8c9a-45f8-4119-b068-f61e76d04322)
|
|
271
|
-
- [WPS Office 开发文档](https://wps.cn/developer/)
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
## 🤝 贡献与反馈
|
|
276
|
-
|
|
277
|
-
如果您在使用过程中遇到问题或有改进建议,欢迎:
|
|
278
|
-
|
|
279
|
-
- 提交 [Issue](https://github.com/YYago/cuxml/issues)
|
|
280
|
-
- 发起 [Pull Request](https://github.com/YYago/cuxml/pulls)
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## 📄 许可证
|
|
285
|
-
|
|
286
|
-
ISC
|
package/test/ribbon.js
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import Util from './js/util.js'
|
|
2
|
-
import SystemDemo from './js/systemdemo.js'
|
|
3
|
-
|
|
4
|
-
//这个函数在整个wps加载项中是第一个执行的
|
|
5
|
-
function OnAddinLoad(ribbonUI) {
|
|
6
|
-
if (typeof window.Application.ribbonUI != 'object') {
|
|
7
|
-
window.Application.ribbonUI = ribbonUI
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
if (typeof window.Application.Enum != 'object') {
|
|
11
|
-
// 如果没有内置枚举值
|
|
12
|
-
window.Application.Enum = Util.WPS_Enum
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
//这几个导出函数是给外部业务系统调用的
|
|
16
|
-
window.openOfficeFileFromSystemDemo = SystemDemo.openOfficeFileFromSystemDemo
|
|
17
|
-
window.InvokeFromSystemDemo = SystemDemo.InvokeFromSystemDemo
|
|
18
|
-
|
|
19
|
-
window.Application.PluginStorage.setItem('EnableFlag', false) //往PluginStorage中设置一个标记,用于控制两个按钮的置灰
|
|
20
|
-
window.Application.PluginStorage.setItem('ApiEventFlag', false) //往PluginStorage中设置一个标记,用于控制ApiEvent的按钮label
|
|
21
|
-
return true
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
var WebNotifycount = 0
|
|
25
|
-
function OnAction(control) {
|
|
26
|
-
const eleId = control.Id
|
|
27
|
-
switch (eleId) {
|
|
28
|
-
case 'btnShowMsg':
|
|
29
|
-
{
|
|
30
|
-
const doc = window.Application.ActiveDocument
|
|
31
|
-
if (!doc) {
|
|
32
|
-
alert('当前没有打开任何文档')
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
alert(doc.Name)
|
|
36
|
-
}
|
|
37
|
-
break
|
|
38
|
-
case 'btnIsEnbable': {
|
|
39
|
-
let bFlag = window.Application.PluginStorage.getItem('EnableFlag')
|
|
40
|
-
window.Application.PluginStorage.setItem('EnableFlag', !bFlag)
|
|
41
|
-
|
|
42
|
-
//通知wps刷新以下几个按饰的状态
|
|
43
|
-
window.Application.ribbonUI.InvalidateControl('btnIsEnbable')
|
|
44
|
-
window.Application.ribbonUI.InvalidateControl('btnShowDialog')
|
|
45
|
-
window.Application.ribbonUI.InvalidateControl('btnShowTaskPane')
|
|
46
|
-
//window.Application.ribbonUI.Invalidate(); 这行代码打开则是刷新所有的按钮状态
|
|
47
|
-
break
|
|
48
|
-
}
|
|
49
|
-
case 'btnShowDialog': {
|
|
50
|
-
window.Application.ShowDialog(
|
|
51
|
-
Util.GetUrlPath() + Util.GetRouterHash() + '/dialog',
|
|
52
|
-
'这是一个对话框网页',
|
|
53
|
-
400 * window.devicePixelRatio,
|
|
54
|
-
400 * window.devicePixelRatio,
|
|
55
|
-
false
|
|
56
|
-
)
|
|
57
|
-
break
|
|
58
|
-
}
|
|
59
|
-
case 'btnShowTaskPane':
|
|
60
|
-
{
|
|
61
|
-
let tsId = window.Application.PluginStorage.getItem('taskpane_id')
|
|
62
|
-
if (!tsId) {
|
|
63
|
-
let tskpane = window.Application.CreateTaskPane(Util.GetUrlPath() + Util.GetRouterHash() + '/taskpane')
|
|
64
|
-
let id = tskpane.ID
|
|
65
|
-
window.Application.PluginStorage.setItem('taskpane_id', id)
|
|
66
|
-
tskpane.Visible = true
|
|
67
|
-
} else {
|
|
68
|
-
let tskpane = window.Application.GetTaskPane(tsId)
|
|
69
|
-
tskpane.Visible = !tskpane.Visible
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
break
|
|
73
|
-
case 'btnApiEvent':
|
|
74
|
-
{
|
|
75
|
-
let bFlag = window.Application.PluginStorage.getItem('ApiEventFlag')
|
|
76
|
-
let bRegister = bFlag ? false : true
|
|
77
|
-
window.Application.PluginStorage.setItem('ApiEventFlag', bRegister)
|
|
78
|
-
if (bRegister) {
|
|
79
|
-
window.Application.ApiEvent.AddApiEventListener('DocumentNew', 'ribbon.OnNewDocumentApiEvent')
|
|
80
|
-
} else {
|
|
81
|
-
window.Application.ApiEvent.RemoveApiEventListener('DocumentNew', 'ribbon.OnNewDocumentApiEvent')
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
window.Application.ribbonUI.InvalidateControl('btnApiEvent')
|
|
85
|
-
}
|
|
86
|
-
break
|
|
87
|
-
case 'btnWebNotify':
|
|
88
|
-
{
|
|
89
|
-
let currentTime = new Date()
|
|
90
|
-
let timeStr =
|
|
91
|
-
currentTime.getHours() + ':' + currentTime.getMinutes() + ':' + currentTime.getSeconds()
|
|
92
|
-
window.Application.OAAssist.WebNotify(
|
|
93
|
-
'这行内容由wps加载项主动送达给业务系统,可以任意自定义, 比如时间值:' +
|
|
94
|
-
timeStr +
|
|
95
|
-
',次数:' +
|
|
96
|
-
++WebNotifycount,
|
|
97
|
-
true
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
break
|
|
101
|
-
default:
|
|
102
|
-
break
|
|
103
|
-
}
|
|
104
|
-
return true
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function GetImage(control) {
|
|
108
|
-
const eleId = control.Id
|
|
109
|
-
switch (eleId) {
|
|
110
|
-
case 'btnShowMsg':
|
|
111
|
-
return 'images/1.svg'
|
|
112
|
-
case 'btnShowDialog':
|
|
113
|
-
return 'images/2.svg'
|
|
114
|
-
case 'btnShowTaskPane':
|
|
115
|
-
return 'images/3.svg'
|
|
116
|
-
default:
|
|
117
|
-
}
|
|
118
|
-
return 'images/newFromTemp.svg'
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function OnGetEnabled(control) {
|
|
122
|
-
const eleId = control.Id
|
|
123
|
-
switch (eleId) {
|
|
124
|
-
case 'btnShowMsg':
|
|
125
|
-
return true
|
|
126
|
-
case 'btnShowDialog': {
|
|
127
|
-
let bFlag = window.Application.PluginStorage.getItem('EnableFlag')
|
|
128
|
-
return bFlag
|
|
129
|
-
}
|
|
130
|
-
case 'btnShowTaskPane': {
|
|
131
|
-
let bFlag = window.Application.PluginStorage.getItem('EnableFlag')
|
|
132
|
-
return bFlag
|
|
133
|
-
}
|
|
134
|
-
default:
|
|
135
|
-
break
|
|
136
|
-
}
|
|
137
|
-
return true
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function OnGetVisible(control) {
|
|
141
|
-
const eleId = control.Id
|
|
142
|
-
console.log(eleId)
|
|
143
|
-
return true
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function OnGetLabel(control) {
|
|
147
|
-
const eleId = control.Id
|
|
148
|
-
switch (eleId) {
|
|
149
|
-
case 'btnIsEnbable': {
|
|
150
|
-
let bFlag = window.Application.PluginStorage.getItem('EnableFlag')
|
|
151
|
-
return bFlag ? '按钮Disable' : '按钮Enable'
|
|
152
|
-
}
|
|
153
|
-
case 'btnApiEvent': {
|
|
154
|
-
let bFlag = window.Application.PluginStorage.getItem('ApiEventFlag')
|
|
155
|
-
return bFlag ? '清除新建文件事件' : '注册新建文件事件'
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return ''
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function OnNewDocumentApiEvent(doc) {
|
|
162
|
-
alert('新建文件事件响应,取文件名: ' + doc.Name)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
//这些函数是给wps客户端调用的
|
|
166
|
-
export default {
|
|
167
|
-
OnAddinLoad,
|
|
168
|
-
OnAction,
|
|
169
|
-
GetImage,
|
|
170
|
-
OnGetEnabled,
|
|
171
|
-
OnGetVisible,
|
|
172
|
-
OnGetLabel,
|
|
173
|
-
OnNewDocumentApiEvent
|
|
174
|
-
}
|
package/test/ribbon.xml
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" onLoad="ribbon.OnAddinLoad">
|
|
2
|
-
<ribbon startFromScratch="false">
|
|
3
|
-
<tabs>
|
|
4
|
-
<tab id="wpsAddinTab" label="wps加载项示例">
|
|
5
|
-
<group id="btnDemoGroup" label="group1">
|
|
6
|
-
<button id="btnShowMsg" label="弹出消息框" onAction="ribbon.OnAction" getEnabled="ribbon.OnGetEnabled" getImage="ribbon.GetImage" visible="true" size="large"/>
|
|
7
|
-
<button id="btnIsEnbable" getLabel="ribbon.OnGetLabel" onAction="ribbon.OnAction" enabled="true" getImage="ribbon.GetImage" visible="true" size="large"/>
|
|
8
|
-
<button id="btnShowDialog" label="弹对话框网页" onAction="ribbon.OnAction" getEnabled="ribbon.OnGetEnabled" getImage="ribbon.GetImage" getVisible="ribbon.OnGetVisible" size="large"/>
|
|
9
|
-
<button id="btnShowTaskPane" label="弹任务窗格网页" onAction="ribbon.OnAction" getEnabled="ribbon.OnGetEnabled" getImage="ribbon.GetImage" getVisible="ribbon.OnGetVisible" size="large"/>
|
|
10
|
-
<button id="btnApiEvent" getLabel="ribbon.OnGetLabel" onAction="ribbon.OnAction" getEnabled="ribbon.OnGetEnabled" getImage="ribbon.GetImage" getVisible="ribbon.OnGetVisible" size="large"/>
|
|
11
|
-
<button id="btnWebNotify" label="给业务系统发通知" onAction="ribbon.OnAction" enabled="true" getImage="ribbon.GetImage" getVisible="ribbon.OnGetVisible" size="large"/>
|
|
12
|
-
</group>
|
|
13
|
-
</tab>
|
|
14
|
-
</tabs>
|
|
15
|
-
</ribbon>
|
|
16
|
-
</customUI>
|
package/test/template.js
DELETED
|
File without changes
|
package/test/test.js
DELETED
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const cuxml = require('../index.js');
|
|
6
|
-
|
|
7
|
-
// 测试结果目录
|
|
8
|
-
const RESULT_DIR = path.join(__dirname, 'result');
|
|
9
|
-
|
|
10
|
-
// 确保结果目录存在
|
|
11
|
-
if (!fs.existsSync(RESULT_DIR)) {
|
|
12
|
-
fs.mkdirSync(RESULT_DIR, { recursive: true });
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// 测试结果收集
|
|
16
|
-
const testResults = [];
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 记录测试结果
|
|
20
|
-
*/
|
|
21
|
-
function logTest(name, success, message, data = null) {
|
|
22
|
-
const result = {
|
|
23
|
-
name,
|
|
24
|
-
success,
|
|
25
|
-
message,
|
|
26
|
-
data,
|
|
27
|
-
timestamp: new Date().toISOString()
|
|
28
|
-
};
|
|
29
|
-
testResults.push(result);
|
|
30
|
-
|
|
31
|
-
const icon = success ? '✅' : '❌';
|
|
32
|
-
console.log(`${icon} ${name}: ${message}`);
|
|
33
|
-
if (data) {
|
|
34
|
-
console.log(` ${JSON.stringify(data)}`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 清理测试文件
|
|
40
|
-
*/
|
|
41
|
-
function cleanup() {
|
|
42
|
-
const files = [
|
|
43
|
-
path.join(RESULT_DIR, 'output1.js'),
|
|
44
|
-
path.join(RESULT_DIR, 'output2.js'),
|
|
45
|
-
path.join(RESULT_DIR, 'ribbon_temp.js')
|
|
46
|
-
];
|
|
47
|
-
files.forEach(file => {
|
|
48
|
-
if (fs.existsSync(file)) {
|
|
49
|
-
fs.unlinkSync(file);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* 测试 1: 检查 XML 文件(同步)
|
|
56
|
-
*/
|
|
57
|
-
async function test1_checkXMLSync() {
|
|
58
|
-
try {
|
|
59
|
-
const result = cuxml.checkSync(path.join(__dirname, 'ribbon.xml'));
|
|
60
|
-
logTest(
|
|
61
|
-
'XML 检查(同步)',
|
|
62
|
-
result.success,
|
|
63
|
-
`检查完成: ${result.passed}/${result.totalNodes} 通过`,
|
|
64
|
-
{ errors: result.errors, warnings: result.warnings }
|
|
65
|
-
);
|
|
66
|
-
return result.success;
|
|
67
|
-
} catch (error) {
|
|
68
|
-
logTest('XML 检查(同步)', false, error.message);
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* 测试 2: 检查 XML 文件(异步)
|
|
75
|
-
*/
|
|
76
|
-
async function test2_checkXMLAsync() {
|
|
77
|
-
try {
|
|
78
|
-
const result = await cuxml.check(path.join(__dirname, 'ribbon.xml'));
|
|
79
|
-
logTest(
|
|
80
|
-
'XML 检查(异步)',
|
|
81
|
-
result.success,
|
|
82
|
-
`检查完成: ${result.passed}/${result.totalNodes} 通过`,
|
|
83
|
-
{ errors: result.errors, warnings: result.warnings }
|
|
84
|
-
);
|
|
85
|
-
return result.success;
|
|
86
|
-
} catch (error) {
|
|
87
|
-
logTest('XML 检查(异步)', false, error.message);
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* 测试 3: 转换 XML 到 JavaScript(同步)
|
|
94
|
-
*/
|
|
95
|
-
async function test3_convertSync() {
|
|
96
|
-
try {
|
|
97
|
-
const inputFile = path.join(__dirname, 'ribbon.xml');
|
|
98
|
-
const outputFile = path.join(RESULT_DIR, 'output1.js');
|
|
99
|
-
|
|
100
|
-
const result = cuxml.convertSync(inputFile, outputFile);
|
|
101
|
-
|
|
102
|
-
const fileExists = fs.existsSync(outputFile);
|
|
103
|
-
logTest(
|
|
104
|
-
'XML 转换(同步)',
|
|
105
|
-
result.success && fileExists,
|
|
106
|
-
`生成 ${result.functionsGenerated} 个回调函数`,
|
|
107
|
-
{ outputPath: result.outputPath, fileExists }
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
return result.success && fileExists;
|
|
111
|
-
} catch (error) {
|
|
112
|
-
logTest('XML 转换(同步)', false, error.message);
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* 测试 4: 转换 XML 到 JavaScript(异步)
|
|
119
|
-
*/
|
|
120
|
-
async function test4_convertAsync() {
|
|
121
|
-
try {
|
|
122
|
-
const inputFile = path.join(__dirname, 'ribbon.xml');
|
|
123
|
-
const outputFile = path.join(RESULT_DIR, 'output2.js');
|
|
124
|
-
|
|
125
|
-
const result = await cuxml.convert(inputFile, outputFile);
|
|
126
|
-
|
|
127
|
-
const fileExists = fs.existsSync(outputFile);
|
|
128
|
-
logTest(
|
|
129
|
-
'XML 转换(异步)',
|
|
130
|
-
result.success && fileExists,
|
|
131
|
-
`生成 ${result.functionsGenerated} 个回调函数`,
|
|
132
|
-
{ outputPath: result.outputPath, fileExists }
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
return result.success && fileExists;
|
|
136
|
-
} catch (error) {
|
|
137
|
-
logTest('XML 转换(异步)', false, error.message);
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* 测试 5: 检查 XML 字符串
|
|
144
|
-
*/
|
|
145
|
-
async function test5_checkXMLString() {
|
|
146
|
-
try {
|
|
147
|
-
const xmlString = fs.readFileSync(path.join(__dirname, 'ribbon.xml'), 'utf-8');
|
|
148
|
-
const result = cuxml.checkSync(xmlString);
|
|
149
|
-
|
|
150
|
-
logTest(
|
|
151
|
-
'XML 字符串检查',
|
|
152
|
-
result.success,
|
|
153
|
-
`检查完成: ${result.passed}/${result.totalNodes} 通过`,
|
|
154
|
-
{ errors: result.errors, warnings: result.warnings }
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
return result.success;
|
|
158
|
-
} catch (error) {
|
|
159
|
-
logTest('XML 字符串检查', false, error.message);
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* 测试 6: 格式化检查结果
|
|
166
|
-
*/
|
|
167
|
-
async function test6_formatCheckResult() {
|
|
168
|
-
try {
|
|
169
|
-
const inputFile = path.join(__dirname, 'ribbon.xml');
|
|
170
|
-
const result = cuxml.checkSync(inputFile, { detailed: true });
|
|
171
|
-
const formatted = cuxml.formatCheckResult(result);
|
|
172
|
-
|
|
173
|
-
const hasFormat = typeof formatted === 'string' && formatted.length > 0;
|
|
174
|
-
logTest(
|
|
175
|
-
'格式化检查结果',
|
|
176
|
-
hasFormat,
|
|
177
|
-
`格式化输出长度: ${formatted.length} 字符`,
|
|
178
|
-
{ outputLength: formatted.length }
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
return hasFormat;
|
|
182
|
-
} catch (error) {
|
|
183
|
-
logTest('格式化检查结果', false, error.message);
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* 测试 7: 输出文件内容验证
|
|
190
|
-
*/
|
|
191
|
-
async function test7_verifyOutputContent() {
|
|
192
|
-
try {
|
|
193
|
-
const outputFile = path.join(RESULT_DIR, 'output1.js');
|
|
194
|
-
|
|
195
|
-
if (!fs.existsSync(outputFile)) {
|
|
196
|
-
logTest('输出文件内容验证', false, '输出文件不存在');
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const content = fs.readFileSync(outputFile, 'utf-8');
|
|
201
|
-
|
|
202
|
-
// 检查是否包含预期的函数
|
|
203
|
-
const expectedFunctions = ['OnAddinLoad', 'OnGetLabel', 'OnGetEnabled', 'OnGetVisible'];
|
|
204
|
-
const foundFunctions = expectedFunctions.filter(fn => content.includes(`function ${fn}`));
|
|
205
|
-
|
|
206
|
-
const allFound = foundFunctions.length === expectedFunctions.length;
|
|
207
|
-
logTest(
|
|
208
|
-
'输出文件内容验证',
|
|
209
|
-
allFound,
|
|
210
|
-
`找到 ${foundFunctions.length}/${expectedFunctions.length} 个预期函数`,
|
|
211
|
-
{ foundFunctions, expectedFunctions }
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
return allFound;
|
|
215
|
-
} catch (error) {
|
|
216
|
-
logTest('输出文件内容验证', false, error.message);
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* 保存测试结果
|
|
223
|
-
*/
|
|
224
|
-
function saveResults() {
|
|
225
|
-
const resultFile = path.join(RESULT_DIR, 'test_results.json');
|
|
226
|
-
const successCount = testResults.filter(r => r.success).length;
|
|
227
|
-
const totalCount = testResults.length;
|
|
228
|
-
|
|
229
|
-
const summary = {
|
|
230
|
-
totalTests: totalCount,
|
|
231
|
-
successTests: successCount,
|
|
232
|
-
failedTests: totalCount - successCount,
|
|
233
|
-
successRate: `${((successCount / totalCount) * 100).toFixed(2)}%`,
|
|
234
|
-
timestamp: new Date().toISOString(),
|
|
235
|
-
tests: testResults
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
fs.writeFileSync(resultFile, JSON.stringify(summary, null, 2));
|
|
239
|
-
|
|
240
|
-
// 保存 Markdown 格式的报告
|
|
241
|
-
const mdReport = generateMarkdownReport(summary);
|
|
242
|
-
const mdFile = path.join(RESULT_DIR, 'test_report.md');
|
|
243
|
-
fs.writeFileSync(mdFile, mdReport);
|
|
244
|
-
|
|
245
|
-
console.log('\n📊 测试报告已保存:');
|
|
246
|
-
console.log(` JSON: ${resultFile}`);
|
|
247
|
-
console.log(` Markdown: ${mdFile}`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* 生成 Markdown 报告
|
|
252
|
-
*/
|
|
253
|
-
function generateMarkdownReport(summary) {
|
|
254
|
-
return `# CUXML v2.0.0 测试报告
|
|
255
|
-
|
|
256
|
-
**测试时间**: ${summary.timestamp}
|
|
257
|
-
**测试通过率**: ${summary.successRate} (${summary.successTests}/${summary.totalTests})
|
|
258
|
-
|
|
259
|
-
## 测试结果概览
|
|
260
|
-
|
|
261
|
-
| 测试名称 | 状态 | 消息 |
|
|
262
|
-
|---------|------|------|
|
|
263
|
-
${summary.tests.map(test =>
|
|
264
|
-
`| ${test.name} | ${test.success ? '✅ 通过' : '❌ 失败'} | ${test.message} |`
|
|
265
|
-
).join('\n')}
|
|
266
|
-
|
|
267
|
-
## 详细测试结果
|
|
268
|
-
|
|
269
|
-
${summary.tests.map(test => `
|
|
270
|
-
### ${test.name}
|
|
271
|
-
|
|
272
|
-
- **状态**: ${test.success ? '✅ 通过' : '❌ 失败'}
|
|
273
|
-
- **消息**: ${test.message}
|
|
274
|
-
- **时间**: ${test.timestamp}
|
|
275
|
-
${test.data ? `- **数据**: \`${JSON.stringify(test.data)}\`` : ''}
|
|
276
|
-
`).join('\n')}
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
|
-
## 统计信息
|
|
281
|
-
|
|
282
|
-
- 总测试数: ${summary.totalTests}
|
|
283
|
-
- 成功: ${summary.successTests}
|
|
284
|
-
- 失败: ${summary.failedTests}
|
|
285
|
-
- 成功率: ${summary.successRate}
|
|
286
|
-
`;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* 运行所有测试
|
|
291
|
-
*/
|
|
292
|
-
async function runAllTests() {
|
|
293
|
-
console.log('🧪 开始 CUXML v2.0.0 功能测试\n');
|
|
294
|
-
console.log('='.repeat(60));
|
|
295
|
-
|
|
296
|
-
// 清理之前的测试文件
|
|
297
|
-
cleanup();
|
|
298
|
-
|
|
299
|
-
// 运行测试
|
|
300
|
-
const tests = [
|
|
301
|
-
test1_checkXMLSync,
|
|
302
|
-
test2_checkXMLAsync,
|
|
303
|
-
test3_convertSync,
|
|
304
|
-
test4_convertAsync,
|
|
305
|
-
test5_checkXMLString,
|
|
306
|
-
test6_formatCheckResult,
|
|
307
|
-
test7_verifyOutputContent
|
|
308
|
-
];
|
|
309
|
-
|
|
310
|
-
for (const test of tests) {
|
|
311
|
-
await test();
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
console.log('\n' + '='.repeat(60));
|
|
315
|
-
console.log(`✅ 测试完成: ${testResults.filter(r => r.success).length}/${testResults.length} 通过`);
|
|
316
|
-
|
|
317
|
-
// 保存测试结果
|
|
318
|
-
saveResults();
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// 运行测试
|
|
322
|
-
runAllTests().catch(error => {
|
|
323
|
-
console.error('❌ 测试运行失败:', error);
|
|
324
|
-
process.exit(1);
|
|
325
|
-
});
|