Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 15x 15x 15x 15x 15x 15x 15x 15x 15x 20x 4x 4x 4x 20x 5x 2x 2x 3x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 20x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 22x 4x 4x 4x 4x 4x 1x 1x 4x 22x 22x 22x 22x 22x 22x 22x 2x 2x 2x 2x 2x 1x 1x 2x 22x 1x 1x 1x 1x 1x 1x 1x 1x 11x 11x 11x 11x 10x 10x 10x 1x 1x 1x 1x 1x 1x 1x | /**
* @file MusicXML 主解析器
* @description 解析 MusicXML 文件的入口
*/
import { Score } from '../models/Score';
import { parseXML, getRootElement, isScorePartwise, isScoreTimewise } from '../utils/XMLUtils';
import { parseScorePartwise } from './StructureParser';
/**
* 解析选项
*/
export interface ParseOptions {
/** 是否验证乐谱结构 */
validate?: boolean;
/** 是否严格模式(遇到错误抛出异常) */
strict?: boolean;
}
/**
* 解析结果
*/
export interface ParseResult {
/** 解析后的乐谱 */
score: Score;
/** 警告信息 */
warnings: string[];
/** 错误信息(非严格模式下) */
errors: string[];
}
/**
* MusicXML 解析器类
*/
export class MusicXMLParser {
private options: ParseOptions;
constructor(options: ParseOptions = {}) {
this.options = {
validate: true,
strict: false,
...options
};
}
/**
* 从 XML 字符串解析乐谱
* @param xmlString MusicXML 字符串
* @returns 解析结果
*/
parse(xmlString: string): ParseResult {
const warnings: string[] = [];
const errors: string[] = [];
try {
// 解析 XML
const doc = parseXML(xmlString);
const root = getRootElement(doc);
// 检查格式
if (isScorePartwise(doc)) {
const score = parseScorePartwise(root);
// 验证
if (this.options.validate) {
const validationErrors = score.validate();
if (validationErrors.length > 0) {
if (this.options.strict) {
throw new Error(`乐谱验证失败: ${validationErrors.join('; ')}`);
} else {
warnings.push(...validationErrors);
}
}
}
return { score, warnings, errors };
} else if (isScoreTimewise(doc)) {
// score-timewise 格式需要先转换
throw new Error('暂不支持 score-timewise 格式,请先转换为 score-partwise');
} else {
throw new Error(`不支持的根元素: ${root.tagName}`);
}
} catch (error) {
if (this.options.strict) {
throw error;
}
const errorMessage = error instanceof Error ? error.message : String(error);
errors.push(errorMessage);
// 返回空乐谱
return {
score: new Score({ partList: [], parts: [] }),
warnings,
errors
};
}
}
/**
* 从 URL 加载并解析乐谱
* @param url MusicXML 文件 URL
* @returns 解析结果
*/
async parseFromURL(url: string): Promise<ParseResult> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`加载失败: ${response.status} ${response.statusText}`);
}
const xmlString = await response.text();
return this.parse(xmlString);
}
/**
* 从 File 对象解析乐谱
* @param file File 对象
* @returns 解析结果
*/
async parseFromFile(file: File): Promise<ParseResult> {
const xmlString = await file.text();
return this.parse(xmlString);
}
/**
* 验证 XML 字符串是否为有效的 MusicXML
* @param xmlString XML 字符串
* @returns 是否有效
*/
static isValidMusicXML(xmlString: string): boolean {
try {
const doc = parseXML(xmlString);
const root = getRootElement(doc);
return root.tagName === 'score-partwise' || root.tagName === 'score-timewise';
} catch {
return false;
}
}
/**
* 获取 MusicXML 的版本
* @param xmlString XML 字符串
* @returns 版本号或 undefined
*/
static getVersion(xmlString: string): string | undefined {
try {
const doc = parseXML(xmlString);
const root = getRootElement(doc);
return root.getAttribute('version') || undefined;
} catch {
return undefined;
}
}
}
/**
* 便捷函数:解析 MusicXML 字符串
* @param xmlString MusicXML 字符串
* @param options 解析选项
* @returns 乐谱对象
*/
export function parseMusicXML(xmlString: string, options?: ParseOptions): Score {
const parser = new MusicXMLParser(options);
const result = parser.parse(xmlString);
if (result.errors.length > 0 && options?.strict) {
throw new Error(result.errors.join('; '));
}
return result.score;
}
/**
* 便捷函数:从 URL 加载并解析
* @param url MusicXML 文件 URL
* @param options 解析选项
* @returns 乐谱对象
*/
export async function parseMusicXMLFromURL(url: string, options?: ParseOptions): Promise<Score> {
const parser = new MusicXMLParser(options);
const result = await parser.parseFromURL(url);
if (result.errors.length > 0 && options?.strict) {
throw new Error(result.errors.join('; '));
}
return result.score;
}
|