1
0
mirror of https://github.com/erusev/parsedown.git synced 2023-08-10 21:13:06 +03:00
Files
parsedown/src/Components/Inlines/Link.php
2021-10-16 00:45:18 +01:00

155 lines
3.9 KiB
PHP

<?php
namespace Erusev\Parsedown\Components\Inlines;
use Erusev\Parsedown\AST\Handler;
use Erusev\Parsedown\Components\Inline;
use Erusev\Parsedown\Configurables\DefinitionBook;
use Erusev\Parsedown\Configurables\InlineTypes;
use Erusev\Parsedown\Configurables\SafeMode;
use Erusev\Parsedown\Html\Renderables\Element;
use Erusev\Parsedown\Html\Renderables\Text;
use Erusev\Parsedown\Html\Sanitisation\UrlSanitiser;
use Erusev\Parsedown\Parsedown;
use Erusev\Parsedown\Parsing\Excerpt;
use Erusev\Parsedown\State;
/** @psalm-type _Metadata=array{href: string, title?: string} */
final class Link implements Inline
{
use WidthTrait;
/** @var string */
private $label;
/** @var string */
private $url;
/** @var string|null */
private $title;
/**
* @param string $label
* @param string $url
* @param string|null $title
* @param int $width
*/
private function __construct($label, $url, $title, $width)
{
$this->label = $label;
$this->url = $url;
$this->title = $title;
$this->width = $width;
}
/**
* @param Excerpt $Excerpt
* @param State $State
* @return static|null
*/
public static function build(Excerpt $Excerpt, State $State)
{
$remainder = $Excerpt->text();
if (! \preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) {
return null;
}
$label = $matches[1];
$width = \strlen($matches[0]);
$remainder = \substr($remainder, $width);
if (\preg_match('/^[(]\s*+(?:((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+)?[)]/', $remainder, $matches)) {
$url = isset($matches[1]) ? $matches[1] : '';
$title = isset($matches[2]) ? \substr($matches[2], 1, - 1) : null;
$width += \strlen($matches[0]);
return new self($label, $url, $title, $width);
} else {
if (\preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) {
$definition = \strlen($matches[1]) ? $matches[1] : $label;
$definition = \strtolower($definition);
$width += \strlen($matches[0]);
} else {
$definition = \strtolower($label);
}
$definition = \preg_replace('/\s++/', ' ', \trim($definition));
$data = $State->get(DefinitionBook::class)->lookup($definition);
if (! isset($data)) {
return null;
}
$url = $data['url'];
$title = isset($data['title']) ? $data['title'] : null;
return new self($label, $url, $title, $width);
}
}
/** @return string */
public function label()
{
return $this->label;
}
/** @return string */
public function url()
{
return $this->url;
}
/** @return string|null */
public function title()
{
return $this->title;
}
/**
* @return Handler<Element>
*/
public function stateRenderable()
{
return new Handler(
/** @return Element */
function (State $State) {
$attributes = ['href' => $this->url()];
$title = $this->title();
if (isset($title)) {
$attributes['title'] = $title;
}
if ($State->get(SafeMode::class)->isEnabled()) {
$attributes['href'] = UrlSanitiser::filter($attributes['href']);
}
$State = $State->setting(
$State->get(InlineTypes::class)->removing([Url::class])
);
return new Element(
'a',
$attributes,
$State->applyTo(Parsedown::line($this->label(), $State))
);
}
);
}
/**
* @return Text
*/
public function bestPlaintext()
{
return new Text($this->label());
}
}