
# 트랜스포머

트랜스포머를 사용하면 원본 문자열부터 토큰 그리드, 최종 HAST 트리와 HTML까지 각 단계에서 강조 표시된 출력을 원하는 형태로 바꿀 수 있습니다. shikiphp은 Shiki의 트랜스포머 모델을 훅 단위까지 동일하게 포팅하므로, JavaScript 버전과 똑같이 작동합니다.

[`transformers`](https://shikiphp.inline0.dev/ko/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`의 각 항목을
`highlighted-word`를 포함하는 span으로 감쌉니다.

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

### 메타 변환기

[`meta`](https://shikiphp.inline0.dev/ko/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` — 줄 사이의 줄바꿈 텍스트 노드(Shiki의 `transformerRemoveLineBreak`)를 제거합니다.

### `StyleToClass`

`Shikiphp\Transformer\StyleToClass` — 인라인 스타일을 생성된 클래스로 옮기고 `getCSS()`로 가져오는 스타일시트를 수집합니다. 클래스 이름은 Shiki의 `cyrb53`으로 스타일을 해시하므로, 범위를 포함해 출력이 `@shikijs/transformers` 4.2.0와 바이트 단위로 동일합니다.

- `<pre>` 스타일은 항상 클래스로 변환됩니다.
- 토큰 span은 사전 생성된 스타일이 있을 때만 변환되며, 이는 `themes` 맵을 사용하는 경우입니다. 단일 `theme`을 사용하면 토큰 span은 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_`입니다.
