'Verse', 'Refrain' => 'Chorus', 'Brücke' => 'Bridge', 'Vorrefrain' => 'Pre-Chorus', 'Schluss' => 'Ending', 'Zwischenspiel' => 'Interlude', ]; public static function isSectionLabel(string $line): bool { return (bool) preg_match(self::SECTION_LABEL_PATTERN, trim($line)); } public static function isMetadataLine(string $line): bool { return (bool) preg_match(self::METADATA_PATTERN, $line); } public static function normalizeLabelName(string $label): string { $trimmed = trim($label); if (! preg_match('/^(?Verse|Chorus|Bridge|Pre-Chorus|Tag|Ending|Intro|Interlude|Outro|Misc|Strophe|Refrain|Brücke|Vorrefrain|Schluss|Zwischenspiel)(?\s+\d+[a-z]?)?$/iu', $trimmed, $matches)) { return $trimmed; } $kind = $matches['kind']; $suffix = $matches['suffix'] ?? ''; return (self::LABEL_NAME_MAP[$kind] ?? $kind).$suffix; } /** * @return array{kind: string, number: string|null, modifier: string|null}|null */ public static function parseLabel(string $line): ?array { $trimmed = trim($line); if (! preg_match('/^(?Verse|Chorus|Bridge|Pre-Chorus|Tag|Ending|Intro|Interlude|Outro|Misc|Strophe|Refrain|Brücke|Vorrefrain|Schluss|Zwischenspiel)(?:\s+(?\d+[a-z]?))?(?:\s*\((?Repeat|Wdh\.?)\))?(?:\s*[xX]\s*\d+)?$/iu', $trimmed, $matches)) { return null; } $modifier = $matches['modifier'] ?? null; return [ 'kind' => $matches['kind'], 'number' => $matches['number'] ?? null, 'modifier' => $modifier !== null ? rtrim($modifier, '.') : null, ]; } }