2 /////////////////////////////////////////////////
3 // PukiWiki - Yet another WikiWikiWeb clone.
5 // $Id: convert_html.php,v 1.5 2004/11/23 11:27:25 henoheno Exp $
8 function convert_html($lines)
10 global $vars, $digest;
11 static $contents_id = 0;
14 $digest = md5(join('', get_source($vars['page'])));
16 if (! is_array($lines)) $lines = explode("\n", $lines);
18 $body = & new Body(++$contents_id);
21 return $body->toString();
26 var $parent; // ¿ÆÍ×ÁÇ
27 var $last; // ¼¡¤ËÍ×ÁǤòÁÞÆþ¤¹¤ëÀè
28 var $elements; // Í×ÁǤÎÇÛÎó
32 $this->elements = array();
33 $this->last = & $this;
36 function setParent(& $parent)
38 $this->parent = & $parent;
41 function & add(& $obj)
43 if ($this->canContain($obj)) {
44 return $this->insert($obj);
46 return $this->parent->add($obj);
50 function & insert(& $obj)
52 $obj->setParent($this);
53 $this->elements[] = & $obj;
55 return $this->last = & $obj->last;
58 function canContain($obj)
63 function wrap($string, $tag, $param = '', $canomit = TRUE)
65 return ($canomit && $string == '') ? '' : "<$tag$param>$string</$tag>";
71 foreach (array_keys($this->elements) as $key) {
72 $ret[] = $this->elements[$key]->toString();
75 return join("\n", $ret);
78 function dump($indent = 0)
80 $ret = str_repeat(' ', $indent) . get_class($this) . "\n";
84 foreach (array_keys($this->elements) as $key) {
85 $ret .= is_object($this->elements[$key]) ?
86 $this->elements[$key]->dump($indent) : '';
87 //str_repeat(' ',$indent).$this->elements[$key];
94 function & Factory_Inline($text)
96 if (substr($text, 0, 1) == '~') {
97 // ¹ÔƬ '~' ¡£¥Ñ¥é¥°¥é¥Õ³«»Ï
98 return new Paragraph(' ' . substr($text, 1));
100 return new Inline($text);
104 function & Factory_DList(& $root, $text)
106 $out = explode('|', ltrim($text), 2);
107 if (count($out) < 2) {
108 return Factory_Inline($text);
110 return new DList($out);
114 // '|'-separated table
115 function & Factory_Table(& $root, $text)
117 if (! preg_match("/^\|(.+)\|([hHfFcC]?)$/", $text, $out)) {
118 return Factory_Inline($text);
120 return new Table($out);
124 // Comma-separated table
125 function & Factory_YTable(& $root, $text)
128 return Factory_Inline($text);
130 return new YTable(csv_explode(',', substr($text, 1)));
134 function & Factory_Div(& $root, $text)
136 if (! preg_match("/^\#([^\(]+)(?:\((.*)\))?/", $text, $out) || ! exist_plugin_convert($out[1])) {
137 return new Paragraph($text);
139 return new Div($out);
144 class Inline extends Element
146 function Inline($text)
149 $this->elements[] = trim((substr($text, 0, 1) == "\n") ? $text : make_link($text));
152 function & insert(& $obj)
154 $this->elements[] = $obj->elements[0];
158 function canContain($obj)
160 return is_a($obj, 'Inline');
166 return join($line_break ? "<br />\n" : "\n", $this->elements);
169 function & toPara($class = '')
171 $obj = & new Paragraph('', $class);
177 // Paragraph: blank-line-separated sentences
178 class Paragraph extends Element
182 function Paragraph($text, $param = '')
186 $this->param = $param;
188 if ($text == '') return;
190 if (substr($text, 0, 1) == '~')
191 $text = ' ' . substr($text, 1);
193 $this->insert(Factory_Inline($text));
196 function canContain($obj)
198 return is_a($obj, 'Inline');
203 return $this->wrap(parent::toString(), 'p', $this->param);
210 class Heading extends Element
216 function Heading(& $root, $text)
220 $this->level = min(3, strspn($text, '*'));
221 list($text, $this->msg_top, $this->id) = $root->getAnchor($text, $this->level);
222 $this->insert(Factory_Inline($text));
223 $this->level++; // h2,h3,h4
226 function & insert(& $obj)
228 parent::insert($obj);
229 return $this->last = & $this;
232 function canContain(& $obj)
239 return $this->msg_top . $this->wrap(parent::toString(), 'h' . $this->level, " id=\"{$this->id}\"");
245 class HRule extends Element
247 function HRule(& $root, $text)
252 function canContain(& $obj)
264 class ListContainer extends Element
273 function ListContainer($tag, $tag2, $head, $text)
278 $var_margin = "_{$tag}_margin";
279 $var_left_margin = "_{$tag}_left_margin";
281 global $$var_margin, $$var_left_margin;
283 $this->margin = $$var_margin;
284 $this->left_margin = $$var_left_margin;
289 $this->level = min(3, strspn($text, $head));
290 $text = ltrim(substr($text, $this->level));
292 parent::insert(new ListElement($this->level, $tag2));
294 $this->last = & $this->last->insert(Factory_Inline($text));
297 function canContain(& $obj)
299 return (! is_a($obj, 'ListContainer')
300 || ($this->tag == $obj->tag && $this->level == $obj->level));
303 function setParent(& $parent)
305 global $_list_pad_str;
307 parent::setParent($parent);
309 $step = $this->level;
310 if (isset($parent->parent) && is_a($parent->parent, 'ListContainer')) {
311 $step -= $parent->parent->level;
313 $margin = $this->margin * $step;
314 if ($step == $this->level) {
315 $margin += $this->left_margin;
317 $this->style = sprintf($_list_pad_str, $this->level, $margin, $margin);
320 function & insert(& $obj)
322 if (! is_a($obj, get_class($this)))
323 return $this->last = & $this->last->insert($obj);
325 // ¹ÔƬʸ»ú¤Î¤ß¤Î»ØÄê»þ¤ÏUL/OL¥Ö¥í¥Ã¥¯¤òæ½Ð
327 if (count($obj->elements) == 1 && empty($obj->elements[0]->elements))
328 return $this->last->parent; // up to ListElement.
331 foreach(array_keys($obj->elements) as $key)
332 parent::insert($obj->elements[$key]);
339 return $this->wrap(parent::toString(), $this->tag, $this->style);
343 class ListElement extends Element
345 function ListElement($level, $head)
348 $this->level = $level;
352 function canContain(& $obj)
354 return (! is_a($obj, 'ListContainer') || ($obj->level > $this->level));
359 return $this->wrap(parent::toString(), $this->head);
366 class UList extends ListContainer
368 function UList(& $root, $text)
370 parent::ListContainer('ul', 'li', '-', $text);
377 class OList extends ListContainer
379 function OList(& $root, $text)
381 parent::ListContainer('ol', 'li', '+', $text);
385 // : definition1 | description1
386 // : definition2 | description2
387 // : definition3 | description3
388 class DList extends ListContainer
392 parent::ListContainer('dl', 'dt', ':', $out[0]);
394 $this->last = & Element::insert(new ListElement($this->level, 'dd'));
396 $this->last = & $this->last->insert(Factory_Inline($out[1]));
402 // > like E-mail text
403 class BQuote extends Element
407 function BQuote(& $root, $text)
411 $head = substr($text, 0, 1);
412 $this->level = min(3, strspn($text, $head));
413 $text = ltrim(substr($text, $this->level));
415 if ($head == '<') { // Blockquote close
416 $level = $this->level;
418 $this->last = & $this->end($root, $level);
420 $this->last = & $this->last->insert(Factory_Inline($text));
422 $this->insert(Factory_Inline($text));
426 function canContain(& $obj)
428 return (! is_a($obj, get_class($this)) || $obj->level >= $this->level);
431 function & insert(& $obj)
433 // BugTrack/521, BugTrack/545
434 if (is_a($obj, 'inline'))
435 return parent::insert($obj->toPara(' class="quotation"'));
437 if (is_a($obj, 'BQuote') && $obj->level == $this->level && count($obj->elements)) {
438 $obj = & $obj->elements[0];
439 if (is_a($this->last, 'Paragraph') && count($obj->elements))
440 $obj = & $obj->elements[0];
442 return parent::insert($obj);
447 return $this->wrap(parent::toString(), 'blockquote');
450 function & end(& $root, $level)
452 $parent = & $root->last;
454 while (is_object($parent)) {
455 if (is_a($parent, 'BQuote') && $parent->level == $level)
456 return $parent->parent;
457 $parent = & $parent->parent;
463 class TableCell extends Element
465 var $tag = 'td'; // {td|th}
468 var $style; // is array('width'=>, 'align'=>...);
470 function TableCell($text, $is_template = FALSE)
473 $this->style = $matches = array();
475 while (preg_match('/^(?:(LEFT|CENTER|RIGHT)|(BG)?COLOR\(([#\w]+)\)|SIZE\((\d+)\)):(.*)$/',
478 $this->style['align'] = 'text-align:' . strtolower($matches[1]) . ';';
480 } else if ($matches[3]) {
481 $name = $matches[2] ? 'background-color' : 'color';
482 $this->style[$name] = $name . ':' . htmlspecialchars($matches[3]) . ';';
484 } else if ($matches[4]) {
485 $this->style['size'] = 'font-size:' . htmlspecialchars($matches[4]) . 'px;';
489 if ($is_template && is_numeric($text)) {
490 $this->style['width'] = "width:{$text}px;";
495 } else if ($text == '~') {
497 } else if (substr($text, 0, 1) == '~') {
499 $text = substr($text, 1);
502 if ($text != '' && $text{0} == '#') {
503 // ¥»¥ëÆâÍƤ¬'#'¤Ç»Ï¤Þ¤ë¤È¤¤ÏDiv¥¯¥é¥¹¤òÄ̤·¤Æ¤ß¤ë
504 $obj = & Factory_Div($this, $text);
505 if (is_a($obj, 'Paragraph')) {
506 $obj = & $obj->elements[0];
509 $obj = & Factory_Inline($text);
515 function setStyle(& $style)
517 foreach ($style as $key=>$value) {
518 if (! isset($this->style[$key]))
519 $this->style[$key] = $value;
525 if ($this->rowspan == 0 || $this->colspan == 0) return '';
527 $param = " class=\"style_{$this->tag}\"";
528 if ($this->rowspan > 1) {
529 $param .= " rowspan=\"{$this->rowspan}\"";
531 if ($this->colspan > 1) {
532 $param .= " colspan=\"{$this->colspan}\"";
533 unset($this->style['width']);
535 if (! empty($this->style)) {
536 $param .= ' style="' . join(' ', $this->style) . '"';
539 return $this->wrap(parent::toString(), $this->tag, $param, FALSE);
543 // | title1 | title2 | title3 |
544 // | cell1 | cell2 | cell3 |
545 // | cell4 | cell5 | cell6 |
546 class Table extends Element
550 var $col; // number of column
556 $cells = explode('|', $out[1]);
557 $this->col = count($cells);
558 $this->type = strtolower($out[2]);
559 $this->types = array($this->type);
560 $is_template = ($this->type == 'c');
562 foreach ($cells as $cell) {
563 $row[] = & new TableCell($cell, $is_template);
565 $this->elements[] = $row;
568 function canContain(& $obj)
570 return is_a($obj, 'Table') && ($obj->col == $this->col);
573 function & insert(& $obj)
575 $this->elements[] = $obj->elements[0];
576 $this->types[] = $obj->type;
582 static $parts = array('h'=>'thead', 'f'=>'tfoot', ''=>'tbody');
584 // rowspan¤òÀßÄê(²¼¤«¤é¾å¤Ø)
585 for ($ncol = 0; $ncol < $this->col; $ncol++) {
587 foreach (array_reverse(array_keys($this->elements)) as $nrow) {
588 $row = & $this->elements[$nrow];
589 if ($row[$ncol]->rowspan == 0) {
593 $row[$ncol]->rowspan = $rowspan;
594 while (--$rowspan) { // ¹Ô¼ïÊ̤ò·Ñ¾µ¤¹¤ë
595 $this->types[$nrow + $rowspan] = $this->types[$nrow];
601 // colspan,style¤òÀßÄê
603 foreach (array_keys($this->elements) as $nrow) {
604 $row = & $this->elements[$nrow];
605 if ($this->types[$nrow] == 'c') {
609 foreach (array_keys($row) as $ncol) {
610 if ($row[$ncol]->colspan == 0) {
614 $row[$ncol]->colspan = $colspan;
615 if ($stylerow !== NULL) {
616 $row[$ncol]->setStyle($stylerow[$ncol]->style);
617 while (--$colspan) { // Îó¥¹¥¿¥¤¥ë¤ò·Ñ¾µ¤¹¤ë
618 $row[$ncol - $colspan]->setStyle($stylerow[$ncol]->style);
627 foreach ($parts as $type => $part)
630 foreach (array_keys($this->elements) as $nrow) {
631 if ($this->types[$nrow] != $type)
633 $row = & $this->elements[$nrow];
635 foreach (array_keys($row) as $ncol) {
636 $row_string .= $row[$ncol]->toString();
638 $part_string .= $this->wrap($row_string, 'tr');
640 $string .= $this->wrap($part_string, $part);
642 $string = $this->wrap($string, 'table', ' class="style_table" cellspacing="1" border="0"');
644 return $this->wrap($string, 'div', ' class="ie5"');
648 // , title1 , title2 , title3
649 // , cell1 , cell2 , cell3
650 // , cell4 , cell5 , cell6
651 class YTable extends Element
655 function YTable($_value)
659 $align = $value = $matches = array();
660 foreach($_value as $val) {
661 if (preg_match('/^(\s+)?(.+?)(\s+)?$/', $val, $matches)) {
662 $align[] =($matches[1] != '') ?
663 ((isset($matches[3]) && $matches[3] != '') ?
664 ' style="text-align:center"' :
665 ' style="text-align:right"'
667 $value[] = $matches[2];
673 $this->col = count($value);
675 foreach ($value as $val) {
676 $colspan[] = ($val == '==') ? 0 : 1;
679 for ($i = 0; $i < count($value); $i++) {
681 while ($i + $colspan[$i] < count($value) && $value[$i + $colspan[$i]] == '==') {
684 $colspan[$i] = ($colspan[$i] > 1) ? " colspan=\"{$colspan[$i]}\"" : '';
685 $str .= "<td class=\"style_td\"{$align[$i]}{$colspan[$i]}>".make_link($value[$i]).'</td>';
688 $this->elements[] = $str;
691 function canContain(& $obj)
693 return is_a($obj, 'YTable') && ($obj->col == $this->col);
696 function & insert(& $obj)
698 $this->elements[] = $obj->elements[0];
705 foreach ($this->elements as $str) {
706 $rows .= "\n<tr class=\"style_tr\">$str</tr>\n";
708 $rows = $this->wrap($rows, 'table', ' class="style_table" cellspacing="1" border="0"');
709 return $this->wrap($rows, 'div', ' class="ie5"');
713 // ' 'Space-beginning sentence
714 // ' 'Space-beginning sentence
715 // ' 'Space-beginning sentence
716 class Pre extends Element
718 function Pre(& $root, $text)
720 global $preformat_ltrim;
723 $this->elements[] = htmlspecialchars(
724 (! $preformat_ltrim || $text == '' || $text{0} != ' ') ? $text : substr($text, 1)
728 function canContain(& $obj)
730 return is_a($obj, 'Pre');
733 function & insert(& $obj)
735 $this->elements[] = $obj->elements[0];
741 return $this->wrap(join("\n", $this->elements), 'pre');
745 // #someting(started with '#')
746 class Div extends Element
754 list(, $this->name, $this->param) = array_pad($out, 3, '');
757 function canContain(& $obj)
764 return do_plugin_convert($this->name, $this->param);
768 // LEFT:/CENTER:/RIGHT:
769 class Align extends Element
773 function Align($align)
776 $this->align = $align;
779 function canContain(& $obj)
781 return is_a($obj, 'Inline');
786 return $this->wrap(parent::toString(), 'div', ' style="text-align:' . $this->align . '"');
791 class Body extends Element
797 var $classes = array(
803 var $factories = array(
813 $this->contents = & new Element();
814 $this->contents_last = & $this->contents;
818 function parse(& $lines)
820 $this->last = & $this;
823 while (! empty($lines)) {
824 $line = array_shift($lines);
827 if (substr($line, 0, 2) == '//') continue;
829 if (preg_match('/^(LEFT|CENTER|RIGHT):(.*)$/', $line, $matches)) {
830 // <div style="text-align:...">
831 $this->last = & $this->last->add(new Align(strtolower($matches[1])));
833 if ($matches[2] == '') continue;
838 $line = preg_replace("/[\r\n]*$/", '', $line);
842 $this->last = & $this;
847 if (substr($line, 0, 4) == '----') {
848 $this->insert(new HRule($this, $line));
852 // The first character
857 $this->insert(new Heading($this, $line));
862 if ($head == ' ' || $head == "\t") {
863 $this->last = & $this->last->add(new Pre($this, $line));
868 if (substr($line, -1) == '~') {
869 $line = substr($line,0,-1)."\r";
873 if (isset($this->classes[$head])) {
874 $classname = $this->classes[$head];
875 $this->last = & $this->last->add(new $classname($this, $line));
880 if (isset($this->factories[$head])) {
881 $factoryname = 'Factory_' . $this->factories[$head];
882 $this->last = & $this->last->add($factoryname($this, $line));
887 $this->last = & $this->last->add(Factory_Inline($line));
891 function getAnchor($text, $level)
893 global $top, $_symbol_anchor;
895 $id = make_heading($text, FALSE); // Cut fixed-anchor from $text
896 $anchor = ($id == '') ? '' : " &aname($id,super,full)\{$_symbol_anchor};";
899 $id = "content_{$this->id}_{$this->count}";
901 $this->contents_last = & $this->contents_last->add(new Contents_UList($text, $level, $id));
903 return array($text . $anchor, $this->count > 1 ? "\n$top" : '', $id);
906 function & insert(& $obj)
908 if (is_a($obj, 'Inline')) $obj = & $obj->toPara();
909 return parent::insert($obj);
916 $text = parent::toString();
919 $text = preg_replace_callback('/(<p[^>]*>)<del>#contents<\/del>(\s*)(<\/p>)/', array(& $this, 'replace_contents'), $text);
922 // <p>¤Î¤È¤¤Ï¹ÔƬ¤«¤é¡¢<del>¤Î¤È¤¤Ï¾¤ÎÍ×ÁǤλÒÍ×ÁǤȤ·¤Æ¸ºß
923 $text = preg_replace_callback('/(<p[^>]*>)<del>#related<\/del>(\s*)(<\/p>)/', array(& $this, 'replace_related'), $text);
924 $text = preg_replace('/<del>#related<\/del>/', make_related($vars['page'], 'del'), $text);
928 function replace_contents($arr)
930 $contents = "<div class=\"contents\">\n";
931 $contents .= "<a id=\"contents_{$this->id}\"></a>";
932 $contents .= $this->contents->toString();
933 $contents .= "</div>\n";
936 return ($arr[1] != '') ? $contents . join('', $arr) : $contents;
939 function replace_related($arr)
942 static $related = NULL;
944 if (is_null($related))
945 $related = make_related($vars['page'],'p');
949 return ($arr[1] != '') ? $related . join('', $arr) : $related;
953 class Contents_UList extends ListContainer
955 function Contents_UList($text, $level, $id)
957 // ¥Æ¥¥¹¥È¤Î¥ê¥Õ¥©¡¼¥à
958 // ¹ÔƬ\n¤ÇÀ°·ÁºÑ¤ß¤òɽ¤¹ ... X(
960 $text = "\n<a href=\"#$id\">$text</a>\n";
961 parent::ListContainer('ul', 'li', '-', str_repeat('-', $level));
962 $this->insert(Factory_Inline($text));
965 function setParent(& $parent)
967 global $_list_pad_str;
969 parent::setParent($parent);
970 $step = $this->level;
971 $margin = $this->left_margin;
972 if (isset($parent->parent) && is_a($parent->parent, 'ListContainer')) {
973 $step -= $parent->parent->level;
976 $margin += $this->margin * ($step == $this->level ? 1 : $step);
977 $this->style = sprintf($_list_pad_str, $this->level, $margin, $margin);