fix(ccli): parse common CCLI metadata
This commit is contained in:
parent
9412ca71c9
commit
e4e5df912e
|
|
@ -84,3 +84,7 @@ ### 2026-05-10 CcliPasteParser Implementation
|
|||
- Parser trims pasted lines, treats blank lines as separators, extracts first two header lines as title/author, and excludes CCLI metadata from lyric sections.
|
||||
- EN/DE side-by-side imports merge only adjacent labels with different raw kinds but the same `CcliLabels::normalizeLabelName()` canonical kind/number, preserving German lyrics in `linesTranslated`.
|
||||
- DDEV/Linux path is `tests/fixtures/ccli` (lowercase); macOS accepted `tests/Fixtures/ccli`, but tests must use lowercase for container portability.
|
||||
|
||||
### 2026-05-10 CCLI Parser Review Fixes
|
||||
- CCLI SongSelect metadata can appear as `CCLI Song #`, `CCLI-Nr.` or `CCLI-Liednummer`; extraction must ignore `CCLI License/Lizenz` numbers.
|
||||
- Parsed section `kind` is canonicalized via `CcliLabels::normalizeLabelName()`, while the original pasted label remains available in `label`; translation pairing still compares raw label kinds internally.
|
||||
|
|
|
|||
|
|
@ -63,8 +63,9 @@ public function parse(string $rawText): ParsedCcliSong
|
|||
}
|
||||
|
||||
if ($isMetadataLine($line)) {
|
||||
if (preg_match('/CCLI[\s#-]*(\d+)/iu', $line, $matches)) {
|
||||
$ccliId = $matches[1];
|
||||
$extractedCcliId = CcliLabels::extractCcliId($line);
|
||||
if ($extractedCcliId !== null) {
|
||||
$ccliId = $extractedCcliId;
|
||||
}
|
||||
|
||||
if (str_contains($line, '©')) {
|
||||
|
|
@ -90,7 +91,8 @@ public function parse(string $rawText): ParsedCcliSong
|
|||
|
||||
$current = [
|
||||
'label' => $line,
|
||||
'kind' => $label['kind'],
|
||||
'kind' => CcliLabels::normalizeLabelName($label['kind']),
|
||||
'rawKind' => $label['kind'],
|
||||
'number' => $label['number'],
|
||||
'modifier' => $label['modifier'],
|
||||
'lines' => [],
|
||||
|
|
@ -126,7 +128,7 @@ public function parse(string $rawText): ParsedCcliSong
|
|||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array{label: string, kind: string, number: string|null, modifier: string|null, lines: string[]}> $sections
|
||||
* @param array<int, array{label: string, kind: string, rawKind: string, number: string|null, modifier: string|null, lines: string[]}> $sections
|
||||
* @return ParsedCcliSection[]
|
||||
*/
|
||||
private function mergeTranslatedSections(array $sections): array
|
||||
|
|
@ -160,12 +162,12 @@ private function mergeTranslatedSections(array $sections): array
|
|||
}
|
||||
|
||||
/**
|
||||
* @param array{kind: string, number: string|null} $section
|
||||
* @param array{kind: string, number: string|null} $next
|
||||
* @param array{kind: string, rawKind: string, number: string|null} $section
|
||||
* @param array{kind: string, rawKind: string, number: string|null} $next
|
||||
*/
|
||||
private function isTranslatedPair(array $section, array $next): bool
|
||||
{
|
||||
return mb_strtolower($section['kind']) !== mb_strtolower($next['kind'])
|
||||
return mb_strtolower($section['rawKind']) !== mb_strtolower($next['rawKind'])
|
||||
&& $this->canonicalLabel($section) === $this->canonicalLabel($next);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,19 @@ public static function isMetadataLine(string $line): bool
|
|||
return (bool) preg_match(self::METADATA_PATTERN, $line);
|
||||
}
|
||||
|
||||
public static function extractCcliId(string $line): ?string
|
||||
{
|
||||
if (preg_match('/CCLI\s*(?:License|Lizenz)/iu', $line)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! preg_match('/CCLI(?:[\s-]*(?:Song|Lied(?:nummer)?|Nr\.?))?[\s#:\-.]*(\d+)/iu', $line, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
public static function normalizeLabelName(string $label): string
|
||||
{
|
||||
$trimmed = trim($label);
|
||||
|
|
|
|||
|
|
@ -62,13 +62,26 @@ function ccliFixtureContent(string $filename): string
|
|||
expect($first->linesTranslated)->not->toBeEmpty();
|
||||
});
|
||||
|
||||
test('german-only.txt detects German section labels', function (): void {
|
||||
test('german-only.txt detects German labels and normalizes section kind', function (): void {
|
||||
$parser = new CcliPasteParser();
|
||||
$result = $parser->parse(ccliFixtureContent('german-only.txt'));
|
||||
|
||||
$labels = array_map(fn (ParsedCcliSection $section): string => $section->label, $result->sections);
|
||||
$kinds = array_map(fn (ParsedCcliSection $section): string => $section->kind, $result->sections);
|
||||
$hasGermanKind = array_filter($kinds, fn (string $kind): bool => in_array(mb_strtolower($kind), ['strophe', 'refrain', 'brücke'], true));
|
||||
expect(count($hasGermanKind))->toBeGreaterThanOrEqual(1, 'Should detect at least one German section label');
|
||||
|
||||
expect($labels)->toContain('Strophe 1');
|
||||
expect($kinds)->toContain('Verse');
|
||||
expect($kinds)->toContain('Chorus');
|
||||
});
|
||||
|
||||
test('common CCLI metadata formats extract the song ID but not license numbers', function (): void {
|
||||
$parser = new CcliPasteParser();
|
||||
|
||||
$result = $parser->parse("Test Song\nTest Artist\n\nVerse 1\nLine\n\nCCLI Song # 1234567\nCCLI License # 111222");
|
||||
expect($result->ccliId)->toBe('1234567');
|
||||
|
||||
$result = $parser->parse("Test Song\nTest Artist\n\nStrophe 1\nZeile\n\nCCLI-Nr. 7654321");
|
||||
expect($result->ccliId)->toBe('7654321');
|
||||
});
|
||||
|
||||
test('repeat-marker.txt preserves modifier in section DTO', function (): void {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,17 @@
|
|||
'Test Song 1',
|
||||
]);
|
||||
|
||||
test('extractCcliId parses song number metadata without license numbers', function (string $line, ?string $expected) {
|
||||
expect(CcliLabels::extractCcliId($line))->toBe($expected);
|
||||
})->with([
|
||||
['CCLI # 4760', '4760'],
|
||||
['CCLI Song # 1234567', '1234567'],
|
||||
['CCLI-Nr. 7654321', '7654321'],
|
||||
['CCLI-Liednummer 9999001', '9999001'],
|
||||
['CCLI License # 123456', null],
|
||||
['CCLI-Lizenz # 123456', null],
|
||||
]);
|
||||
|
||||
test('normalizeLabelName converts german labels to english', function (string $input, string $expected) {
|
||||
expect(CcliLabels::normalizeLabelName($input))->toBe($expected);
|
||||
})->with([
|
||||
|
|
|
|||
Loading…
Reference in a new issue