Diffs
HatenaSyntax/trunk/code/HatenaSyntax/Element.php
@@ -1,36 +0,0 @@
-<?php
-
-// - Element := Header | DefinitionList | Table | List |
-// BlockQuote | Pre | SuperPre | Line<-(optional=true)
-class HatenaSyntax_Element implements PEG_IParser
-{
- protected $p;
- function __construct()
- {
- $blockquote = HatenaSyntax_BlockQuote::getInstance();
- $blockquote->setElement($this);
- $line = new PEG_Sequence(array(new HatenaSyntax_Line(true), HatenaSyntax_NewLine::getInstance()));
- $parser = new PEG_Choice();
- $parser->with(new HatenaSyntax_Header)
- ->with($blockquote)
- ->with(new HatenaSyntax_DefinitionList)
- ->with(new HatenaSyntax_Table)
- ->with(new HatenaSyntax_List)
- ->with(HatenaSyntax_Pre::getInstance())
- ->with(HatenaSyntax_SuperPre::getInstance())
- ->with(new PEG_CallbackAction(array($this, 'processLine'), $line));
- $this->p = $parser;
- }
- function processLine($result)
- {
- return array('type' => 'line', 'body' => $result[0]);
- }
- function parse(PEG_IContext $c)
- {
- return $this->p->parse($c);
- }
- function processNewLine($result)
- {
- return array('type' => 'empty_line');
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/Parser.php
@@ -1,20 +0,0 @@
-<?php
-
-class HatenaSyntax_Parser
-{
- protected $p;
- function __construct()
- {
- $this->p = new PEG_Many(new HatenaSyntax_Element);
- }
- function parse($str)
- {
- try {
- $context = new PEG_Context($str);
- $result = $this->p->parse($context);
- } catch (PEG_Failure $e) {
- return false;
- }
- return $result;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/Link.php
@@ -1,14 +0,0 @@
-<?php
-
-class HatenaSyntax_Link extends PEG_Choice
-{
- function __construct()
- {
- parent::__construct(array(HatenaSyntax_HttpLink::getInstance()));
- }
- static function getInstance()
- {
- static $o = null;
- return $o ? $o : $o = new self;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/HttpLink.php
@@ -1,56 +0,0 @@
-<?php
-
-// - HttpLink := "[" ("http://" | "https://") ((?!"]" | ":title=") LineChar)+ (":title=" ((?!"]") LineChar)+)? "]"
-class HatenaSyntax_HttpLink extends PEG_Action
-{
- function __construct()
- {
- $line_char = HatenaSyntax_LineChar::getInstance();
-
- $url_elt = new PEG_Sequence();
- $url_elt->with(new PEG_LookaheadNot(new PEG_Choice(array(PEG_Token::get(']'),
- PEG_Token::get(':title=')))))
- ->with($line_char);
-
- $title_elt = new PEG_Sequence();
- $title_elt->with(new PEG_LookaheadNot(PEG_Token::get(']')))
- ->with($line_char);
-
- $title = new PEG_Sequence();
- $title->with(PEG_Token::get(':title='))
- ->with(new PEG_Many1($title_elt));
-
- $parser = new PEG_Sequence();
- $parser->with(PEG_Token::get('['))
- ->with(new PEG_Choice(array(PEG_Token::get('http://'), PEG_Token::get('https://'))))
- ->with(new PEG_Many1($url_elt))
- ->with(new PEG_Optional($title))
- ->with(PEG_Token::get(']'));
- parent::__construct($parser);
- }
- function process($result)
- {
- $ret = $result[1];
-
- foreach ($result[2] as $elt) {
- $ret .= $elt[1];
- }
-
- if ($result[3]) {
- $title = '';
- foreach ($result[3][1] as $c) {
- $title .= $c[1];
- }
- }
- else {
- $title = null;
- }
-
- return array('type' => 'link', 'title' => $title, 'body' => trim($ret));
- }
- static function getInstance()
- {
- static $o = null;
- return $o ? $o : $o = new self;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/Footnote.php
@@ -1,27 +0,0 @@
-<?php
-
-class HatenaSyntax_Footnote extends PEG_Action
-{
- function __construct()
- {
- $content_elt = new PEG_Sequence();
- $content_elt->with(new PEG_LookaheadNot(PEG_Token::get('))')))
- ->with(new PEG_Choice(array(HatenaSyntax_Link::getInstance(), HatenaSyntax_LineChar::getInstance())));
- $parser = new PEG_Sequence();
- $parser->with(PEG_Token::get('(('))
- ->with(new PEG_Many1($content_elt))
- ->with(PEG_Token::get('))'));
- parent::__construct($parser);
- }
- function process($result)
- {
- $ret = '';
- foreach ($result[1] as $elt) $ret .= $elt[1];
- return array('type' => 'footnote', 'body' => $ret);
- }
- static function getInstance()
- {
- static $o = null;
- return $o ? $o : $o = new self;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/NewLine.php
@@ -1,16 +0,0 @@
-<?php
-
-class HatenaSyntax_NewLine extends PEG_Choice
-{
- function __construct()
- {
- parent::__construct(array(PEG_Token::get("\r\n"),
- PEG_Token::get("\n"),
- PEG_Token::get("\r")));
- }
- static function getInstance()
- {
- static $o = null;
- return $o ? $o : $o = new self;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/LineElement.php
@@ -1,20 +0,0 @@
-<?php
-
-class HatenaSyntax_LineElement extends PEG_Action
-{
- function __construct(PEG_IParser $arg_parser = null)
- {
- $item = new PEG_Choice(array(HatenaSyntax_Link::getInstance(),
- HatenaSyntax_Footnote::getInstance(),
- HatenaSyntax_LineChar::getInstance()));
- $parser = new PEG_Sequence(array(is_null($arg_parser) ?
- new PEG_Lookahead(PEG_Anything::getInstance()) :
- new PEG_LookaheadNot($arg_parser),
- $item));
- parent::__construct($parser);
- }
- function process($result)
- {
- return $result[1];
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/EndOfLine.php
@@ -1,21 +0,0 @@
-<?php
-
-class HatenaSyntax_EndOfLine extends PEG_Action
-{
- function __construct()
- {
- $p = new PEG_Choice(array(HatenaSyntax_NewLine::getInstance(),
- PEG_EOS::getInstance()));
- parent::__construct($p);
- }
- function process($result)
- {
- if ($result === false) $result = '';
- return $result;
- }
- static function getInstance()
- {
- static $obj = null;
- return $obj ? $obj : $obj = new self;
- }
-}
HatenaSyntax/trunk/code/HatenaSyntax/SuperPre.php
@@ -1,45 +0,0 @@
-<?php
-
-// SuperPre := ">|" (alpha alnum*)? "|" SuperPreElement+ "||<" EndOfLine @doing
-class HatenaSyntax_SuperPre implements PEG_IParser
-{
- protected $p;
- function __construct()
- {
- $this->p = new PEG_Many1(new HatenaSyntax_SuperPreElement());
- }
- function parse(PEG_IContext $context)
- {
- PEG_Token::get('>|')->parse($context);
- $ext = '';
- for (;;) {
- if ($context->eos()) throw new PEG_Failure;
- $c = $context->read(1);
- if (ctype_alnum($c)) {
- $ext .= $c;
- }
- else {
- $context->seek($context->tell() - 1);
- break;
- }
- }
- PEG_Token::get('|')->parse($context);
-
- $result = $this->p->parse($context);
-
- $nl = new PEG_Optional(HatenaSyntax_NewLine::getInstance());
- $nl->parse($context);
-
- PEG_Token::get('||<')->parse($context);
- HatenaSyntax_EndOfLine::getInstance()->parse($context);
-
- return array('type' => 'superpre',
- 'ext' => $ext,
- 'body' => $result);
- }
- static function getInstance()
- {
- static $o = null;
- return $o ? $o : $o = new self;
- }
-}
HatenaSyntax/trunk/code/HatenaSyntax/BlockQuote.php
@@ -1,33 +0,0 @@
-<?php
-
-// - BlockQuote := ">>" NewLine Element* "<<" EndOfLine
-class HatenaSyntax_BlockQuote implements PEG_IParser
-{
- protected $p;
- function setElement(HatenaSyntax_Element $elt)
- {
- $elt = new PEG_Sequence(array(new PEG_LookaheadNot(new PEG_Sequence(array(PEG_Token::get('<<'), HatenaSyntax_EndOfLine::getInstance()))), $elt));
- $this->p = new PEG_Sequence();
- $this->p->with(PEG_Token::get('>>'))
- ->with(HatenaSyntax_NewLine::getInstance())
- ->with(new PEG_Many($elt))
- ->with(PEG_Token::get('<<'))
- ->with(HatenaSyntax_EndOfLine::getInstance());
- }
- function parse(PEG_IContext $context)
- {
- $result = $this->p->parse($context);
- $ret = array();
- foreach ($result[2] as $elt) {
- $ret[] = $elt[1];
- }
-
- return array('type' => 'blockquote',
- 'body' => $ret);
- }
- static function getInstance()
- {
- static $o = null;
- return $o ? $o : $o = new self;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/LineSegment.php
@@ -1,25 +0,0 @@
-<?php
-
-class HatenaSyntax_LineSegment extends PEG_Action
-{
- function __construct(PEG_IParser $p, $optional = false)
- {
- $elt = new HatenaSyntax_LineElement($p);
- $parser = $optional ? new PEG_Many($elt) : new PEG_Many1($elt);
- parent::__construct($parser);
- }
- function process($result)
- {
- $ret = array();
- foreach ($result as $elt) {
- if (!count($ret) || !is_string($elt) || !is_string(end($ret))) {
- $ret[] = $elt;
- }
- elseif (count($ret) && is_string($elt) && is_string(end($ret))) {
- $ret[count($ret) - 1] .= $elt;
- }
- else throw new Exception($elt);
- }
- return $ret;
- }
-}
HatenaSyntax/trunk/code/HatenaSyntax/List.php
@@ -1,60 +0,0 @@
-<?php
-
-// - ListLine := ("+" | "-")+ Line<-(optional=true) EndOfLine
-// - List := ListLine+
-
-class HatenaSyntax_List extends PEG_Action
-{
- function __construct()
- {
- $line = new PEG_Sequence;
- $line->with(new PEG_Many1(new PEG_Choice(array(PEG_Token::get('-'),
- PEG_Token::get('+')))))
- ->with(new HatenaSyntax_Line(true))
- ->with(HatenaSyntax_EndOfLine::getInstance());
- $line = new PEG_CallbackAction(array($this, 'processLine'), $line);
-
- parent::__construct(new PEG_Many1($line));
- }
- function process($result)
- {
- return $this->make($result);
- }
- protected function make(Array &$arr, $level = 0)
- {
- $body = array();
-
- if ($elt = current($arr)) {
- $head = $elt['type'];
- }
- else {
- $head = '';
- }
-
- while ($elt = current($arr)) {
- if ($elt['level'] === $level) {
- $body[] = $elt['body'];
- next($arr);
- }
- elseif ($elt['level'] > $level) {
- $body[] = $this->make($arr, $level + 1);
- }
- else {
- break;
- }
- }
-
- return array('type' => 'list',
- 'head' => $head,
- 'body' => $body);
- }
- function processLine($result)
- {
- $type = end($result[0]); // '+' or '-'
- $level = count($result[0]) - 1;
- $body = $result[1];
- return array('type' => $type,
- 'level' => $level,
- 'body' => $body);
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/SuperPreElement.php
@@ -1,28 +0,0 @@
-<?php
-
-// SuperPreElement := NewLine (?! "||<" EndOfLine) ((?! "||<" EndOfLine) LineChar)*
-class HatenaSyntax_SuperPreElement extends PEG_Action
-{
- function __construct()
- {
- $cond = new PEG_LookaheadNot(new PEG_Sequence(array(PEG_Token::get('||<',
- HatenaSyntax_EndOfLine::getInstance()))));
- $elt = new PEG_Sequence();
- $elt->with($cond)
- ->with(HatenaSyntax_LineChar::getInstance());
-
- $parser = new PEG_Sequence();
- $parser->with(HatenaSyntax_NewLine::getInstance())
- ->with($cond)
- ->with(new PEG_Many($elt));
- parent::__construct($parser);
- }
- function process($result)
- {
- $line = '';
- foreach ($result[2] as $elt) {
- $line .= $elt[1];
- }
- return $line;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/DefinitionList.php
@@ -1,14 +0,0 @@
-<?php
-
-class HatenaSyntax_DefinitionList extends PEG_Action
-{
- function __construct()
- {
- parent::__construct(new PEG_Many1(new HatenaSyntax_Definition));
- }
- function process($result)
- {
- return array('type' => 'definitionlist',
- 'body' => $result);
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/LineChar.php
@@ -1,22 +0,0 @@
-<?php
-
-class HatenaSyntax_LineChar extends PEG_Action
-{
- function __construct()
- {
- $nl = new PEG_Choice();
- $nl->with(PEG_Token::get("\n"))->with(PEG_Token::get("\r"));
- $lookahead = new PEG_Lookahead(new PEG_Not($nl));
- $linechar = new PEG_Sequence(array($lookahead, PEG_Anything::getInstance()));
- parent::__construct($linechar);
- }
- function process($result)
- {
- return $result[1];
- }
- static function getInstance()
- {
- static $obj = null;
- return $obj ? $obj : $obj = new self;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/Pre.php
@@ -1,42 +0,0 @@
-<?php
-
-// Pre := ">|" (NewLine LineSegment<-( "|<" EndOfLine, true))+ "|<" EndOfLine
-class HatenaSyntax_Pre extends PEG_Action
-{
- function __construct()
- {
- $newline = HatenaSyntax_NewLine::getInstance();
-
- $eol = HatenaSyntax_EndOfLine::getInstance();
-
- $cond = new PEG_Sequence();
- $cond->with(PEG_Token::get('|<'))
- ->with($eol);
-
- $line = new PEG_Sequence();
- $line->with($newline)
- ->with(new HatenaSyntax_LineSegment($cond, true));
-
- $parser = new PEG_Sequence();
- $parser->with(PEG_Token::get('>|'))
- ->with(new PEG_Many1($line))
- ->with(PEG_Token::get('|<'))
- ->with($eol);
- parent::__construct($parser);
- }
- function process($result)
- {
- $ret = array();
- $arr = $result[1];
- foreach ($arr as $elt) if (count($elt[1])) {
- $ret[] = $elt[1];
- }
- return array('type' => 'pre',
- 'body' => $ret);
- }
- static function getInstance()
- {
- static $o = null;
- return $o ? $o : $o = new self;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/Line.php
@@ -1,26 +0,0 @@
-<?php
-
-class HatenaSyntax_Line extends PEG_Action
-{
- function __construct($optional = false)
- {
- $elt = new HatenaSyntax_LineElement;
- $parser = $optional ? new PEG_Many($elt) : new PEG_Many1($elt);
- parent::__construct($parser);
- }
- function process($result)
- {
-
- $ret = array();
- foreach ($result as $elt) {
- if (!count($ret) || !is_string($elt) || !is_string(end($ret))) {
- $ret[] = $elt;
- }
- elseif (count($ret) && is_string($elt) && is_string(end($ret))) {
- $ret[count($ret) - 1] .= $elt;
- }
- else throw new Exception($elt);
- }
- return $ret;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/Table.php
@@ -1,32 +0,0 @@
-<?php
-
-// - TableLine := TableCell+ "|" EndOfLine
-// - Table := TableLine+
-class HatenaSyntax_Table extends PEG_Action
-{
- function __construct()
- {
- $line = new PEG_Sequence();
- $line->with(new PEG_Many1(new HatenaSyntax_TableCell()))
- ->with(PEG_Token::get('|'))
- ->with(HatenaSyntax_EndOfLine::getInstance());
- $parser = new PEG_Many1($line);
-
- parent::__construct($parser);
- }
- function process($result)
- {
- $ret = array();
- foreach ($result as $elt) {
- $ret[] = $elt[0];
- }
-
- return array('type' => 'table',
- 'body' => $ret);
- }
- static function getInstance()
- {
- static $o = null;
- return $o ? $o : $o = new self;
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/TableCell.php
@@ -1,20 +0,0 @@
-<?php
-
-// - TableCell := "|" "*"? LineSegment<-("|", optional=true)
-class HatenaSyntax_TableCell extends PEG_Action
-{
- function __construct()
- {
- $parser = new PEG_Sequence();
- $parser->with(PEG_Token::get('|'))
- ->with(new PEG_LookaheadNot(HatenaSyntax_EndOfLine::getInstance()))
- ->with(new PEG_Optional(PEG_Token::get('*')))
- ->with(new HatenaSyntax_LineSegment(PEG_Token::get('|'), true));
- parent::__construct($parser);
- }
- function process($result)
- {
- return array('header' => !!$result[2],
- 'body' => $result[3]);
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/Header.php
@@ -1,30 +0,0 @@
-<?php
-
-class HatenaSyntax_Header extends PEG_Action
-{
- function __construct()
- {
- $lc = HatenaSyntax_LineChar::getInstance();
- $fn = HatenaSyntax_Footnote::getInstance();
- $parser = new PEG_Sequence();
- $parser->with(new PEG_Many1(PEG_Token::get('*')))
- ->with(new PEG_Many(new PEG_Choice(array($fn, $lc))))
- ->with(HatenaSyntax_EndOfLine::getInstance());
- parent::__construct($parser);
- }
- protected function process($result)
- {
- $body = array();
- foreach ($result[1] as $elt) {
- if (count($body) && is_string($elt) && is_string(end($body))) {
- $body[count($body) - 1] .= $elt;
- }
- else {
- $body[] = $elt;
- }
- }
- return array('type' => 'header',
- 'level' => count($result[0]),
- 'body' => $body);
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/code/HatenaSyntax/Definition.php
@@ -1,22 +0,0 @@
-<?php
-
-class HatenaSyntax_Definition extends PEG_Action
-{
- function __construct()
- {
- $eol = HatenaSyntax_EndOfLine::getInstance();
-
- $content = new HatenaSyntax_LineSegment(PEG_Token::get(':'));
- $parser = new PEG_Sequence();
- $parser->with(PEG_Token::get(':'))
- ->with($content)
- ->with(PEG_Token::get(':'))
- ->with($content)
- ->with($eol);
- parent::__construct($parser);
- }
- function process($result)
- {
- return array($result[1], $result[3]);
- }
-}
\ ファイルの末尾に改行がありません
HatenaSyntax/trunk/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/trunk/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/trunk/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/trunk/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/trunk/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/trunk/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/trunk/code/HatenaSyntax.php
@@ -1,23 +1,23 @@
<?php
-include_once 'PEG.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/BlockQuote.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/Definition.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/DefinitionList.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/Element.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/EndOfLine.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/Footnote.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/Header.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/HttpLink.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/Line.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/LineChar.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/LineElement.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/LineSegment.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/Link.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/List.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/NewLine.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/Parser.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/Pre.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/SuperPre.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/SuperPreElement.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/Table.php';
-include_once dirname(__FILE__) . '/HatenaSyntax/TableCell.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);
+ }
+}
\ ファイルの末尾に改行がありません