Commit 9190e0f4 by Carsten Brandt

Changed markdown library to cebe/markdown

- supports GFM now - allows defining different flavors fixes #47
parent d7da4e5a
...@@ -75,8 +75,7 @@ ...@@ -75,8 +75,7 @@
"yiisoft/yii2-composer": "*", "yiisoft/yii2-composer": "*",
"yiisoft/jquery": "~2.0 | ~1.10", "yiisoft/jquery": "~2.0 | ~1.10",
"ezyang/htmlpurifier": "4.6.*", "ezyang/htmlpurifier": "4.6.*",
"michelf/php-markdown": "1.3.*", "cebe/markdown": "0.9.*"
"phpspec/php-diff": ">=1.0.2"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "3.7.*", "phpunit/phpunit": "3.7.*",
......
...@@ -21,8 +21,7 @@ ...@@ -21,8 +21,7 @@
"require": { "require": {
"yiisoft/yii2": "*", "yiisoft/yii2": "*",
"yiisoft/yii2-bootstrap": "*", "yiisoft/yii2-bootstrap": "*",
"phpdocumentor/reflection": ">=1.0.3", "phpdocumentor/reflection": ">=1.0.3"
"erusev/parsedown": "0.9.*"
}, },
"autoload": { "autoload": {
"psr-4": { "yii\\apidoc\\": "" } "psr-4": { "yii\\apidoc\\": "" }
......
...@@ -7,10 +7,12 @@ ...@@ -7,10 +7,12 @@
namespace yii\apidoc\helpers; namespace yii\apidoc\helpers;
use cebe\markdown\GithubMarkdown;
use phpDocumentor\Reflection\DocBlock\Type\Collection; use phpDocumentor\Reflection\DocBlock\Type\Collection;
use yii\apidoc\models\MethodDoc; use yii\apidoc\models\MethodDoc;
use yii\apidoc\models\TypeDoc; use yii\apidoc\models\TypeDoc;
use yii\apidoc\templates\BaseRenderer; use yii\apidoc\templates\BaseRenderer;
use yii\helpers\Markdown;
/** /**
* A Markdown helper with support for class reference links. * A Markdown helper with support for class reference links.
...@@ -18,26 +20,39 @@ use yii\apidoc\templates\BaseRenderer; ...@@ -18,26 +20,39 @@ use yii\apidoc\templates\BaseRenderer;
* @author Carsten Brandt <mail@cebe.cc> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class ApiMarkdown extends Markdown class ApiMarkdown extends GithubMarkdown
{ {
/** /**
* @var BaseRenderer * @var BaseRenderer
*/ */
public static $renderer; public static $renderer;
protected $context;
/** /**
* @var ApiMarkdown * Renders a code block
*/ */
private static $_instance; protected function renderCode($block)
{
private $context; if (isset($block['language'])) {
$class = isset($block['language']) ? ' class="language-' . $block['language'] . '"' : '';
return "<pre><code$class>" . $this->highlight(implode("\n", $block['content']) . "\n", $block['language']) . '</code></pre>';
} else {
return parent::renderCode($block);
}
}
public function highlight(&$block, &$markup) protected function highlight($code, $language)
{ {
if ($language !== 'php') {
return htmlspecialchars($code, ENT_NOQUOTES, 'UTF-8');
}
// TODO improve code highlighting // TODO improve code highlighting
if (strncmp($block['text'], '<?php', 5) === 0) { if (strncmp($code, '<?php', 5) === 0) {
$text = highlight_string(trim($block['text']), true); $text = highlight_string(trim($code), true);
} else { } else {
$text = highlight_string("<?php ".trim($block['text']), true); $text = highlight_string("<?php ".trim($code), true);
$text = str_replace('&lt;?php', '', $text); $text = str_replace('&lt;?php', '', $text);
if (($pos = strpos($text, '&nbsp;')) !== false) { if (($pos = strpos($text, '&nbsp;')) !== false) {
$text = substr($text, 0, $pos) . substr($text, $pos + 6); $text = substr($text, 0, $pos) . substr($text, $pos + 6);
...@@ -46,93 +61,93 @@ class ApiMarkdown extends Markdown ...@@ -46,93 +61,93 @@ class ApiMarkdown extends Markdown
// remove <code><span style="color: #000000">\n and </span>tags added by php // remove <code><span style="color: #000000">\n and </span>tags added by php
$text = substr(trim($text), 36, -16); $text = substr(trim($text), 36, -16);
$code = '<pre><code'; return $text;
if (isset($block['language']))
{
if ($block['language'] !== 'php') {
return false;
}
$code .= ' class="language-'.$block['language'].'"';
}
$code .= '>'.$text.'</code></pre>'."\n";
$markup .= $code;
return true;
} }
public function init() protected function inlineMarkers()
{ {
$this->registerBlockHander('code', [$this, 'highlight']); return array_merge(parent::inlineMarkers(), [
$this->registerBlockHander('fenced', [$this, 'highlight']); '[[' => 'parseApiLinks',
]);
}
$context = &$this->context; protected function parseApiLinks($text)
// register marker for code links {
$this->unregisterInlineMarkerHandler('['); $context = $this->context;
$this->registerInlineMarkerHandler('[[', function($text, &$markup) use (&$context) {
if (preg_match('/^\[\[([\w\d\\\\\(\):$]+)(\|[^\]]*)?\]\]/', $text, $matches)) { if (preg_match('/^\[\[([\w\d\\\\\(\):$]+)(\|[^\]]*)?\]\]/', $text, $matches)) {
$offset = strlen($matches[0]); $offset = strlen($matches[0]);
$object = $matches[1]; $object = $matches[1];
$title = (empty($matches[2]) || $matches[2] == '|') ? null : substr($matches[2], 1); $title = (empty($matches[2]) || $matches[2] == '|') ? null : substr($matches[2], 1);
if (($pos = strpos($object, '::')) !== false) { if (($pos = strpos($object, '::')) !== false) {
$typeName = substr($object, 0, $pos); $typeName = substr($object, 0, $pos);
$subjectName = substr($object, $pos + 2); $subjectName = substr($object, $pos + 2);
if ($context !== null) { if ($context !== null) {
// Collection resolves relative types // Collection resolves relative types
$typeName = (new Collection([$typeName], $context->phpDocContext))->__toString(); $typeName = (new Collection([$typeName], $context->phpDocContext))->__toString();
} }
$type = static::$renderer->context->getType($typeName); $type = static::$renderer->context->getType($typeName);
if ($type === null) { if ($type === null) {
static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $typeName . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''),
];
return [
'<span style="background: #f00;">' . $typeName . '::' . $subjectName . '</span>',
$offset
];
} else {
if (($subject = $type->findSubject($subjectName)) !== null) {
if ($title === null) {
$title = $type->name . '::' . $subject->name;
if ($subject instanceof MethodDoc) {
$title .= '()';
}
}
return [
static::$renderer->subjectLink($subject, $title),
$offset
];
} else {
static::$renderer->context->errors[] = [ static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null, 'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $typeName . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''), 'message' => 'broken link to ' . $type->name . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''),
];
return [
'<span style="background: #ff0;">' . $type->name . '</span><span style="background: #f00;">::' . $subjectName . '</span>',
$offset
]; ];
$markup .= '<span style="background: #f00;">' . $typeName . '::' . $subjectName . '</span>';
} else {
if (($subject = $type->findSubject($subjectName)) !== null) {
if ($title === null) {
$title = $type->name . '::' . $subject->name;
if ($subject instanceof MethodDoc) {
$title .= '()';
}
}
$markup .= static::$renderer->subjectLink($subject, $title);
} else {
static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $type->name . '::' . $subjectName . (($context !== null) ? ' in ' . $context->name : ''),
];
$markup .= '<span style="background: #ff0;">' . $type->name . '</span><span style="background: #f00;">::' . $subjectName . '</span>';
}
} }
return $offset;
} elseif ($context !== null && ($subject = $context->findSubject($object)) !== null) {
$markup .= static::$renderer->subjectLink($subject, $title);
return $offset;
} }
if ($context !== null) { } elseif ($context !== null && ($subject = $context->findSubject($object)) !== null) {
// Collection resolves relative types return [
$object = (new Collection([$object], $context->phpDocContext))->__toString(); static::$renderer->subjectLink($subject, $title),
} $offset
if (($type = static::$renderer->context->getType($object)) !== null) {
$markup .= static::$renderer->typeLink($type, $title);
return $offset;
}
static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $object . (($context !== null) ? ' in ' . $context->name : ''),
]; ];
$markup .= '<span style="background: #f00;">' . $object . '</span>';
return $offset;
} else {
$markup .= '[[';
return 2;
} }
}); if ($context !== null) {
$this->registerInlineMarkerHandler('[', null); // Collection resolves relative types
$object = (new Collection([$object], $context->phpDocContext))->__toString();
}
if (($type = static::$renderer->context->getType($object)) !== null) {
return [
static::$renderer->typeLink($type, $title),
$offset
];
}
static::$renderer->context->errors[] = [
'file' => ($context !== null) ? $context->sourceFile : null,
'message' => 'broken link to ' . $object . (($context !== null) ? ' in ' . $context->name : ''),
];
return [
'<span style="background: #f00;">' . $object . '</span>',
$offset
];
}
return ['[[', 2];
} }
/** /**
...@@ -140,23 +155,24 @@ class ApiMarkdown extends Markdown ...@@ -140,23 +155,24 @@ class ApiMarkdown extends Markdown
* *
* @param string $content * @param string $content
* @param TypeDoc $context * @param TypeDoc $context
* @param bool $paragraph
* @return string * @return string
*/ */
public static function process($content, $context = null, $line = false) public static function process($content, $context = null, $paragraph = false)
{ {
if (static::$_instance === null) { if (!isset(Markdown::$flavors['api'])) {
static::$_instance = new static; Markdown::$flavors['api'] = new static;
} }
if (is_string($context)) { if (is_string($context)) {
$context = static::$renderer->context->getType($context); $context = static::$renderer->context->getType($context);
} }
static::$_instance->context = $context; Markdown::$flavors['api']->context = $context;
if ($line) { if ($paragraph) {
return static::$_instance->parseLine($content); return Markdown::processParagraph($content, 'api');
} else { } else {
return static::$_instance->parse($content); return Markdown::process($content, 'api');
} }
} }
} }
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\apidoc\helpers;
use Parsedown;
use yii\base\Component;
/**
* A Markdown helper with support for class reference links.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class Markdown extends Component
{
private $_parseDown;
protected function getParseDown()
{
if ($this->_parseDown === null) {
$this->_parseDown = new ParseDown();
}
return $this->_parseDown;
}
public function parse($markdown)
{
return $this->getParseDown()->parse($markdown);
}
public function parseLine($markdown)
{
return $this->getParseDown()->parseLine($markdown);
}
public function registerBlockHander($blockName, $callback)
{
$this->getParseDown()->register_block_handler($blockName, $callback);
}
public function unregisterBlockHander($blockName)
{
$this->getParseDown()->remove_block_handler($blockName);
}
public function registerInlineMarkerHandler($marker, $callback)
{
$this->getParseDown()->add_span_marker($marker, $callback);
}
public function unregisterInlineMarkerHandler($marker)
{
$this->getParseDown()->remove_span_marker($marker);
}
}
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
"yiisoft/yii2-composer": "*", "yiisoft/yii2-composer": "*",
"yiisoft/jquery": "~2.0 | ~1.10", "yiisoft/jquery": "~2.0 | ~1.10",
"ezyang/htmlpurifier": "4.6.*", "ezyang/htmlpurifier": "4.6.*",
"michelf/php-markdown": "1.3.*" "cebe/markdown": "0.9.*"
}, },
"autoload": { "autoload": {
"psr-4": { "yii\\": "" } "psr-4": { "yii\\": "" }
......
...@@ -7,38 +7,94 @@ ...@@ -7,38 +7,94 @@
namespace yii\helpers; namespace yii\helpers;
use Michelf\MarkdownExtra; use Yii;
use yii\base\InvalidParamException;
/** /**
* BaseMarkdown provides concrete implementation for [[Markdown]]. * BaseMarkdown provides concrete implementation for [[Markdown]].
* *
* Do not use BaseMarkdown. Use [[Markdown]] instead. * Do not use BaseMarkdown. Use [[Markdown]] instead.
* *
* @author Alexander Makarov <sam@rmcreative.ru> * @author Carsten Brandt <mail@cebe.cc>
* @since 2.0 * @since 2.0
*/ */
class BaseMarkdown class BaseMarkdown
{ {
/** /**
* @var MarkdownExtra * @var array a map of markdown flavor names to corresponding parser class configurations.
*/ */
protected static $markdown; public static $flavors = [
'original' => [
'class' => 'cebe\markdown\Markdown',
'html5' => true,
],
'gfm' => [
'class' => 'cebe\markdown\GithubMarkdown',
'html5' => true,
],
'gfm-comment' => [
'class' => 'cebe\markdown\Markdown',
'html5' => true,
'enableNewlines' => true,
],
];
/**
* @var string the markdown flavor to use when none is specified explicitly.
* Defaults to `original`.
* @see $flavors
*/
public static $defaultFlavor = 'original';
/** /**
* Converts markdown into HTML * Converts markdown into HTML.
* *
* @param string $content * @param string $markdown the markdown text to parse
* @param array $config * @param string $flavor the markdown flavor to use. See [[$flavors]] for available values.
* @return string * @return string the parsed HTML output
* @throws \yii\base\InvalidParamException when an undefined flavor is given.
*/ */
public static function process($content, $config = []) public static function process($markdown, $flavor = 'original')
{ {
if (static::$markdown === null) { $parser = static::getParser($flavor);
static::$markdown = new MarkdownExtra(); return $parser->parse($parser);
} }
foreach ($config as $name => $value) {
static::$markdown->{$name} = $value; /**
* Converts markdown into HTML but only parses inline elements.
*
* This can be useful for parsing small comments or description lines.
*
* @param string $markdown the markdown text to parse
* @param string $flavor the markdown flavor to use. See [[$flavors]] for available values.
* @return string the parsed HTML output
* @throws \yii\base\InvalidParamException when an undefined flavor is given.
*/
public static function processParagraph($markdown, $flavor = 'original')
{
$parser = static::getParser($flavor);
return $parser->parseParagraph($parser);
}
/**
* @param string $flavor
* @return \cebe\markdown\Parser
* @throws \yii\base\InvalidParamException when an undefined flavor is given.
*/
private static function getParser($flavor)
{
/** @var \cebe\markdown\Markdown $parser */
if (!isset(static::$flavors[$flavor])) {
throw new InvalidParamException("Markdown flavor '$flavor' is not defined.'");
} elseif(!is_object($config = static::$flavors[$flavor])) {
$parser = Yii::createObject($config);
if (is_array($config)) {
foreach ($config as $name => $value) {
$parser->{$name} = $value;
}
}
static::$flavors[$flavor] = $parser;
} }
return static::$markdown->transform($content); return static::$flavors[$flavor];
} }
} }
...@@ -13,21 +13,15 @@ namespace yii\helpers; ...@@ -13,21 +13,15 @@ namespace yii\helpers;
* Basic usage is the following: * Basic usage is the following:
* *
* ```php * ```php
* $myHtml = Markdown::process($myText); * $myHtml = Markdown::process($myText); // use original markdown flavor
* $myHtml = Markdown::process($myText, 'gfm'); // use github flavored markdown
* ``` * ```
* *
* If you want to configure the parser: * You can configure multiple flavors using the [[$flavors]] property.
* *
* ```php * For more details please refer to the [Markdown library documentation](https://github.com/cebe/markdown#readme).
* $myHtml = Markdown::process($myText, [
* 'fn_id_prefix' => 'footnote_',
* ]);
* ```
*
* Note that in order to use this helper you need to install "michelf/php-markdown" Composer package.
* *
* For more details please refer to [PHP Markdown library documentation](http://michelf.ca/projects/php-markdown/). * @author Carsten Brandt <mail@cebe.cc>
* @author Alexander Makarov <sam@rmcreative.ru>
* @since 2.0 * @since 2.0
*/ */
class Markdown extends BaseMarkdown class Markdown extends BaseMarkdown
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment