1

我正在尝试使用 League 的CommonMark 包创建一个扰流板。

该块由三个倒置的感叹号打开,可选地后跟摘要;三个正常的感叹号结束该块。

这是我到目前为止的代码:

元素

<?php
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Cursor;
class Spoiler extends AbstractBlock {
    private $summary;
    public function __construct($summary = null) {
        parent::__construct();
        $this->summary = $summary;
    }
    public function getSummary() { return $this->summary; }
    public function canContain(AbstractBlock $block) { return true; }
    public function acceptsLines() { return true; }
    public function isCode() { return false; }
    public function matchesNextLine(Cursor $cursor) {
        if ($cursor->match('(^!!!$)')) {
            $this->lastLineBlank = true;
            return false;
        }
        return true;
    }
}

解析器

<?php
use League\CommonMark\Block\Parser\AbstractBlockParser;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
class SpoilerParser extends AbstractBlockParser {
    public function parse(ContextInterface $context, Cursor $cursor) {
        if ($cursor->isIndented()) return false;

        $previousState = $cursor->saveState();
        $spoiler = $cursor->match('(^¡¡¡(\s*.+)?)');
        if (is_null($spoiler)) {
            $cursor->restoreState($previousState);
            return false;
        }

        $summary = trim(mb_substr($spoiler, mb_strlen('¡¡¡')));
        if ($summary !== '') {
            $context->addBlock(new Spoiler($summary));
        } else {
            $context->addBlock(new Spoiler());
        }

        return true;
    }
}

渲染器

<?php
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Renderer\BlockRendererInterface;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
class SpoilerRenderer implements BlockRendererInterface {
    public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, $inTightList = false) {
        if (!($block instanceof Spoiler)) throw new \InvalidArgumentException('Incompatible block type: ' . get_class($block));
        $summary = new HtmlElement('summary', [], $block->getSummary() ?: 'Click to expand spoiler');
        $content = $summary . "\n" . $htmlRenderer->renderBlocks($block->children());
        return new HtmlElement('details', [], $content);
    }
}

当我嵌套多个扰流块时会出现问题:第一个终止符关闭所有块。

¡¡¡
1
¡¡¡
2
¡¡¡
Hello
!!!
3
!!!
4
!!!

这是解析的 AST:

League\CommonMark\Block\Element\Document
    App\Helpers\Formatting\Element\Spoiler
        League\CommonMark\Block\Element\Paragraph
            League\CommonMark\Inline\Element\Text "1"
        App\Helpers\Formatting\Element\Spoiler
            League\CommonMark\Block\Element\Paragraph
                League\CommonMark\Inline\Element\Text "2"
            App\Helpers\Formatting\Element\Spoiler
                League\CommonMark\Block\Element\Paragraph
                    League\CommonMark\Inline\Element\Text "Hello"
    League\CommonMark\Block\Element\Paragraph
        League\CommonMark\Inline\Element\Text "3"
        League\CommonMark\Inline\Element\Newline
        League\CommonMark\Inline\Element\Text "!!!"
        League\CommonMark\Inline\Element\Newline
        League\CommonMark\Inline\Element\Text "4"
        League\CommonMark\Inline\Element\Newline
        League\CommonMark\Inline\Element\Text "!!!"

这是预期的 AST:

League\CommonMark\Block\Element\Document
    App\Helpers\Formatting\Element\Spoiler
        League\CommonMark\Block\Element\Paragraph
            League\CommonMark\Inline\Element\Text "1"
        App\Helpers\Formatting\Element\Spoiler
            League\CommonMark\Block\Element\Paragraph
                League\CommonMark\Inline\Element\Text "2"
            App\Helpers\Formatting\Element\Spoiler
                League\CommonMark\Block\Element\Paragraph
                    League\CommonMark\Inline\Element\Text "Hello"
            League\CommonMark\Block\Element\Paragraph
                League\CommonMark\Inline\Element\Text "3"
        League\CommonMark\Block\Element\Paragraph
            League\CommonMark\Inline\Element\Text "4"
4

1 回答 1

1

在这种情况下,将始终根据迭代 AST的方式matchesNextLine()在顶层运行。相反,我建议使用来检查结束语法。例如,您可以在现有解析器中添加类似这样的内容:SpoilerDocParser::resetContainer()SpoilerParser::parse()

if ($cursor->match('/^!!!$/')) {
    $container = $context->getContainer();
    do {
        if ($container instanceof Spoiler) {
            $context->setContainer($container);
            $context->setTip($container);
            $context->getBlockCloser()->setLastMatchedContainer($container);
            return true;
        }
    } while ($container = $container->parent());
}

这似乎产生了预期的输出:

<details><summary>Click to expand spoiler</summary>
<p>1</p>
<details><summary>Click to expand spoiler</summary>
<p>2</p>
<details><summary>Click to expand spoiler</summary>
<p>Hello</p></details>
<p>3</p></details>
<p>4</p></details>
<p></p>

免责声明:虽然基于此输出的 AST 可能是正确的,但我没有验证 AST 本身。我也没有检查我的建议是否会对解析过程产生负面影响,可能会导致其他元素出现问题或更深的嵌套,因此您可能需要跟踪它。但是这种通用方法(!!!在解析器中解析并操作上下文/AST)可能是您最好的选择。

我希望这会有所帮助!

于 2016-12-18T23:59:23.570 回答