
# Transformer

Mit Transformern kannst du die hervorgehobene Ausgabe in jeder Phase neu gestalten – vom rohen Quelltext über das Token-Raster bis zum fertigen HAST-Baum und HTML.
shikiphp übernimmt Shikis Transformermodell Hook für Hook, sodass sich Transformer genauso verhalten wie ihre JavaScript-Entsprechungen.

Übergib sie über die Option [`transformers`](https://shikiphp.inline0.dev/de/docs/options#transformers):

```php
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\Notation\NotationHighlight;
use Shikiphp\Transformer\Notation\NotationDiff;

$code = <<<'JS'
console.log('this line is highlighted') // [!code highlight]
console.log('old line') // [!code --]
console.log('new line') // [!code ++]
JS;

echo Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'theme'        => 'vitesse-dark',
    'transformers' => [
        new NotationHighlight(),
        new NotationDiff(),
    ],
]);
```

## Die Pipeline

Jeder Transformer ist ein Objekt, das `Shikiphp\Transformer\Transformer` implementiert.
Die Pipeline führt die Hooks der Reihe nach beim Aufbau eines einzelnen Blocks aus:

| Hook | Signatur | Zeitpunkt |
| --- | --- | --- |
| `preprocess` | `(string $code, array &$options, $context): ?string` | vor der Tokenisierung; kann den Quelltext umschreiben und Optionen ändern |
| `tokens` | `(array $tokens, $context): ?array` | nach der Tokenisierung, auf dem 2D-`ThemedToken`-Raster |
| `root` | `(Element $el, $context): ?Element` | auf der HAST-Wurzel |
| `pre` | `(Element $el, $context): ?Element` | auf dem `<pre>`-Element |
| `code` | `(Element $el, $context): ?Element` | auf dem `<code>`-Element |
| `line` | `(Element $el, int $line, $context): ?Element` | pro Zeilenelement (ab 1 gezählt) |
| `span` | `(Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element` | pro Token-Span |
| `postprocess` | `(string $html, array $options, $context): ?string` | auf dem zuletzt serialisierten HTML-String |

Jeder Hook ist optional. Ein Rückgabewert ersetzt die Eingabe; `null`
lässt sie unverändert.

`enforce()` legt die Reihenfolge der Transformer fest: `'pre'` wird zuerst ausgeführt, `'post'` zuletzt und `null` (der Standard) dazwischen, in der Reihenfolge ihrer Registrierung.

## Transformer schreiben

Erweitere `Shikiphp\Transformer\AbstractTransformer`, damit du nur die Hooks überschreiben musst,
die du benötigst; die übrigen bleiben wirkungslos. Der Transformer-Kontext stellt
`addClassToHast()` bereit, um Elementen Klassen hinzuzufügen.

```php
use Shikiphp\Hast\Element;
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\AbstractTransformer;
use Shikiphp\Transformer\TransformerContext;

final class AddLanguageClass extends AbstractTransformer
{
    public function pre(Element $element, TransformerContext $context): ?Element
    {
        if ($context->lang !== null) {
            $context->addClassToHast($element, 'language-' . $context->lang);
        }

        return $element;
    }
}

echo Shikiphp::codeToHtml('const answer = 42', [
    'lang'         => 'js',
    'theme'        => 'nord',
    'transformers' => [new AddLanguageClass()],
]);
// <pre class="shiki nord language-js" …>
```

## Integrierte Transformer

### Notations-Transformer

Diese lesen spezielle `// [!code …]`-Kommentare im Quelltext (die Kommentar-Syntax der Sprache wird automatisch erkannt) und wandeln sie in Klassen um; der Kommentar selbst wird entfernt. Sie sind direkte Portierungen des `@shikijs/transformers`-Notationssatzes.

#### `NotationHighlight`

`Shikiphp\Transformer\Notation\NotationHighlight` — `// [!code highlight]` (oder
`// [!code hl]`) fügt der Zeile die Klasse `highlighted` hinzu.

```js
console.log('kept plain')
console.log('this line is highlighted') // [!code highlight]
```

#### `NotationDiff`

`Shikiphp\Transformer\Notation\NotationDiff` — `// [!code ++]` markiert eine Zeile als
hinzugefügt (`diff add`), `// [!code --]` als entfernt (`diff remove`).

```js
console.log('old line') // [!code --]
console.log('new line') // [!code ++]
```

#### `NotationFocus`

`Shikiphp\Transformer\Notation\NotationFocus` — `// [!code focus]` fügt die
Klasse `focused` hinzu.

```js
console.log('focus on me') // [!code focus]
```

#### `NotationErrorLevel`

`Shikiphp\Transformer\Notation\NotationErrorLevel` — `// [!code error]`,
`// [!code warning]` und `// [!code info]` fügen die Klasse `highlighted` sowie
`error` / `warning` / `info` hinzu.

```js
throwSomething() // [!code error]
maybeRisky()     // [!code warning]
```

#### `NotationWordHighlight`

`Shikiphp\Transformer\Notation\NotationWordHighlight` — `// [!code word:foo]`
(optional `// [!code word:foo:2]`, um die Anzahl auf die nächsten N Zeilen zu begrenzen) umschließt jedes Vorkommen von `foo` mit einem Span, der `highlighted-word` enthält.

```js
// [!code word:config]
const config = loadConfig()
```

### Metadaten-Transformer

Diese lesen den **Metadaten-String** des Code-Fences, der über die Option
[`meta`](https://shikiphp.inline0.dev/de/docs/options#meta) bereitgestellt wird (`meta['__raw']`) – so wie Shiki den Text nach der Sprache eines abgegrenzten Codeblocks ausliest.

#### `MetaHighlight`

`Shikiphp\Transformer\MetaHighlight` — hebt die Zeilen hervor, die in einem Metadatenbereich wie `{1,3-5}` angegeben sind, und fügt die Klasse `highlighted` hinzu.

```php
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\MetaHighlight;

$code = "const a = 1\nconst b = 2\nconst c = 3\nconst d = 4\nconst e = 5";

echo Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'theme'        => 'nord',
    'meta'         => ['__raw' => '{1,3-5}'],
    'transformers' => [new MetaHighlight()],
]);
```

#### `MetaWordHighlight`

`Shikiphp\Transformer\MetaWordHighlight` — hebt Wörter hervor, die im Metadaten-String mit der Syntax `/word/` angegeben sind, und fügt `highlighted-word` hinzu.

### Leerraum und Struktur

#### `RenderWhitespace`

`Shikiphp\Transformer\RenderWhitespace` — umschließt Leerzeichen und Tabulatoren mit Spans (standardmäßig die Klassen `space` und `tab`), damit sie sichtbar dargestellt werden können.
Der Konstruktor erhält `(classSpace, classTab, position)`, wobei `position` `'all'` (Standard), `'boundary'`, `'trailing'` oder `'leading'` ist.

#### `CompactLineOptions`

`Shikiphp\Transformer\CompactLineOptions` — wendet Klassen auf bestimmte Zeilen an, basierend auf einer Liste von `{line, classes}`-Einträgen.

```php
use Shikiphp\Transformer\CompactLineOptions;

$transformer = new CompactLineOptions([
    ['line' => 1, 'classes' => 'highlighted'],
    ['line' => 3, 'classes' => ['highlighted', 'error']],
]);
```

Zeilennummern beginnen bei 1. Übergib das Objekt wie jeden anderen Transformer über die Option `transformers`.

#### `RemoveNotationEscape`

`Shikiphp\Transformer\RemoveNotationEscape` — hebt die Escape-Sequenz `[\!code …]` auf, mit der du einen wörtlichen Notationskommentar schreiben kannst, ohne einen Notations-Transformer auszulösen.

#### `RemoveLineBreak`

`Shikiphp\Transformer\RemoveLineBreak` — entfernt die Zeilenumbruch-Textknoten zwischen den Zeilen (Shikis `transformerRemoveLineBreak`).

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — verschiebt Inline-Stile in generierte Klassen
und sammelt ein Stylesheet, das du mit `getCSS()` abrufst. Klassennamen hashen den Stil
mit `cyrb53` von Shiki, sodass die Ausgabe byteidentisch mit `@shikijs/transformers`
4.2.0 ist, einschließlich des Geltungsbereichs:

- Der Stil `<pre>` wird immer zu einer Klasse.
- Token-Spans werden nur dann umgewandelt, wenn sie einen vorgefertigten Stil enthalten, wie bei einer `themes`-Map. Bei einem einzelnen `theme` behalten die Token-Spans ihre Inline-`style="color:…"`, wie in Shiki.

```php
use Shikiphp\Shikiphp;
use Shikiphp\Transformer\StyleToClass;

$code = 'const answer = 42';
$styleToClass = new StyleToClass();

$html = Shikiphp::codeToHtml($code, [
    'lang'         => 'js',
    'themes'       => ['light' => 'github-light', 'dark' => 'github-dark'],
    'transformers' => [$styleToClass],
]);

$css = $styleToClass->getCSS(); // collected stylesheet for the classes above
```

Der Konstruktor erhält `(classPrefix, classSuffix, classReplacer)`; das Präfix ist standardmäßig `__shiki_`.
