
# トランスフォーマー

トランスフォーマーを使うと、元のソース文字列からトークングリッド、最終的な HAST ツリー、HTMLに至るまで、各段階でハイライト結果を加工できます。
shikiphp は Shiki のトランスフォーマーモデルをフックごとに移植しているため、トランスフォーマーはJavaScript版と同じように動作します。

[`transformers`](https://shikiphp.inline0.dev/ja/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]`（次の N 行に限定する場合は `// [!code word:foo:2]`）を指定すると、`foo` の各出現箇所が `highlighted-word` を持つスパンで囲まれます。

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

### メタトランスフォーマー

コードフェンスの [`meta`](https://shikiphp.inline0.dev/ja/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` — 空白文字とタブ文字をスパンで囲みます（デフォルトのクラスは `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` — 行間の改行テキストノードを削除します（Shiki の `transformerRemoveLineBreak`）。

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — インラインスタイルを生成クラスに移し、`getCSS()` で取得できるスタイルシートを収集します。クラス名には Shiki の `cyrb53` でスタイルのハッシュ値を使うため、適用範囲も含めて Shiki 4.2.0 とバイト単位で同一の出力になります`@shikijs/transformers`。

- `<pre>` スタイルは常にクラスになります。
- トークンスパンの変換は、あらかじめスタイルが設定されている場合に限られます。これは `themes` マップを使う場合です。単一の `theme` を使う場合、トークンスパンは Shiki と同様にインラインの `style="color:…"` を維持します。

```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_` です。
