feat(labels): add reader for global Labels file

Mirrors the Macros reader: LabelsFileReader -> LabelLibrary -> Label wraps
the protobuf ProLabelsDocument and exposes each label's name (the proto
'text' field) plus optional RGBA color with a #RRGGBB hex helper. Includes
CLI tool, PHPUnit suite against the bundled reference Labels sample, and
api/labels.md docs.
This commit is contained in:
Thorsten Bus 2026-05-03 20:53:45 +02:00
parent b30918af41
commit 4e1ac9b7ea
10 changed files with 560 additions and 7 deletions

View file

@ -20,6 +20,7 @@ All project documentation lives in `doc/`. Load only what you need.
| Parse/modify `.proplaylist` files | `doc/api/playlist.md` | | Parse/modify `.proplaylist` files | `doc/api/playlist.md` |
| Parse/modify `.probundle` files | `doc/api/bundle.md` | | Parse/modify `.probundle` files | `doc/api/bundle.md` |
| Read the global `Macros` file | `doc/api/macros.md` | | Read the global `Macros` file | `doc/api/macros.md` |
| Read the global `Labels` file | `doc/api/labels.md` |
| Understand `.pro` binary format | `doc/formats/pp_song_spec.md` | | Understand `.pro` binary format | `doc/formats/pp_song_spec.md` |
| Understand `.proplaylist` binary format | `doc/formats/pp_playlist_spec.md` | | Understand `.proplaylist` binary format | `doc/formats/pp_playlist_spec.md` |
| Understand `.probundle` binary format | `doc/formats/pp_bundle_spec.md` | | Understand `.probundle` binary format | `doc/formats/pp_bundle_spec.md` |
@ -40,7 +41,9 @@ doc/
├── api/ ← PHP API docs (read/write/generate) ├── api/ ← PHP API docs (read/write/generate)
│ ├── song.md │ ├── song.md
│ ├── playlist.md │ ├── playlist.md
│ └── bundle.md │ ├── bundle.md
│ ├── macros.md
│ └── labels.md
└── internal/ ← Dev notes (learnings, decisions, issues) └── internal/ ← Dev notes (learnings, decisions, issues)
├── learnings.md ├── learnings.md
├── decisions.md ├── decisions.md
@ -55,6 +58,7 @@ PHP tools for parsing, modifying, and generating ProPresenter 7 files:
- **Playlists** (`.proplaylist`) — ZIP64 archives containing playlist metadata and embedded songs - **Playlists** (`.proplaylist`) — ZIP64 archives containing playlist metadata and embedded songs
- **Bundles** (`.probundle`) — ZIP archives containing a single presentation with embedded media assets - **Bundles** (`.probundle`) — ZIP archives containing a single presentation with embedded media assets
- **Macros** (`Macros`, no extension) — Global protobuf-encoded macro library with collections - **Macros** (`Macros`, no extension) — Global protobuf-encoded macro library with collections
- **Labels** (`Labels`, no extension) — Global protobuf-encoded label library (slide labels with optional UI colors)
### CLI Tools ### CLI Tools
@ -62,6 +66,7 @@ PHP tools for parsing, modifying, and generating ProPresenter 7 files:
php bin/parse-song.php path/to/song.pro php bin/parse-song.php path/to/song.pro
php bin/parse-playlist.php path/to/playlist.proplaylist php bin/parse-playlist.php path/to/playlist.proplaylist
php bin/parse-macros.php path/to/Macros php bin/parse-macros.php path/to/Macros
php bin/parse-labels.php path/to/Labels
``` ```
### Key Source Files ### Key Source Files

47
bin/parse-labels.php Executable file
View file

@ -0,0 +1,47 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use ProPresenter\Parser\LabelsFileReader;
if ($argc < 2) {
echo "Usage: parse-labels.php <Labels>\n";
exit(1);
}
$filePath = $argv[1];
try {
$library = LabelsFileReader::read($filePath);
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
exit(1);
}
$labels = $library->getLabels();
echo "Labels (" . count($labels) . "):\n";
foreach ($labels as $index => $label) {
$number = $index + 1;
$name = $label->getName();
$displayName = $name === '' ? '(unnamed)' : $name;
if ($label->hasColor()) {
$color = $label->getColor();
$colorPart = sprintf(
'%s rgba(%.3f, %.3f, %.3f, %.3f)',
$label->getColorHex(),
$color['r'],
$color['g'],
$color['b'],
$color['a'],
);
} else {
$colorPart = '(no color)';
}
echo sprintf(" [%d] %s :: %s\n", $number, $displayName, $colorPart);
}

View file

@ -10,6 +10,7 @@
| Parse/modify `.proplaylist` files | [api/playlist.md](api/playlist.md) | | Parse/modify `.proplaylist` files | [api/playlist.md](api/playlist.md) |
| Parse/modify `.probundle` files | [api/bundle.md](api/bundle.md) | | Parse/modify `.probundle` files | [api/bundle.md](api/bundle.md) |
| Read the global `Macros` file | [api/macros.md](api/macros.md) | | Read the global `Macros` file | [api/macros.md](api/macros.md) |
| Read the global `Labels` file | [api/labels.md](api/labels.md) |
| Understand `.pro` binary format | [formats/pp_song_spec.md](formats/pp_song_spec.md) | | Understand `.pro` binary format | [formats/pp_song_spec.md](formats/pp_song_spec.md) |
| Understand `.proplaylist` format | [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md) | | Understand `.proplaylist` format | [formats/pp_playlist_spec.md](formats/pp_playlist_spec.md) |
| Understand `.probundle` format | [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) | | Understand `.probundle` format | [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) |
@ -30,6 +31,7 @@
- [api/playlist.md](api/playlist.md) — Playlist parser API (read, modify, generate `.proplaylist` files) - [api/playlist.md](api/playlist.md) — Playlist parser API (read, modify, generate `.proplaylist` files)
- [api/bundle.md](api/bundle.md) — Bundle parser API (read, write `.probundle` files with media) - [api/bundle.md](api/bundle.md) — Bundle parser API (read, write `.probundle` files with media)
- [api/macros.md](api/macros.md) — Macros library API (read the global `Macros` file) - [api/macros.md](api/macros.md) — Macros library API (read the global `Macros` file)
- [api/labels.md](api/labels.md) — Labels library API (read the global `Labels` file)
### Internal Reference ### Internal Reference
- [internal/learnings.md](internal/learnings.md) — Development learnings and conventions discovered - [internal/learnings.md](internal/learnings.md) — Development learnings and conventions discovered
@ -57,7 +59,8 @@ doc/
│ ├── song.md │ ├── song.md
│ ├── playlist.md │ ├── playlist.md
│ ├── bundle.md │ ├── bundle.md
│ └── macros.md │ ├── macros.md
│ └── labels.md
└── internal/ ← Development notes (optional context) └── internal/ ← Development notes (optional context)
├── learnings.md ├── learnings.md
├── decisions.md ├── decisions.md
@ -128,6 +131,8 @@ This project provides PHP tools to parse, modify, and generate ProPresenter 7 fi
| `src/ProBundleWriter.php` | Write `.probundle` files | | `src/ProBundleWriter.php` | Write `.probundle` files |
| `src/MacroLibrary.php` | Macros library wrapper (read global `Macros` file) | | `src/MacroLibrary.php` | Macros library wrapper (read global `Macros` file) |
| `src/MacrosFileReader.php` | Read global `Macros` file | | `src/MacrosFileReader.php` | Read global `Macros` file |
| `src/LabelLibrary.php` | Labels library wrapper (read global `Labels` file) |
| `src/LabelsFileReader.php` | Read global `Labels` file |
### CLI Tools ### CLI Tools
@ -140,4 +145,7 @@ php bin/parse-playlist.php path/to/playlist.proplaylist
# Parse and display the global Macros file # Parse and display the global Macros file
php bin/parse-macros.php path/to/Macros php bin/parse-macros.php path/to/Macros
# Parse and display the global Labels file
php bin/parse-labels.php path/to/Labels
``` ```

137
doc/api/labels.md Normal file
View file

@ -0,0 +1,137 @@
# Labels Library API
> PHP module for reading the global ProPresenter `Labels` file (raw protobuf,
> no extension) and exposing each label's name and UI color.
## Quick Reference
```php
use ProPresenter\Parser\LabelsFileReader;
$library = LabelsFileReader::read('/path/to/Labels');
foreach ($library->getLabels() as $label) {
$label->getName(); // "KeyVisual Beamer"
$label->hasColor(); // bool
$label->getColor(); // ['r'=>0.0,'g'=>0.408,'b'=>0.702,'a'=>1.0] | null
$label->getColorHex(); // "#0068B3" | null
}
```
---
## File Layout
The `Labels` file is the protobuf-serialised
[`ProLabelsDocument`](../../proto/labels.proto):
| Field | Type | Description |
|-------|------|-------------|
| `labels` | repeated `Action.Label` | Definitions: text + optional color |
Each `Action.Label` carries:
| Field | Type | Description |
|-------|------|-------------|
| `text` | string | Display name (exposed as `getName()` on the wrapper) |
| `color` | `Color` (optional) | RGBA float channels in 0..1; absent for system / "no color" labels |
Labels are identified by name only — there is no UUID. Slides reference
labels by name from inside `.pro` files.
---
## Reading
```php
use ProPresenter\Parser\LabelsFileReader;
$library = LabelsFileReader::read('/Users/me/.../Labels');
```
Throws `InvalidArgumentException` for missing files and `RuntimeException` for
empty / unreadable files.
---
## LabelLibrary
Top-level wrapper around `Rv\Data\ProLabelsDocument`. Indexes labels by name
for fast lookup.
```php
$library->getLabels(); // Label[]
$library->count(); // int
$library->getLabelByName('Szene 1'); // ?Label (case-sensitive)
$library->findLabelByName('szene 1'); // ?Label (case-insensitive)
$library->getDocument(); // \Rv\Data\ProLabelsDocument
```
If the same name appears more than once in the source document the first
occurrence wins for both lookup helpers; every entry is preserved in
`getLabels()` in document order.
---
## Label
```php
$label->getName(); // "KeyVisual Beamer" (proto field is `text`)
$label->hasColor(); // bool — was a Color message present?
$label->getColor(); // ['r'=>..,'g'=>..,'b'=>..,'a'=>..] | null
$label->getColorHex(); // "#RRGGBB" uppercase, alpha dropped, or null
$label->getProto(); // \Rv\Data\Action\Label (raw protobuf)
```
Color channels are floats in 0..1 as ProPresenter stores them. `getColorHex()`
clamps and rounds each channel to 8 bits before formatting.
A label can legitimately exist without a `color` message. Treat that as
"use the default UI color", not as black. The reference sample's first four
labels (`Leere Folie`, `Instrumental`, `Wiederholen`, `Gesprochenes Wort`)
hit this case.
---
## CLI Tool
```bash
php bin/parse-labels.php /path/to/Labels
```
Output:
```
Labels (15):
[1] Leere Folie :: (no color)
[2] Instrumental :: (no color)
[3] Wiederholen :: (no color)
[4] Gesprochenes Wort :: (no color)
[5] KeyVisual Stream & Beamer mit Countdown :: #CC298B rgba(0.800, 0.161, 0.545, 1.000)
[6] KeyVisual Stream & Beamer mit Jingle :: #7600CC rgba(0.463, 0.000, 0.800, 1.000)
...
```
---
## Key Files
| File | Purpose |
|------|---------|
| `src/LabelLibrary.php` | Document-level wrapper with name lookups |
| `src/Label.php` | Single label wrapper (name, color, hex) |
| `src/LabelsFileReader.php` | Reads the `Labels` file |
| `bin/parse-labels.php` | CLI tool |
| `proto/labels.proto` | Protobuf schema (just imports `Action.Label`) |
| `proto/action.proto` | Defines the inner `Action.Label` message |
| `generated/Rv/Data/ProLabelsDocument.php` | Generated message class |
| `generated/Rv/Data/Action/Label.php` | Generated label message class |
---
## Scope Notes
This module is read-only by design. Writing the `Labels` file back, editing
slide-side label references on `.pro` files, or syncing labels across devices
are not implemented here. Add them by mirroring the `ProFileWriter` /
`ProFileGenerator` pattern when needed.

View file

@ -74,16 +74,20 @@
| image | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 3 | | image | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 3 |
| video | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 6 | | video | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5, [formats/pp_bundle_spec.md](formats/pp_bundle_spec.md) Section 6 |
| cue | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 | | cue | [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 |
| label | [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 | | label | [api/labels.md](api/labels.md), [api/song.md](api/song.md), [formats/pp_song_spec.md](formats/pp_song_spec.md) Section 5 |
| Labels file | [api/labels.md](api/labels.md) |
| LabelLibrary | [api/labels.md](api/labels.md) |
| LabelsFileReader | [api/labels.md](api/labels.md) |
| color | [api/labels.md](api/labels.md), [api/macros.md](api/macros.md) |
## PHP API ## PHP API
| Keyword | Document | | Keyword | Document |
|---------|----------| |---------|----------|
| read | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md) | | read | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
| write | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md) | | write | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md) |
| generate | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md) | | generate | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md) |
| parse | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md) | | parse | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/bundle.md](api/bundle.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
| MacrosFileReader | [api/macros.md](api/macros.md) | | MacrosFileReader | [api/macros.md](api/macros.md) |
| ProFileReader | [api/song.md](api/song.md) | | ProFileReader | [api/song.md](api/song.md) |
| ProFileWriter | [api/song.md](api/song.md) | | ProFileWriter | [api/song.md](api/song.md) |
@ -96,8 +100,8 @@
| PresentationBundle | [api/bundle.md](api/bundle.md) | | PresentationBundle | [api/bundle.md](api/bundle.md) |
| Song | [api/song.md](api/song.md) | | Song | [api/song.md](api/song.md) |
| PlaylistArchive | [api/playlist.md](api/playlist.md) | | PlaylistArchive | [api/playlist.md](api/playlist.md) |
| CLI | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md) | | CLI | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
| command line | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md) | | command line | [api/song.md](api/song.md), [api/playlist.md](api/playlist.md), [api/macros.md](api/macros.md), [api/labels.md](api/labels.md) |
## Protobuf ## Protobuf

Binary file not shown.

91
src/Label.php Normal file
View file

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace ProPresenter\Parser;
use Rv\Data\Action\Label as LabelProto;
/**
* Wraps a protobuf {@see LabelProto} from the global ProPresenter `Labels`
* file. Surfaces the label's display name (the protobuf `text` field) and an
* optional color used by the UI to tint slides / cues that carry the label.
*
* Labels do not have a UUID identity is the name string.
*/
class Label
{
public function __construct(
private readonly LabelProto $label,
) {
}
/**
* Display name (the protobuf `text` field; this is what the
* ProPresenter UI renders next to the swatch).
*/
public function getName(): string
{
return $this->label->getText();
}
/**
* Whether the label carries an explicit color message (the absence of a
* color is not the same as black: ProPresenter renders unset labels with
* the default UI color).
*/
public function hasColor(): bool
{
return $this->label->hasColor();
}
/**
* Get the label's color as an associative array, or null when unset.
*
* Channels are floats in the 0..1 range as stored by ProPresenter.
*
* @return array{r: float, g: float, b: float, a: float}|null
*/
public function getColor(): ?array
{
if (!$this->label->hasColor()) {
return null;
}
$color = $this->label->getColor();
return [
'r' => $color->getRed(),
'g' => $color->getGreen(),
'b' => $color->getBlue(),
'a' => $color->getAlpha(),
];
}
/**
* Convenience: render the color as a 6-digit `#RRGGBB` hex string,
* dropping alpha. Returns null when the label has no color.
*/
public function getColorHex(): ?string
{
$color = $this->getColor();
if ($color === null) {
return null;
}
return sprintf(
'#%02X%02X%02X',
(int) round(max(0.0, min(1.0, $color['r'])) * 255),
(int) round(max(0.0, min(1.0, $color['g'])) * 255),
(int) round(max(0.0, min(1.0, $color['b'])) * 255),
);
}
/**
* Get the underlying protobuf Label message.
*/
public function getProto(): LabelProto
{
return $this->label;
}
}

83
src/LabelLibrary.php Normal file
View file

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace ProPresenter\Parser;
use Rv\Data\ProLabelsDocument;
/**
* Wraps the protobuf {@see ProLabelsDocument} the global ProPresenter
* `Labels` file which lists every named label and its UI color.
*
* Labels are identified by name only (there is no UUID field). Names are
* expected to be unique inside the document; if the source file violates
* that, the first occurrence wins for {@see getLabelByName()} but every
* label is preserved in {@see getLabels()} in document order.
*/
class LabelLibrary
{
/** @var Label[] */
private array $labels = [];
/** @var array<string, Label> */
private array $labelsByName = [];
/** @var array<string, Label> */
private array $labelsByNameLower = [];
public function __construct(
private readonly ProLabelsDocument $document,
) {
foreach ($this->document->getLabels() as $labelProto) {
$label = new Label($labelProto);
$this->labels[] = $label;
$name = $label->getName();
if ($name === '') {
continue;
}
$this->labelsByName[$name] ??= $label;
$this->labelsByNameLower[strtolower($name)] ??= $label;
}
}
/**
* @return Label[]
*/
public function getLabels(): array
{
return $this->labels;
}
public function count(): int
{
return count($this->labels);
}
/**
* Exact-name lookup. Use {@see findLabelByName()} for a
* case-insensitive variant.
*/
public function getLabelByName(string $name): ?Label
{
return $this->labelsByName[$name] ?? null;
}
/**
* Case-insensitive name lookup.
*/
public function findLabelByName(string $name): ?Label
{
return $this->labelsByNameLower[strtolower($name)] ?? null;
}
/**
* Get the underlying protobuf ProLabelsDocument.
*/
public function getDocument(): ProLabelsDocument
{
return $this->document;
}
}

42
src/LabelsFileReader.php Normal file
View file

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace ProPresenter\Parser;
use InvalidArgumentException;
use RuntimeException;
use Rv\Data\ProLabelsDocument;
/**
* Reader for the global ProPresenter `Labels` file (a raw protobuf
* serialisation of {@see ProLabelsDocument}, no extension).
*/
final class LabelsFileReader
{
public static function read(string $filePath): LabelLibrary
{
if ($filePath === '' || !is_file($filePath)) {
throw new InvalidArgumentException(sprintf('Labels file not found: %s', $filePath));
}
$size = filesize($filePath);
if ($size === false) {
throw new RuntimeException(sprintf('Unable to determine file size: %s', $filePath));
}
if ($size === 0) {
throw new RuntimeException(sprintf('Labels file is empty: %s', $filePath));
}
$data = file_get_contents($filePath);
if ($data === false) {
throw new RuntimeException(sprintf('Unable to read labels file: %s', $filePath));
}
$document = new ProLabelsDocument();
$document->mergeFromString($data);
return new LabelLibrary($document);
}
}

View file

@ -0,0 +1,136 @@
<?php
declare(strict_types=1);
namespace ProPresenter\Parser\Tests;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use ProPresenter\Parser\Label;
use ProPresenter\Parser\LabelLibrary;
use ProPresenter\Parser\LabelsFileReader;
class LabelsFileReaderTest extends TestCase
{
private const REFERENCE_PATH = __DIR__ . '/../doc/reference_samples/Labels';
#[Test]
public function readThrowsOnMissingFile(): void
{
$this->expectException(InvalidArgumentException::class);
LabelsFileReader::read(__DIR__ . '/../doc/reference_samples/does-not-exist-labels');
}
#[Test]
public function readReturnsLabelLibraryWithExpectedCount(): void
{
$library = LabelsFileReader::read(self::REFERENCE_PATH);
$this->assertInstanceOf(LabelLibrary::class, $library);
$this->assertCount(15, $library->getLabels());
$this->assertSame(15, $library->count());
}
#[Test]
public function labelsExposeNames(): void
{
$library = LabelsFileReader::read(self::REFERENCE_PATH);
$labels = $library->getLabels();
$this->assertInstanceOf(Label::class, $labels[0]);
$this->assertSame('Leere Folie', $labels[0]->getName());
$this->assertSame('Instrumental', $labels[1]->getName());
$this->assertSame('KeyVisual Stream & Beamer mit Countdown', $labels[4]->getName());
$this->assertSame('Szene 3', $labels[14]->getName());
}
#[Test]
public function labelsWithoutColorReportNullColor(): void
{
$library = LabelsFileReader::read(self::REFERENCE_PATH);
$leereFolie = $library->getLabelByName('Leere Folie');
$this->assertNotNull($leereFolie);
$this->assertFalse($leereFolie->hasColor());
$this->assertNull($leereFolie->getColor());
$this->assertNull($leereFolie->getColorHex());
}
#[Test]
public function labelsWithColorReturnFloatChannels(): void
{
$library = LabelsFileReader::read(self::REFERENCE_PATH);
$beamer = $library->getLabelByName('KeyVisual Beamer');
$this->assertNotNull($beamer);
$this->assertTrue($beamer->hasColor());
$color = $beamer->getColor();
$this->assertIsArray($color);
$this->assertEqualsWithDelta(0.0, $color['r'], 0.001);
$this->assertEqualsWithDelta(0.4078, $color['g'], 0.001);
$this->assertEqualsWithDelta(0.7020, $color['b'], 0.001);
$this->assertEqualsWithDelta(1.0, $color['a'], 0.001);
}
#[Test]
public function colorHexIsSixDigitUppercase(): void
{
$library = LabelsFileReader::read(self::REFERENCE_PATH);
$beamer = $library->getLabelByName('KeyVisual Beamer');
$this->assertNotNull($beamer);
$this->assertSame('#0068B3', $beamer->getColorHex());
$countdown = $library->getLabelByName('KeyVisual Stream & Beamer mit Countdown');
$this->assertNotNull($countdown);
$this->assertSame('#CC298B', $countdown->getColorHex());
$kill = $library->getLabelByName('Namenseinblender Kill');
$this->assertNotNull($kill);
$this->assertSame('#000000', $kill->getColorHex());
}
#[Test]
public function getLabelByNameIsCaseSensitive(): void
{
$library = LabelsFileReader::read(self::REFERENCE_PATH);
$this->assertNotNull($library->getLabelByName('Szene 1'));
$this->assertNull($library->getLabelByName('szene 1'));
}
#[Test]
public function findLabelByNameIsCaseInsensitive(): void
{
$library = LabelsFileReader::read(self::REFERENCE_PATH);
$exact = $library->findLabelByName('Szene 1');
$lower = $library->findLabelByName('szene 1');
$upper = $library->findLabelByName('SZENE 1');
$this->assertNotNull($exact);
$this->assertSame($exact, $lower);
$this->assertSame($exact, $upper);
$this->assertSame('Szene 1', $exact->getName());
}
#[Test]
public function unknownNameReturnsNull(): void
{
$library = LabelsFileReader::read(self::REFERENCE_PATH);
$this->assertNull($library->getLabelByName('does-not-exist'));
$this->assertNull($library->findLabelByName('does-not-exist'));
}
#[Test]
public function documentIsExposedForRawAccess(): void
{
$library = LabelsFileReader::read(self::REFERENCE_PATH);
$document = $library->getDocument();
$this->assertCount(15, $document->getLabels());
}
}