Skip to content

Instantly share code, notes, and snippets.

@DarkFighter
Created December 27, 2013 01:02
Show Gist options
  • Save DarkFighter/0095077337449fdfe1ba to your computer and use it in GitHub Desktop.
Save DarkFighter/0095077337449fdfe1ba to your computer and use it in GitHub Desktop.
<?php
namespace Ciconia\Extension\Core;
use Ciconia\Common\Text;
use Ciconia\Event\EmitterAwareInterface;
use Ciconia\Event\EmitterAwareTrait;
use Ciconia\Extension\ExtensionInterface;
use Ciconia\Renderer\RendererAwareInterface;
use Ciconia\Renderer\RendererAwareTrait;
use Ciconia\Markdown;
/**
* Form HTML ordered (numbered) and unordered (bulleted) lists.
*
* Original source code from Markdown.pl
*
* > Copyright (c) 2004 John Gruber
* > <http://daringfireball.net/projects/markdown/>
*
* @author Kazuyuki Hayashi <hayashi@valnur.net>
*/
class ListExtension implements ExtensionInterface, RendererAwareInterface, EmitterAwareInterface
{
use RendererAwareTrait;
use EmitterAwareTrait;
/**
* @var Markdown
*/
private $markdown;
/**
* @var string
*/
private $ul = '[*+\-]';
/**
* @var string
*/
private $ol = '\d+[\.]';
/**
* {@inheritdoc}
*/
public function register(Markdown $markdown)
{
$this->markdown = $markdown;
$markdown->on('block', array($this, 'processList'), 30);
}
/**
* Form HTML ordered (numbered) and unordered (bulleted) lists.
*
* @param Text $text
* @param array $options
* @param int $level
*/
//protected $list_level = 0;
public function processList(Text $text, array $options = array(), $level = 0)
{
$options['tabWidth'] = 5; // deepest level
$lessThanTab = $options['tabWidth'] - 1;
$markers_relist = array(
$this->ul => $this->ol,
$this->ol => $this->ul,
);
$callback = function (Text $list, Text $marker, Text $numberOfSpaces, Text $firstItemMarker, Text $itemItext) use ($options, $level) {
$type = preg_match('/' . $this->ul . '/', $firstItemMarker) ? 'ul' : 'ol';
// $list->replace('/\n{2,}/', "\n\n\n");
$marker_re = ($type == 'ul' ? $this->ul : $this->ol);
$list->append("\n");
$this->processListItems($list, $marker_re, $options, $level);
return "\n" . $this->getRenderer()->renderList($list, array('type' => $type)) . "\n\n";
};
foreach ($markers_relist as $marker_re => $other_marker_re) {
$whole_list_re = '
#( # $1 = whole list
( # $2
([ ]{0,' . $lessThanTab . '}) # $3 = number of spaces
(' . $marker_re . ') # $4 = first list item marker
[ ]+
)
(?s:.+?)
( # $5
\z
|
\n{2,}
(?=\S)
(?! # Negative lookahead for another list item marker
[ ]*
' . $marker_re . '[ ]+
)
|
(?= # Lookahead for another kind of list
\n
\3 # Must have the same indentation
' . $other_marker_re . '[ ]+
)
)
#)
'; // mx
if ($level) {
// $text->replace("{^$whole_list_re}mx", $callback);
$text->replace("{^$whole_list_re}mx", $callback);
} else {
//$text->replace('{(?:(?<=\n\n)|\A\n?)' . $whole_list_re . '}mx', $callback);
$text->replace('{(?:(?<=\n)\n|\A\n?)' . $whole_list_re . '}mx', $callback);
}
}
/*
$wholeList = '
#( # $1 = whole list
( # $2
[ ]{0,' . $lessThanTab . '}
(' . $this->getPattern() . ') # $3 = first list item marker
[ \t]+
)
(?s:.+?)
(?: # $4
\z
|
\n{2,}
(?=\S)
(?! # Negative lookahead for another list item marker
[ \t]*
' . $this->getPattern() . '[ \t]+
)
)
#)
';
*/
/** @noinspection PhpUnusedParameterInspection */
}
public function processListItems(Text $list, $marker_any_re, array $options = array(), $level = 0) {
//protected function processListItems($list_str, $marker_any_re, array $options = array(), $level = 0) {
// $list->replace('/\n{2,}\z/', "\n");
$list->replace("/\n{2,}\\z/", "\n");
$list->replace('{
(\n)? # leading line = $1
(^[ ]*) # leading whitespace = $2
('.$marker_any_re.' # list marker and space = $3
(?:[ ]+|(?=\n)) # space only required if item is not empty
)
((?s:.*?)) # list item text = $4
(?:(\n+(?=\n))|\n) # tailing blank line = $5
(?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
}mx', function (Text $w, Text $leadingLine, Text $w, Text $markerSpace, Text $item, Text $tailingBlankLine) use ($marker_any_re, $options, $level) {
/*
if ((string)$leadingLine or $item->match('/\n{2,}/')) {
$this->markdown->emit('outdent', array($item));
$this->markdown->emit('block', array($item));
} else {
$this->markdown->emit('outdent', array($item));
$this->processList($item, $options, ++$level);
$item->rtrim();
$this->markdown->emit('inline', array($item));
}
*/
/*
$item = $matches[4];
$leading_line =& $matches[1];
$leading_space =& $matches[2];
$marker_space = $matches[3];
$tailing_blank_line =& $matches[5];
*/
if ((string)$leadingLine || (string)$tailingBlankLine || $item->match('/\n{2,}/')) {
$item->setString( $leadingLine->getString() . str_repeat(' ', $markerSpace->getLength()) . $item->getString() );
$item->replace('/^(\t|[ ]{1,'.$options['tabWidth'].'})/m', '');
$item->append("\n");
// тут может возникнуть баг: в оригинале делается так:
// $this->markdown->emit('outdent', array($item));
// $this->markdown->emit('block', array($item));
// но почему??
$this->processList($item, $options, ++$level);
$item->replace('/\n+$/', '');
$this->markdown->emit('inline', array($item));
// $this->markdown->emit('outdent', array($item));
// $this->markdown->emit('block', array($item));
} else {
// тут что-то не так!
// $item->replace('/^(\t|[ ]{1,'.$options['tabWidth'].'})/m', '');
#var_dump($item);
#echo '<hr />';
$this->processList($item, $options, ++$level);
$item->replace('/\n+$/', '');
// $item->rtrim();
$this->markdown->emit('inline', array($item));
/*
$item = $this->doLists($this->outdent($item));
$item = preg_replace('/\n+$/', '', $item);
$item = $this->runSpanGamut($item);
*/
}
return $this->getRenderer()->renderListItem($item) . "\n";
});
/*
$list_str = preg_replace_callback('{
(\n)? # leading line = $1
(^[ ]*) # leading whitespace = $2
('.$marker_any_re.' # list marker and space = $3
(?:[ ]+|(?=\n)) # space only required if item is not empty
)
((?s:.*?)) # list item text = $4
(?:(\n+(?=\n))|\n) # tailing blank line = $5
(?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
}xm',
array(&$this, '_processListItems_callback'), $list_str);
$this->list_level--;
return $list_str;
*/
}
/**
* Process the contents of a single ordered or unordered list, splitting it into individual list items.
*
* @param Text $list
* @param array $options
* @param int $level
*/
public function processListItemsOld(Text $list, array $options = array(), $level = 0)
{
$list->replace('/\n{2,}\z/', "\n");
/** @noinspection PhpUnusedParameterInspection */
$list->replace('{
(\n)? # leading line = $1
(^[ \t]*) # leading whitespace = $2
(' . $this->getPattern() . ') [ \t]+ # list marker = $3
((?s:.+?) # list item text = $4
(\n{1,2}))
(?= \n* (\z | \2 (' . $this->getPattern() . ') [ \t]+))
}mx', function (Text $w, Text $leadingLine, Text $ls, Text $m, Text $item) use ($options, $level) {
if ((string)$leadingLine or $item->match('/\n{2,}/')) {
$this->markdown->emit('outdent', array($item));
$this->markdown->emit('block', array($item));
} else {
$this->markdown->emit('outdent', array($item));
$this->processList($item, $options, ++$level);
$item->rtrim();
$this->markdown->emit('inline', array($item));
}
return $this->getRenderer()->renderListItem($item) . "\n";
});
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'list';
}
/**
* @return string
*/
protected function getPattern()
{
return '(?:' . $this->ul . '|' . $this->ol . ')';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment