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/GPX/processor.js
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import { Parser } from "htmlparser2";
|
|
2
|
-
/**
|
|
3
|
-
* 流水线阶段
|
|
4
|
-
*/
|
|
5
|
-
export var PipelineStage;
|
|
6
|
-
(function (PipelineStage) {
|
|
7
|
-
PipelineStage["TOKENIZE"] = "tokenize";
|
|
8
|
-
PipelineStage["AST_GENERATE"] = "ast_generate";
|
|
9
|
-
PipelineStage["CONVERT"] = "convert";
|
|
10
|
-
PipelineStage["COMPLETE"] = "complete";
|
|
11
|
-
})(PipelineStage || (PipelineStage = {}));
|
|
12
|
-
/**
|
|
13
|
-
* Tokenize处理器, 将xml转为 token
|
|
14
|
-
*/
|
|
15
|
-
export class TokenizeProcessor {
|
|
16
|
-
constructor() {
|
|
17
|
-
this.stage = PipelineStage.TOKENIZE;
|
|
18
|
-
}
|
|
19
|
-
async process(context) {
|
|
20
|
-
const startTime = Date.now();
|
|
21
|
-
if (!context.xmlContent) {
|
|
22
|
-
throw new Error("XML内容不能为空");
|
|
23
|
-
}
|
|
24
|
-
const tokens = [];
|
|
25
|
-
const parser = new Parser({
|
|
26
|
-
onopentag(name, attribs) {
|
|
27
|
-
tokens.push({
|
|
28
|
-
type: "open",
|
|
29
|
-
tag: name,
|
|
30
|
-
attributes: attribs,
|
|
31
|
-
});
|
|
32
|
-
},
|
|
33
|
-
ontext(text) {
|
|
34
|
-
const filteredText = text.replace(/\n/g, "").trim();
|
|
35
|
-
if (filteredText) {
|
|
36
|
-
tokens.push({ type: "text", tag: "text", value: filteredText });
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
onclosetag(name) {
|
|
40
|
-
tokens.push({ type: "close", tag: name });
|
|
41
|
-
},
|
|
42
|
-
}, { xmlMode: true });
|
|
43
|
-
parser.write(context.xmlContent);
|
|
44
|
-
parser.end();
|
|
45
|
-
context.tokens = tokens;
|
|
46
|
-
context.performance.tokenizeTime = Date.now() - startTime;
|
|
47
|
-
return context;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* AST生成处理器
|
|
52
|
-
*/
|
|
53
|
-
export class AstGenerateProcessor {
|
|
54
|
-
constructor() {
|
|
55
|
-
this.stage = PipelineStage.AST_GENERATE;
|
|
56
|
-
}
|
|
57
|
-
async process(context) {
|
|
58
|
-
const startTime = Date.now();
|
|
59
|
-
if (!context.tokens) {
|
|
60
|
-
throw new Error("Tokens不能为空");
|
|
61
|
-
}
|
|
62
|
-
const stack = [];
|
|
63
|
-
let root = undefined;
|
|
64
|
-
let current = null;
|
|
65
|
-
for (const token of context.tokens) {
|
|
66
|
-
if (token.type === "open") {
|
|
67
|
-
const newElement = {
|
|
68
|
-
tag: token.tag,
|
|
69
|
-
attributes: token.attributes || {},
|
|
70
|
-
children: [],
|
|
71
|
-
};
|
|
72
|
-
if (!root) {
|
|
73
|
-
root = newElement;
|
|
74
|
-
}
|
|
75
|
-
else if (current) {
|
|
76
|
-
current.children?.push(newElement);
|
|
77
|
-
}
|
|
78
|
-
stack.push(newElement);
|
|
79
|
-
current = newElement;
|
|
80
|
-
}
|
|
81
|
-
else if (token.type === "text" && current) {
|
|
82
|
-
current.value = token.value;
|
|
83
|
-
}
|
|
84
|
-
else if (token.type === "close") {
|
|
85
|
-
stack.pop();
|
|
86
|
-
current = stack[stack.length - 1] || null;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
context.ast = root;
|
|
90
|
-
context.performance.astTime = Date.now() - startTime;
|
|
91
|
-
return context;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* 转换处理器
|
|
96
|
-
*/
|
|
97
|
-
export class ConvertProcessor {
|
|
98
|
-
constructor(getConverter) {
|
|
99
|
-
this.getConverter = getConverter;
|
|
100
|
-
this.stage = PipelineStage.CONVERT;
|
|
101
|
-
}
|
|
102
|
-
async process(context) {
|
|
103
|
-
const startTime = Date.now();
|
|
104
|
-
if (!context.ast) {
|
|
105
|
-
throw new Error("AST不能为空");
|
|
106
|
-
}
|
|
107
|
-
const gpx = {};
|
|
108
|
-
// 处理根节点属性
|
|
109
|
-
if (context.ast.attributes) {
|
|
110
|
-
Object.entries(context.ast.attributes).forEach(([key, value]) => {
|
|
111
|
-
if (key.startsWith("xmlns:")) {
|
|
112
|
-
gpx[key] = value.replace(/'/g, "'");
|
|
113
|
-
}
|
|
114
|
-
else if (key === "xsi:schemaLocation") {
|
|
115
|
-
gpx[key] = value.replace(/'/g, "'");
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
gpx[key] = value;
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
// 处理子节点
|
|
123
|
-
context.ast.children?.forEach((child) => {
|
|
124
|
-
const converter = this.getConverter(child.tag);
|
|
125
|
-
if (!converter) {
|
|
126
|
-
console.error(`标签 ${child.tag} 未找到对应的 converter`);
|
|
127
|
-
}
|
|
128
|
-
try {
|
|
129
|
-
const result = converter.convert(child, context);
|
|
130
|
-
if (result) {
|
|
131
|
-
this.assignResult(gpx, child.tag, result);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
catch (error) {
|
|
135
|
-
context.errors.push(error);
|
|
136
|
-
console.error(`转换器 ${converter.name} 处理标签 ${child.tag} 时出错:`, error);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
context.result = gpx;
|
|
140
|
-
context.performance.convertTime = Date.now() - startTime;
|
|
141
|
-
return context;
|
|
142
|
-
}
|
|
143
|
-
assignResult(gpx, tag, result) {
|
|
144
|
-
switch (tag) {
|
|
145
|
-
case "metadata":
|
|
146
|
-
gpx.metadata = result;
|
|
147
|
-
break;
|
|
148
|
-
case "wpt":
|
|
149
|
-
if (!gpx.wpt)
|
|
150
|
-
gpx.wpt = [];
|
|
151
|
-
gpx.wpt.push(result);
|
|
152
|
-
break;
|
|
153
|
-
case "rte":
|
|
154
|
-
if (!gpx.rte)
|
|
155
|
-
gpx.rte = [];
|
|
156
|
-
gpx.rte.push(result);
|
|
157
|
-
break;
|
|
158
|
-
case "trk":
|
|
159
|
-
if (!gpx.trk)
|
|
160
|
-
gpx.trk = [];
|
|
161
|
-
gpx.trk.push(result);
|
|
162
|
-
break;
|
|
163
|
-
case "extensions":
|
|
164
|
-
gpx.extensions = result;
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* 完成处理器
|
|
171
|
-
*/
|
|
172
|
-
export class CompleteProcessor {
|
|
173
|
-
constructor() {
|
|
174
|
-
this.stage = PipelineStage.COMPLETE;
|
|
175
|
-
}
|
|
176
|
-
async process(context) {
|
|
177
|
-
context.performance.endTime = Date.now();
|
|
178
|
-
// 记录性能指标
|
|
179
|
-
const totalTime = context.performance.endTime - context.performance.startTime;
|
|
180
|
-
context.metadata.set("performance", {
|
|
181
|
-
totalTime,
|
|
182
|
-
tokenizeTime: context.performance.tokenizeTime,
|
|
183
|
-
astTime: context.performance.astTime,
|
|
184
|
-
convertTime: context.performance.convertTime,
|
|
185
|
-
});
|
|
186
|
-
return context;
|
|
187
|
-
}
|
|
188
|
-
}
|
package/dist/GPX/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/GPX/util.js
DELETED
package/dist/TCX/base.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { BaseConverter, BaseMiddleware, } from "../core/base.js";
|
|
2
|
-
// ============ TCX 特定基础类 ============
|
|
3
|
-
/**
|
|
4
|
-
* TCX 基础转换器抽象类(继承通用基础转换器)
|
|
5
|
-
*/
|
|
6
|
-
export class BaseTCXConverter extends BaseConverter {
|
|
7
|
-
// TCX 特定的工具方法可以在这里添加
|
|
8
|
-
/**
|
|
9
|
-
* 解析 TCX 心率值
|
|
10
|
-
*/
|
|
11
|
-
parseHeartRate(value) {
|
|
12
|
-
const hr = this.parseFloat(value);
|
|
13
|
-
return hr > 0 && hr < 300 ? hr : undefined; // 合理的心率范围
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* 解析 TCX 时间格式(ISO 8601)
|
|
17
|
-
*/
|
|
18
|
-
parseTCXTime(value) {
|
|
19
|
-
if (!value)
|
|
20
|
-
return undefined;
|
|
21
|
-
// TCX 使用 ISO 8601 格式
|
|
22
|
-
const date = new Date(value);
|
|
23
|
-
return isNaN(date.getTime()) ? undefined : date;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* 解析 TCX 距离值(米)
|
|
27
|
-
*/
|
|
28
|
-
parseDistance(value) {
|
|
29
|
-
const distance = this.parseFloat(value);
|
|
30
|
-
return distance >= 0 ? distance : undefined;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* 解析 TCX 速度值(m/s)
|
|
34
|
-
*/
|
|
35
|
-
parseSpeed(value) {
|
|
36
|
-
const speed = this.parseFloat(value);
|
|
37
|
-
return speed >= 0 ? speed : undefined;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* 解析 TCX 海拔值(米)
|
|
41
|
-
*/
|
|
42
|
-
parseAltitude(value) {
|
|
43
|
-
return this.parseFloat(value);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* 解析 TCX 坐标值(纬度/经度)
|
|
47
|
-
*/
|
|
48
|
-
parseCoordinate(value) {
|
|
49
|
-
const coord = this.parseFloat(value);
|
|
50
|
-
return coord !== 0 ? coord : undefined;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* TCX 基础中间件抽象类(继承通用基础中间件)
|
|
55
|
-
*/
|
|
56
|
-
export class BaseTCXMiddleware extends BaseMiddleware {
|
|
57
|
-
// TCX 特定的中间件逻辑可以在这里添加
|
|
58
|
-
async onError(error, context) {
|
|
59
|
-
console.error(`TCX中间件 ${this.name} 处理错误:`, error);
|
|
60
|
-
context.warnings.push(`中间件 ${this.name} 错误: ${error.message}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
package/dist/TCX/converters.js
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import { BaseTCXConverter } from "./base.js";
|
|
2
|
-
// ============ 基础元素转换器 ============
|
|
3
|
-
/**
|
|
4
|
-
* 位置转换器
|
|
5
|
-
*/
|
|
6
|
-
export class PositionConverter extends BaseTCXConverter {
|
|
7
|
-
constructor() {
|
|
8
|
-
super(...arguments);
|
|
9
|
-
this.name = "PositionConverter";
|
|
10
|
-
this.supportedTags = ["Position"];
|
|
11
|
-
}
|
|
12
|
-
convert(ast, context) {
|
|
13
|
-
const position = {};
|
|
14
|
-
this.processChildren(ast, position, {
|
|
15
|
-
LatitudeDegrees: (child, target) => (target.LatitudeDegrees = this.parseString(child.value)),
|
|
16
|
-
LongitudeDegrees: (child, target) => (target.LongitudeDegrees = this.parseString(child.value)),
|
|
17
|
-
});
|
|
18
|
-
return position;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* 轨迹点转换器
|
|
23
|
-
*/
|
|
24
|
-
export class TrackpointConverter extends BaseTCXConverter {
|
|
25
|
-
constructor() {
|
|
26
|
-
super(...arguments);
|
|
27
|
-
this.name = "TrackpointConverter";
|
|
28
|
-
this.supportedTags = ["Trackpoint"];
|
|
29
|
-
}
|
|
30
|
-
convert(ast, context) {
|
|
31
|
-
const trackpoint = {};
|
|
32
|
-
this.processChildren(ast, trackpoint, {
|
|
33
|
-
Time: (child, target) => (target.Time = this.parseString(child.value)),
|
|
34
|
-
Position: (child, target) => {
|
|
35
|
-
const decoder = context.metadata.get("decoder");
|
|
36
|
-
target.Position = decoder
|
|
37
|
-
?.getConverter("Position")
|
|
38
|
-
?.convert(child, context);
|
|
39
|
-
},
|
|
40
|
-
AltitudeMeters: (child, target) => (target.AltitudeMeters = this.parseFloat(child.value)),
|
|
41
|
-
DistanceMeters: (child, target) => (target.DistanceMeters = this.parseFloat(child.value)),
|
|
42
|
-
HeartRateBpm: (child, target) => {
|
|
43
|
-
// 处理两种格式:<HeartRateBpm>123</HeartRateBpm> 或 <HeartRateBpm><Value>123</Value></HeartRateBpm>
|
|
44
|
-
if (child.value) {
|
|
45
|
-
target.HeartRateBpm = this.parseFloat(child.value);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
const valueChild = this.getChildByTag(child, "Value");
|
|
49
|
-
if (valueChild?.value) {
|
|
50
|
-
target.HeartRateBpm = this.parseFloat(valueChild.value);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
Cadence: (child, target) => {
|
|
55
|
-
const cadence = { Low: 0, High: 0 };
|
|
56
|
-
this.processChildren(child, cadence, {
|
|
57
|
-
Low: (subChild, subTarget) => (subTarget.Low = this.parseFloat(subChild.value)),
|
|
58
|
-
High: (subChild, subTarget) => (subTarget.High = this.parseFloat(subChild.value)),
|
|
59
|
-
});
|
|
60
|
-
target.Cadence = cadence;
|
|
61
|
-
},
|
|
62
|
-
SensorState: (child, target) => (target.SensorState = child.value),
|
|
63
|
-
Extensions: (child, target) => (target.Extensions = this.convertExtensions(child)),
|
|
64
|
-
});
|
|
65
|
-
return trackpoint;
|
|
66
|
-
}
|
|
67
|
-
convertExtensions(extensionsAST) {
|
|
68
|
-
const extensions = {};
|
|
69
|
-
extensionsAST.children?.forEach((child) => {
|
|
70
|
-
extensions[child.tag] = this.convertExtensionContent(child);
|
|
71
|
-
});
|
|
72
|
-
return extensions;
|
|
73
|
-
}
|
|
74
|
-
convertExtensionContent(extension) {
|
|
75
|
-
if (extension.children?.length) {
|
|
76
|
-
const result = {};
|
|
77
|
-
extension.children.forEach((child) => {
|
|
78
|
-
result[child.tag] = this.convertExtensionContent(child);
|
|
79
|
-
});
|
|
80
|
-
return result;
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
return extension.value;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* 轨迹转换器
|
|
89
|
-
*/
|
|
90
|
-
export class TrackConverter extends BaseTCXConverter {
|
|
91
|
-
constructor() {
|
|
92
|
-
super(...arguments);
|
|
93
|
-
this.name = "TrackConverter";
|
|
94
|
-
this.supportedTags = ["Track"];
|
|
95
|
-
}
|
|
96
|
-
convert(ast, context) {
|
|
97
|
-
const track = {
|
|
98
|
-
Trackpoint: [],
|
|
99
|
-
};
|
|
100
|
-
this.processChildren(ast, track, {
|
|
101
|
-
Trackpoint: (child, target) => {
|
|
102
|
-
const decoder = context.metadata.get("decoder");
|
|
103
|
-
const trackpoint = decoder
|
|
104
|
-
?.getConverter("Trackpoint")
|
|
105
|
-
?.convert(child, context);
|
|
106
|
-
if (trackpoint && target.Trackpoint) {
|
|
107
|
-
target.Trackpoint.push(trackpoint);
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
return track;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* 活动圈数转换器
|
|
116
|
-
*/
|
|
117
|
-
export class ActivityLapConverter extends BaseTCXConverter {
|
|
118
|
-
constructor() {
|
|
119
|
-
super(...arguments);
|
|
120
|
-
this.name = "ActivityLapConverter";
|
|
121
|
-
this.supportedTags = ["Lap"];
|
|
122
|
-
}
|
|
123
|
-
convert(ast, context) {
|
|
124
|
-
const lap = {};
|
|
125
|
-
// 提取属性
|
|
126
|
-
this.extractAttributes(ast, lap, {
|
|
127
|
-
StartTime: "Id",
|
|
128
|
-
});
|
|
129
|
-
this.processChildren(ast, lap, {
|
|
130
|
-
TotalTimeSeconds: (child, target) => (target.TotalTimeSeconds = this.parseFloat(child.value)),
|
|
131
|
-
DistanceMeters: (child, target) => (target.DistanceMeters = this.parseFloat(child.value)),
|
|
132
|
-
MaximumSpeed: (child, target) => (target.MaximumSpeed = this.parseFloat(child.value)),
|
|
133
|
-
Calories: (child, target) => (target.Calories = this.parseFloat(child.value)),
|
|
134
|
-
AverageHeartRateBpm: (child, target) => (target.AverageHeartRateBpm = this.parseFloat(child.value)),
|
|
135
|
-
MaximumHeartRateBpm: (child, target) => (target.MaximumHeartRateBpm = this.parseFloat(child.value)),
|
|
136
|
-
Intensity: (child, target) => (target.Intensity = child.value),
|
|
137
|
-
TriggerMethod: (child, target) => (target.TriggerMethod = child.value),
|
|
138
|
-
Track: (child, target) => {
|
|
139
|
-
if (!target.Track)
|
|
140
|
-
target.Track = [];
|
|
141
|
-
const decoder = context.metadata.get("decoder");
|
|
142
|
-
const track = decoder?.getConverter("Track")?.convert(child, context);
|
|
143
|
-
if (track)
|
|
144
|
-
target.Track.push(track);
|
|
145
|
-
},
|
|
146
|
-
Notes: (child, target) => (target.Notes = this.parseString(child.value)),
|
|
147
|
-
Extensions: (child, target) => (target.Extensions = this.convertExtensions(child)),
|
|
148
|
-
});
|
|
149
|
-
return lap;
|
|
150
|
-
}
|
|
151
|
-
convertExtensions(extensionsAST) {
|
|
152
|
-
const extensions = {};
|
|
153
|
-
extensionsAST.children?.forEach((child) => {
|
|
154
|
-
extensions[child.tag] = this.convertExtensionContent(child);
|
|
155
|
-
});
|
|
156
|
-
return extensions;
|
|
157
|
-
}
|
|
158
|
-
convertExtensionContent(extension) {
|
|
159
|
-
if (extension.children?.length) {
|
|
160
|
-
const result = {};
|
|
161
|
-
extension.children.forEach((child) => {
|
|
162
|
-
result[child.tag] = this.convertExtensionContent(child);
|
|
163
|
-
});
|
|
164
|
-
return result;
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
return extension.value;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* 活动转换器
|
|
173
|
-
*/
|
|
174
|
-
export class ActivityConverter extends BaseTCXConverter {
|
|
175
|
-
constructor() {
|
|
176
|
-
super(...arguments);
|
|
177
|
-
this.name = "ActivityConverter";
|
|
178
|
-
this.supportedTags = ["Activity"];
|
|
179
|
-
}
|
|
180
|
-
convert(ast, context) {
|
|
181
|
-
const activity = {
|
|
182
|
-
Id: "",
|
|
183
|
-
Lap: [],
|
|
184
|
-
};
|
|
185
|
-
// 提取属性
|
|
186
|
-
this.extractAttributes(ast, activity, {
|
|
187
|
-
Sport: "Sport",
|
|
188
|
-
});
|
|
189
|
-
this.processChildren(ast, activity, {
|
|
190
|
-
Id: (child, target) => (target.Id = this.parseString(child.value)),
|
|
191
|
-
Lap: (child, target) => {
|
|
192
|
-
const decoder = context.metadata.get("decoder");
|
|
193
|
-
const lap = decoder?.getConverter("Lap")?.convert(child, context);
|
|
194
|
-
if (lap)
|
|
195
|
-
target.Lap.push(lap);
|
|
196
|
-
},
|
|
197
|
-
Notes: (child, target) => (target.Notes = this.parseString(child.value)),
|
|
198
|
-
Training: (child, target) => (target.Training = this.convertTraining(child)),
|
|
199
|
-
Creator: (child, target) => (target.Creator = this.convertCreator(child)),
|
|
200
|
-
Extensions: (child, target) => (target.Extensions = this.convertExtensions(child)),
|
|
201
|
-
});
|
|
202
|
-
return activity;
|
|
203
|
-
}
|
|
204
|
-
convertTraining(ast) {
|
|
205
|
-
const training = {};
|
|
206
|
-
// 处理属性
|
|
207
|
-
if (ast.attributes) {
|
|
208
|
-
Object.entries(ast.attributes).forEach(([key, value]) => {
|
|
209
|
-
training[key] = value;
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
// 处理子元素
|
|
213
|
-
ast.children?.forEach((child) => {
|
|
214
|
-
training[child.tag] = this.convertExtensionContent(child);
|
|
215
|
-
});
|
|
216
|
-
return training;
|
|
217
|
-
}
|
|
218
|
-
convertCreator(ast) {
|
|
219
|
-
const creator = {};
|
|
220
|
-
ast.children?.forEach((child) => {
|
|
221
|
-
creator[child.tag] = this.convertExtensionContent(child);
|
|
222
|
-
});
|
|
223
|
-
return creator;
|
|
224
|
-
}
|
|
225
|
-
convertExtensions(extensionsAST) {
|
|
226
|
-
const extensions = {};
|
|
227
|
-
extensionsAST.children?.forEach((child) => {
|
|
228
|
-
extensions[child.tag] = this.convertExtensionContent(child);
|
|
229
|
-
});
|
|
230
|
-
return extensions;
|
|
231
|
-
}
|
|
232
|
-
convertExtensionContent(extension) {
|
|
233
|
-
if (extension.children?.length) {
|
|
234
|
-
const result = {};
|
|
235
|
-
extension.children.forEach((child) => {
|
|
236
|
-
result[child.tag] = this.convertExtensionContent(child);
|
|
237
|
-
});
|
|
238
|
-
return result;
|
|
239
|
-
}
|
|
240
|
-
else {
|
|
241
|
-
return extension.value;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
/**
|
|
246
|
-
* 活动列表转换器
|
|
247
|
-
*/
|
|
248
|
-
export class ActivityListConverter extends BaseTCXConverter {
|
|
249
|
-
constructor() {
|
|
250
|
-
super(...arguments);
|
|
251
|
-
this.name = "ActivityListConverter";
|
|
252
|
-
this.supportedTags = ["Activities"];
|
|
253
|
-
}
|
|
254
|
-
convert(ast, context) {
|
|
255
|
-
const activities = {
|
|
256
|
-
Activity: [],
|
|
257
|
-
MultiSportSession: [],
|
|
258
|
-
};
|
|
259
|
-
this.processChildren(ast, activities, {
|
|
260
|
-
Activity: (child, target) => {
|
|
261
|
-
const decoder = context.metadata.get("decoder");
|
|
262
|
-
const activity = decoder
|
|
263
|
-
?.getConverter("Activity")
|
|
264
|
-
?.convert(child, context);
|
|
265
|
-
if (activity && target.Activity) {
|
|
266
|
-
target.Activity.push(activity);
|
|
267
|
-
}
|
|
268
|
-
},
|
|
269
|
-
MultiSportSession: (child, target) => {
|
|
270
|
-
const decoder = context.metadata.get("decoder");
|
|
271
|
-
const session = decoder
|
|
272
|
-
?.getConverter("MultiSportSession")
|
|
273
|
-
?.convert(child, context);
|
|
274
|
-
if (session && target.MultiSportSession) {
|
|
275
|
-
target.MultiSportSession.push(session);
|
|
276
|
-
}
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
return activities;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
/**
|
|
283
|
-
* 多运动会话转换器
|
|
284
|
-
*/
|
|
285
|
-
export class MultiSportSessionConverter extends BaseTCXConverter {
|
|
286
|
-
constructor() {
|
|
287
|
-
super(...arguments);
|
|
288
|
-
this.name = "MultiSportSessionConverter";
|
|
289
|
-
this.supportedTags = ["MultiSportSession"];
|
|
290
|
-
}
|
|
291
|
-
convert(ast, context) {
|
|
292
|
-
const session = {};
|
|
293
|
-
this.processChildren(ast, session, {
|
|
294
|
-
Id: (child, target) => (target.Id = this.parseString(child.value)),
|
|
295
|
-
FirstSport: (child, target) => {
|
|
296
|
-
const decoder = context.metadata.get("decoder");
|
|
297
|
-
target.FirstSport = decoder
|
|
298
|
-
?.getConverter("FirstSport")
|
|
299
|
-
?.convert(child, context);
|
|
300
|
-
},
|
|
301
|
-
NextSport: (child, target) => {
|
|
302
|
-
if (!target.NextSport)
|
|
303
|
-
target.NextSport = [];
|
|
304
|
-
const decoder = context.metadata.get("decoder");
|
|
305
|
-
const nextSport = decoder
|
|
306
|
-
?.getConverter("NextSport")
|
|
307
|
-
?.convert(child, context);
|
|
308
|
-
if (nextSport)
|
|
309
|
-
target.NextSport.push(nextSport);
|
|
310
|
-
},
|
|
311
|
-
Notes: (child, target) => (target.Notes = this.parseString(child.value)),
|
|
312
|
-
});
|
|
313
|
-
return session;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
// ============ 辅助转换器 ============
|
|
317
|
-
/**
|
|
318
|
-
* 抽象来源转换器
|
|
319
|
-
*/
|
|
320
|
-
export class AbstractSourceConverter extends BaseTCXConverter {
|
|
321
|
-
constructor() {
|
|
322
|
-
super(...arguments);
|
|
323
|
-
this.name = "AbstractSourceConverter";
|
|
324
|
-
this.supportedTags = ["Author", "Creator"];
|
|
325
|
-
}
|
|
326
|
-
convert(ast, context) {
|
|
327
|
-
const source = {};
|
|
328
|
-
this.processChildren(ast, source, {
|
|
329
|
-
Name: (child, target) => (target.Name = this.parseString(child.value)),
|
|
330
|
-
});
|
|
331
|
-
return source;
|
|
332
|
-
}
|
|
333
|
-
}
|