在 DokuWiki 中,如果你自定义一个语法标记,而这个标记内部包含多个子指令(嵌套的语法元素),你需要使用 DokuWiki 的嵌套处理机制来正确渲染它们。
处理嵌套内容的关键步骤
- 定义允许的子语法类型:通过
getAllowedTypes()
方法指定你的语法插件内部允许哪些类型的语法元素 - 使用 Doku_Handler_Nest 处理嵌套内容:在
handle()
方法中使用嵌套处理器 - 在
render()
方法中使用$renderer->nest()
渲染嵌套内容
具体实现方法
1. 定义允许的子语法类型
首先,你需要在你的插件中通过 getAllowedTypes()
方法指定允许哪些类型的语法元素嵌套在你的标记内部:
public function getAllowedTypes() {
// 允许格式化和替换类型的语法元素在插件内部使用
return ['formatting', 'substition'];
}
2. 在 handle() 方法中处理嵌套内容
在 handle()
方法中,你需要使用 Doku_Handler_Nest
类来处理嵌套内容:
public function handle($match, $state, $pos, \Doku_Handler $handler) {
switch ($state) {
case DOKU_LEXER_ENTER:
// 处理开始标记
return ['state' => $state];
case DOKU_LEXER_UNMATCHED:
// 这里是关键 - 创建一个嵌套处理器来处理内部内容
$nestHandler = new \dokuwiki\Parsing\Handler\Nest($handler->getCallWriter());
// 解析嵌套内容
$instructions = p_get_instructions($match);
// 存储解析后的指令
return ['state' => $state, 'instructions' => $instructions];
case DOKU_LEXER_EXIT:
// 处理结束标记
return ['state' => $state];
}
return false;
}
3. 在 render() 方法中渲染嵌套内容
最后,在 render()
方法中,你需要使用 $renderer->nest()
方法来渲染嵌套内容:
public function render($format, \Doku_Renderer $renderer, $data) {
if ($format == 'xhtml') {
switch ($data['state']) {
case DOKU_LEXER_ENTER:
// 开始你的自定义容器
$renderer->doc .= '<div class="my-custom-container">';
break;
case DOKU_LEXER_UNMATCHED:
if (isset($data['instructions'])) {
// 使用 nest() 方法渲染嵌套内容
$renderer->nest($data['instructions']);
} else {
// 如果没有嵌套指令,就直接输出文本
$renderer->doc .= $renderer->_xmlEntities($data['text']);
}
break;
case DOKU_LEXER_EXIT:
// 结束你的自定义容器
$renderer->doc .= '</div>';
break;
}
return true;
}
return false;
}
完整示例
下面是一个完整的示例,展示如何创建一个支持嵌套内容的自定义语法插件:
<?php
/**
* 自定义容器语法插件示例 - 支持嵌套内容
*/
class syntax_plugin_example_container extends \dokuwiki\Extension\SyntaxPlugin {
/**
* 返回插件类型
*/
public function getType() {
return 'container';
}
/**
* 返回段落类型
*/
public function getPType() {
return 'block';
}
/**
* 返回允许的模式类型 - 这里是关键
*/
public function getAllowedTypes() {
// 允许这些类型的语法元素在我们的容器内部使用
return ['formatting', 'substition', 'disabled', 'protected'];
}
/**
* 返回模式排序值
*/
public function getSort() {
return 50;
}
/**
* 将模式连接到词法分析器
*/
public function connectTo($mode) {
$this->Lexer->addEntryPattern('<mycontainer>(?=.*?</mycontainer>)', $mode, 'plugin_example_container');
}
public function postConnect() {
$this->Lexer->addExitPattern('</mycontainer>', 'plugin_example_container');
}
/**
* 处理匹配的数据
*/
public function handle($match, $state, $pos, \Doku_Handler $handler) {
switch ($state) {
case DOKU_LEXER_ENTER:
return ['state' => $state];
case DOKU_LEXER_UNMATCHED:
// 创建一个嵌套处理器
$nestHandler = new \dokuwiki\Parsing\Handler\Nest($handler->getCallWriter());
// 解析嵌套内容
$instructions = p_get_instructions($match);
// 返回解析后的指令
return ['state' => $state, 'instructions' => $instructions];
case DOKU_LEXER_EXIT:
return ['state' => $state];
}
return false;
}
/**
* 创建输出
*/
public function render($format, \Doku_Renderer $renderer, $data) {
if ($format == 'xhtml') {
switch ($data['state']) {
case DOKU_LEXER_ENTER:
$renderer->doc .= '<div class="my-custom-container">';
break;
case DOKU_LEXER_UNMATCHED:
if (isset($data['instructions'])) {
// 渲染嵌套内容
$renderer->nest($data['instructions']);
}
break;
case DOKU_LEXER_EXIT:
$renderer->doc .= '</div>';
break;
}
return true;
}
return false;
}
}
关键点解释
nest()
方法:DokuWiki 的渲染器提供了nest()
方法,专门用于渲染嵌套的指令。这个方法在Doku_Renderer
类中定义: renderer.php:136-143- 嵌套处理:当 DokuWiki 渲染嵌套内容时,它会递归地处理每条指令,并调用相应的渲染器方法: renderer.php:138-142
- 允许的类型:通过
getAllowedTypes()
方法,你可以控制哪些类型的语法元素可以在你的插件内部使用: SyntaxPlugin.php:111-129
实际应用
使用这种方法,你可以创建复杂的自定义语法结构,例如:
<mycontainer>
这是**粗体文本**,这是//斜体文本//,
这是一个[[内部链接]],这是一个{{图片}}。
甚至可以包含代码块:
<code>
function example() {
return true;
}
</code>
</mycontainer>
所有这些嵌套的语法元素都会被正确解析和渲染,只要你在 getAllowedTypes()
方法中允许了相应的类型。
Notes
处理嵌套内容是 DokuWiki 语法插件开发中较为复杂的部分,但通过正确使用 getAllowedTypes()
和 nest()
方法,你可以创建支持丰富内容的自定义语法结构。
需要注意的是,p_get_instructions()
函数用于将文本转换为指令数组,而 $renderer->nest()
方法则用于渲染这些指令。这两个函数是处理嵌套内容的核心。