thunderer/shortcode

PHP的高级短代码(BBCode)解析器和引擎

v0.7.5 2022-01-13 18:53 UTC

README

Build Status Latest Stable Version Total Downloads License Psalm coverage Code Coverage Scrutinizer Code Quality

Shortcode是一个框架无关的PHP库,允许查找、提取和处理称为“短代码”或“BBCodes”的文本片段。下面展示了它们常用的语法和用法示例

[user-profile /]
[image width=600]
[link href="http://google.pl" color=red]
[quote="Thunderer"]This is a quote.[/quote]
[text color="red"]This is a text.[/text]

该库分为几个部分,每个部分都包含负责不同阶段和数据处理方式的逻辑

  • 解析器从文本中提取短代码并将其转换为对象,
  • 处理器将短代码转换为所需的替换,
  • 处理器使用解析器和处理器提取短代码、计算替换并将它们应用到文本中,
  • 事件修改处理器的工作方式,以提供更好的整个过程控制,
  • 序列化器将短代码从和到不同格式如文本、XML、JSON和YAML的转换。

每个部分都在本文档的专门部分中描述。

安装

没有必需的依赖项,并支持从5.3到最新的8.1的所有PHP版本 都已测试。此库在Composer/Packagist上作为thunderer/shortcode提供,要安装它,请执行

composer require thunderer/shortcode=^0.7

或手动更新您的composer.json

(...)
"require": {
    "thunderer/shortcode": "^0.7"
}
(...)

然后运行composer installcomposer update。如果您不使用Composer,请从GitHub下载源代码并按需加载它们。但请真的使用Composer。

使用

外观

为了简化此库的使用,有一个名为ShortcodeFacade的类,用于满足大多数常见需求。它包含以下部分中描述的所有功能的快捷方法

  • addHandler():添加短代码处理器,
  • addHandlerAlias():添加短代码处理器别名,
  • process():处理文本并替换短代码,
  • parse():将文本解析为短代码,
  • setParser():更改处理器的解析器,
  • addEventHandler():添加事件处理器,
  • serialize():将短代码对象序列化为指定的格式,
  • unserialize():从序列化输入创建短代码对象。

处理

短代码通过Processor进行处理,它需要一个解析器和处理器。以下示例展示了如何实现一个示例,该示例使用作为参数传递的名称问候人员

use Thunder\Shortcode\HandlerContainer\HandlerContainer;
use Thunder\Shortcode\Parser\RegularParser;
use Thunder\Shortcode\Processor\Processor;
use Thunder\Shortcode\Shortcode\ShortcodeInterface;

$handlers = new HandlerContainer();
$handlers->add('hello', function(ShortcodeInterface $s) {
    return sprintf('Hello, %s!', $s->getParameter('name'));
});
$processor = new Processor(new RegularParser(), $handlers);

$text = '
    <div class="user">[hello name="Thomas"]</div>
    <p>Your shortcodes are very good, keep it up!</p>
    <div class="user">[hello name="Peter"]</div>
';
echo $processor->process($text);

外观示例

use Thunder\Shortcode\ShortcodeFacade;
use Thunder\Shortcode\Shortcode\ShortcodeInterface;

$facade = new ShortcodeFacade();
$facade->addHandler('hello', function(ShortcodeInterface $s) {
    return sprintf('Hello, %s!', $s->getParameter('name'));
});

$text = '
    <div class="user">[hello name="Thomas"]</div>
    <p>Your shortcodes are very good, keep it up!</p>
    <div class="user">[hello name="Peter"]</div>
';
echo $facade->process($text);

两者都产生以下结果

    <div class="user">Hello, Thomas!</div>
    <p>Your shortcodes are very good, keep it up!</p>
    <div class="user">Hello, Peter!</div>

配置

Processor有几个配置选项可用,作为with*()方法,这些方法返回新的、更改后的实例以保持对象不可变。

  • withRecursionDepth($depth)控制嵌套级别 - 实际处理了多少层短代码。如果达到此限制,则忽略级别以下的全部短代码。如果$depth值是null(默认值),则不检查嵌套级别,如果它是零,则禁用嵌套(只处理最顶层的短代码)。任何大于零的整数设置嵌套级别限制,
  • withMaxIterations($iterations)控制源文本处理的次数。这意味着源文本在内部处理该次数,直到达到限制或没有短代码为止。如果$iterations参数值是null,则没有迭代限制,任何大于零的整数设置限制。默认为一轮迭代,
  • withAutoProcessContent($flag)控制在调用其处理器之前自动处理短代码的内容。如果$flag参数是true,则处理器接收已处理内容的短代码,如果false,则处理器必须自己处理嵌套短代码(或将其留待剩余迭代处理)。这是默认开启的,
  • withEventContainer($events) 注册事件容器,它为处理文本各个阶段的所有事件提供处理程序。有关事件的更多信息,请参阅专门介绍事件的部分。

事件

如果处理器配置了事件容器,则有几个方法可以更改处理短代码的方式

  • Events::FILTER_SHORTCODES 使用 FilterShortcodesEvent 类。它接收当前父短代码和解析器中的短代码数组。它的目的是在处理之前修改该数组
  • Events::REPLACE_SHORTCODES 使用 ReplaceShortcodesEvent 类并接收父短代码、当前处理文本和替换数组。它可以更改短代码处理程序结果应用到源文本的方式。如果没有监听器设置结果,则使用默认方法。

Thunder\Shortcode\EventHandler 命名空间中包含几个可用的事件处理器

  • FilterRawEventHandler 实现 FilterShortcodesEvent 并允许实现任意数量的“原始”短代码,其内容不进行处理
  • ReplaceJoinEventHandler 实现 ReplaceShortcodesEvent 并提供通过丢弃文本并仅返回替换内容来应用短代码替换的机制

以下示例展示了如何手动实现返回其内容而不调用嵌套短代码处理程序的 [raw] 短代码

use Thunder\Shortcode\Event\FilterShortcodesEvent;
use Thunder\Shortcode\EventContainer\EventContainer;
use Thunder\Shortcode\Events;
use Thunder\Shortcode\HandlerContainer\HandlerContainer;
use Thunder\Shortcode\Parser\RegularParser;
use Thunder\Shortcode\Processor\Processor;
use Thunder\Shortcode\Shortcode\ShortcodeInterface;

$handlers = new HandlerContainer();
$handlers->add('raw', function(ShortcodeInterface $s) { return $s->getContent(); });
$handlers->add('n', function(ShortcodeInterface $s) { return $s->getName(); });
$handlers->add('c', function(ShortcodeInterface $s) { return $s->getContent(); });

$events = new EventContainer();
$events->addListener(Events::FILTER_SHORTCODES, function(FilterShortcodesEvent $event) {
    $parent = $event->getParent();
    if($parent && ($parent->getName() === 'raw' || $parent->hasAncestor('raw'))) {
        $event->setShortcodes(array());
    }
});

$processor = new Processor(new RegularParser(), $handlers);
$processor = $processor->withEventContainer($events);

assert(' [n /] [c]cnt[/c] ' === $processor->process('[raw] [n /] [c]cnt[/c] [/raw]'));
assert('n true  [n /] ' === $processor->process('[n /] [c]true[/c] [raw] [n /] [/raw]'));

外观示例

use Thunder\Shortcode\Event\FilterShortcodesEvent;
use Thunder\Shortcode\Events;
use Thunder\Shortcode\Shortcode\ShortcodeInterface;
use Thunder\Shortcode\ShortcodeFacade;

$facade = new ShortcodeFacade();
$facade->addHandler('raw', function(ShortcodeInterface $s) { return $s->getContent(); });
$facade->addHandler('n', function(ShortcodeInterface $s) { return $s->getName(); });
$facade->addHandler('c', function(ShortcodeInterface $s) { return $s->getContent(); });

$facade->addEventHandler(Events::FILTER_SHORTCODES, function(FilterShortcodesEvent $event) {
    $parent = $event->getParent();
    if($parent && ($parent->getName() === 'raw' || $parent->hasAncestor('raw'))) {
        $event->setShortcodes(array());
    }
});

assert(' [n /] [c]cnt[/c] ' === $facade->process('[raw] [n /] [c]cnt[/c] [/raw]'));
assert('n true  [n /] ' === $facade->process('[n /] [c]true[/c] [raw] [n /] [/raw]'));

解析

本节讨论可用的短代码解析器。无论您选择哪个解析器,请记住

  • 短代码名称只能是字母数字字符和破折号 -,基本上必须符合 [a-zA-Z0-9-]+ 正则表达式
  • 不支持的短代码(未注册处理程序或默认处理程序)将被忽略并保持原样
  • 不匹配的关闭短代码([code]content[/codex])将被忽略,打开标签将被解释为自闭合短代码,例如 [code /]
  • 重叠短代码([code]content[inner][/code]content[/inner])将被解释为自闭合,例如 [code]content[inner /][/code],第二个关闭标签将被忽略

此库包含三个解析器

  • RegularParser 是此库中最强大、最正确的解析器。它包含一个实际解析器,用于处理与短代码相关的所有问题,如适当嵌套或检测无效短代码语法。它比下面描述的基于正则表达式的解析器稍微慢一些
  • RegexParser 使用专门为处理短代码语法而精心制作的正则表达式。它是此库中解析器中最快的,但它无法正确处理嵌套,这意味着具有相同名称的嵌套短代码也被视为重叠 - 假设短代码 [c] 返回其内容,字符串 [c]x[c]y[/c]z[/c] 将被解释为 xyz[/c](第一个关闭标签与第一个打开标签匹配)。这可以通过别名处理程序名称来解决,例如 [c]x[d]y[/d]z[/c] 将正确处理
  • WordpressParser 包含从最新可用的 WordPress(4.3.1)中复制的代码。它也是一个基于正则表达式的解析器,但包含的正则表达式相当弱,例如它不会支持 BBCode 语法([name="param" /])。默认情况下,此解析器支持短代码名称规则,但可以在使用命名构造函数(createFromHandlers()createFromNames())创建时破坏它,这些构造函数将其行为更改为仅捕获配置的名称。所有这些都是为了保持与 WordPress 能够做到的兼容性

语法

所有解析器(除 WordpressParser 外)都支持可配置的短代码语法,可以通过传递 SyntaxInterface 对象作为第一个参数来配置。有一个方便的类 CommonSyntax,其中包含默认语法。使用方法在下面的示例中展示

use Thunder\Shortcode\HandlerContainer\HandlerContainer;
use Thunder\Shortcode\Parser\RegexParser;
use Thunder\Shortcode\Parser\RegularParser;
use Thunder\Shortcode\Processor\Processor;
use Thunder\Shortcode\Shortcode\ShortcodeInterface;
use Thunder\Shortcode\Syntax\CommonSyntax;
use Thunder\Shortcode\Syntax\Syntax;
use Thunder\Shortcode\Syntax\SyntaxBuilder;

$builder = new SyntaxBuilder();

默认语法(在此库中称为“common”)

$defaultSyntax = new Syntax(); // without any arguments it defaults to common syntax
$defaultSyntax = new CommonSyntax(); // convenience class
$defaultSyntax = new Syntax('[', ']', '/', '=', '"'); // created explicitly
$defaultSyntax = $builder->getSyntax(); // builder defaults to common syntax

具有双标记的语法

$doubleSyntax = new Syntax('[[', ']]', '//', '==', '""');
$doubleSyntax = $builder // actually using builder
    ->setOpeningTag('[[')
    ->setClosingTag(']]')
    ->setClosingTagMarker('//')
    ->setParameterValueSeparator('==')
    ->setParameterValueDelimiter('""')
    ->getSyntax();

为了展示可能性,展示一些完全不同的事物

$differentSyntax = new Syntax('@', '#', '!', '&', '~');

验证每个语法是否正确工作

$handlers = new HandlerContainer();
$handlers->add('up', function(ShortcodeInterface $s) {
    return strtoupper($s->getContent());
});

$defaultRegex = new Processor(new RegexParser($defaultSyntax), $handlers);
$doubleRegex = new Processor(new RegexParser($doubleSyntax), $handlers);
$differentRegular = new Processor(new RegularParser($differentSyntax), $handlers);

assert('a STRING z' === $defaultRegex->process('a [up]string[/up] z'));
assert('a STRING z' === $doubleRegex->process('a [[up]]string[[//up]] z'));
assert('a STRING z' === $differentRegular->process('a @up#string@!up# z'));

序列化

此库支持多种(反)序列化格式 - XML、YAML、JSON和文本。下面的示例展示了如何以每种格式序列化和反序列化相同的短代码

use Thunder\Shortcode\Serializer\JsonSerializer;
use Thunder\Shortcode\Serializer\TextSerializer;
use Thunder\Shortcode\Serializer\XmlSerializer;
use Thunder\Shortcode\Serializer\YamlSerializer;
use Thunder\Shortcode\Shortcode\Shortcode;

$shortcode = new Shortcode('quote', array('name' => 'Thomas'), 'This is a quote!');

文本

$text = '[quote name=Thomas]This is a quote![/quote]';
$textSerializer = new TextSerializer();

$serializedText = $textSerializer->serialize($shortcode);
assert($text === $serializedText);
$unserializedFromText = $textSerializer->unserialize($serializedText);
assert($unserializedFromText->getName() === $shortcode->getName());

JSON

$json = '{"name":"quote","parameters":{"name":"Thomas"},"content":"This is a quote!","bbCode":null}';
$jsonSerializer = new JsonSerializer();
$serializedJson = $jsonSerializer->serialize($shortcode);
assert($json === $serializedJson);
$unserializedFromJson = $jsonSerializer->unserialize($serializedJson);
assert($unserializedFromJson->getName() === $shortcode->getName());

YAML

$yaml = "name: quote
parameters:
    name: Thomas
content: 'This is a quote!'
bbCode: null
";
$yamlSerializer = new YamlSerializer();
$serializedYaml = $yamlSerializer->serialize($shortcode);
assert($yaml === $serializedYaml);
$unserializedFromYaml = $yamlSerializer->unserialize($serializedYaml);
assert($unserializedFromYaml->getName() === $shortcode->getName());

XML

$xml = '<?xml version="1.0" encoding="UTF-8"?>
<shortcode name="quote">
  <bbCode/>
  <parameters>
    <parameter name="name"><![CDATA[Thomas]]></parameter>
  </parameters>
  <content><![CDATA[This is a quote!]]></content>
</shortcode>
';
$xmlSerializer = new XmlSerializer();
$serializedXml = $xmlSerializer->serialize($shortcode);
assert($xml === $serializedXml);
$unserializedFromXml = $xmlSerializer->unserialize($serializedXml);
assert($unserializedFromXml->getName() === $shortcode->getName());

门面也支持以所有可用格式进行序列化

use Thunder\Shortcode\Shortcode\Shortcode;
use Thunder\Shortcode\ShortcodeFacade;

$facade = new ShortcodeFacade();

$shortcode = new Shortcode('name', array('arg' => 'val'), 'content', 'bbCode');

$text = $facade->serialize($shortcode, 'text');
$textShortcode = $facade->unserialize($text, 'text');
assert($shortcode->getName() === $textShortcode->getName());

$json = $facade->serialize($shortcode, 'json');
$jsonShortcode = $facade->unserialize($json, 'json');
assert($shortcode->getName() === $jsonShortcode->getName());

$yaml = $facade->serialize($shortcode, 'yaml');
$yamlShortcode = $facade->unserialize($yaml, 'yaml');
assert($shortcode->getName() === $yamlShortcode->getName());

$xml = $facade->serialize($shortcode, 'xml');
$xmlShortcode = $facade->unserialize($xml, 'xml');
assert($shortcode->getName() === $xmlShortcode->getName());

处理器

Thunder\Shortcode\Handler 命名空间中,有几个内置的短代码处理器可用。下面的描述假设给定的处理器以 xyz 名称注册

  • NameHandler 总是返回短代码的名称。 [xyz arg=val]content[/xyz] 变为 xyz
  • ContentHandler 总是返回短代码的内容。它丢弃其开头和结尾标签。 [xyz]code[/xyz] 变为 code
  • RawHandler 返回未处理的短代码内容。其行为与 FilterRawEventHandler 不同,因为如果内容自动处理被打开,则将调用嵌套的短代码处理器,只是其结果被丢弃,
  • NullHandler 完全删除带有所有嵌套短代码的短代码,
  • DeclareHandler 允许动态创建以名称作为第一个参数的短代码处理器,该名称也将替换文本中传递的所有占位符。例如:[declare xyz]您的年龄是 %age%.[/declare] 创建了用于短代码 xyz 的处理器,当像 [xyz age=18] 这样使用时,结果是 您的年龄是 18。
  • EmailHandler 将电子邮件地址或短代码内容替换为可点击的 mailto: 链接
    • [xyz="email@example.com" /] 变为 <a href="email@example.com">email@example.com</a>
    • [xyz]email@example.com[/xyz] 变为 <a href="email@example.com">email@example.com</a>
    • [xyz="email@example.com"]联系我![/xyz] 变为 <a href="email@example.com">联系我!</a>
  • PlaceholderHandler 将短代码内容中的所有占位符替换为传递的参数的值。 [xyz year=1970]来自 %year% 的新闻。[/xyz] 变为 来自 1970 年的新闻。
  • SerializerHandler 使用类构造函数中传递的序列化器将短代码替换为其序列化值。如果配置为 JsonSerializer,则 [xyz /] 变为 {"name":"json", "arguments": [], "content": null, "bbCode": null}。这可以用于调试您的短代码,
  • UrlHandler 将其内容替换为可点击的链接
    • [xyz]http://example.com[/xyz] 变为 <a href="http://example.com">http://example.com</a>
    • [xyz="http://example.com"]访问我的网站![/xyz] 变为 <a href="http://example.com">访问我的网站!</a>
  • WrapHandler 允许指定应在短代码内容之前和之后放置的值。如果配置为 <strong></strong>,则文本 [xyz]粗体文本.[/xyz] 变为 <strong>粗体文本.</strong>

贡献

想要贡献?太好了!提交一个问题或拉取请求,并解释您想在这个库中看到什么。

许可

请参阅此库主目录中的 LICENSE 文件。