65 lines
2.0 KiB
TypeScript
65 lines
2.0 KiB
TypeScript
/**
|
|
* QRC 解析
|
|
*/
|
|
|
|
// 定义函数签名类型
|
|
type QRCParserFn = (xmlStr: string) => string
|
|
|
|
// 实现 DOM 策略
|
|
const domParser: QRCParserFn = (xmlStr) => {
|
|
if (!xmlStr || !xmlStr.trim()) return ''
|
|
try {
|
|
const parser = new DOMParser()
|
|
const doc = parser.parseFromString(xmlStr, 'text/xml')
|
|
// 检查解析错误(如未转义的 & 等)
|
|
const parseError = doc.querySelector('parsererror')
|
|
if (parseError) {
|
|
// DOM 解析失败,回退到正则解析
|
|
return regexParser(xmlStr)
|
|
}
|
|
|
|
return doc.documentElement.getAttribute('LyricContent') || ''
|
|
} catch (e) {
|
|
console.error('[QRC Parser] Fatal Error:', e)
|
|
// 回退到正则解析
|
|
return regexParser(xmlStr)
|
|
}
|
|
}
|
|
|
|
const decodeXml = (str: string) => {
|
|
return str
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, "'")
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/&/g, '&')
|
|
}
|
|
|
|
// 实现正则策略
|
|
const regexParser: QRCParserFn = (xmlStr) => {
|
|
if (!xmlStr) return ''
|
|
|
|
// 尝试贪婪匹配 (处理 LyricContent="... "..." ..." 这种非标准 XML 引号未转义的情况)
|
|
// 匹配从 LyricContent=" 开始,直到标签结束前的最后一个引号
|
|
const greedyMatch = /LyricContent\s*=\s*"([\s\S]*)"\s*\/?>/.exec(xmlStr)
|
|
if (greedyMatch) {
|
|
const content = greedyMatch[1]
|
|
// 启发式检查:如果提取的内容中包含类似 ` Attribute="` 的结构,说明贪婪匹配吃掉了其他属性
|
|
// 这种情况下回退到非贪婪匹配
|
|
if (!/\s+\w+\s*=\s*"/.test(content)) {
|
|
return decodeXml(content)
|
|
}
|
|
}
|
|
|
|
// 标准非贪婪匹配 (兼容标准 XML 属性)
|
|
const match = /LyricContent\s*=\s*"([^"]*)"/.exec(xmlStr)
|
|
|
|
// 如果没有匹配到 XML 结构,假设输入本身就是内容 (保持原有逻辑的兼容性)
|
|
const result = match?.[1] || xmlStr
|
|
return decodeXml(result)
|
|
}
|
|
|
|
// 避免每次调用时的运行时检查
|
|
export const extractLyricContent: QRCParserFn =
|
|
typeof DOMParser !== 'undefined' ? domParser : regexParser
|