You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
7.1 KiB
143 lines
7.1 KiB
"use strict"; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
exports.processMusicalNotation = processMusicalNotation; |
|
/** |
|
* Processes musical notation in HTML content |
|
* Wraps musical notation in appropriate HTML for rendering |
|
*/ |
|
function processMusicalNotation(html) { |
|
// First, clean up any corrupted abc-notation divs with very long data-abc attributes |
|
// These were created by a buggy regex that matched the entire HTML document |
|
html = html.replace(/<div[^>]*class="[^"]*abc-notation[^"]*"[^>]*data-abc="([^"]{500,})"[^>]*>([\s\S]*?)<\/div>/gi, (match, dataAbc, content) => { |
|
// This is corrupted - extract just the ABC notation from the beginning |
|
let decoded = dataAbc |
|
.replace(/</g, '<') |
|
.replace(/>/g, '>') |
|
.replace(/&/g, '&') |
|
.replace(/"/g, '"') |
|
.replace(/'/g, "'"); |
|
// Find the actual ABC notation (starts with X:) |
|
const abcMatch = decoded.match(/^(X:\s*\d+[\s\S]{0,2000}?)(?:\n[^XTCMALK]|<|<\/|sect|div|pre|code)/); |
|
if (abcMatch) { |
|
const cleanAbc = abcMatch[1].trim(); |
|
return `<div class="abc-notation" data-abc="${escapeForAttr(cleanAbc)}">${content}</div>`; |
|
} |
|
// If we can't extract clean ABC, remove the div entirely |
|
return content; |
|
}); |
|
// Clean up code blocks that contain corrupted abc-notation divs inside them |
|
// The corrupted structure is: <code><div class="abc-notation" data-abc="...entire HTML...">...</div></code> |
|
html = html.replace(/<pre[^>]*><code[^>]*class="[^"]*language-abc[^"]*"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (match, codeContent) => { |
|
// Check if codeContent contains an abc-notation div with a very long data-abc attribute (>500 chars = corrupted) |
|
const longDataAbcMatch = codeContent.match(/<div[^>]*class="[^"]*abc-notation[^"]*"[^>]*data-abc="([^"]{500,})"/i); |
|
if (longDataAbcMatch) { |
|
// Extract just the ABC notation from the beginning of the corrupted data-abc value |
|
let decoded = longDataAbcMatch[1] |
|
.replace(/</g, '<') |
|
.replace(/>/g, '>') |
|
.replace(/&/g, '&') |
|
.replace(/"/g, '"') |
|
.replace(/'/g, "'"); |
|
// The ABC notation ends where the HTML document starts (</code> or </pre>) |
|
// Extract everything from X: up to (but not including) </code> or </pre> |
|
const abcMatch = decoded.match(/^(X:\s*\d+[\s\S]*?)(?=<\/code>|<\/pre>)/); |
|
if (abcMatch) { |
|
let cleanAbc = abcMatch[1].trim(); |
|
// Remove any trailing HTML entities |
|
cleanAbc = cleanAbc.replace(/<.*$/, '').trim(); |
|
// Validate it's reasonable ABC notation |
|
if (cleanAbc.length > 10 && cleanAbc.length < 2000 && cleanAbc.match(/^X:\s*\d+/m)) { |
|
// Return clean code block - the processing step will wrap it in abc-notation div |
|
return `<pre class="highlightjs hljs"><code class="language-abc hljs" data-lang="abc">${cleanAbc}</code></pre>`; |
|
} |
|
} |
|
// If extraction fails, just remove the corrupted div and return empty code block |
|
// This prevents the corrupted data from being rendered |
|
return `<pre class="highlightjs hljs"><code class="language-abc hljs" data-lang="abc"></code></pre>`; |
|
} |
|
return match; |
|
}); |
|
// Process ABC notation blocks - ONLY code blocks explicitly marked with language-abc class |
|
// These come from: [source,abc], [source, abc], [abc] in AsciiDoc, or ```abc in Markdown |
|
// We do NOT auto-detect ABC notation - it must be explicitly marked |
|
html = html.replace(/<pre[^>]*><code[^>]*class="[^"]*language-abc[^"]*"[^>]*>([\s\S]*?)<\/code><\/pre>/gi, (match, codeContent) => { |
|
// Skip if already processed or corrupted |
|
if (codeContent.includes('abc-notation') || |
|
codeContent.includes('class="abc-notation"') || |
|
codeContent.includes('<div') || |
|
codeContent.includes('</div>') || |
|
codeContent.length > 5000) { |
|
return match; |
|
} |
|
// Extract ABC content from the code block |
|
let abcContent = codeContent |
|
.replace(/</g, '<') |
|
.replace(/>/g, '>') |
|
.replace(/&/g, '&') |
|
.replace(/"/g, '"') |
|
.replace(/'/g, "'") |
|
.replace(/'/g, "'") |
|
.replace(///g, '/'); |
|
// Remove any HTML tags |
|
abcContent = abcContent.replace(/<[^>]+>/g, '').trim(); |
|
// Only process if it looks like valid ABC notation (starts with X:) |
|
// Since this is explicitly marked as ABC, we trust it's ABC notation |
|
if (abcContent.match(/^X:\s*\d+/m) && |
|
abcContent.length < 3000 && |
|
!abcContent.includes('</') && |
|
!abcContent.includes('<div') && |
|
!abcContent.includes('sect') && |
|
!abcContent.includes('class=')) { |
|
// Extract just the ABC notation (stop at first non-ABC line or reasonable limit) |
|
const lines = abcContent.split('\n'); |
|
const abcLines = []; |
|
for (const line of lines) { |
|
if (line.includes('</') || line.includes('<div') || line.includes('sect') || line.includes('class=')) { |
|
break; |
|
} |
|
if (line.length > 200) { |
|
break; |
|
} |
|
abcLines.push(line); |
|
if (abcLines.join('\n').length > 2000) { |
|
break; |
|
} |
|
} |
|
const cleanAbc = abcLines.join('\n').trim(); |
|
if (cleanAbc.match(/^X:\s*\d+/m) && cleanAbc.length > 10 && cleanAbc.length < 2000) { |
|
return `<div class="abc-notation" data-abc="${escapeForAttr(cleanAbc)}">${match}</div>`; |
|
} |
|
} |
|
return match; |
|
}); |
|
// Process LilyPond notation blocks |
|
const lilypondPattern = /(\\relative[^}]+})/gs; |
|
html = html.replace(lilypondPattern, (match) => { |
|
const lilypondContent = match.trim(); |
|
return `<div class="lilypond-notation" data-lilypond="${escapeForAttr(lilypondContent)}">${lilypondContent}</div>`; |
|
}); |
|
// Process inline chord notation: [C], [Am], [F#m7], etc. |
|
const chordPattern = /\[([A-G][#b]?m?[0-9]?[^\[\]]*)\]/g; |
|
html = html.replace(chordPattern, (match, chord) => { |
|
return `<span class="chord" data-chord="${escapeForAttr(chord)}">[${chord}]</span>`; |
|
}); |
|
// Process MusicXML-like notation |
|
const musicxmlPattern = /(<music[^>]*>.*?<\/music>)/gs; |
|
html = html.replace(musicxmlPattern, (match) => { |
|
const musicxmlContent = match.trim(); |
|
return `<div class="musicxml-notation" data-musicxml="${escapeForAttr(musicxmlContent)}">${musicxmlContent}</div>`; |
|
}); |
|
return html; |
|
} |
|
/** |
|
* Escapes a string for use in HTML attributes |
|
*/ |
|
function escapeForAttr(text) { |
|
return text |
|
.replace(/"/g, '"') |
|
.replace(/'/g, ''') |
|
.replace(/</g, '<') |
|
.replace(/>/g, '>') |
|
.replace(/\n/g, ' ') |
|
.replace(/\r/g, ''); |
|
}
|
|
|