cuxml 1.0.6 → 2.1.0
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 +225 -112
- package/index.js +14 -36
- package/{cbfTypes.json → lib/cbfTypes.json} +13 -13
- package/package.json +2 -2
- package/src/api.js +185 -0
- package/src/checker.js +244 -0
- package/src/cli.js +134 -0
- package/src/converter.js +260 -0
- package/src/mcp.js +289 -0
- package/src/parser.js +117 -0
- package/src/templates.js +79 -0
- package/test/Agents.md +76 -0
- package/test/agent_new.md +286 -0
- package/test/ribbon.js +174 -0
- package/test/ribbon.xml +16 -0
- package/test/template.js +0 -0
- package/test/test.js +325 -0
- package/check.js +0 -114
- package/createCBF.js +0 -68
- package/cxml_w.js +0 -80
- package/xml2cbFn.js +0 -126
- package/xml2json.js +0 -40
- /package/{StDelegate.json → lib/StDelegate.json} +0 -0
- /package/{clashAttributes.json → lib/clashAttributes.json} +0 -0
- /package/{simpleTypes.json → lib/simpleTypes.json} +0 -0
package/src/converter.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const lodash = require('lodash');
|
|
4
|
+
const XMLParser = require('./parser');
|
|
5
|
+
const { generateCallbackTemplate, processFunctionName } = require('./templates');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 回调函数转换器
|
|
9
|
+
* 负责将 XML 中的回调函数配置转换为 JavaScript 代码
|
|
10
|
+
*/
|
|
11
|
+
class XMLConverter {
|
|
12
|
+
/**
|
|
13
|
+
* 将 XML 文件转换为 JavaScript 回调函数文件
|
|
14
|
+
* @param {string} inputPath - 输入的 XML 文件路径
|
|
15
|
+
* @param {string} outputPath - 输出的 JS 文件路径
|
|
16
|
+
* @param {object} options - 转换选项
|
|
17
|
+
* @param {boolean} [options.overwrite=false] - 是否覆盖输出文件
|
|
18
|
+
* @returns {object} 转换结果
|
|
19
|
+
*/
|
|
20
|
+
static convert(inputPath, outputPath, options = {}) {
|
|
21
|
+
// 验证输入参数
|
|
22
|
+
if (!inputPath || typeof inputPath !== 'string') {
|
|
23
|
+
throw new Error('输入文件路径必须是非空字符串');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!outputPath || typeof outputPath !== 'string') {
|
|
27
|
+
throw new Error('输出文件路径必须是非空字符串');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!inputPath.endsWith('.xml')) {
|
|
31
|
+
throw new Error('输入文件必须是 .xml 格式');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!outputPath.endsWith('.js')) {
|
|
35
|
+
throw new Error('输出文件必须是 .js 格式');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 检查输入文件是否存在
|
|
39
|
+
if (!fs.existsSync(inputPath)) {
|
|
40
|
+
throw new Error(`输入文件不存在: ${inputPath}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 加载配置
|
|
44
|
+
const clashAttributes = require('../lib/clashAttributes.json').crashs;
|
|
45
|
+
const stDelegate = require('../lib/StDelegate.json');
|
|
46
|
+
|
|
47
|
+
// 解析 XML
|
|
48
|
+
const flatElements = XMLParser.parseAndFlatten(inputPath);
|
|
49
|
+
|
|
50
|
+
// 提取有回调函数的元素
|
|
51
|
+
const callbackElements = this.extractCallbackElements(flatElements, stDelegate, clashAttributes);
|
|
52
|
+
|
|
53
|
+
if (callbackElements.length === 0) {
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
message: '没有找到需要转换的回调函数',
|
|
57
|
+
functionsGenerated: 0
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 按函数名分组
|
|
62
|
+
const functionGroups = this.groupByFunctionName(callbackElements);
|
|
63
|
+
|
|
64
|
+
// 生成回调函数代码
|
|
65
|
+
const generatedFunctions = this.generateFunctions(functionGroups);
|
|
66
|
+
|
|
67
|
+
// 写入输出文件
|
|
68
|
+
this.writeOutputFile(outputPath, generatedFunctions, options.overwrite);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
message: '转换成功',
|
|
73
|
+
functionsGenerated: generatedFunctions.length,
|
|
74
|
+
outputPath: outputPath
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 提取包含回调函数的元素
|
|
80
|
+
* @param {Array} elements - 扁平化的元素数组
|
|
81
|
+
* @param {object} stDelegate - 回调函数配置
|
|
82
|
+
* @param {Array} clashAttributes - 冲突属性配置
|
|
83
|
+
* @returns {Array} 包含回调函数的元素
|
|
84
|
+
*/
|
|
85
|
+
static extractCallbackElements(elements, stDelegate, clashAttributes) {
|
|
86
|
+
const results = [];
|
|
87
|
+
|
|
88
|
+
elements.forEach(element => {
|
|
89
|
+
if (!element.attributes || Object.keys(element.attributes).length === 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 获取该元素支持的回调函数属性
|
|
94
|
+
const elementCallbacks = stDelegate[element.name];
|
|
95
|
+
if (!elementCallbacks) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 检查是否有回调函数
|
|
100
|
+
const attrKeys = Object.keys(element.attributes);
|
|
101
|
+
const hasCallback = elementCallbacks.some(cb => {
|
|
102
|
+
return attrKeys.includes(cb) && element.attributes[cb].trim() !== '';
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (!hasCallback) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 检查是否有冲突属性
|
|
110
|
+
const hasClash = clashAttributes.some(clash => {
|
|
111
|
+
return lodash.intersection(attrKeys, clash).length > 1;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (hasClash) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 提取回调函数信息
|
|
119
|
+
elementCallbacks.forEach(cb => {
|
|
120
|
+
const callbackValue = element.attributes[cb];
|
|
121
|
+
if (callbackValue && callbackValue.trim()) {
|
|
122
|
+
results.push({
|
|
123
|
+
functionName: processFunctionName(callbackValue),
|
|
124
|
+
xmlPath: element.xmlpath,
|
|
125
|
+
id: element.attributes.id,
|
|
126
|
+
elementName: element.name,
|
|
127
|
+
attribute: cb
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 按函数名分组
|
|
138
|
+
* @param {Array} callbackElements - 回调元素数组
|
|
139
|
+
* @returns {object} 分组后的对象
|
|
140
|
+
*/
|
|
141
|
+
static groupByFunctionName(callbackElements) {
|
|
142
|
+
return lodash.groupBy(callbackElements, 'functionName');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 生成回调函数代码
|
|
147
|
+
* @param {object} functionGroups - 函数分组对象
|
|
148
|
+
* @returns {Array} 生成的函数信息数组
|
|
149
|
+
*/
|
|
150
|
+
static generateFunctions(functionGroups) {
|
|
151
|
+
const functions = [];
|
|
152
|
+
|
|
153
|
+
Object.keys(functionGroups).forEach(functionName => {
|
|
154
|
+
const elements = functionGroups[functionName];
|
|
155
|
+
const controls = elements.map(el => ({
|
|
156
|
+
id: el.id,
|
|
157
|
+
xmlPath: el.xmlPath
|
|
158
|
+
}));
|
|
159
|
+
|
|
160
|
+
const functionCode = generateCallbackTemplate(functionName, controls);
|
|
161
|
+
|
|
162
|
+
functions.push({
|
|
163
|
+
name: functionName,
|
|
164
|
+
code: functionCode,
|
|
165
|
+
controls: controls.length
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return functions;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 写入输出文件
|
|
174
|
+
* @param {string} outputPath - 输出文件路径
|
|
175
|
+
* @param {Array} functions - 函数信息数组
|
|
176
|
+
* @param {boolean} overwrite - 是否覆盖
|
|
177
|
+
*/
|
|
178
|
+
static writeOutputFile(outputPath, functions, overwrite = false) {
|
|
179
|
+
const dir = path.dirname(outputPath);
|
|
180
|
+
|
|
181
|
+
// 确保目录存在
|
|
182
|
+
if (!fs.existsSync(dir)) {
|
|
183
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 如果是覆盖模式,先清空文件
|
|
187
|
+
if (overwrite) {
|
|
188
|
+
fs.writeFileSync(outputPath, '', 'utf-8');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 如果文件不存在,创建空文件
|
|
192
|
+
if (!fs.existsSync(outputPath)) {
|
|
193
|
+
fs.writeFileSync(outputPath, '', 'utf-8');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 读取现有内容
|
|
197
|
+
const existingContent = fs.readFileSync(outputPath, 'utf-8');
|
|
198
|
+
|
|
199
|
+
// 追加新函数
|
|
200
|
+
functions.forEach(func => {
|
|
201
|
+
// 检查函数是否已存在
|
|
202
|
+
const functionRegex = new RegExp(`function\\s+${func.name}\\s*\\(`);
|
|
203
|
+
|
|
204
|
+
if (!existingContent.match(functionRegex)) {
|
|
205
|
+
fs.appendFileSync(outputPath, func.code + '\n', 'utf-8');
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// 添加导出语句
|
|
210
|
+
const exportStatement = `
|
|
211
|
+
// 导出所有回调函数(如果不需要导出,可以注释掉以下语句)
|
|
212
|
+
module.exports = {
|
|
213
|
+
${functions.map(func => ` ${func.name},`).join('\n')}
|
|
214
|
+
};
|
|
215
|
+
`;
|
|
216
|
+
|
|
217
|
+
// 检查是否已经有导出语句
|
|
218
|
+
if (!existingContent.includes('module.exports = {')) {
|
|
219
|
+
fs.appendFileSync(outputPath, exportStatement, 'utf-8');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 监视文件变化并自动转换
|
|
225
|
+
* @param {string} inputPath - 输入文件路径
|
|
226
|
+
* @param {string} outputPath - 输出文件路径
|
|
227
|
+
* @param {object} options - 转换选项
|
|
228
|
+
* @returns {object} watcher 对象
|
|
229
|
+
*/
|
|
230
|
+
static watch(inputPath, outputPath, options = {}) {
|
|
231
|
+
const chokidar = require('chokidar');
|
|
232
|
+
|
|
233
|
+
console.log(`👀 开始监视文件: ${inputPath}`);
|
|
234
|
+
|
|
235
|
+
// 初始转换
|
|
236
|
+
this.convert(inputPath, outputPath, options);
|
|
237
|
+
|
|
238
|
+
const watcher = chokidar.watch(inputPath);
|
|
239
|
+
|
|
240
|
+
watcher.on('change', () => {
|
|
241
|
+
console.log(`📝 检测到文件变化,开始转换...`);
|
|
242
|
+
try {
|
|
243
|
+
const result = this.convert(inputPath, outputPath, options);
|
|
244
|
+
if (result.success) {
|
|
245
|
+
console.log(`✅ 转换完成,生成了 ${result.functionsGenerated} 个函数`);
|
|
246
|
+
}
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error(`❌ 转换失败: ${error.message}`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
watcher.on('error', (error) => {
|
|
253
|
+
console.error(`❌ 监视错误: ${error.message}`);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return watcher;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = XMLConverter;
|
package/src/mcp.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
const CUXMLAPI = require('./api');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CUXML MCP (Model Context Protocol) 插件
|
|
5
|
+
* 提供 MCP 标准接口供 AI 工具链调用
|
|
6
|
+
*/
|
|
7
|
+
class CUXMLMCP {
|
|
8
|
+
/**
|
|
9
|
+
* MCP 服务器信息
|
|
10
|
+
*/
|
|
11
|
+
static getServerInfo() {
|
|
12
|
+
return {
|
|
13
|
+
name: 'cuxml',
|
|
14
|
+
version: require('../package.json').version,
|
|
15
|
+
description: 'CUXML - WPS Office RibbonUI XML 处理工具'
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* MCP 工具列表
|
|
21
|
+
*/
|
|
22
|
+
static getTools() {
|
|
23
|
+
return [
|
|
24
|
+
{
|
|
25
|
+
name: 'convert',
|
|
26
|
+
description: '将 RibbonUI XML 文件转换为 JavaScript 回调函数文件',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
inputFile: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: '输入的 XML 文件路径'
|
|
33
|
+
},
|
|
34
|
+
outputFile: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
description: '输出的 JS 文件路径'
|
|
37
|
+
},
|
|
38
|
+
overwrite: {
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
description: '是否覆盖输出文件',
|
|
41
|
+
default: false
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
required: ['inputFile', 'outputFile']
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'check',
|
|
49
|
+
description: '检查 XML 文件或字符串是否符合 CustomUI 规范',
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
input: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: 'XML 文件路径或 XML 字符串'
|
|
56
|
+
},
|
|
57
|
+
detailed: {
|
|
58
|
+
type: 'boolean',
|
|
59
|
+
description: '是否返回详细信息',
|
|
60
|
+
default: false
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
required: ['input']
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 执行 MCP 工具
|
|
71
|
+
* @param {string} toolName - 工具名称
|
|
72
|
+
* @param {object} args - 工具参数
|
|
73
|
+
* @returns {Promise<object>} 执行结果
|
|
74
|
+
*/
|
|
75
|
+
static async executeTool(toolName, args) {
|
|
76
|
+
try {
|
|
77
|
+
switch (toolName) {
|
|
78
|
+
case 'convert':
|
|
79
|
+
return await this.convert(args);
|
|
80
|
+
case 'check':
|
|
81
|
+
return await this.check(args);
|
|
82
|
+
default:
|
|
83
|
+
throw new Error(`未知的工具: ${toolName}`);
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: error.message
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 转换 XML 文件
|
|
95
|
+
* @param {object} args - 参数
|
|
96
|
+
* @param {string} args.inputFile - 输入文件
|
|
97
|
+
* @param {string} args.outputFile - 输出文件
|
|
98
|
+
* @param {boolean} args.overwrite - 是否覆盖
|
|
99
|
+
* @returns {Promise<object>} 转换结果
|
|
100
|
+
*/
|
|
101
|
+
static async convert(args) {
|
|
102
|
+
const { inputFile, outputFile, overwrite = false } = args;
|
|
103
|
+
|
|
104
|
+
const result = await CUXMLAPI.convert(inputFile, outputFile, { overwrite });
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
success: result.success,
|
|
108
|
+
message: result.message,
|
|
109
|
+
functionsGenerated: result.functionsGenerated,
|
|
110
|
+
outputPath: result.outputPath
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 检查 XML
|
|
116
|
+
* @param {object} args - 参数
|
|
117
|
+
* @param {string} args.input - XML 文件或字符串
|
|
118
|
+
* @param {boolean} args.detailed - 是否详细
|
|
119
|
+
* @returns {Promise<object>} 检查结果
|
|
120
|
+
*/
|
|
121
|
+
static async check(args) {
|
|
122
|
+
const { input, detailed = false } = args;
|
|
123
|
+
|
|
124
|
+
const result = await CUXMLAPI.check(input, { detailed });
|
|
125
|
+
|
|
126
|
+
if (detailed) {
|
|
127
|
+
return {
|
|
128
|
+
success: result.errors === 0,
|
|
129
|
+
...result
|
|
130
|
+
};
|
|
131
|
+
} else {
|
|
132
|
+
return {
|
|
133
|
+
success: result.success,
|
|
134
|
+
totalNodes: result.totalNodes,
|
|
135
|
+
passed: result.passed,
|
|
136
|
+
errors: result.errors,
|
|
137
|
+
warnings: result.warnings
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* MCP 资源列表
|
|
144
|
+
*/
|
|
145
|
+
static getResources() {
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
uri: 'cuxml://config/simpleTypes',
|
|
149
|
+
name: 'Simple Types 配置',
|
|
150
|
+
description: 'CustomUI Simple Types 规范配置',
|
|
151
|
+
mimeType: 'application/json'
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
uri: 'cuxml://config/clashAttributes',
|
|
155
|
+
name: '冲突属性配置',
|
|
156
|
+
description: 'CustomUI 冲突属性配置',
|
|
157
|
+
mimeType: 'application/json'
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
uri: 'cuxml://config/stDelegate',
|
|
161
|
+
name: '回调函数属性配置',
|
|
162
|
+
description: 'CustomUI 回调函数属性配置',
|
|
163
|
+
mimeType: 'application/json'
|
|
164
|
+
}
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 读取 MCP 资源
|
|
170
|
+
* @param {string} uri - 资源 URI
|
|
171
|
+
* @returns {Promise<object>} 资源内容
|
|
172
|
+
*/
|
|
173
|
+
static async readResource(uri) {
|
|
174
|
+
try {
|
|
175
|
+
switch (uri) {
|
|
176
|
+
case 'cuxml://config/simpleTypes':
|
|
177
|
+
return {
|
|
178
|
+
uri: uri,
|
|
179
|
+
mimeType: 'application/json',
|
|
180
|
+
content: JSON.stringify(require('../lib/simpleTypes.json'), null, 2)
|
|
181
|
+
};
|
|
182
|
+
case 'cuxml://config/clashAttributes':
|
|
183
|
+
return {
|
|
184
|
+
uri: uri,
|
|
185
|
+
mimeType: 'application/json',
|
|
186
|
+
content: JSON.stringify(require('../lib/clashAttributes.json'), null, 2)
|
|
187
|
+
};
|
|
188
|
+
case 'cuxml://config/stDelegate':
|
|
189
|
+
return {
|
|
190
|
+
uri: uri,
|
|
191
|
+
mimeType: 'application/json',
|
|
192
|
+
content: JSON.stringify(require('../lib/StDelegate.json'), null, 2)
|
|
193
|
+
};
|
|
194
|
+
default:
|
|
195
|
+
throw new Error(`未知的资源: ${uri}`);
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
return {
|
|
199
|
+
uri: uri,
|
|
200
|
+
error: error.message
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* MCP 提示模板
|
|
207
|
+
*/
|
|
208
|
+
static getPrompts() {
|
|
209
|
+
return [
|
|
210
|
+
{
|
|
211
|
+
name: 'generate-ribbon-ui',
|
|
212
|
+
description: '生成 RibbonUI XML 配置',
|
|
213
|
+
arguments: [
|
|
214
|
+
{
|
|
215
|
+
name: 'requirements',
|
|
216
|
+
description: '功能需求描述',
|
|
217
|
+
required: true
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: 'debug-xml',
|
|
223
|
+
description: '调试 XML 配置问题',
|
|
224
|
+
arguments: [
|
|
225
|
+
{
|
|
226
|
+
name: 'xmlContent',
|
|
227
|
+
description: 'XML 内容',
|
|
228
|
+
required: true
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: 'error',
|
|
232
|
+
description: '错误信息',
|
|
233
|
+
required: false
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 如果直接运行此文件,启动 MCP 服务器
|
|
242
|
+
if (require.main === module) {
|
|
243
|
+
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
244
|
+
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
245
|
+
|
|
246
|
+
const server = new Server(
|
|
247
|
+
CUXMLMCP.getServerInfo(),
|
|
248
|
+
{
|
|
249
|
+
capabilities: {
|
|
250
|
+
tools: {},
|
|
251
|
+
resources: {},
|
|
252
|
+
prompts: {}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// 注册工具
|
|
258
|
+
server.setRequestHandler('tools/list', async () => {
|
|
259
|
+
return { tools: CUXMLMCP.getTools() };
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
server.setRequestHandler('tools/call', async (request) => {
|
|
263
|
+
const { name, arguments: args } = request.params;
|
|
264
|
+
return await CUXMLMCP.executeTool(name, args);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// 注册资源
|
|
268
|
+
server.setRequestHandler('resources/list', async () => {
|
|
269
|
+
return { resources: CUXMLMCP.getResources() };
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
server.setRequestHandler('resources/read', async (request) => {
|
|
273
|
+
return await CUXMLMCP.readResource(request.params.uri);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// 注册提示
|
|
277
|
+
server.setRequestHandler('prompts/list', async () => {
|
|
278
|
+
return { prompts: CUXMLMCP.getPrompts() };
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// 启动服务器
|
|
282
|
+
const transport = new StdioServerTransport();
|
|
283
|
+
server.connect(transport).catch(error => {
|
|
284
|
+
console.error('MCP 服务器启动失败:', error);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = CUXMLMCP;
|
package/src/parser.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const xmljs = require('xml-js');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const lodash = require('lodash');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* XML 解析器模块
|
|
7
|
+
* 负责解析 XML 文件并将其转换为扁平化的 JSON 结构
|
|
8
|
+
*/
|
|
9
|
+
class XMLParser {
|
|
10
|
+
/**
|
|
11
|
+
* 解析 XML 文件
|
|
12
|
+
* @param {string} xmlPath - XML 文件路径
|
|
13
|
+
* @returns {object} 解析后的 JSON 对象
|
|
14
|
+
* @throws {Error} 如果文件不存在或解析失败
|
|
15
|
+
*/
|
|
16
|
+
static parseFile(xmlPath) {
|
|
17
|
+
if (!xmlPath || typeof xmlPath !== 'string') {
|
|
18
|
+
throw new Error('XML 文件路径必须是非空字符串');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!fs.existsSync(xmlPath)) {
|
|
22
|
+
throw new Error(`XML 文件不存在: ${xmlPath}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const xmlContent = fs.readFileSync(xmlPath, 'utf-8');
|
|
26
|
+
|
|
27
|
+
if (!xmlContent.trim()) {
|
|
28
|
+
throw new Error('XML 文件为空');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return this.parseString(xmlContent);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 解析 XML 字符串
|
|
36
|
+
* @param {string} xmlString - XML 字符串
|
|
37
|
+
* @returns {object} 解析后的 JSON 对象
|
|
38
|
+
* @throws {Error} 如果解析失败
|
|
39
|
+
*/
|
|
40
|
+
static parseString(xmlString) {
|
|
41
|
+
try {
|
|
42
|
+
return xmljs.xml2js(xmlString, { compact: false, spaces: 4 });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new Error(`XML 解析失败: ${error.message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 将 XML JSON 对象扁平化为数组
|
|
50
|
+
* @param {object} obj - XML JSON 对象
|
|
51
|
+
* @returns {Array} 扁平化后的元素数组
|
|
52
|
+
*/
|
|
53
|
+
static flatten(obj) {
|
|
54
|
+
const result = [];
|
|
55
|
+
const resNames = [];
|
|
56
|
+
|
|
57
|
+
function flattenInternal(currentObj, path = []) {
|
|
58
|
+
// 提取 type, name, attributes
|
|
59
|
+
const wanted = lodash.pick(currentObj, ['type', 'name', 'attributes']);
|
|
60
|
+
|
|
61
|
+
if (Object.keys(wanted).length > 0) {
|
|
62
|
+
Object.assign(wanted, { xmlpath: resNames.join('>') });
|
|
63
|
+
result.push(wanted);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (currentObj && typeof currentObj === 'object') {
|
|
67
|
+
Object.keys(currentObj).forEach(key => {
|
|
68
|
+
const value = currentObj[key];
|
|
69
|
+
const newPath = path.concat(key);
|
|
70
|
+
|
|
71
|
+
// 处理数组索引
|
|
72
|
+
if (Number.isInteger(Number(key))) {
|
|
73
|
+
let eItem = `${value.name}[${key}]`;
|
|
74
|
+
|
|
75
|
+
if (Number(key) > 0) {
|
|
76
|
+
const beDeleted = `${value.name}[${key - 1}]`;
|
|
77
|
+
const index = resNames.lastIndexOf(beDeleted);
|
|
78
|
+
const resNamesCopy = resNames.slice(0, index);
|
|
79
|
+
resNames.splice(0, resNames.length, ...resNamesCopy, eItem);
|
|
80
|
+
} else {
|
|
81
|
+
resNames.push(eItem);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (value && typeof value === 'object') {
|
|
86
|
+
flattenInternal(value, newPath);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
flattenInternal(obj);
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 解析并扁平化 XML 文件
|
|
98
|
+
* @param {string} xmlPath - XML 文件路径
|
|
99
|
+
* @returns {Array} 扁平化后的元素数组
|
|
100
|
+
*/
|
|
101
|
+
static parseAndFlatten(xmlPath) {
|
|
102
|
+
const jsonObj = this.parseFile(xmlPath);
|
|
103
|
+
return this.flatten(jsonObj);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 解析字符串并扁平化
|
|
108
|
+
* @param {string} xmlString - XML 字符串
|
|
109
|
+
* @returns {Array} 扁平化后的元素数组
|
|
110
|
+
*/
|
|
111
|
+
static parseStringAndFlatten(xmlString) {
|
|
112
|
+
const jsonObj = this.parseString(xmlString);
|
|
113
|
+
return this.flatten(jsonObj);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = XMLParser;
|