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 @@
"yiisoft/yii2-composer": "*",
"yiisoft/jquery": "~2.0 | ~1.10",
"ezyang/htmlpurifier": "4.6.*",
"michelf/php-markdown": "1.3.*",
"phpspec/php-diff": ">=1.0.2"
"cebe/markdown": "0.9.*"
},
"require-dev": {
"phpunit/phpunit": "3.7.*",
......
......@@ -21,8 +21,7 @@
"require": {
"yiisoft/yii2": "*",
"yiisoft/yii2-bootstrap": "*",
"phpdocumentor/reflection": ">=1.0.3",
"erusev/parsedown": "0.9.*"
"phpdocumentor/reflection": ">=1.0.3"
},
"autoload": {
"psr-4": { "yii\\apidoc\\": "" }
......
......@@ -7,10 +7,12 @@
namespace yii\apidoc\helpers;
use cebe\markdown\GithubMarkdown;
use phpDocumentor\Reflection\DocBlock\Type\Collection;
use yii\apidoc\models\MethodDoc;
use yii\apidoc\models\TypeDoc;
use yii\apidoc\templates\BaseRenderer;
use yii\helpers\Markdown;
/**
* A Markdown helper with support for class reference links.
......@@ -18,26 +20,39 @@ use yii\apidoc\templates\BaseRenderer;
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ApiMarkdown extends Markdown
class ApiMarkdown extends GithubMarkdown
{
/**
* @var BaseRenderer
*/
public static $renderer;
protected $context;
/**
* @var ApiMarkdown
* Renders a code block
*/
private static $_instance;
private $context;
protected function renderCode($block)
{
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
if (strncmp($block['text'], '<?php', 5) === 0) {
$text = highlight_string(trim($block['text']), true);
if (strncmp($code, '<?php', 5) === 0) {
$text = highlight_string(trim($code), true);
} else {
$text = highlight_string("<?php ".trim($block['text']), true);
$text = highlight_string("<?php ".trim($code), true);
$text = str_replace('&lt;?php', '', $text);
if (($pos = strpos($text, '&nbsp;')) !== false) {
$text = substr($text, 0, $pos) . substr($text, $pos + 6);
......@@ -46,93 +61,93 @@ class ApiMarkdown extends Markdown
// remove <code><span style="color: #000000">\n and </span>tags added by php
$text = substr(trim($text), 36, -16);
$code = '<pre><code';
if (isset($block['language']))
{
if ($block['language'] !== 'php') {
return false;
}
$code .= ' class="language-'.$block['language'].'"';
}
$code .= '>'.$text.'</code></pre>'."\n";
$markup .= $code;
return true;
return $text;
}
public function init()
protected function inlineMarkers()
{
$this->registerBlockHander('code', [$this, 'highlight']);
$this->registerBlockHander('fenced', [$this, 'highlight']);
return array_merge(parent::inlineMarkers(), [
'[[' => 'parseApiLinks',
]);
}
$context = &$this->context;
// register marker for code links
$this->unregisterInlineMarkerHandler('[');
$this->registerInlineMarkerHandler('[[', function($text, &$markup) use (&$context) {
protected function parseApiLinks($text)
{
$context = $this->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];
$title = (empty($matches[2]) || $matches[2] == '|') ? null : substr($matches[2], 1);
$object = $matches[1];
$title = (empty($matches[2]) || $matches[2] == '|') ? null : substr($matches[2], 1);
if (($pos = strpos($object, '::')) !== false) {
$typeName = substr($object, 0, $pos);
$subjectName = substr($object, $pos + 2);
if ($context !== null) {
// Collection resolves relative types
$typeName = (new Collection([$typeName], $context->phpDocContext))->__toString();
}
$type = static::$renderer->context->getType($typeName);
if ($type === null) {
if (($pos = strpos($object, '::')) !== false) {
$typeName = substr($object, 0, $pos);
$subjectName = substr($object, $pos + 2);
if ($context !== null) {
// Collection resolves relative types
$typeName = (new Collection([$typeName], $context->phpDocContext))->__toString();
}
$type = static::$renderer->context->getType($typeName);
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[] = [
'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) {
// Collection resolves relative types
$object = (new Collection([$object], $context->phpDocContext))->__toString();
}
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 : ''),
} elseif ($context !== null && ($subject = $context->findSubject($object)) !== null) {
return [
static::$renderer->subjectLink($subject, $title),
$offset
];
$markup .= '<span style="background: #f00;">' . $object . '</span>';
return $offset;
} else {
$markup .= '[[';
return 2;
}
});
$this->registerInlineMarkerHandler('[', null);
if ($context !== 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
*
* @param string $content
* @param TypeDoc $context
* @param bool $paragraph
* @return string
*/
public static function process($content, $context = null, $line = false)
public static function process($content, $context = null, $paragraph = false)
{
if (static::$_instance === null) {
static::$_instance = new static;
if (!isset(Markdown::$flavors['api'])) {
Markdown::$flavors['api'] = new static;
}
if (is_string($context)) {
$context = static::$renderer->context->getType($context);
}
static::$_instance->context = $context;
Markdown::$flavors['api']->context = $context;
if ($line) {
return static::$_instance->parseLine($content);
if ($paragraph) {
return Markdown::processParagraph($content, 'api');
} 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 @@
"yiisoft/yii2-composer": "*",
"yiisoft/jquery": "~2.0 | ~1.10",
"ezyang/htmlpurifier": "4.6.*",
"michelf/php-markdown": "1.3.*"
"cebe/markdown": "0.9.*"
},
"autoload": {
"psr-4": { "yii\\": "" }
......
......@@ -7,38 +7,94 @@
namespace yii\helpers;
use Michelf\MarkdownExtra;
use Yii;
use yii\base\InvalidParamException;
/**
* BaseMarkdown provides concrete implementation for [[Markdown]].
*
* Do not use BaseMarkdown. Use [[Markdown]] instead.
*
* @author Alexander Makarov <sam@rmcreative.ru>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
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 array $config
* @return string
* @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 process($content, $config = [])
public static function process($markdown, $flavor = 'original')
{
if (static::$markdown === null) {
static::$markdown = new MarkdownExtra();
}
foreach ($config as $name => $value) {
static::$markdown->{$name} = $value;
$parser = static::getParser($flavor);
return $parser->parse($parser);
}
/**
* 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;
* Basic usage is the following:
*
* ```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
* $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 the [Markdown library documentation](https://github.com/cebe/markdown#readme).
*
* For more details please refer to [PHP Markdown library documentation](http://michelf.ca/projects/php-markdown/).
* @author Alexander Makarov <sam@rmcreative.ru>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
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