
# Transformátory

Transformátory umožňují upravovat zvýrazněný výstup v každé fázi — od
původního zdrojového řetězce přes mřížku tokenů až po výsledný strom HAST a HTML.
shikiphp přebírá model transformátorů Shiki včetně všech hooků, takže se transformátory chovají
stejně jako jejich protějšky v JavaScript.

Předávejte je prostřednictvím volby [`transformers`](https://shikiphp.inline0.dev/cs/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(),
    ],
]);
```

## Postup zpracování

Každý transformátor je objekt implementující `Shikiphp\Transformer\Transformer`.
Řetězec zpracování spouští hooky v daném pořadí při sestavování jednoho bloku:

| Hook | Signatura | Kdy |
| --- | --- | --- |
| `preprocess` | `(string $code, array &$options, $context): ?string` | před tokenizací; může přepsat zdroj a změnit možnosti |
| `tokens` | `(array $tokens, $context): ?array` | po tokenizaci, na 2D mřížce `ThemedToken` |
| `root` | `(Element $el, $context): ?Element` | na kořenovém uzlu HAST |
| `pre` | `(Element $el, $context): ?Element` | na prvku `<pre>` |
| `code` | `(Element $el, $context): ?Element` | na prvku `<code>` |
| `line` | `(Element $el, int $line, $context): ?Element` | pro každý prvek řádku (index od 1) |
| `span` | `(Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element` | pro každý rozsah tokenu |
| `postprocess` | `(string $html, array $options, $context): ?string` | s výsledným serializovaným řetězcem HTML |

Všechny hooky jsou nepovinné. Vrácená hodnota nahradí vstup; vrácení `null`
zachová původní hodnotu.

Pořadí mezi transformátory řídí `enforce()`: `'pre'` se spouští první, `'post'`
poslední a `null` (výchozí hodnota) mezi nimi v pořadí registrace.

## Vytvoření transformátoru

Rozšiřte `Shikiphp\Transformer\AbstractTransformer` a přepište pouze hooky,
které potřebujete; ostatní neprovedou žádnou akci. Kontext transformátoru zpřístupňuje
`addClassToHast()` pro přidávání tříd prvkům.

```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" …>
```

## Vestavěné transformátory

### Transformátory zápisu

Tyto nástroje čtou speciální komentáře `// [!code …]` ve zdrojovém kódu (syntaxe komentářů jazyka se rozpozná automaticky) a převádějí je na třídy, přičemž samotný komentář odstraní. Jde o přímé porty sady zápisu `@shikijs/transformers`.

#### `NotationHighlight`

`Shikiphp\Transformer\Notation\NotationHighlight` — `// [!code highlight]` (nebo `// [!code hl]`) přidá řádku třídu `highlighted`.

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

#### `NotationDiff`

`Shikiphp\Transformer\Notation\NotationDiff` — `// [!code ++]` označí řádek jako přidaný (`diff add`), `// [!code --]` jako odebraný (`diff remove`).

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

#### `NotationFocus`

`Shikiphp\Transformer\Notation\NotationFocus` — `// [!code focus]` přidá třídu `focused`.

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

#### `NotationErrorLevel`

`Shikiphp\Transformer\Notation\NotationErrorLevel` — `// [!code error]`, `// [!code warning]` a `// [!code info]` přidají třídu `highlighted` a také `error` / `warning` / `info`.

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

#### `NotationWordHighlight`

`Shikiphp\Transformer\Notation\NotationWordHighlight` — `// [!code word:foo]` (volitelně `// [!code word:foo:2]` omezí na následujících N řádků) obalí každý výskyt `foo` prvkem span nesoucím `highlighted-word`.

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

### Transformátory metadat

Čtou **meta řetězec** zadaný v metadatech bloku kódu přes volbu [`meta`](https://shikiphp.inline0.dev/cs/docs/options#meta) (`meta['__raw']`), tedy text za jazykem bloku kódu, který čte Shiki.

#### `MetaHighlight`

`Shikiphp\Transformer\MetaHighlight` — zvýrazní řádky uvedené v rozsahu metadat, například `{1,3-5}`, a přidá třídu `highlighted`.

```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` — zvýrazní slova uvedená v meta řetězci pomocí syntaxe `/word/` a přidá `highlighted-word`.

### Bílé znaky a struktura

#### `RenderWhitespace`

`Shikiphp\Transformer\RenderWhitespace` — obalí mezery a tabulátory do prvků span (ve výchozím nastavení třídy `space` a `tab`), aby je bylo možné viditelně vykreslit. Konstruktor přijímá `(classSpace, classTab, position)`, kde `position` je `'all'` (výchozí hodnota), `'boundary'`, `'trailing'` nebo `'leading'`.

#### `CompactLineOptions`

`Shikiphp\Transformer\CompactLineOptions` — použije třídy na konkrétní řádky podle seznamu položek `{line, classes}`.

```php
use Shikiphp\Transformer\CompactLineOptions;

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

Číslování řádků začíná od 1. Objekt předejte ve volbě `transformers` stejně jako jakýkoli jiný transformátor.

#### `RemoveNotationEscape`

`Shikiphp\Transformer\RemoveNotationEscape` — odstraní escape z podoby `[\!code …]`, která slouží k zápisu doslovného komentáře se zápisem bez spuštění transformátoru zápisu.

#### `RemoveLineBreak`

`Shikiphp\Transformer\RemoveLineBreak` — odstraní textové uzly zalomení řádků mezi řádky (`transformerRemoveLineBreak` nástroje Shiki).

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — přesune vložené styly do generovaných tříd a shromáždí šablonu stylů, kterou získáte přes `getCSS()`. Názvy tříd jsou hashem stylu pomocí `cyrb53` nástroje Shiki, takže výstup je bajtově totožný s `@shikijs/transformers` 4.2.0 včetně jeho rozsahu:

- Styl `<pre>` se vždy převede na třídu.
- Rozpětí tokenů se převádějí jen tehdy, když nesou předem vytvořený styl, což nastává u mapy `themes`. Při jediném `theme` si rozpětí tokenů zachovají vložené `style="color:…"`, stejně jako v 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
```

Konstruktor přijímá `(classPrefix, classSuffix, classReplacer)`; výchozí prefix je `__shiki_`.
