
# Трансформатори

Трансформаторите ви позволяват да преобразувате откроения изход на всеки етап — от
суровия низ с изходния код, през мрежата от токени до крайното HAST дърво и HTML.
shikiphp възпроизвежда модела на трансформаторите на Shiki кукичка по кукичка, така че трансформаторите се държат
по същия начин като съответните си варианти за JavaScript.

Подайте ги чрез опцията [`transformers`](https://shikiphp.inline0.dev/bg/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(),
    ],
]);
```

## Конвейерът

Всеки трансформатор е обект, който реализира `Shikiphp\Transformer\Transformer`.
Конвейерът изпълнява кукичките последователно при изграждането на един блок:

| Кукичка | Сигнатура | Кога |
| --- | --- | --- |
| `preprocess` | `(string $code, array &$options, $context): ?string` | преди токенизацията; може да пренапише изходния код и да промени опциите |
| `tokens` | `(array $tokens, $context): ?array` | след токенизацията, върху 2D мрежата от `ThemedToken` |
| `root` | `(Element $el, $context): ?Element` | върху корена на HAST |
| `pre` | `(Element $el, $context): ?Element` | върху елемента `<pre>` |
| `code` | `(Element $el, $context): ?Element` | върху елемента `<code>` |
| `line` | `(Element $el, int $line, $context): ?Element` | за всеки елемент на ред (от 1) |
| `span` | `(Element $el, int $line, int $col, Element $lineEl, ThemedToken $token, $context): ?Element` | за всеки обхват на токен |
| `postprocess` | `(string $html, array $options, $context): ?string` | върху крайния сериализиран низ HTML |

Всички кукички са незадължителни. Връщането на стойност заменя входа; връщането на `null`
го оставя непроменен.

`enforce()` управлява реда на трансформаторите: `'pre'` се изпълнява първо, `'post'`
се изпълнява последно, а `null` (по подразбиране) се изпълнява между тях според реда на регистрация.

## Написване на трансформатор

Разширете `Shikiphp\Transformer\AbstractTransformer`, за да заменяте само нужните ви кукички; останалите не правят нищо. Контекстът на трансформатора предоставя `addClassToHast()` за добавяне на класове към елементите.

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

## Вградени трансформатори

### Трансформатори за нотация

Те прочитат специални коментари `// [!code …]` в изходния код (синтаксисът за коментари на езика се открива автоматично) и ги преобразуват в класове, като премахват самия коментар. Това са директни портове на набора от обозначения `@shikijs/transformers`.

#### `NotationHighlight`

`Shikiphp\Transformer\Notation\NotationHighlight` — `// [!code highlight]` (или `// [!code hl]`) добавя класа `highlighted` към реда.

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

#### `NotationDiff`

`Shikiphp\Transformer\Notation\NotationDiff` — `// [!code ++]` маркира ред като добавен (`diff add`), а `// [!code --]` — като премахнат (`diff remove`).

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

#### `NotationFocus`

`Shikiphp\Transformer\Notation\NotationFocus` — `// [!code focus]` добавя класа `focused`.

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

#### `NotationErrorLevel`

`Shikiphp\Transformer\Notation\NotationErrorLevel` — `// [!code error]`, `// [!code warning]` и `// [!code info]` добавят класа `highlighted` плюс `error` / `warning` / `info`.

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

#### `NotationWordHighlight`

`Shikiphp\Transformer\Notation\NotationWordHighlight` — `// [!code word:foo]` (по избор `// [!code word:foo:2]` за ограничаване до следващите N реда) обгръща всяко срещане на `foo` в span с `highlighted-word`.

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

### Метатрансформатори

Те прочитат **метаниза** на ограден блок с код, подаден чрез опцията [`meta`](https://shikiphp.inline0.dev/bg/docs/options#meta) (`meta['__raw']`), подобно на начина, по който Shiki прочита текста след езика на ограден блок с код.

#### `MetaHighlight`

`Shikiphp\Transformer\MetaHighlight` — откроява редовете, посочени в метадиапазон, например `{1,3-5}`, като добавя класа `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` — откроява думите, посочени в метаниза с помощта на синтаксиса `/word/`, като добавя `highlighted-word`.

### Интервали и структура

#### `RenderWhitespace`

`Shikiphp\Transformer\RenderWhitespace` — обгръща интервалите и табулациите в span елементи (по подразбиране с класове `space` и `tab`), за да могат да се визуализират. Конструкторът приема `(classSpace, classTab, position)`, където `position` е `'all'` (по подразбиране), `'boundary'`, `'trailing'` или `'leading'`.

#### `CompactLineOptions`

`Shikiphp\Transformer\CompactLineOptions` — прилага класове към конкретни редове от списък с елементи `{line, classes}`.

```php
use Shikiphp\Transformer\CompactLineOptions;

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

Номерата на редовете започват от 1. Подайте обекта в опцията `transformers` като всеки друг трансформатор.

#### `RemoveNotationEscape`

`Shikiphp\Transformer\RemoveNotationEscape` — премахва екранирането на формата `[\!code …]`, използвана за записване на буквален коментар с обозначение, без да задейства трансформатор за обозначения.

#### `RemoveLineBreak`

`Shikiphp\Transformer\RemoveLineBreak` — премахва текстовите възли за нов ред между редовете (`transformerRemoveLineBreak` на Shiki).

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — премества вградените стилове в генерирани класове и събира таблица със стилове, която се извлича с `getCSS()`. Имената на класовете са хеширани от стила чрез `cyrb53` на Shiki, така че изходът е байт по байт идентичен с `@shikijs/transformers` 4.2.0, включително обхвата му:

- Стилът `<pre>` винаги става клас.
- Обхватите на токените се преобразуват само когато имат предварително създаден стил, какъвто е случаят с карта `themes`. При единичен `theme` обхватите на токените запазват вградения си `style="color:…"`, както в 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
```

Конструкторът приема `(classPrefix, classSuffix, classReplacer)`; префиксът по подразбиране е `__shiki_`.
