powered by nequal
Home » HatenaSyntax » Timeline » 470

Changeset 470 -- 2009-03-01 22:08:40

Comment
[Add Tag:Release] HatenaSyntax

Diffs

HatenaSyntax/tags/0.9.0-beta/code/HatenaSyntax/NodeCreater.php

@@ -0,0 +1,27 @@
+<?php
+
+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.0-beta/code/HatenaSyntax/Util.php

@@ -0,0 +1,67 @@
+<?php
+
+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.0-beta/code/HatenaSyntax/Locator.php

@@ -0,0 +1,232 @@
+<?php
+
+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.0-beta/code/HatenaSyntax/Node.php

@@ -0,0 +1,21 @@
+<?php
+
+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.0-beta/code/HatenaSyntax/Renderer.php

@@ -0,0 +1,207 @@
+<?php
+
+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)
+    {
+        return join(PHP_EOL, array_map(array('HatenaSyntax_Renderer', 'escape'), $lines));
+    }
+
+    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 = is_string($elt) ? ($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_footntoe_%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);
+        $ret[] = $this->line('</pre>');
+        return join(PHP_EOL, $ret);
+    }
+
+    protected function renderSuperPre(Array $data)
+    {
+        $ret = array();
+        list($type, $lines) = array($data['type'], $data['body']);
+        $ret[] = $this->line('<pre class="superpre ' . $type . '">');
+        $ret[] = call_user_func($this->config['superprehandler'], $type, $lines);
+        $ret[] = $this->line('</pre>');
+        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.0-beta/code/HatenaSyntax/Factory.php

@@ -0,0 +1,38 @@
+<?php
+
+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.0-beta/code/HatenaSyntax.php

@@ -0,0 +1,23 @@
+<?php
+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);
+    }
+}
\ ファイルの末尾に改行がありません