parse($lines);
return $body->toString();
}
// Block elements
class Element
{
var $parent;
var $elements; // References of childs
var $last; // Insert new one at the back of the $last
function Element()
{
$this->elements = array();
$this->last = & $this;
}
function setParent(& $parent)
{
$this->parent = & $parent;
}
function & add(& $obj)
{
if ($this->canContain($obj)) {
return $this->insert($obj);
} else {
return $this->parent->add($obj);
}
}
function & insert(& $obj)
{
$obj->setParent($this);
$this->elements[] = & $obj;
return $this->last = & $obj->last;
}
function canContain($obj)
{
return TRUE;
}
function wrap($string, $tag, $param = '', $canomit = TRUE)
{
return ($canomit && $string == '') ? '' :
'<' . $tag . $param . '>' . $string . '' . $tag . '>';
}
function toString()
{
$ret = array();
foreach (array_keys($this->elements) as $key)
$ret[] = $this->elements[$key]->toString();
return join("\n", $ret);
}
function dump($indent = 0)
{
$ret = str_repeat(' ', $indent) . get_class($this) . "\n";
$indent += 2;
foreach (array_keys($this->elements) as $key) {
$ret .= is_object($this->elements[$key]) ?
$this->elements[$key]->dump($indent) : '';
//str_repeat(' ', $indent) . $this->elements[$key];
}
return $ret;
}
}
// Returns inline-related object
function & Factory_Inline($text)
{
// Check the first letter of the line
if (substr($text, 0, 1) == '~') {
return new Paragraph(' ' . substr($text, 1));
} else {
return new Inline($text);
}
}
function & Factory_DList(& $root, $text)
{
$out = explode('|', ltrim($text), 2);
if (count($out) < 2) {
return Factory_Inline($text);
} else {
return new DList($out);
}
}
// '|'-separated table
function & Factory_Table(& $root, $text)
{
if (! preg_match('/^\|(.+)\|([hHfFcC]?)$/', $text, $out)) {
return Factory_Inline($text);
} else {
return new Table($out);
}
}
// Comma-separated table
function & Factory_YTable(& $root, $text)
{
if ($text == ',') {
return Factory_Inline($text);
} else {
return new YTable(csv_explode(',', substr($text, 1)));
}
}
function & Factory_Div(& $root, $text)
{
$matches = array();
// Seems block plugin?
if (PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK) {
// Usual code
if (preg_match('/^\#([^\(]+)(?:\((.*)\))?/', $text, $matches) &&
exist_plugin_convert($matches[1])) {
return new Div($matches);
}
} else {
// Hack code
if(preg_match('/^#([^\(\{]+)(?:\(([^\r]*)\))?(\{*)/', $text, $matches) &&
exist_plugin_convert($matches[1])) {
$len = strlen($matches[3]);
$body = array();
if ($len == 0) {
return new Div($matches); // Seems legacy block plugin
} else if (preg_match('/\{{' . $len . '}\s*\r(.*)\r\}{' . $len . '}/', $text, $body)) {
$matches[2] .= "\r" . $body[1] . "\r";
return new Div($matches); // Seems multiline-enabled block plugin
}
}
}
return new Paragraph($text);
}
// Inline elements
class Inline extends Element
{
function Inline($text)
{
parent::Element();
$this->elements[] = trim((substr($text, 0, 1) == "\n") ?
$text : make_link($text));
}
function & insert(& $obj)
{
$this->elements[] = $obj->elements[0];
return $this;
}
function canContain($obj)
{
return is_a($obj, 'Inline');
}
function toString()
{
global $line_break;
return join(($line_break ? '
' . "\n" : "\n"), $this->elements);
}
function & toPara($class = '')
{
$obj = new Paragraph('', $class);
$obj->insert($this);
return $obj;
}
}
// Paragraph: blank-line-separated sentences
class Paragraph extends Element
{
var $param;
function Paragraph($text, $param = '')
{
parent::Element();
$this->param = $param;
if ($text == '') return;
if (substr($text, 0, 1) == '~')
$text = ' ' . substr($text, 1);
$this->insert(Factory_Inline($text));
}
function canContain($obj)
{
return is_a($obj, 'Inline');
}
function toString()
{
return $this->wrap(parent::toString(), 'p', $this->param);
}
}
// * Heading1
// ** Heading2
// *** Heading3
class Heading extends Element
{
var $level;
var $id;
var $msg_top;
function Heading(& $root, $text)
{
parent::Element();
$this->level = min(3, strspn($text, '*'));
list($text, $this->msg_top, $this->id) = $root->getAnchor($text, $this->level);
$this->insert(Factory_Inline($text));
$this->level++; // h2,h3,h4
}
function & insert(& $obj)
{
parent::insert($obj);
return $this->last = & $this;
}
function canContain(& $obj)
{
return FALSE;
}
function toString()
{
return $this->msg_top . $this->wrap(parent::toString(),
'h' . $this->level, ' id="' . $this->id . '"');
}
}
// ----
// Horizontal Rule
class HRule extends Element
{
function HRule(& $root, $text)
{
parent::Element();
}
function canContain(& $obj)
{
return FALSE;
}
function toString()
{
global $hr;
return $hr;
}
}
// Lists (UL, OL, DL)
class ListContainer extends Element
{
var $tag;
var $tag2;
var $level;
var $style;
var $margin;
var $left_margin;
function ListContainer($tag, $tag2, $head, $text)
{
parent::Element();
$var_margin = '_' . $tag . '_margin';
$var_left_margin = '_' . $tag . '_left_margin';
global $$var_margin, $$var_left_margin;
$this->margin = $$var_margin;
$this->left_margin = $$var_left_margin;
$this->tag = $tag;
$this->tag2 = $tag2;
$this->level = min(3, strspn($text, $head));
$text = ltrim(substr($text, $this->level));
parent::insert(new ListElement($this->level, $tag2));
if ($text != '')
$this->last = & $this->last->insert(Factory_Inline($text));
}
function canContain(& $obj)
{
return (! is_a($obj, 'ListContainer')
|| ($this->tag == $obj->tag && $this->level == $obj->level));
}
function setParent(& $parent)
{
global $_list_pad_str;
parent::setParent($parent);
$step = $this->level;
if (isset($parent->parent) && is_a($parent->parent, 'ListContainer'))
$step -= $parent->parent->level;
$margin = $this->margin * $step;
if ($step == $this->level)
$margin += $this->left_margin;
$this->style = sprintf($_list_pad_str, $this->level, $margin, $margin);
}
function & insert(& $obj)
{
if (! is_a($obj, get_class($this)))
return $this->last = & $this->last->insert($obj);
// Break if no elements found (BugTrack/524)
if (count($obj->elements) == 1 && empty($obj->elements[0]->elements))
return $this->last->parent; // up to ListElement
// Move elements
foreach(array_keys($obj->elements) as $key)
parent::insert($obj->elements[$key]);
return $this->last;
}
function toString()
{
return $this->wrap(parent::toString(), $this->tag, $this->style);
}
}
class ListElement extends Element
{
function ListElement($level, $head)
{
parent::Element();
$this->level = $level;
$this->head = $head;
}
function canContain(& $obj)
{
return (! is_a($obj, 'ListContainer') || ($obj->level > $this->level));
}
function toString()
{
return $this->wrap(parent::toString(), $this->head);
}
}
// - One
// - Two
// - Three
class UList extends ListContainer
{
function UList(& $root, $text)
{
parent::ListContainer('ul', 'li', '-', $text);
}
}
// + One
// + Two
// + Three
class OList extends ListContainer
{
function OList(& $root, $text)
{
parent::ListContainer('ol', 'li', '+', $text);
}
}
// : definition1 | description1
// : definition2 | description2
// : definition3 | description3
class DList extends ListContainer
{
function DList($out)
{
parent::ListContainer('dl', 'dt', ':', $out[0]);
$this->last = & Element::insert(new ListElement($this->level, 'dd'));
if ($out[1] != '')
$this->last = & $this->last->insert(Factory_Inline($out[1]));
}
}
// > Someting cited
// > like E-mail text
class BQuote extends Element
{
var $level;
function BQuote(& $root, $text)
{
parent::Element();
$head = substr($text, 0, 1);
$this->level = min(3, strspn($text, $head));
$text = ltrim(substr($text, $this->level));
if ($head == '<') { // Blockquote close
$level = $this->level;
$this->level = 0;
$this->last = & $this->end($root, $level);
if ($text != '')
$this->last = & $this->last->insert(Factory_Inline($text));
} else {
$this->insert(Factory_Inline($text));
}
}
function canContain(& $obj)
{
return (! is_a($obj, get_class($this)) || $obj->level >= $this->level);
}
function & insert(& $obj)
{
// BugTrack/521, BugTrack/545
if (is_a($obj, 'inline'))
return parent::insert($obj->toPara(' class="quotation"'));
if (is_a($obj, 'BQuote') && $obj->level == $this->level && count($obj->elements)) {
$obj = & $obj->elements[0];
if (is_a($this->last, 'Paragraph') && count($obj->elements))
$obj = & $obj->elements[0];
}
return parent::insert($obj);
}
function toString()
{
return $this->wrap(parent::toString(), 'blockquote');
}
function & end(& $root, $level)
{
$parent = & $root->last;
while (is_object($parent)) {
if (is_a($parent, 'BQuote') && $parent->level == $level)
return $parent->parent;
$parent = & $parent->parent;
}
return $this;
}
}
class TableCell extends Element
{
var $tag = 'td'; // {td|th}
var $colspan = 1;
var $rowspan = 1;
var $style; // is array('width'=>, 'align'=>...);
function TableCell($text, $is_template = FALSE)
{
parent::Element();
$this->style = $matches = array();
while (preg_match('/^(?:(LEFT|CENTER|RIGHT)|(BG)?COLOR\(([#\w]+)\)|SIZE\((\d+)\)):(.*)$/',
$text, $matches)) {
if ($matches[1]) {
$this->style['align'] = 'text-align:' . strtolower($matches[1]) . ';';
$text = $matches[5];
} else if ($matches[3]) {
$name = $matches[2] ? 'background-color' : 'color';
$this->style[$name] = $name . ':' . htmlsc($matches[3]) . ';';
$text = $matches[5];
} else if ($matches[4]) {
$this->style['size'] = 'font-size:' . htmlsc($matches[4]) . 'px;';
$text = $matches[5];
}
}
if ($is_template && is_numeric($text))
$this->style['width'] = 'width:' . $text . 'px;';
if ($text == '>') {
$this->colspan = 0;
} else if ($text == '~') {
$this->rowspan = 0;
} else if (substr($text, 0, 1) == '~') {
$this->tag = 'th';
$text = substr($text, 1);
}
if ($text != '' && $text{0} == '#') {
// Try using Div class for this $text
$obj = & Factory_Div($this, $text);
if (is_a($obj, 'Paragraph'))
$obj = & $obj->elements[0];
} else {
$obj = & Factory_Inline($text);
}
$this->insert($obj);
}
function setStyle(& $style)
{
foreach ($style as $key=>$value)
if (! isset($this->style[$key]))
$this->style[$key] = $value;
}
function toString()
{
if ($this->rowspan == 0 || $this->colspan == 0) return '';
$param = ' class="style_' . $this->tag . '"';
if ($this->rowspan > 1)
$param .= ' rowspan="' . $this->rowspan . '"';
if ($this->colspan > 1) {
$param .= ' colspan="' . $this->colspan . '"';
unset($this->style['width']);
}
if (! empty($this->style))
$param .= ' style="' . join(' ', $this->style) . '"';
return $this->wrap(parent::toString(), $this->tag, $param, FALSE);
}
}
// | title1 | title2 | title3 |
// | cell1 | cell2 | cell3 |
// | cell4 | cell5 | cell6 |
class Table extends Element
{
var $type;
var $types;
var $col; // number of column
function Table($out)
{
parent::Element();
$cells = explode('|', $out[1]);
$this->col = count($cells);
$this->type = strtolower($out[2]);
$this->types = array($this->type);
$is_template = ($this->type == 'c');
$row = array();
foreach ($cells as $cell)
$row[] = new TableCell($cell, $is_template);
$this->elements[] = $row;
}
function canContain(& $obj)
{
return is_a($obj, 'Table') && ($obj->col == $this->col);
}
function & insert(& $obj)
{
$this->elements[] = $obj->elements[0];
$this->types[] = $obj->type;
return $this;
}
function toString()
{
static $parts = array('h'=>'thead', 'f'=>'tfoot', ''=>'tbody');
// Set rowspan (from bottom, to top)
for ($ncol = 0; $ncol < $this->col; $ncol++) {
$rowspan = 1;
foreach (array_reverse(array_keys($this->elements)) as $nrow) {
$row = & $this->elements[$nrow];
if ($row[$ncol]->rowspan == 0) {
++$rowspan;
continue;
}
$row[$ncol]->rowspan = $rowspan;
// Inherits row type
while (--$rowspan)
$this->types[$nrow + $rowspan] = $this->types[$nrow];
$rowspan = 1;
}
}
// Set colspan and style
$stylerow = NULL;
foreach (array_keys($this->elements) as $nrow) {
$row = & $this->elements[$nrow];
if ($this->types[$nrow] == 'c')
$stylerow = & $row;
$colspan = 1;
foreach (array_keys($row) as $ncol) {
if ($row[$ncol]->colspan == 0) {
++$colspan;
continue;
}
$row[$ncol]->colspan = $colspan;
if ($stylerow !== NULL) {
$row[$ncol]->setStyle($stylerow[$ncol]->style);
// Inherits column style
while (--$colspan)
$row[$ncol - $colspan]->setStyle($stylerow[$ncol]->style);
}
$colspan = 1;
}
}
// toString
$string = '';
foreach ($parts as $type => $part)
{
$part_string = '';
foreach (array_keys($this->elements) as $nrow) {
if ($this->types[$nrow] != $type)
continue;
$row = & $this->elements[$nrow];
$row_string = '';
foreach (array_keys($row) as $ncol)
$row_string .= $row[$ncol]->toString();
$part_string .= $this->wrap($row_string, 'tr');
}
$string .= $this->wrap($part_string, $part);
}
$string = $this->wrap($string, 'table', ' class="style_table" cellspacing="1" border="0"');
return $this->wrap($string, 'div', ' class="ie5"');
}
}
// , cell1 , cell2 , cell3
// , cell4 , cell5 , cell6
// , cell7 , right,==
// ,left ,==, cell8
class YTable extends Element
{
var $col; // Number of columns
// TODO: Seems unable to show literal '==' without tricks.
// But it will be imcompatible.
// TODO: Why toString() or toXHTML() here
function YTable($row = array('cell1 ', ' cell2 ', ' cell3'))
{
parent::Element();
$str = array();
$col = count($row);
$matches = $_value = $_align = array();
foreach($row as $cell) {
if (preg_match('/^(\s+)?(.+?)(\s+)?$/', $cell, $matches)) {
if ($matches[2] == '==') {
// Colspan
$_value[] = FALSE;
$_align[] = FALSE;
} else {
$_value[] = $matches[2];
if ($matches[1] == '') {
$_align[] = ''; // left
} else if (isset($matches[3])) {
$_align[] = 'center';
} else {
$_align[] = 'right';
}
}
} else {
$_value[] = $cell;
$_align[] = '';
}
}
for ($i = 0; $i < $col; $i++) {
if ($_value[$i] === FALSE) continue;
$colspan = 1;
while (isset($_value[$i + $colspan]) && $_value[$i + $colspan] === FALSE) ++$colspan;
$colspan = ($colspan > 1) ? ' colspan="' . $colspan . '"' : '';
$align = $_align[$i] ? ' style="text-align:' . $_align[$i] . '"' : '';
$str[] = '