转换器
转换器可在每个阶段重塑高亮输出——从原始源字符串、令牌网格,到最终的 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 |
分词后,在二维 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](可选用 // [!code word:foo:2] 限定到接下来的 N 行)会将每个 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 对样式进行哈希,因此输出与 @shikijs/transformers 4.2.0 字节完全一致,包括作用域:
<pre>样式始终会转换为类。- 令牌跨度只有在包含预先构建的样式时才会转换,例如使用
themes映射时。使用单个theme时,令牌跨度会保留其内联style="color:…",与 Shiki 中的行为一致。
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_。