lyrics-structure
Version:
Parser for lyrics with structured sections, names, and indications
92 lines (91 loc) • 3.31 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLyricsParts = getLyricsParts;
/**
* Splits lyrics into structured parts, handling named sections, repetitions, and indications.
* Works with lyrics formatted using square brackets for section names and optional parentheses for indications.
*
* Example input:
* ```
* [verse 1] (first time)
* Lyrics content here
* [/verse 1]
*
* [chorus]
* Chorus lyrics
* [/chorus]
* ```
*
* @param lyrics - The input lyrics text to be parsed into parts
* @returns Array of LyricPart objects containing structured lyrics data
*/
function getLyricsParts(lyrics) {
const parts = [];
const seenParts = new Map();
const partContentMap = new Map();
// First pass: extract all content with closing tags
const cleanedText = lyrics.replace(/\[([^\]]+)\](?:\s*\(([^)]+)\))?\s*([\s\S]*?)\[\/\1\]/g, (match, key, indication, content) => {
if (!partContentMap.has(key)) {
partContentMap.set(key, content.trim());
}
return `[${key}]${indication ? ` (${indication})` : ''}`;
});
// Split the lyrics into lines and process them
const lines = cleanedText.split('\n');
let currentPart = null;
let currentUnnamedContent = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
// Check for part start
const partStartMatch = line.match(/^\[([^\]]+)\](?:\s*\(([^)]+)\))?$/);
if (partStartMatch) {
// If we have accumulated unnamed content, add it as a part
if (currentUnnamedContent.length > 0) {
parts.push({
name: undefined,
repetition: false,
indication: null,
content: currentUnnamedContent.join('\n'),
});
currentUnnamedContent = [];
}
const partName = partStartMatch[1];
const indication = partStartMatch[2] || null;
// Check if this part has been seen before
const partCount = (seenParts.get(partName) || 0) + 1;
seenParts.set(partName, partCount);
currentPart = {
name: partName,
repetition: partCount > 1,
indication,
content: partContentMap.get(partName),
};
parts.push(currentPart);
continue;
}
// Handle content without a part container
if (line && !line.startsWith('[') && !line.startsWith('[/')) {
currentUnnamedContent.push(line);
}
else if (currentUnnamedContent.length > 0) {
// If we hit a part marker or empty line, add the accumulated content
parts.push({
name: undefined,
repetition: false,
indication: null,
content: currentUnnamedContent.join('\n'),
});
currentUnnamedContent = [];
}
}
// Add any remaining unnamed content
if (currentUnnamedContent.length > 0) {
parts.push({
name: undefined,
repetition: false,
indication: null,
content: currentUnnamedContent.join('\n'),
});
}
return parts;
}