trackswap 0.0.2 → 0.0.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 +322 -281
- package/dist/FIT/base.d.ts +0 -30
- package/dist/FIT/decoder.d.ts +0 -48
- package/dist/FIT/encoder.d.ts +0 -97
- package/dist/FIT/mesg.d.ts +0 -3
- package/dist/FIT/middleware.d.ts +0 -15
- package/dist/FIT/plugins.d.ts +0 -9
- package/dist/FIT/processor.d.ts +0 -24
- package/dist/FIT/types/mesg_num.d.ts +102 -102
- package/dist/FIT/types/sport.d.ts +44 -44
- package/dist/FIT/types.d.ts +0 -35
- package/dist/GPX/base.d.ts +0 -39
- package/dist/GPX/converters.d.ts +0 -30
- package/dist/GPX/decoder.d.ts +0 -66
- package/dist/GPX/encoder.d.ts +0 -109
- package/dist/GPX/middleware.d.ts +0 -9
- package/dist/GPX/processor.d.ts +0 -18
- package/dist/GPX/types.d.ts +0 -108
- package/dist/TCX/base.d.ts +0 -33
- package/dist/TCX/converters.d.ts +0 -24
- package/dist/TCX/decoder.d.ts +0 -54
- package/dist/TCX/encoder.d.ts +0 -109
- package/dist/TCX/index.d.ts +4 -1
- package/dist/TCX/middleware.d.ts +0 -9
- package/dist/TCX/processor.d.ts +0 -18
- package/dist/TCX/types.d.ts +0 -13
- package/dist/TrackSwap.d.ts +37 -33
- package/dist/core/base.d.ts +0 -46
- package/dist/core/xml-utils.d.ts +0 -24
- package/dist/index.js +24 -5
- package/dist/sport/base.d.ts +63 -0
- package/dist/sport/converters.d.ts +33 -0
- package/dist/sport/encoders.d.ts +34 -0
- package/dist/sport/index.d.ts +17 -0
- package/dist/sport/processor.d.ts +19 -0
- package/dist/types.d.ts +91 -13
- package/package.json +18 -3
- package/dist/FIT/base.js +0 -101
- package/dist/FIT/crc.js +0 -16
- package/dist/FIT/decoder.js +0 -323
- package/dist/FIT/encoder.js +0 -445
- package/dist/FIT/index.js +0 -13
- package/dist/FIT/mesg.js +0 -208
- package/dist/FIT/middleware.js +0 -206
- package/dist/FIT/plugins.js +0 -104
- package/dist/FIT/processor.js +0 -170
- package/dist/FIT/types/course_point.js +0 -58
- package/dist/FIT/types/event.js +0 -50
- package/dist/FIT/types/event_type.js +0 -19
- package/dist/FIT/types/file.js +0 -24
- package/dist/FIT/types/index.js +0 -297
- package/dist/FIT/types/mesg_num.js +0 -106
- package/dist/FIT/types/sport.js +0 -48
- package/dist/FIT/types.js +0 -1
- package/dist/FIT/util.js +0 -7
- package/dist/GPX/base.js +0 -56
- package/dist/GPX/converters.js +0 -352
- package/dist/GPX/decoder.js +0 -409
- package/dist/GPX/encoder.js +0 -513
- package/dist/GPX/extensions/index.js +0 -14
- package/dist/GPX/index.js +0 -11
- package/dist/GPX/middleware.js +0 -93
- package/dist/GPX/processor.js +0 -188
- package/dist/GPX/types.js +0 -1
- package/dist/GPX/util.js +0 -18
- package/dist/TCX/base.js +0 -62
- package/dist/TCX/converters.js +0 -333
- package/dist/TCX/decoder.js +0 -350
- package/dist/TCX/encoder.js +0 -513
- package/dist/TCX/index.js +0 -9
- package/dist/TCX/middleware.js +0 -93
- package/dist/TCX/processor.js +0 -197
- package/dist/TCX/types.js +0 -1
- package/dist/TrackSwap.js +0 -84
- package/dist/core/base.js +0 -138
- package/dist/core/decoder.d.ts +0 -66
- package/dist/core/decoder.js +0 -90
- package/dist/core/index.js +0 -4
- package/dist/core/processor.d.ts +0 -69
- package/dist/core/processor.js +0 -265
- package/dist/core/xml-utils.js +0 -140
- package/dist/types.js +0 -1
package/dist/TCX/decoder.js
DELETED
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
import { PositionConverter, TrackpointConverter, TrackConverter, ActivityLapConverter, ActivityConverter, ActivityListConverter, MultiSportSessionConverter, AbstractSourceConverter, } from "./converters.js";
|
|
2
|
-
import { AstGenerateProcessor, CompleteProcessor, ConvertProcessor, TokenizeProcessor, PipelineStage, } from "./processor.js";
|
|
3
|
-
/**
|
|
4
|
-
* https://www8.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd
|
|
5
|
-
*/
|
|
6
|
-
export class TCXDecoder {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.converterPlugins = new Map();
|
|
9
|
-
this.registeredPlugins = new Set(); // 跟踪已注册的插件名
|
|
10
|
-
this.middlewarePlugins = [];
|
|
11
|
-
this.processors = []; // 固定的核心处理器
|
|
12
|
-
this.initialized = false;
|
|
13
|
-
this.defaultConvertersRegistered = false;
|
|
14
|
-
this.initializePipeline();
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* 初始化流水线 - 注册固定的核心处理器
|
|
18
|
-
*/
|
|
19
|
-
initializePipeline() {
|
|
20
|
-
// 固定的核心处理器,不允许用户修改
|
|
21
|
-
this.processors = [
|
|
22
|
-
new TokenizeProcessor(),
|
|
23
|
-
new AstGenerateProcessor(),
|
|
24
|
-
new ConvertProcessor((tag) => this.getConverter(tag)),
|
|
25
|
-
new CompleteProcessor(),
|
|
26
|
-
];
|
|
27
|
-
console.log("🔧 TCX核心流水线处理器已初始化 (固定不可修改)");
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* 自动注册默认转换器 - 处理标准TCX标签
|
|
31
|
-
*/
|
|
32
|
-
async registerDefaultConverters() {
|
|
33
|
-
if (this.defaultConvertersRegistered)
|
|
34
|
-
return;
|
|
35
|
-
const defaultConverters = [
|
|
36
|
-
new PositionConverter(),
|
|
37
|
-
new TrackpointConverter(),
|
|
38
|
-
new TrackConverter(),
|
|
39
|
-
new ActivityLapConverter(),
|
|
40
|
-
new ActivityConverter(),
|
|
41
|
-
new ActivityListConverter(),
|
|
42
|
-
new MultiSportSessionConverter(),
|
|
43
|
-
new AbstractSourceConverter(),
|
|
44
|
-
];
|
|
45
|
-
for (const converter of defaultConverters) {
|
|
46
|
-
// 内部注册,不显示日志避免干扰
|
|
47
|
-
converter.supportedTags?.forEach((tag) => {
|
|
48
|
-
this.addConverterForTag(tag, converter);
|
|
49
|
-
});
|
|
50
|
-
this.registeredPlugins.add(converter.name);
|
|
51
|
-
}
|
|
52
|
-
this.defaultConvertersRegistered = true;
|
|
53
|
-
console.log("✅ 默认TCX转换器已自动注册 (Activities, Position, Track等)");
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* 为指定标签添加转换器,按优先级排序
|
|
57
|
-
*/
|
|
58
|
-
addConverterForTag(tag, converter) {
|
|
59
|
-
if (!this.converterPlugins.has(tag)) {
|
|
60
|
-
this.converterPlugins.set(tag, []);
|
|
61
|
-
}
|
|
62
|
-
const converters = this.converterPlugins.get(tag);
|
|
63
|
-
converters.push(converter);
|
|
64
|
-
// 按优先级排序(数字越小优先级越高)
|
|
65
|
-
converters.sort((a, b) => (a.priority || 100) - (b.priority || 100));
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* 注册转换器插件
|
|
69
|
-
*/
|
|
70
|
-
async registerConverter(plugin) {
|
|
71
|
-
// 检查插件是否已注册
|
|
72
|
-
if (this.registeredPlugins.has(plugin.name)) {
|
|
73
|
-
throw new Error(`转换器插件 ${plugin.name} 已存在`);
|
|
74
|
-
}
|
|
75
|
-
// 注册支持的标签(允许多个转换器处理同一标签)
|
|
76
|
-
plugin.supportedTags?.forEach((tag) => {
|
|
77
|
-
this.addConverterForTag(tag, plugin);
|
|
78
|
-
});
|
|
79
|
-
this.registeredPlugins.add(plugin.name);
|
|
80
|
-
// 显示优先级信息
|
|
81
|
-
const priorityInfo = plugin.priority !== undefined ? ` (优先级: ${plugin.priority})` : "";
|
|
82
|
-
console.log(`📦 已注册自定义TCX转换器: ${plugin.name}${priorityInfo}, 支持标签: ${plugin.supportedTags?.join(", ")}`);
|
|
83
|
-
// 显示标签的转换器优先级顺序
|
|
84
|
-
plugin.supportedTags?.forEach((tag) => {
|
|
85
|
-
const converters = this.converterPlugins.get(tag) || [];
|
|
86
|
-
const converterNames = converters.map((c) => `${c.name}(${c.priority || 100})`);
|
|
87
|
-
console.log(` 📋 标签 "${tag}" 转换器优先级: ${converterNames.join(" > ")}`);
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* 注册中间件插件 - 真正的扩展点
|
|
92
|
-
*/
|
|
93
|
-
async registerMiddleware(plugin) {
|
|
94
|
-
const existingIndex = this.middlewarePlugins.findIndex((p) => p.name === plugin.name);
|
|
95
|
-
if (existingIndex !== -1) {
|
|
96
|
-
this.middlewarePlugins[existingIndex] = plugin;
|
|
97
|
-
console.log(`🔄 已更新TCX中间件: ${plugin.name}`);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
this.middlewarePlugins.push(plugin);
|
|
101
|
-
console.log(`🔌 已注册TCX中间件: ${plugin.name}`);
|
|
102
|
-
}
|
|
103
|
-
// 显示中间件支持的钩子
|
|
104
|
-
const hooks = [];
|
|
105
|
-
if (plugin.onTokenize)
|
|
106
|
-
hooks.push("onTokenize");
|
|
107
|
-
if (plugin.onAstGenerate)
|
|
108
|
-
hooks.push("onAstGenerate");
|
|
109
|
-
if (plugin.onConvert)
|
|
110
|
-
hooks.push("onConvert");
|
|
111
|
-
if (plugin.onComplete)
|
|
112
|
-
hooks.push("onComplete");
|
|
113
|
-
if (plugin.onError)
|
|
114
|
-
hooks.push("onError");
|
|
115
|
-
if (hooks.length > 0) {
|
|
116
|
-
console.log(` 🎣 支持的钩子: ${hooks.join(", ")}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* 获取转换器(返回优先级最高的)
|
|
121
|
-
*/
|
|
122
|
-
getConverter(tag) {
|
|
123
|
-
const converters = this.converterPlugins.get(tag);
|
|
124
|
-
return converters && converters.length > 0 ? converters[0] : undefined;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* 获取指定标签的所有转换器(按优先级排序)
|
|
128
|
-
*/
|
|
129
|
-
getAllConverters(tag) {
|
|
130
|
-
return this.converterPlugins.get(tag) || [];
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* 移除转换器
|
|
134
|
-
*/
|
|
135
|
-
unregisterConverter(pluginName) {
|
|
136
|
-
// 找到插件实例
|
|
137
|
-
let pluginToRemove;
|
|
138
|
-
for (const [tag, converters] of this.converterPlugins) {
|
|
139
|
-
const index = converters.findIndex((plugin) => plugin.name === pluginName);
|
|
140
|
-
if (index !== -1) {
|
|
141
|
-
pluginToRemove = converters[index];
|
|
142
|
-
converters.splice(index, 1);
|
|
143
|
-
// 如果该标签没有转换器了,删除整个条目
|
|
144
|
-
if (converters.length === 0) {
|
|
145
|
-
this.converterPlugins.delete(tag);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (!pluginToRemove) {
|
|
150
|
-
console.warn(`TCX转换器插件 ${pluginName} 不存在`);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
this.registeredPlugins.delete(pluginName);
|
|
154
|
-
console.log(`🗑️ 已移除TCX转换器: ${pluginName}`);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* 移除中间件
|
|
158
|
-
*/
|
|
159
|
-
unregisterMiddleware(name) {
|
|
160
|
-
const index = this.middlewarePlugins.findIndex((p) => p.name === name);
|
|
161
|
-
if (index !== -1) {
|
|
162
|
-
this.middlewarePlugins.splice(index, 1);
|
|
163
|
-
console.log(`🗑️ 已移除TCX中间件: ${name}`);
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
console.warn(`TCX中间件 ${name} 不存在`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* 初始化解码器
|
|
171
|
-
*/
|
|
172
|
-
async initialize() {
|
|
173
|
-
if (this.initialized)
|
|
174
|
-
return;
|
|
175
|
-
// 首先注册默认转换器
|
|
176
|
-
await this.registerDefaultConverters();
|
|
177
|
-
const context = this.createContext();
|
|
178
|
-
// 初始化所有插件
|
|
179
|
-
const allPlugins = [
|
|
180
|
-
...Array.from(this.converterPlugins.values()).flat(),
|
|
181
|
-
...this.middlewarePlugins,
|
|
182
|
-
];
|
|
183
|
-
for (const plugin of allPlugins) {
|
|
184
|
-
try {
|
|
185
|
-
await plugin.initialize?.(context);
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
console.error(`TCX插件 ${plugin.name} 初始化失败:`, error);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
this.initialized = true;
|
|
192
|
-
}
|
|
193
|
-
/**
|
|
194
|
-
* 销毁解码器
|
|
195
|
-
*/
|
|
196
|
-
async destroy() {
|
|
197
|
-
if (!this.initialized)
|
|
198
|
-
return;
|
|
199
|
-
const context = this.createContext();
|
|
200
|
-
// 销毁所有插件
|
|
201
|
-
const allPlugins = [
|
|
202
|
-
...Array.from(this.converterPlugins.values()).flat(),
|
|
203
|
-
...this.middlewarePlugins,
|
|
204
|
-
];
|
|
205
|
-
for (const plugin of allPlugins) {
|
|
206
|
-
try {
|
|
207
|
-
await plugin.destroy?.(context);
|
|
208
|
-
}
|
|
209
|
-
catch (error) {
|
|
210
|
-
console.error(`TCX插件 ${plugin.name} 销毁失败:`, error);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
this.initialized = false;
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* 创建解码上下文
|
|
217
|
-
*/
|
|
218
|
-
createContext() {
|
|
219
|
-
return {
|
|
220
|
-
metadata: new Map(),
|
|
221
|
-
errors: [],
|
|
222
|
-
warnings: [],
|
|
223
|
-
stats: {
|
|
224
|
-
startTime: Date.now(),
|
|
225
|
-
processedTokens: 0,
|
|
226
|
-
convertedElements: 0,
|
|
227
|
-
},
|
|
228
|
-
performance: {
|
|
229
|
-
startTime: Date.now(),
|
|
230
|
-
},
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* 执行中间件钩子
|
|
235
|
-
*/
|
|
236
|
-
async executeMiddlewareHook(hookName, data, context) {
|
|
237
|
-
let result = data;
|
|
238
|
-
for (const middleware of this.middlewarePlugins) {
|
|
239
|
-
const hook = middleware[hookName];
|
|
240
|
-
if (hook) {
|
|
241
|
-
try {
|
|
242
|
-
result = (await hook.call(middleware, result, context)) || result;
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
context.errors.push(error);
|
|
246
|
-
await middleware.onError?.(error, context);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return result;
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* 主解析方法 - 固定的流水线 + 灵活的中间件
|
|
254
|
-
*/
|
|
255
|
-
async parseByBuffer(buffer) {
|
|
256
|
-
await this.initialize();
|
|
257
|
-
let context = this.createContext();
|
|
258
|
-
context.rawData = buffer;
|
|
259
|
-
context.xmlContent = buffer.toString("utf-8");
|
|
260
|
-
// 在上下文中传递解码器实例引用
|
|
261
|
-
context.metadata.set("decoder", this);
|
|
262
|
-
try {
|
|
263
|
-
// 固定的流水线阶段顺序执行
|
|
264
|
-
for (const processor of this.processors) {
|
|
265
|
-
try {
|
|
266
|
-
// 1. 执行核心处理器
|
|
267
|
-
context = await processor.process(context);
|
|
268
|
-
// 2. 根据阶段执行对应的中间件钩子
|
|
269
|
-
switch (processor.stage) {
|
|
270
|
-
case PipelineStage.TOKENIZE:
|
|
271
|
-
if (context.tokens) {
|
|
272
|
-
context.tokens = await this.executeMiddlewareHook("onTokenize", context.tokens, context);
|
|
273
|
-
}
|
|
274
|
-
break;
|
|
275
|
-
case PipelineStage.AST_GENERATE:
|
|
276
|
-
if (context.ast) {
|
|
277
|
-
context.ast = await this.executeMiddlewareHook("onAstGenerate", context.ast, context);
|
|
278
|
-
}
|
|
279
|
-
break;
|
|
280
|
-
case PipelineStage.CONVERT:
|
|
281
|
-
if (context.result) {
|
|
282
|
-
context.result = await this.executeMiddlewareHook("onConvert", context.result, context);
|
|
283
|
-
}
|
|
284
|
-
break;
|
|
285
|
-
case PipelineStage.COMPLETE:
|
|
286
|
-
if (context.result) {
|
|
287
|
-
context.result = await this.executeMiddlewareHook("onComplete", context.result, context);
|
|
288
|
-
}
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
catch (error) {
|
|
293
|
-
context.errors.push(error);
|
|
294
|
-
// 执行错误处理中间件
|
|
295
|
-
for (const middleware of this.middlewarePlugins) {
|
|
296
|
-
await middleware.onError?.(error, context);
|
|
297
|
-
}
|
|
298
|
-
console.error(`TCX流水线阶段 ${processor.stage} 处理失败:`, error);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
return context.result;
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
console.error("TCX解析失败:", error);
|
|
305
|
-
throw error;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* 转换Extensions(向后兼容)
|
|
310
|
-
*/
|
|
311
|
-
convertExtensions(extensionsAST) {
|
|
312
|
-
const extensions = {};
|
|
313
|
-
const { attributes, tag, value } = extensionsAST;
|
|
314
|
-
// 处理属性
|
|
315
|
-
if (attributes) {
|
|
316
|
-
Object.entries(attributes).forEach(([key, value]) => {
|
|
317
|
-
extensions[key] = value;
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
// 如果有子节点,直接处理子节点,不再包装外层标签
|
|
321
|
-
if (extensionsAST?.children?.length) {
|
|
322
|
-
extensionsAST.children?.forEach((child) => {
|
|
323
|
-
extensions[child.tag] =
|
|
324
|
-
this.convertExtensions(child);
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
else if (value !== undefined) {
|
|
328
|
-
// 如果没有子节点但有值,直接返回值
|
|
329
|
-
return value;
|
|
330
|
-
}
|
|
331
|
-
return extensions;
|
|
332
|
-
}
|
|
333
|
-
// ==================== 向后兼容方法 ====================
|
|
334
|
-
/**
|
|
335
|
-
* @deprecated 使用 parseByBuffer 替代
|
|
336
|
-
*/
|
|
337
|
-
async parserByBuffer(buffer) {
|
|
338
|
-
return this.parseByBuffer(buffer);
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* @deprecated 使用新的插件系统替代
|
|
342
|
-
*/
|
|
343
|
-
async parseByString(xmlContent) {
|
|
344
|
-
const buffer = Buffer.from(xmlContent, "utf-8");
|
|
345
|
-
return this.parseByBuffer(buffer);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
export default {
|
|
349
|
-
TCXDecoder,
|
|
350
|
-
};
|