
# 转换器

转换器可在每个阶段重塑高亮输出——从原始源字符串、令牌网格，到最终的 HAST 树和 HTML。shikiphp逐个钩子照搬了 Shiki的转换器模型，因此转换器的行为与其 JavaScript版本相同。

通过 [`transformers`](https://shikiphp.inline0.dev/zh/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` | 分词后，在二维 `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` 的出现包裹在带有 `highlighted-word` 的跨度中。

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

### 元数据转换器

这些转换器会读取通过 [`meta`](https://shikiphp.inline0.dev/zh/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` 对样式进行哈希，因此输出与 `@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_`。
