Skip to content

Commit 0b28a79

Browse files
committed
feat(parser): 增强 ABC 解析器以支持作词和作曲者的区分
- 更新 parseScore 方法,允许传递文件头元数据以支持 %%lyricist 指令。 - 在解析 C: 字段时,添加对作词和作曲者的前缀支持,确保正确区分并存储贡献者信息。 - 优化 parseScoreHeader 方法,增强对元数据的处理能力,提升解析的准确性和灵活性。 该变更提升了 ABC 解析器的功能,确保乐谱信息的准确解析和存储。
1 parent 2725898 commit 0b28a79

File tree

1 file changed

+82
-8
lines changed

1 file changed

+82
-8
lines changed

packages/simple-notation/src/data/parser/abc-parser.ts

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,10 @@ export class AbcParser extends BaseParser<SNAbcInput> {
106106
}
107107

108108
return root.addChildren(
109-
scores.map((scoreData): SNParserScore => this.parseScore(scoreData)),
109+
scores.map(
110+
(scoreData): SNParserScore =>
111+
this.parseScore(scoreData, parsedFileHeader || undefined),
112+
),
110113
);
111114
}
112115

@@ -188,9 +191,9 @@ export class AbcParser extends BaseParser<SNAbcInput> {
188191
return rootMeta;
189192
}
190193

191-
parseScore(scoreData: string): SNParserScore {
194+
parseScore(scoreData: string, rootMeta?: SNRootMeta): SNParserScore {
192195
const { head, body } = this.splitScoreHeadAndBody(scoreData);
193-
const { id, meta, props } = this.parseScoreHeader(head);
196+
const { id, meta, props } = this.parseScoreHeader(head, rootMeta);
194197
const sections = this.parseScoreBody(body);
195198

196199
return new SNParserScore({
@@ -242,8 +245,13 @@ export class AbcParser extends BaseParser<SNAbcInput> {
242245
/**
243246
* 解析 Score 头部
244247
* 通用布局信息存入 props,ABC 特有元数据存入 meta
248+
* @param header - Score 头部字符串
249+
* @param rootMeta - 文件头元数据(可选,用于读取 %%lyricist 等指令)
245250
*/
246-
private parseScoreHeader(header: string): {
251+
private parseScoreHeader(
252+
header: string,
253+
rootMeta?: SNRootMeta,
254+
): {
247255
id: string | null;
248256
meta: SNScoreMeta;
249257
props: SNScoreProps;
@@ -280,12 +288,36 @@ export class AbcParser extends BaseParser<SNAbcInput> {
280288
props.subtitle = value;
281289
}
282290
break;
283-
case 'C':
291+
case 'C': {
284292
if (!props.contributors) {
285293
props.contributors = [];
286294
}
287-
props.contributors.push({ name: value, role: 'composer' as const });
295+
// 支持在 C: 字段中使用前缀来区分作词和作曲
296+
// 格式:C: 作词:张三 或 C: 作曲:李四
297+
const lyricistMatch = value.match(/^[:]\s*(.+)$/);
298+
const composerMatch = value.match(/^[:]\s*(.+)$/);
299+
300+
if (lyricistMatch) {
301+
// 作词者
302+
props.contributors.push({
303+
name: lyricistMatch[1].trim(),
304+
role: 'lyricist' as const,
305+
});
306+
} else if (composerMatch) {
307+
// 作曲者
308+
props.contributors.push({
309+
name: composerMatch[1].trim(),
310+
role: 'composer' as const,
311+
});
312+
} else {
313+
// 默认作为作曲者(保持向后兼容)
314+
props.contributors.push({
315+
name: value,
316+
role: 'composer' as const,
317+
});
318+
}
288319
break;
320+
}
289321
case 'O':
290322
meta.origin = value;
291323
break;
@@ -379,6 +411,24 @@ export class AbcParser extends BaseParser<SNAbcInput> {
379411
}
380412
}
381413

414+
// 检查文件头是否有 %%lyricist 指令
415+
if (rootMeta?.directives?.lyricist) {
416+
if (!props.contributors) {
417+
props.contributors = [];
418+
}
419+
// 检查是否已经存在相同的作词者(避免重复)
420+
const existingLyricist = props.contributors.find(
421+
(c) =>
422+
c.role === 'lyricist' && c.name === rootMeta.directives!.lyricist,
423+
);
424+
if (!existingLyricist) {
425+
props.contributors.push({
426+
name: rootMeta.directives.lyricist,
427+
role: 'lyricist' as const,
428+
});
429+
}
430+
}
431+
382432
return { id, meta, props };
383433
}
384434

@@ -597,12 +647,36 @@ export class AbcParser extends BaseParser<SNAbcInput> {
597647
props.subtitle = value;
598648
}
599649
break;
600-
case 'C':
650+
case 'C': {
601651
if (!props.contributors) {
602652
props.contributors = [];
603653
}
604-
props.contributors.push({ name: value, role: 'composer' as const });
654+
// 支持在 C: 字段中使用前缀来区分作词和作曲
655+
// 格式:C: 作词:张三 或 C: 作曲:李四
656+
const lyricistMatch = value.match(/^[:]\s*(.+)$/);
657+
const composerMatch = value.match(/^[:]\s*(.+)$/);
658+
659+
if (lyricistMatch) {
660+
// 作词者
661+
props.contributors.push({
662+
name: lyricistMatch[1].trim(),
663+
role: 'lyricist' as const,
664+
});
665+
} else if (composerMatch) {
666+
// 作曲者
667+
props.contributors.push({
668+
name: composerMatch[1].trim(),
669+
role: 'composer' as const,
670+
});
671+
} else {
672+
// 默认作为作曲者(保持向后兼容)
673+
props.contributors.push({
674+
name: value,
675+
role: 'composer' as const,
676+
});
677+
}
605678
break;
679+
}
606680
case 'M': {
607681
const timeMatch = value.match(/^(\d+)\/(\d+)$/);
608682
if (timeMatch) {

0 commit comments

Comments
 (0)