Diffs
HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/NodeCreater.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @package HatenaSyntax
+ * @author anatoo<anatoo@nequal.jp>
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @version $Id$
+ */
+
+class HatenaSyntax_NodeCreater implements PEG_IParser
+{
+ protected $keys, $parser;
+ function __construct($type, PEG_IParser $parser, Array $keys = array())
+ {
+ $this->type = $type;
+ $this->keys = $keys;
+ $this->parser = $parser;
+ }
+ function parse(PEG_IContext $context)
+ {
+ $result = $this->parser->parse($context);
+ if ($result instanceof PEG_Failure) return $result;
+
+ $data = array();
+ if (count($this->keys) > 0) foreach ($this->keys as $i => $key) {
+ $data[$key] = $result[$i];
+ }
+ else {
+ $data = $result;
+ }
+
+ return new HatenaSyntax_Node($this->type, $data);
+ }
+}
\ ファイルの末尾に改行がありません
属性に変更があったパス: HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/NodeCreater.php
___________________________________________________________________
名前: svn:keywords
+ Id
HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/Util.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * @package HatenaSyntax
+ * @author anatoo<anatoo@nequal.jp>
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @version $Id$
+ */
+
+class HatenaSyntax_Util
+{
+ static function normalizeList(Array $data)
+ {
+ return self::filterLevel(self::lower(self::levels($data), $data));
+ }
+
+ static function segment(PEG_IParser $p)
+ {
+ return PEG::callbackAction(array('HatenaSyntax_Util', 'normalizeLineSegment'), $p);
+ }
+
+ static function normalizeLineSegment(Array $data)
+ {
+ for ($ret = array(), $i = 0, $len = count($data); $i < $len; $i++) {
+ if (is_string($data[$i])) {
+ for ($str = $data[$i++]; $i < $len && is_string($data[$i]); $i++) {
+ $str .= $data[$i];
+ }
+ $ret[] = $str;
+ if ($i < $len) $ret[] = $data[$i];
+ }
+ else {
+ $ret[] = $data[$i];
+ }
+ }
+ return $ret;
+ }
+
+ static protected function filterLevel($struct)
+ {
+ foreach ($struct as &$node)
+ $node = is_string($node[0]) ? array($node[0], $node[2]) : self::filterLevel($node);
+ return $struct;
+ }
+
+ static protected function lower(Array $levels, Array $data)
+ {
+ $level = array_pop($levels);
+
+ for ($i = 0, $ret = array(), $len = count($data); $i < $len; $i++) {
+ if ($data[$i][1] <= $level) {
+ $ret[] = $data[$i];
+ }
+ else {
+ for ($arr = array($data[$i++]); $i < $len && $data[$i][1] > $level; $i++) {
+ $arr[] = $data[$i];
+ }
+ $ret[] = self::lower($levels, $arr);
+ if ($i < $len) $ret[] = $data[$i];
+ }
+ }
+
+ return $ret;
+ }
+
+ static protected function levels(Array $data)
+ {
+ $levels = array();
+ foreach ($data as $li) $levels[$li[1]] = true;
+ $levels = array_keys($levels);
+ rsort($levels, SORT_NUMERIC);
+ return $levels;
+ }
+}
\ ファイルの末尾に改行がありません
属性に変更があったパス: HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/Util.php
___________________________________________________________________
名前: svn:keywords
+ Id
HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/Locator.php
@@ -0,0 +1,238 @@
+<?php
+/**
+ * @package HatenaSyntax
+ * @author anatoo<anatoo@nequal.jp>
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @version $Id$
+ */
+
+class HatenaSyntax_Locator
+{
+ protected $objects = array();
+
+ private function __construct()
+ {
+ $this->setup();
+ }
+
+ static function it()
+ {
+ static $obj = null;
+ return $obj ? $obj : $obj = new self;
+ }
+
+ function __get($name)
+ {
+ return isset($this->objects[$name]) ?
+ $this->objects[$name] :
+ $this->objects[$name] = $this->{'create' . $name}();
+ }
+
+ protected function createFactory()
+ {
+ return new HatenaSyntax_Factory($this);
+ }
+
+ protected function createLineChar()
+ {
+ return PEG::not(PEG::char("\n\r"));
+ }
+
+ protected function createEndOfLine()
+ {
+ return PEG::choice(PEG::newLine(), PEG::eos());
+ }
+
+ protected function createFootnote()
+ {
+ $close = '))';
+ $elt = PEG::andalso(PEG::not($close),
+ PEG::choice($this->link, $this->lineChar));
+
+ $parser = PEG::pack('((',
+ HatenaSyntax_Util::segment(PEG::many1($elt)),
+ $close);
+
+ return $this->factory->createNodeCreater('footnote', $parser);
+ }
+
+ protected function createLineElement()
+ {
+ return $this->factory->createLineElement();
+ }
+
+ protected function createLineSegment()
+ {
+ return HatenaSyntax_Util::segment(PEG::many($this->lineElement));
+ }
+
+ protected function createHttpLink()
+ {
+ $title_char = PEG::andalso(PEG::not(']'),
+ $this->lineChar);
+
+ $title = PEG::secondSeq(':title=', PEG::join(PEG::many1($title_char)));
+
+ $url_char = PEG::andalso(PEG::not(PEG::choice(']', ':title=')),
+ $this->lineChar);
+
+ $url = PEG::join(PEG::seq(PEG::choice('http://', 'https://'),
+ PEG::many1($url_char)));
+ $parser = PEG::seq($url, PEG::optional($title));
+
+ return $this->factory->createNodeCreater('httplink', $parser, array('href', 'title'));
+ }
+
+ protected function createLink()
+ {
+ return PEG::pack('[', PEG::choice($this->httpLink), ']');
+ }
+
+ protected function createDefinition()
+ {
+ $c = PEG::token(':');
+ $sep = PEG::drop($c);
+ $factory = $this->factory;
+ $parser = PEG::seq($sep,
+ $factory->createLineSegment($c, true),
+ $sep,
+ $factory->createLineSegment($c),
+ PEG::drop($this->endOfLine));
+ return $parser;
+ }
+
+ protected function createDefinitionList()
+ {
+ $parser = PEG::many1($this->definition);
+ return $this->factory->createNodeCreater('definitionlist', $parser);
+ }
+
+ protected function createPre()
+ {
+ $nl = PEG::newLine();
+ $closing = PEG::seq(PEG::optional($nl), '|<', $this->endOfLine);
+ $line = PEG::secondSeq($nl, $this->factory->createLineSegment($closing));
+ $parser = PEG::pack('>|', PEG::many1($line), $closing);
+
+ return $this->factory->createNodeCreater('pre', $parser);
+ }
+
+ protected function createSuperPreElement()
+ {
+ $cond = PEG::lookaheadNot(PEG::seq('||<', $this->endOfLine));
+ $elt = PEG::secondSeq($cond, $this->lineChar);
+ $parser = PEG::thirdSeq(PEG::newLine(), $cond, PEG::join(PEG::many($elt)));
+
+ return $parser;
+
+ }
+
+ protected function createHeader()
+ {
+ $parser = PEG::seq(PEG::drop('*'),
+ PEG::count(PEG::many('*')),
+ HatenaSyntax_Util::segment(PEG::many(PEG::choice($this->lineChar, $this->footnote))),
+ PEG::drop($this->endOfLine));
+
+ return $this->factory->createNodeCreater('header', $parser, array('level', 'body'));
+ }
+
+ protected function createSuperPre()
+ {
+ $open = PEG::pack('>|',
+ PEG::join(PEG::many(PEG::not(PEG::char("\r\n|")))),
+ '|');
+ $body = PEG::many1($this->superPreElement);
+
+ $close = PEG::drop(PEG::optional(PEG::newLine()),
+ '||<',
+ $this->endOfLine);
+
+ $parser = PEG::seq($open, $body, $close);
+
+ return $this->factory->createNodeCreater('superpre', $parser, array('type', 'body'));
+ }
+
+ protected function createList()
+ {
+ $c = PEG::char('-+');
+ $item = PEG::seq($c,
+ PEG::count(PEG::many($c)),
+ $this->lineSegment,
+ PEG::drop($this->endOfLine));
+ $list = PEG::callbackAction(array('HatenaSyntax_Util', 'normalizeList'), PEG::many1($item));
+
+ return $this->factory->createNodeCreater('list', $list);
+ }
+
+ protected function createTableCell()
+ {
+ $parser = PEG::seq(PEG::drop('|', PEG::lookaheadNot($this->endOfLine)),
+ PEG::optional('*'),
+ $this->factory->createLineSegment(PEG::token('|'), true));
+ return $parser;
+ }
+
+ protected function createTable()
+ {
+ $line = PEG::firstSeq(PEG::many1($this->tableCell),
+ '|',
+ $this->endOfLine);
+ $parser = PEG::many1($line);
+
+ return $this->factory->createNodeCreater('table', $parser);
+ }
+
+ protected function createBlockQuote()
+ {
+ $elt = PEG::secondSeq(PEG::lookaheadNot(PEG::seq('<<', $this->endOfLine)),
+ $this->element);
+
+ $parser = PEG::thirdSeq('>>',
+ PEG::newLine(),
+ PEG::many1($elt),
+ '<<',
+ $this->endOfLine);
+
+ return $this->factory->createNodeCreater('blockquote', $parser);
+ }
+
+ protected function createParagraph()
+ {
+ $parser = PEG::firstSeq($this->lineSegment, $this->endOfLine);
+
+ return $this->factory->createNodeCreater('paragraph', $parser);
+ }
+
+ protected function createEmptyParagraph()
+ {
+ $parser = PEG::count(PEG::many1(PEG::newLine()));
+ return $this->factory->createNodeCreater('emptyparagraph', $parser);
+ }
+
+ protected function createElement()
+ {
+ $parser = PEG::ref();
+ return $parser;
+ }
+
+ protected function createParser()
+ {
+ return $this->factory->createNodeCreater('root', PEG::many($this->element));
+ }
+
+ protected function setup()
+ {
+ $this->element->is(PEG::choice($this->header,
+ $this->blockQuote,
+ $this->definitionList,
+ $this->table,
+ $this->list,
+ $this->pre,
+ $this->superpre,
+ $this->emptyParagraph,
+ $this->paragraph));
+ }
+
+
+}
\ ファイルの末尾に改行がありません
属性に変更があったパス: HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/Locator.php
___________________________________________________________________
名前: svn:keywords
+ Id
HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/Node.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * @package HatenaSyntax
+ * @author anatoo<anatoo@nequal.jp>
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @version $Id$
+ */
+
+class HatenaSyntax_Node
+{
+ protected $type, $data = array();
+ function __construct($type, $data)
+ {
+ $this->type = $type;
+ $this->data = $data;
+ }
+
+ function getType()
+ {
+ return $this->type;
+ }
+
+ function getData()
+ {
+ return $this->data;
+ }
+}
\ ファイルの末尾に改行がありません
属性に変更があったパス: HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/Node.php
___________________________________________________________________
名前: svn:keywords
+ Id
HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/Renderer.php
@@ -0,0 +1,211 @@
+<?php
+/**
+ * @package HatenaSyntax
+ * @author anatoo<anatoo@nequal.jp>
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @version $Id$
+ */
+
+class HatenaSyntax_Renderer
+{
+ protected $config, $footnote, $fncount, $padding, $encoding;
+
+ function __construct(Array $config = array())
+ {
+ $this->config = $config + array(
+ 'headerlevel' => 1,
+ 'htmlescape' => true,
+ 'id' => uniqid('sec'),
+ 'sectionclass' => 'section',
+ 'footnoteclass' => 'footnote',
+ 'superprehandler' => array($this, 'superPreHandler')
+ );
+ }
+
+ function render(HatenaSyntax_Node $node)
+ {
+ $this->footnote = '';
+ $this->fncount = 0;
+ $this->padding = 0;
+
+ $ret = $this->renderNode($node);
+ $ret = '<div class="' . $this->config['sectionclass'] . '">' . PHP_EOL . $ret . PHP_EOL . '</div>' . PHP_EOL;
+ if ($this->fncount > 0) {
+ $ret .= PHP_EOL . PHP_EOL . '<div class="' . $this->config['footnoteclass'] . '">' .
+ PHP_EOL . $this->footnote . '</div>';
+ }
+
+ return $ret;
+ }
+
+ static function superPreHandler($type, $lines)
+ {
+ $body = join(PHP_EOL, array_map(array('HatenaSyntax_Renderer', 'escape'), $lines));
+ return '<pre class="superpre">' . PHP_EOL . $body . '</pre>';
+ }
+
+ protected function renderNode(HatenaSyntax_Node $node)
+ {
+ $this->padding++;
+ $ret = $this->{'render' . $node->getType()}($node->getData());
+ $this->padding--;
+ return $ret;
+ }
+
+ protected function renderRoot(Array $arr)
+ {
+ $this->padding--;
+ foreach ($arr as &$elt) $elt = $this->renderNode($elt);
+ $this->padding++;
+ return join(PHP_EOL, $arr);
+ }
+
+ protected function renderHeader(Array $data)
+ {
+ $level = $data['level'] + $this->config['headerlevel'];
+ return $this->line("<h{$level}>" . $this->renderLineSegment($data['body']) . "</h{$level}>");
+ }
+
+ protected function renderLineSegment(Array $data)
+ {
+ foreach ($data as &$elt)
+ $elt = !$elt instanceof HatenaSyntax_Node ? ($this->config['htmlescape'] ? $this->escape($elt) : $elt)
+ : $this->renderNode($elt);
+ return join('', $data);
+ }
+
+ protected function renderFootnote(Array $data)
+ {
+ $this->fncount++;
+ $id = $this->config['id'];
+ $n = $this->fncount;
+ $title = $body = $this->renderLineSegment($data);
+ $title = strip_tags($body);
+ if (!$this->config['htmlescape']) {
+ $title = $this->escape($title);
+ }
+
+ $this->footnote .= sprintf(' <p><a href="#%s_%d" name="%s_footnote_%d">*%d</a>: %s</p>' . PHP_EOL, $id, $n, $id , $n, $n, $body);
+ return sprintf('(<a href="#%s_footnote_%d" name="%s_%d" title="%s">*%d</a>)', $id, $n, $id, $n, $title, $n);
+ }
+
+ protected function renderHttpLink(Array $data)
+ {
+ list($href, $title) = array($data['href'], $data['title']);
+ $title = $title ? $title : $href;
+ if ($this->config['htmlescape']) $title = $this->escape($title);
+ $href = $this->escape($href);
+ return sprintf('<a href="%s">%s</a>', $href, $title);
+ }
+
+ protected function renderDefinitionList(Array $data)
+ {
+ foreach ($data as &$elt) $elt = $this->renderDefinition($elt);
+ return join(PHP_EOL, array($this->line('<dl>'), join(PHP_EOL, $data), $this->line('</dl>')));
+ }
+
+ protected function renderDefinition(Array $data)
+ {
+ list($dt, $dd) = $data;
+ $ret = array();
+ if ($dt) $ret[] = $this->line('<dt>' . $this->renderLineSegment($dt) . '</dt>', 1);
+ $ret[] = $this->line('<dd>' . $this->renderLineSegment($dd) . '</dd>', 1);
+ return join(PHP_EOL, $ret);
+ }
+
+ protected function renderPre(Array $data)
+ {
+ $ret = array();
+ $ret[] = $this->line('<pre>');
+ foreach ($data as &$elt) $elt = $this->renderLineSegment($elt);
+ $ret[] = join(PHP_EOL, $data) . '</pre>';
+ return join(PHP_EOL, $ret);
+ }
+
+ protected function renderSuperPre(Array $data)
+ {
+ $ret = array();
+ list($type, $lines) = array($data['type'], $data['body']);
+ $ret[] = call_user_func($this->config['superprehandler'], $type, $lines);
+ return join(PHP_EOL, $ret);
+ }
+
+ protected function renderTable(Array $data)
+ {
+ $ret = array();
+ $ret[] = $this->line('<table>');
+ $this->padding++;
+ foreach ($data as $tr) {
+ $ret[] = $this->line('<tr>');
+ foreach ($tr as $td) $ret[] = $this->renderTableCell($td[0], $td[1]);
+ $ret[] = $this->line('</tr>');
+ }
+ $this->padding--;
+ $ret[] = $this->line('</table>');
+ return join(PHP_EOL, $ret);
+ }
+
+ protected function renderTableCell($header, $segment)
+ {
+ $tag = $header ? 'th' : 'td';
+ $ret = $this->line("<{$tag}>" . $this->renderLineSegment($segment) . "</{$tag}>", 1);
+ return $ret;
+ }
+
+ protected function renderBlockQuote(Array $arr)
+ {
+ $ret = array();
+ $ret[] = $this->line('<blockquote>');
+ foreach ($arr as $elt) $ret[] = $this->renderNode($elt);
+ $ret[] = $this->line('</blockquote>');
+ return join(PHP_EOL, $ret);
+ }
+
+ protected function renderParagraph(Array $data)
+ {
+ return $this->line('<p>' . $this->renderLineSegment($data) . '</p>');
+ }
+
+ protected function renderEmptyParagraph($data)
+ {
+ $ret = array();
+ for ($data--; $data > 0; $data--) $ret[] = $this->line('<br>');
+ return join(PHP_EOL, $ret);
+ }
+
+ protected function renderList(Array $data)
+ {
+ $this->padding--;
+ $ret = $this->renderListItem($data);
+ $this->padding++;
+ return $ret;
+ }
+
+ protected function renderListItem(Array $data)
+ {
+ $this->padding++;
+ if (is_string($data[0])) { // leaf case
+ $result = $this->line('<li>' . $this->renderLineSegment($data[1]) . '</li>');
+ }
+ else {
+ $buf = array();
+ $name = $data[0][0] === '+' ? 'ol' : 'ul';
+ $buf[] = $this->line("<{$name}>");
+ foreach ($data as $elt) $buf[] = $this->renderListItem($elt);
+ $buf[] = $this->line("</{$name}>");
+ $result = join(PHP_EOL, $buf);
+ }
+ $this->padding--;
+ return $result;
+ }
+
+ protected function line($str = '', $padding = 0)
+ {
+ return str_repeat(' ', max($this->padding + $padding, 0)) . $str;
+ }
+
+ protected static function escape($str)
+ {
+ return htmlspecialchars($str, ENT_QUOTES);
+ }
+}
\ ファイルの末尾に改行がありません
属性に変更があったパス: HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/Renderer.php
___________________________________________________________________
名前: svn:keywords
+ Id
HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/Factory.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * @package HatenaSyntax
+ * @author anatoo<anatoo@nequal.jp>
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @version $Id$
+ */
+
+class HatenaSyntax_Factory
+{
+ protected $locator;
+
+ function __construct(HatenaSyntax_Locator $locator)
+ {
+ $this->locator = $locator;
+ }
+
+ protected function __get($name)
+ {
+ return strtolower($name) === 'locator' ? $this->locator : $this->locator->$name;
+ }
+
+ function createLineElement(PEG_IParser $cond_parser = null)
+ {
+ $locator = $this->locator;
+
+ $item = PEG::choice($locator->link, $locator->footnote, $locator->lineChar);
+ $parser = is_null($cond_parser) ? $item : PEG::second(PEG::seq(PEG::lookaheadNot($cond_parser), $item));
+
+ return $parser;
+ }
+
+ function createLineSegment(PEG_IParser $cond_parser, $optional = false)
+ {
+ $elt = $this->createLineElement($cond_parser);
+ $parser = $optional ? PEG::many($elt) : PEG::many1($elt);
+ return HatenaSyntax_Util::segment($parser);
+ }
+
+ function createNodeCreater($type, PEG_IParser $parser, Array $keys = array())
+ {
+ return new HatenaSyntax_NodeCreater($type, $parser, $keys);
+ }
+}
\ ファイルの末尾に改行がありません
属性に変更があったパス: HatenaSyntax/tags/0.9.2-beta/HatenaSyntax/Factory.php
___________________________________________________________________
名前: svn:keywords
+ Id
HatenaSyntax/tags/0.9.2-beta/HatenaSyntax.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @package HatenaSyntax
+ * @author anatoo<anatoo@nequal.jp>
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @version $Id$
+ */
+
+include_once 'PEG.php';
+include_once dirname(__FILE__) . '/HatenaSyntax/Node.php';
+include_once dirname(__FILE__) . '/HatenaSyntax/Locator.php';
+include_once dirname(__FILE__) . '/HatenaSyntax/Factory.php';
+include_once dirname(__FILE__) . '/HatenaSyntax/NodeCreater.php';
+include_once dirname(__FILE__) . '/HatenaSyntax/Renderer.php';
+include_once dirname(__FILE__) . '/HatenaSyntax/Util.php';
+
+class HatenaSyntax
+{
+ static function parse($str)
+ {
+ return HatenaSyntax_Locator::it()->parser->parse(PEG::context($str));
+ }
+
+ static function render($str, $config = array())
+ {
+ $node = self::parse($str);
+ $renderer = new HatenaSyntax_Renderer($config);
+ return $renderer->render($node);
+ }
+}
\ ファイルの末尾に改行がありません
属性に変更があったパス: HatenaSyntax/tags/0.9.2-beta/HatenaSyntax.php
___________________________________________________________________
名前: svn:keywords
+ Id