コンテンツへスキップ

トランスフォーマー

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

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 クラスが追加されます。

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

NotationDiff

Shikiphp\Transformer\Notation\NotationDiff — // [!code ++]は行を追加として(diff add)、// [!code --]は削除として(diff remove)マークします。

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

NotationFocus

Shikiphp\Transformer\Notation\NotationFocus — // [!code focus]を指定すると、focused クラスが追加されます。

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

NotationErrorLevel

Shikiphp\Transformer\Notation\NotationErrorLevel — // [!code error]、// [!code warning]、// [!code info]を指定すると、highlighted クラスと error / warning / info が追加されます。

JavaScript
throwSomething() // [!code error]
maybeRisky()     // [!code warning]

NotationWordHighlight

Shikiphp\Transformer\Notation\NotationWordHighlight — // [!code word:foo](次の N 行に限定する場合は // [!code word:foo:2])を指定すると、foo の各出現箇所が highlighted-word を持つスパンで囲まれます。

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

メタトランスフォーマー

コードフェンスの 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_ です。