trackswap 0.0.1 → 0.0.2
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/dist/FIT/base.d.ts +91 -0
- package/dist/FIT/base.js +101 -0
- package/dist/FIT/crc.d.ts +1 -0
- package/dist/FIT/crc.js +16 -0
- package/dist/FIT/decoder.d.ts +83 -0
- package/dist/FIT/decoder.js +323 -0
- package/dist/FIT/encoder.d.ts +142 -0
- package/dist/FIT/encoder.js +445 -0
- package/dist/FIT/index.d.ts +12 -0
- package/dist/FIT/index.js +13 -0
- package/dist/FIT/mesg.d.ts +40 -0
- package/dist/FIT/mesg.js +208 -0
- package/dist/FIT/middleware.d.ts +48 -0
- package/dist/FIT/middleware.js +206 -0
- package/dist/FIT/plugins.d.ts +26 -0
- package/dist/FIT/plugins.js +104 -0
- package/dist/FIT/processor.d.ts +58 -0
- package/dist/FIT/processor.js +170 -0
- package/dist/FIT/types/course_point.d.ts +56 -0
- package/dist/FIT/types/course_point.js +58 -0
- package/dist/FIT/types/event.d.ts +49 -0
- package/dist/FIT/types/event.js +50 -0
- package/dist/FIT/types/event_type.d.ts +18 -0
- package/dist/FIT/types/event_type.js +19 -0
- package/dist/FIT/types/file.d.ts +23 -0
- package/dist/FIT/types/file.js +24 -0
- package/dist/FIT/types/index.d.ts +119 -0
- package/dist/FIT/types/index.js +297 -0
- package/dist/FIT/types/mesg_num.d.ts +105 -0
- package/dist/FIT/types/mesg_num.js +106 -0
- package/dist/FIT/types/sport.d.ts +47 -0
- package/dist/FIT/types/sport.js +48 -0
- package/dist/FIT/types.d.ts +231 -0
- package/dist/FIT/types.js +1 -0
- package/dist/FIT/util.d.ts +1 -0
- package/dist/FIT/util.js +7 -0
- package/dist/GPX/base.d.ts +43 -67
- package/dist/GPX/base.js +40 -67
- package/dist/GPX/converters.d.ts +12 -36
- package/dist/GPX/converters.js +11 -103
- package/dist/GPX/decoder.d.ts +5 -5
- package/dist/GPX/decoder.js +6 -0
- package/dist/GPX/encoder.d.ts +4 -0
- package/dist/GPX/encoder.js +3 -0
- package/dist/GPX/index.d.ts +1 -1
- package/dist/GPX/index.js +1 -1
- package/dist/GPX/middleware.d.ts +27 -0
- package/dist/GPX/middleware.js +93 -0
- package/dist/GPX/processor.d.ts +1 -1
- package/dist/GPX/processor.js +12 -11
- package/dist/GPX/types.d.ts +2 -3
- package/dist/TCX/base.d.ts +52 -0
- package/dist/TCX/base.js +62 -0
- package/dist/TCX/converters.d.ts +75 -0
- package/dist/TCX/converters.js +333 -0
- package/dist/TCX/decoder.d.ts +87 -0
- package/dist/TCX/decoder.js +350 -0
- package/dist/TCX/encoder.d.ts +150 -0
- package/dist/TCX/encoder.js +513 -0
- package/dist/TCX/index.d.ts +10 -0
- package/dist/TCX/index.js +9 -0
- package/dist/TCX/middleware.d.ts +27 -0
- package/dist/TCX/middleware.js +93 -0
- package/dist/TCX/processor.d.ts +48 -0
- package/dist/TCX/processor.js +197 -0
- package/dist/TCX/types.d.ts +203 -0
- package/dist/TCX/types.js +1 -0
- package/dist/core/base.d.ts +119 -0
- package/dist/core/base.js +138 -0
- package/dist/core/decoder.d.ts +66 -0
- package/dist/core/decoder.js +90 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +4 -0
- package/dist/core/processor.d.ts +69 -0
- package/dist/core/processor.js +265 -0
- package/dist/core/xml-utils.d.ts +41 -0
- package/dist/core/xml-utils.js +140 -0
- package/package.json +8 -5
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { IPlugin } from "../core/base.js";
|
|
2
|
+
import { FITContext, FITDecoderMesgs, FITFileType } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* FIT 消息转换器插件接口
|
|
5
|
+
*/
|
|
6
|
+
export interface IFITMessageConverter extends IPlugin<FITContext> {
|
|
7
|
+
/** 支持的消息类型 */
|
|
8
|
+
supportedMessageTypes: string[];
|
|
9
|
+
/** 转换消息 */
|
|
10
|
+
convertMessages(messages: any[], context: FITContext): any;
|
|
11
|
+
/** 是否支持该消息类型 */
|
|
12
|
+
supports(messageType: string): boolean;
|
|
13
|
+
/** 优先级 */
|
|
14
|
+
priority?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* FIT 数据结构化插件接口
|
|
18
|
+
*/
|
|
19
|
+
export interface IFITStructurePlugin extends IPlugin<FITContext> {
|
|
20
|
+
/** 结构化数据 */
|
|
21
|
+
structureData(messages: FITDecoderMesgs, context: FITContext): Partial<FITFileType>;
|
|
22
|
+
/** 优先级 */
|
|
23
|
+
priority?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* FIT 中间件插件接口
|
|
27
|
+
*/
|
|
28
|
+
export interface IFITMiddlewarePlugin extends IPlugin<FITContext> {
|
|
29
|
+
/** 优先级 */
|
|
30
|
+
priority?: number;
|
|
31
|
+
/** 在解析阶段执行 */
|
|
32
|
+
onParse?(buffer: Buffer, context: FITContext): Promise<Buffer> | Buffer;
|
|
33
|
+
/** 在消息提取阶段执行 */
|
|
34
|
+
onExtractMessages?(messages: FITDecoderMesgs, context: FITContext): Promise<FITDecoderMesgs> | FITDecoderMesgs;
|
|
35
|
+
/** 在数据结构化阶段执行 */
|
|
36
|
+
onStructure?(result: Partial<FITFileType>, context: FITContext): Promise<Partial<FITFileType>> | Partial<FITFileType>;
|
|
37
|
+
/** 在完成阶段执行 */
|
|
38
|
+
onComplete?(result: FITFileType, context: FITContext): Promise<FITFileType> | FITFileType;
|
|
39
|
+
/** 错误处理 */
|
|
40
|
+
onError?(error: Error, context: FITContext): Promise<void> | void;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* FIT 消息转换器基类
|
|
44
|
+
*/
|
|
45
|
+
export declare abstract class BaseFITMessageConverter implements IFITMessageConverter {
|
|
46
|
+
abstract name: string;
|
|
47
|
+
abstract supportedMessageTypes: string[];
|
|
48
|
+
version: string;
|
|
49
|
+
priority: number;
|
|
50
|
+
abstract convertMessages(messages: any[], context: FITContext): any;
|
|
51
|
+
supports(messageType: string): boolean;
|
|
52
|
+
initialize(context: FITContext): Promise<void>;
|
|
53
|
+
destroy(context: FITContext): Promise<void>;
|
|
54
|
+
validate(context: FITContext): Promise<boolean>;
|
|
55
|
+
protected parseTimestamp(timestamp: string | undefined): Date | undefined;
|
|
56
|
+
protected calculateDuration(startTime?: string, endTime?: string): number;
|
|
57
|
+
protected filterMessagesByTimeRange(messages: any[], startTime: string, endTime: string, tolerance?: number): any[];
|
|
58
|
+
protected groupMessagesByLap(records: any[], laps: any[]): Array<{
|
|
59
|
+
lap: any;
|
|
60
|
+
records: any[];
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* FIT 数据结构化插件基类
|
|
65
|
+
*/
|
|
66
|
+
export declare abstract class BaseFITStructurePlugin implements IFITStructurePlugin {
|
|
67
|
+
abstract name: string;
|
|
68
|
+
version: string;
|
|
69
|
+
priority: number;
|
|
70
|
+
abstract structureData(messages: FITDecoderMesgs, context: FITContext): Partial<FITFileType>;
|
|
71
|
+
initialize(context: FITContext): Promise<void>;
|
|
72
|
+
destroy(context: FITContext): Promise<void>;
|
|
73
|
+
validate(context: FITContext): Promise<boolean>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* FIT 中间件基类
|
|
77
|
+
*/
|
|
78
|
+
export declare abstract class BaseFITMiddleware implements IFITMiddlewarePlugin {
|
|
79
|
+
abstract name: string;
|
|
80
|
+
version: string;
|
|
81
|
+
description: string;
|
|
82
|
+
priority: number;
|
|
83
|
+
initialize(context: FITContext): Promise<void>;
|
|
84
|
+
destroy(context: FITContext): Promise<void>;
|
|
85
|
+
validate(context: FITContext): Promise<boolean>;
|
|
86
|
+
onParse(buffer: Buffer, context: FITContext): Promise<Buffer>;
|
|
87
|
+
onExtractMessages(messages: FITDecoderMesgs, context: FITContext): Promise<FITDecoderMesgs>;
|
|
88
|
+
onStructure(result: Partial<FITFileType>, context: FITContext): Promise<Partial<FITFileType>>;
|
|
89
|
+
onComplete(result: FITFileType, context: FITContext): Promise<FITFileType>;
|
|
90
|
+
onError(error: Error, context: FITContext): Promise<void>;
|
|
91
|
+
}
|
package/dist/FIT/base.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// ============ 基础工具类 ============
|
|
2
|
+
/**
|
|
3
|
+
* FIT 消息转换器基类
|
|
4
|
+
*/
|
|
5
|
+
export class BaseFITMessageConverter {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.version = "1.0.0";
|
|
8
|
+
this.priority = 100;
|
|
9
|
+
}
|
|
10
|
+
supports(messageType) {
|
|
11
|
+
return this.supportedMessageTypes.includes(messageType);
|
|
12
|
+
}
|
|
13
|
+
async initialize(context) {
|
|
14
|
+
context.metadata.set(`plugin:${this.name}:initialized`, true);
|
|
15
|
+
}
|
|
16
|
+
async destroy(context) {
|
|
17
|
+
context.metadata.delete(`plugin:${this.name}:initialized`);
|
|
18
|
+
}
|
|
19
|
+
async validate(context) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
// ============ FIT 特定工具方法 ============
|
|
23
|
+
parseTimestamp(timestamp) {
|
|
24
|
+
if (!timestamp)
|
|
25
|
+
return undefined;
|
|
26
|
+
const date = new Date(timestamp);
|
|
27
|
+
return isNaN(date.getTime()) ? undefined : date;
|
|
28
|
+
}
|
|
29
|
+
calculateDuration(startTime, endTime) {
|
|
30
|
+
if (!startTime || !endTime)
|
|
31
|
+
return 0;
|
|
32
|
+
const start = new Date(startTime).getTime();
|
|
33
|
+
const end = new Date(endTime).getTime();
|
|
34
|
+
return Math.max(0, (end - start) / 1000); // 返回秒数
|
|
35
|
+
}
|
|
36
|
+
filterMessagesByTimeRange(messages, startTime, endTime, tolerance = 2000 // 默认2秒容错
|
|
37
|
+
) {
|
|
38
|
+
const start = new Date(startTime).getTime();
|
|
39
|
+
const end = new Date(endTime).getTime();
|
|
40
|
+
return messages.filter((message) => {
|
|
41
|
+
if (!message.timestamp)
|
|
42
|
+
return false;
|
|
43
|
+
const messageTime = new Date(message.timestamp).getTime();
|
|
44
|
+
return messageTime >= start - tolerance && messageTime <= end + tolerance;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
groupMessagesByLap(records, laps) {
|
|
48
|
+
return laps.map((lap) => {
|
|
49
|
+
const lapRecords = this.filterMessagesByTimeRange(records, lap.startTime, lap.timestamp);
|
|
50
|
+
return { lap, records: lapRecords };
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* FIT 数据结构化插件基类
|
|
56
|
+
*/
|
|
57
|
+
export class BaseFITStructurePlugin {
|
|
58
|
+
constructor() {
|
|
59
|
+
this.version = "1.0.0";
|
|
60
|
+
this.priority = 100;
|
|
61
|
+
}
|
|
62
|
+
async initialize(context) {
|
|
63
|
+
context.metadata.set(`plugin:${this.name}:initialized`, true);
|
|
64
|
+
}
|
|
65
|
+
async destroy(context) {
|
|
66
|
+
context.metadata.delete(`plugin:${this.name}:initialized`);
|
|
67
|
+
}
|
|
68
|
+
async validate(context) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* FIT 中间件基类
|
|
74
|
+
*/
|
|
75
|
+
export class BaseFITMiddleware {
|
|
76
|
+
constructor() {
|
|
77
|
+
this.version = "1.0.0";
|
|
78
|
+
this.description = "";
|
|
79
|
+
this.priority = 100;
|
|
80
|
+
}
|
|
81
|
+
async initialize(context) { }
|
|
82
|
+
async destroy(context) { }
|
|
83
|
+
async validate(context) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
async onParse(buffer, context) {
|
|
87
|
+
return buffer;
|
|
88
|
+
}
|
|
89
|
+
async onExtractMessages(messages, context) {
|
|
90
|
+
return messages;
|
|
91
|
+
}
|
|
92
|
+
async onStructure(result, context) {
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
async onComplete(result, context) {
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
async onError(error, context) {
|
|
99
|
+
console.error(`FIT中间件 ${this.name} 处理错误:`, error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function crc(buffer: ArrayBuffer | Uint8Array, initial?: number): number;
|
package/dist/FIT/crc.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// prettier-ignore
|
|
2
|
+
const crc_table = [
|
|
3
|
+
0x0000, 0xcc01, 0xd801, 0x1400, 0xf001, 0x3c00, 0x2800, 0xe401,
|
|
4
|
+
0xa001, 0x6c00, 0x7800, 0xb401, 0x5000, 0x9c01, 0x8801, 0x4400
|
|
5
|
+
];
|
|
6
|
+
const update_nibble = (crc, nibble) => ((crc >>> 4) ^ crc_table[(crc & 0xf) ^ nibble]) & 0xffff;
|
|
7
|
+
const update_nibbles = (crc, lo, hi) => update_nibble(update_nibble(crc, lo), hi);
|
|
8
|
+
const update = (crc, byte) => update_nibbles(crc, byte & 0xf, byte >>> 4);
|
|
9
|
+
export function crc(buffer, initial = 0) {
|
|
10
|
+
let crcValue = initial;
|
|
11
|
+
const uint8Array = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer;
|
|
12
|
+
for (let i = 0; i < uint8Array.length; i++) {
|
|
13
|
+
crcValue = update(crcValue, uint8Array[i]);
|
|
14
|
+
}
|
|
15
|
+
return crcValue;
|
|
16
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { IFITMessageConverter, IFITStructurePlugin, IFITMiddlewarePlugin } from "./base.js";
|
|
2
|
+
import { FITFileType } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* FIT 解码器 - 遵循 GPX 风格的插件架构
|
|
5
|
+
*/
|
|
6
|
+
export declare class FITDecoder {
|
|
7
|
+
private converterPlugins;
|
|
8
|
+
private registeredPlugins;
|
|
9
|
+
private structurePlugins;
|
|
10
|
+
private middlewarePlugins;
|
|
11
|
+
private processors;
|
|
12
|
+
private initialized;
|
|
13
|
+
private defaultPluginsRegistered;
|
|
14
|
+
constructor();
|
|
15
|
+
/**
|
|
16
|
+
* 初始化流水线 - 注册固定的核心处理器
|
|
17
|
+
*/
|
|
18
|
+
private initializePipeline;
|
|
19
|
+
/**
|
|
20
|
+
* 自动注册默认插件
|
|
21
|
+
*/
|
|
22
|
+
private registerDefaultPlugins;
|
|
23
|
+
/**
|
|
24
|
+
* 为指定消息类型添加转换器,按优先级排序
|
|
25
|
+
*/
|
|
26
|
+
private addConverterForMessageType;
|
|
27
|
+
/**
|
|
28
|
+
* 添加结构化插件
|
|
29
|
+
*/
|
|
30
|
+
private addStructurePlugin;
|
|
31
|
+
/**
|
|
32
|
+
* 注册消息转换器插件
|
|
33
|
+
*/
|
|
34
|
+
registerMessageConverter(plugin: IFITMessageConverter): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* 注册结构化插件
|
|
37
|
+
*/
|
|
38
|
+
registerStructurePlugin(plugin: IFITStructurePlugin): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* 注册中间件插件
|
|
41
|
+
*/
|
|
42
|
+
registerMiddleware(plugin: IFITMiddlewarePlugin): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* 获取消息类型转换器(返回优先级最高的)
|
|
45
|
+
*/
|
|
46
|
+
getConverter(messageType: string): IFITMessageConverter | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* 获取指定消息类型的所有转换器(按优先级排序)
|
|
49
|
+
*/
|
|
50
|
+
getConverters(messageType: string): IFITMessageConverter[];
|
|
51
|
+
/**
|
|
52
|
+
* 移除插件
|
|
53
|
+
*/
|
|
54
|
+
unregisterPlugin(pluginName: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* 初始化解码器
|
|
57
|
+
*/
|
|
58
|
+
initialize(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* 销毁解码器
|
|
61
|
+
*/
|
|
62
|
+
destroy(): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* 创建解码上下文
|
|
65
|
+
*/
|
|
66
|
+
private createContext;
|
|
67
|
+
/**
|
|
68
|
+
* 执行中间件钩子
|
|
69
|
+
*/
|
|
70
|
+
private executeMiddlewareHook;
|
|
71
|
+
/**
|
|
72
|
+
* 主解析方法 - 固定的流水线 + 灵活的中间件
|
|
73
|
+
*/
|
|
74
|
+
parseByBuffer(buffer: Buffer, options?: {
|
|
75
|
+
skipValidation?: boolean;
|
|
76
|
+
enableStats?: boolean;
|
|
77
|
+
userData?: Record<string, any>;
|
|
78
|
+
}): Promise<FITFileType>;
|
|
79
|
+
}
|
|
80
|
+
declare const _default: {
|
|
81
|
+
FITDecoder: typeof FITDecoder;
|
|
82
|
+
};
|
|
83
|
+
export default _default;
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { ParseProcessor, ExtractProcessor, StructureProcessor, CompleteProcessor, FITPipelineStage, } from "./processor.js";
|
|
2
|
+
import { SessionStructurePlugin, CourseStructurePlugin, FileHeaderPlugin, } from "./plugins.js";
|
|
3
|
+
/**
|
|
4
|
+
* FIT 解码器 - 遵循 GPX 风格的插件架构
|
|
5
|
+
*/
|
|
6
|
+
export class FITDecoder {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.converterPlugins = new Map();
|
|
9
|
+
this.registeredPlugins = new Set(); // 跟踪已注册的插件名
|
|
10
|
+
this.structurePlugins = [];
|
|
11
|
+
this.middlewarePlugins = [];
|
|
12
|
+
this.processors = []; // 固定的核心处理器
|
|
13
|
+
this.initialized = false;
|
|
14
|
+
this.defaultPluginsRegistered = false;
|
|
15
|
+
this.initializePipeline();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 初始化流水线 - 注册固定的核心处理器
|
|
19
|
+
*/
|
|
20
|
+
initializePipeline() {
|
|
21
|
+
// 固定的核心处理器,不允许用户修改
|
|
22
|
+
this.processors = [
|
|
23
|
+
new ParseProcessor(),
|
|
24
|
+
new ExtractProcessor((messageType) => this.getConverters(messageType)),
|
|
25
|
+
new StructureProcessor(() => this.structurePlugins),
|
|
26
|
+
new CompleteProcessor(),
|
|
27
|
+
];
|
|
28
|
+
console.log("🔧 FIT核心流水线处理器已初始化 (固定不可修改)");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 自动注册默认插件
|
|
32
|
+
*/
|
|
33
|
+
async registerDefaultPlugins() {
|
|
34
|
+
if (this.defaultPluginsRegistered)
|
|
35
|
+
return;
|
|
36
|
+
const defaultStructurePlugins = [
|
|
37
|
+
new FileHeaderPlugin(),
|
|
38
|
+
new SessionStructurePlugin(),
|
|
39
|
+
new CourseStructurePlugin(),
|
|
40
|
+
];
|
|
41
|
+
for (const plugin of defaultStructurePlugins) {
|
|
42
|
+
this.addStructurePlugin(plugin);
|
|
43
|
+
this.registeredPlugins.add(plugin.name);
|
|
44
|
+
}
|
|
45
|
+
this.defaultPluginsRegistered = true;
|
|
46
|
+
console.log("✅ 默认FIT结构化插件已自动注册 (FileHeader, Session, Course等)");
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 为指定消息类型添加转换器,按优先级排序
|
|
50
|
+
*/
|
|
51
|
+
addConverterForMessageType(messageType, converter) {
|
|
52
|
+
if (!this.converterPlugins.has(messageType)) {
|
|
53
|
+
this.converterPlugins.set(messageType, []);
|
|
54
|
+
}
|
|
55
|
+
const converters = this.converterPlugins.get(messageType);
|
|
56
|
+
converters.push(converter);
|
|
57
|
+
// 按优先级排序(数字越小优先级越高)
|
|
58
|
+
converters.sort((a, b) => (a.priority || 100) - (b.priority || 100));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 添加结构化插件
|
|
62
|
+
*/
|
|
63
|
+
addStructurePlugin(plugin) {
|
|
64
|
+
this.structurePlugins.push(plugin);
|
|
65
|
+
// 按优先级排序
|
|
66
|
+
this.structurePlugins.sort((a, b) => (a.priority || 100) - (b.priority || 100));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 注册消息转换器插件
|
|
70
|
+
*/
|
|
71
|
+
async registerMessageConverter(plugin) {
|
|
72
|
+
// 检查插件是否已注册
|
|
73
|
+
if (this.registeredPlugins.has(plugin.name)) {
|
|
74
|
+
throw new Error(`消息转换器插件 ${plugin.name} 已存在`);
|
|
75
|
+
}
|
|
76
|
+
// 注册支持的消息类型
|
|
77
|
+
plugin.supportedMessageTypes.forEach((messageType) => {
|
|
78
|
+
this.addConverterForMessageType(messageType, plugin);
|
|
79
|
+
});
|
|
80
|
+
this.registeredPlugins.add(plugin.name);
|
|
81
|
+
// 显示优先级信息
|
|
82
|
+
const priorityInfo = plugin.priority !== undefined ? ` (优先级: ${plugin.priority})` : "";
|
|
83
|
+
console.log(`📦 已注册FIT消息转换器: ${plugin.name}${priorityInfo}, 支持消息类型: ${plugin.supportedMessageTypes.join(", ")}`);
|
|
84
|
+
// 显示消息类型的转换器优先级顺序
|
|
85
|
+
plugin.supportedMessageTypes.forEach((messageType) => {
|
|
86
|
+
const converters = this.converterPlugins.get(messageType) || [];
|
|
87
|
+
const converterNames = converters.map((c) => `${c.name}(${c.priority || 100})`);
|
|
88
|
+
console.log(` 📋 消息类型 "${messageType}" 转换器优先级: ${converterNames.join(" > ")}`);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 注册结构化插件
|
|
93
|
+
*/
|
|
94
|
+
async registerStructurePlugin(plugin) {
|
|
95
|
+
if (this.registeredPlugins.has(plugin.name)) {
|
|
96
|
+
throw new Error(`结构化插件 ${plugin.name} 已存在`);
|
|
97
|
+
}
|
|
98
|
+
this.addStructurePlugin(plugin);
|
|
99
|
+
this.registeredPlugins.add(plugin.name);
|
|
100
|
+
const priorityInfo = plugin.priority !== undefined ? ` (优先级: ${plugin.priority})` : "";
|
|
101
|
+
console.log(`🏗️ 已注册FIT结构化插件: ${plugin.name}${priorityInfo}`);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 注册中间件插件
|
|
105
|
+
*/
|
|
106
|
+
async registerMiddleware(plugin) {
|
|
107
|
+
const existingIndex = this.middlewarePlugins.findIndex((p) => p.name === plugin.name);
|
|
108
|
+
if (existingIndex !== -1) {
|
|
109
|
+
this.middlewarePlugins[existingIndex] = plugin;
|
|
110
|
+
console.log(`🔄 已更新FIT中间件: ${plugin.name}`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.middlewarePlugins.push(plugin);
|
|
114
|
+
// 按优先级排序
|
|
115
|
+
this.middlewarePlugins.sort((a, b) => (a.priority || 100) - (b.priority || 100));
|
|
116
|
+
console.log(`🔌 已注册FIT中间件: ${plugin.name}`);
|
|
117
|
+
}
|
|
118
|
+
// 显示中间件支持的钩子
|
|
119
|
+
const hooks = [];
|
|
120
|
+
if (plugin.onParse)
|
|
121
|
+
hooks.push("onParse");
|
|
122
|
+
if (plugin.onExtractMessages)
|
|
123
|
+
hooks.push("onExtractMessages");
|
|
124
|
+
if (plugin.onStructure)
|
|
125
|
+
hooks.push("onStructure");
|
|
126
|
+
if (plugin.onComplete)
|
|
127
|
+
hooks.push("onComplete");
|
|
128
|
+
if (plugin.onError)
|
|
129
|
+
hooks.push("onError");
|
|
130
|
+
if (hooks.length > 0) {
|
|
131
|
+
console.log(` 🎣 支持的钩子: ${hooks.join(", ")}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 获取消息类型转换器(返回优先级最高的)
|
|
136
|
+
*/
|
|
137
|
+
getConverter(messageType) {
|
|
138
|
+
const converters = this.converterPlugins.get(messageType);
|
|
139
|
+
return converters && converters.length > 0 ? converters[0] : undefined;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 获取指定消息类型的所有转换器(按优先级排序)
|
|
143
|
+
*/
|
|
144
|
+
getConverters(messageType) {
|
|
145
|
+
return this.converterPlugins.get(messageType) || [];
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 移除插件
|
|
149
|
+
*/
|
|
150
|
+
unregisterPlugin(pluginName) {
|
|
151
|
+
if (!this.registeredPlugins.has(pluginName)) {
|
|
152
|
+
console.warn(`FIT插件 ${pluginName} 不存在`);
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
// 从消息转换器中移除
|
|
156
|
+
for (const [messageType, converters] of this.converterPlugins) {
|
|
157
|
+
const index = converters.findIndex((plugin) => plugin.name === pluginName);
|
|
158
|
+
if (index !== -1) {
|
|
159
|
+
converters.splice(index, 1);
|
|
160
|
+
if (converters.length === 0) {
|
|
161
|
+
this.converterPlugins.delete(messageType);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// 从结构化插件中移除
|
|
166
|
+
const structureIndex = this.structurePlugins.findIndex((plugin) => plugin.name === pluginName);
|
|
167
|
+
if (structureIndex !== -1) {
|
|
168
|
+
this.structurePlugins.splice(structureIndex, 1);
|
|
169
|
+
}
|
|
170
|
+
// 从中间件中移除
|
|
171
|
+
const middlewareIndex = this.middlewarePlugins.findIndex((plugin) => plugin.name === pluginName);
|
|
172
|
+
if (middlewareIndex !== -1) {
|
|
173
|
+
this.middlewarePlugins.splice(middlewareIndex, 1);
|
|
174
|
+
}
|
|
175
|
+
this.registeredPlugins.delete(pluginName);
|
|
176
|
+
console.log(`🗑️ 已移除FIT插件: ${pluginName}`);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 初始化解码器
|
|
181
|
+
*/
|
|
182
|
+
async initialize() {
|
|
183
|
+
if (this.initialized)
|
|
184
|
+
return;
|
|
185
|
+
// 首先注册默认插件
|
|
186
|
+
await this.registerDefaultPlugins();
|
|
187
|
+
const context = this.createContext();
|
|
188
|
+
// 初始化所有插件
|
|
189
|
+
const allPlugins = [
|
|
190
|
+
...Array.from(this.converterPlugins.values()).flat(),
|
|
191
|
+
...this.structurePlugins,
|
|
192
|
+
...this.middlewarePlugins,
|
|
193
|
+
];
|
|
194
|
+
for (const plugin of allPlugins) {
|
|
195
|
+
try {
|
|
196
|
+
await plugin.initialize?.(context);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
console.error(`FIT插件 ${plugin.name} 初始化失败:`, error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
this.initialized = true;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* 销毁解码器
|
|
206
|
+
*/
|
|
207
|
+
async destroy() {
|
|
208
|
+
if (!this.initialized)
|
|
209
|
+
return;
|
|
210
|
+
const context = this.createContext();
|
|
211
|
+
// 销毁所有插件
|
|
212
|
+
const allPlugins = [
|
|
213
|
+
...Array.from(this.converterPlugins.values()).flat(),
|
|
214
|
+
...this.structurePlugins,
|
|
215
|
+
...this.middlewarePlugins,
|
|
216
|
+
];
|
|
217
|
+
for (const plugin of allPlugins) {
|
|
218
|
+
try {
|
|
219
|
+
await plugin.destroy?.(context);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
console.error(`FIT插件 ${plugin.name} 销毁失败:`, error);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
this.initialized = false;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 创建解码上下文
|
|
229
|
+
*/
|
|
230
|
+
createContext() {
|
|
231
|
+
return {
|
|
232
|
+
metadata: new Map(),
|
|
233
|
+
errors: [],
|
|
234
|
+
warnings: [],
|
|
235
|
+
stats: {
|
|
236
|
+
startTime: Date.now(),
|
|
237
|
+
processedTokens: 0,
|
|
238
|
+
convertedElements: 0,
|
|
239
|
+
},
|
|
240
|
+
performance: {
|
|
241
|
+
startTime: Date.now(),
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* 执行中间件钩子
|
|
247
|
+
*/
|
|
248
|
+
async executeMiddlewareHook(hookName, data, context) {
|
|
249
|
+
let result = data;
|
|
250
|
+
for (const middleware of this.middlewarePlugins) {
|
|
251
|
+
const hook = middleware[hookName];
|
|
252
|
+
if (hook) {
|
|
253
|
+
try {
|
|
254
|
+
result = (await hook.call(middleware, result, context)) || result;
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
context.errors.push(error);
|
|
258
|
+
await middleware.onError?.(error, context);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* 主解析方法 - 固定的流水线 + 灵活的中间件
|
|
266
|
+
*/
|
|
267
|
+
async parseByBuffer(buffer, options = {}) {
|
|
268
|
+
await this.initialize();
|
|
269
|
+
let context = this.createContext();
|
|
270
|
+
context.rawData = buffer;
|
|
271
|
+
context.userData = options.userData;
|
|
272
|
+
// 在上下文中传递解码器实例引用
|
|
273
|
+
context.metadata.set("decoder", this);
|
|
274
|
+
try {
|
|
275
|
+
// 固定的流水线阶段顺序执行
|
|
276
|
+
for (const processor of this.processors) {
|
|
277
|
+
try {
|
|
278
|
+
// 1. 执行核心处理器
|
|
279
|
+
context = await processor.process(context);
|
|
280
|
+
// 2. 根据阶段执行对应的中间件钩子
|
|
281
|
+
switch (processor.stage) {
|
|
282
|
+
case FITPipelineStage.PARSE:
|
|
283
|
+
if (context.rawData) {
|
|
284
|
+
context.rawData = await this.executeMiddlewareHook("onParse", context.rawData, context);
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
case FITPipelineStage.EXTRACT:
|
|
288
|
+
if (context.rawMessages) {
|
|
289
|
+
context.rawMessages = await this.executeMiddlewareHook("onExtractMessages", context.rawMessages, context);
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
case FITPipelineStage.STRUCTURE:
|
|
293
|
+
if (context.result) {
|
|
294
|
+
context.result = await this.executeMiddlewareHook("onStructure", context.result, context);
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
case FITPipelineStage.COMPLETE:
|
|
298
|
+
if (context.result) {
|
|
299
|
+
context.result = await this.executeMiddlewareHook("onComplete", context.result, context);
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
context.errors.push(error);
|
|
306
|
+
// 执行错误处理中间件
|
|
307
|
+
for (const middleware of this.middlewarePlugins) {
|
|
308
|
+
await middleware.onError?.(error, context);
|
|
309
|
+
}
|
|
310
|
+
console.error(`FIT流水线阶段 ${processor.stage} 处理失败:`, error);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return context.result || {};
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
console.error("FIT解析失败:", error);
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
export default {
|
|
322
|
+
FITDecoder,
|
|
323
|
+
};
|