トランスフォーマー
トランスフォーマーを使うと、元のソース文字列からトークングリッド、最終的な HAST ツリー、HTMLに至るまで、各段階でハイライト結果を加工できます。 shikiphp は Shiki のトランスフォーマーモデルをフックごとに移植しているため、トランスフォーマーはJavaScript版と同じように動作します。
transformersオプションで渡します。
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() が用意されています。
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 クラスが追加されます。
console.log('kept plain')
console.log('this line is highlighted') // [!code highlight]
NotationDiff
Shikiphp\Transformer\Notation\NotationDiff — // [!code ++]は行を追加として(diff add)、// [!code --]は削除として(diff remove)マークします。
console.log('old line') // [!code --]
console.log('new line') // [!code ++]
NotationFocus
Shikiphp\Transformer\Notation\NotationFocus — // [!code focus]を指定すると、focused クラスが追加されます。
console.log('focus on me') // [!code focus]
NotationErrorLevel
Shikiphp\Transformer\Notation\NotationErrorLevel — // [!code error]、// [!code warning]、// [!code info]を指定すると、highlighted クラスと error / warning / info が追加されます。
throwSomething() // [!code error]
maybeRisky() // [!code warning]
NotationWordHighlight
Shikiphp\Transformer\Notation\NotationWordHighlight — // [!code word:foo](次の N 行に限定する場合は // [!code word:foo:2])を指定すると、foo の各出現箇所が highlighted-word を持つスパンで囲まれます。
// [!code word:config]
const config = loadConfig()
メタトランスフォーマー
コードフェンスの metaオプション(meta['__raw'])で渡されたメタ文字列を読み取ります。Shiki がフェンス付きコードブロックの言語名の後にあるテキストを読み取るのと同じ方法です。
MetaHighlight
Shikiphp\Transformer\MetaHighlight — {1,3-5} のようなメタ範囲で指定された行をハイライトし、highlighted クラスを追加します。
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} エントリーのリストに従って、指定した行にクラスを適用します。
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:…"を維持します。
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_ です。