2 /////////////////////////////////////////////////
3 // PukiWiki - Yet another WikiWikiWeb clone.
5 // $Id: convert_html.php,v 1.3 2004/10/07 14:15:27 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 function & Factory_Table(& $root, $text)
116 if (! preg_match("/^\|(.+)\|([hHfFcC]?)$/", $text, $out)) {
117 return Factory_Inline($text);
119 return new Table($out);
123 function & Factory_YTable(& $root, $text)
125 $_value = csv_explode(',', substr($text, 1));
126 if (count($_value) == 0) {
127 return Factory_Inline($text);
129 return new YTable($_value);
133 function & Factory_Div(& $root, $text)
135 if (! preg_match("/^\#([^\(]+)(?:\((.*)\))?/", $text, $out) || ! exist_plugin_convert($out[1])) {
136 return new Paragraph($text);
138 return new Div($out);
143 class Inline extends Element
145 function Inline($text)
148 $this->elements[] = trim((substr($text, 0, 1) == "\n") ? $text : make_link($text));
151 function & insert(& $obj)
153 $this->elements[] = $obj->elements[0];
157 function canContain($obj)
159 return is_a($obj, 'Inline');
165 return join($line_break ? "<br />\n" : "\n", $this->elements);
168 function & toPara($class = '')
170 $obj = & new Paragraph('', $class);
176 class Paragraph extends Element
180 function Paragraph($text, $param = '')
184 $this->param = $param;
186 if ($text == '') return;
188 if (substr($text,0,1) == '~')
189 $text = ' ' . substr($text, 1);
191 $this->insert(Factory_Inline($text));
194 function canContain($obj)
196 return is_a($obj, 'Inline');
201 return $this->wrap(parent::toString(), 'p', $this->param);
208 class Heading extends Element
214 function Heading(& $root, $text)
218 $this->level = min(3, strspn($text, '*'));
219 list($text, $this->msg_top, $this->id) = $root->getAnchor($text, $this->level);
220 $this->insert(Factory_Inline($text));
221 $this->level++; // h2,h3,h4
224 function & insert(& $obj)
226 parent::insert($obj);
227 return $this->last = & $this;
230 function canContain(& $obj)
237 return $this->msg_top . $this->wrap(parent::toString(), 'h' . $this->level, " id=\"{$this->id}\"");
242 class HRule extends Element
244 function HRule(& $root, $text)
249 function canContain(& $obj)
261 class ListContainer extends Element
270 function ListContainer($tag, $tag2, $head, $text)
275 $var_margin = "_{$tag}_margin";
276 $var_left_margin = "_{$tag}_left_margin";
278 global $$var_margin, $$var_left_margin;
280 $this->margin = $$var_margin;
281 $this->left_margin = $$var_left_margin;
286 $this->level = min(3, strspn($text, $head));
287 $text = ltrim(substr($text, $this->level));
289 parent::insert(new ListElement($this->level, $tag2));
291 $this->last = & $this->last->insert(Factory_Inline($text));
294 function canContain(& $obj)
296 return (! is_a($obj, 'ListContainer')
297 || ($this->tag == $obj->tag && $this->level == $obj->level));
300 function setParent(& $parent)
302 global $_list_pad_str;
304 parent::setParent($parent);
306 $step = $this->level;
307 if (isset($parent->parent) && is_a($parent->parent, 'ListContainer')) {
308 $step -= $parent->parent->level;
310 $margin = $this->margin * $step;
311 if ($step == $this->level) {
312 $margin += $this->left_margin;
314 $this->style = sprintf($_list_pad_str, $this->level, $margin, $margin);
317 function & insert(& $obj)
319 if (! is_a($obj, get_class($this)))
320 return $this->last = & $this->last->insert($obj);
322 // ¹ÔƬʸ»ú¤Î¤ß¤Î»ØÄê»þ¤ÏUL/OL¥Ö¥í¥Ã¥¯¤òæ½Ð
324 if (count($obj->elements) == 1 && empty($obj->elements[0]->elements))
325 return $this->last->parent; // up to ListElement.
328 foreach(array_keys($obj->elements) as $key)
329 parent::insert($obj->elements[$key]);
336 return $this->wrap(parent::toString(), $this->tag, $this->style);
340 class ListElement extends Element
342 function ListElement($level, $head)
345 $this->level = $level;
349 function canContain(& $obj)
351 return (! is_a($obj, 'ListContainer') || ($obj->level > $this->level));
356 return $this->wrap(parent::toString(), $this->head);
363 class UList extends ListContainer
365 function UList(& $root, $text)
367 parent::ListContainer('ul', 'li', '-', $text);
374 class OList extends ListContainer
376 function OList(& $root, $text)
378 parent::ListContainer('ol', 'li', '+', $text);
382 // : definition1 | description1
383 // : definition2 | description2
384 // : definition3 | description3
385 class DList extends ListContainer
389 parent::ListContainer('dl', 'dt', ':', $out[0]);
391 $this->last = & Element::insert(new ListElement($this->level, 'dd'));
394 $this->last = & $this->last->insert(Factory_Inline($out[1]));
400 // > like E-mail text
401 class BQuote extends Element
405 function BQuote(& $root, $text)
409 $head = substr($text, 0, 1);
410 $this->level = min(3, strspn($text, $head));
411 $text = ltrim(substr($text, $this->level));
413 if ($head == '<') { // Blockquote close
414 $level = $this->level;
416 $this->last = & $this->end($root, $level);
418 $this->last = & $this->last->insert(Factory_Inline($text));
420 $this->insert(Factory_Inline($text));
424 function canContain(& $obj)
426 return (! is_a($obj, get_class($this)) || $obj->level >= $this->level);
429 function & insert(& $obj)
431 // BugTrack/521, BugTrack/545
432 if (is_a($obj, 'inline'))
433 return parent::insert($obj->toPara(' class="quotation"'));
435 if (is_a($obj, 'BQuote') && $obj->level == $this->level && count($obj->elements)) {
436 $obj = & $obj->elements[0];
437 if (is_a($this->last, 'Paragraph') && count($obj->elements))
438 $obj = & $obj->elements[0];
440 return parent::insert($obj);
445 return $this->wrap(parent::toString(), 'blockquote');
448 function & end(& $root, $level)
450 $parent = & $root->last;
452 while (is_object($parent)) {
453 if (is_a($parent, 'BQuote') && $parent->level == $level)
454 return $parent->parent;
455 $parent = & $parent->parent;
461 class TableCell extends Element
463 var $tag = 'td'; // {td|th}
466 var $style; // is array('width'=>, 'align'=>...);
468 function TableCell($text, $is_template = FALSE)
471 $this->style = $matches = array();
473 while (preg_match('/^(?:(LEFT|CENTER|RIGHT)|(BG)?COLOR\(([#\w]+)\)|SIZE\((\d+)\)):(.*)$/',$text,$matches))
477 $this->style['align'] = 'text-align:'.strtolower($matches[1]).';';
480 else if ($matches[3])
482 $name = $matches[2] ? 'background-color' : 'color';
483 $this->style[$name] = $name.':'.htmlspecialchars($matches[3]).';';
486 else if ($matches[4])
488 $this->style['size'] = 'font-size:'.htmlspecialchars($matches[4]).'px;';
492 if ($is_template && is_numeric($text)) {
493 $this->style['width'] = "width:{$text}px;";
498 } else if ($text == '~') {
500 } else if (substr($text, 0, 1) == '~') {
502 $text = substr($text, 1);
505 if ($text != '' && $text{0} == '#') {
506 // ¥»¥ëÆâÍƤ¬'#'¤Ç»Ï¤Þ¤ë¤È¤¤ÏDiv¥¯¥é¥¹¤òÄ̤·¤Æ¤ß¤ë
507 $obj = & Factory_Div($this, $text);
508 if (is_a($obj, 'Paragraph')) {
509 $obj = & $obj->elements[0];
512 $obj = & Factory_Inline($text);
518 function setStyle(& $style)
520 foreach ($style as $key=>$value) {
521 if (! isset($this->style[$key]))
522 $this->style[$key] = $value;
528 if ($this->rowspan == 0 || $this->colspan == 0) return '';
530 $param = " class=\"style_{$this->tag}\"";
531 if ($this->rowspan > 1) {
532 $param .= " rowspan=\"{$this->rowspan}\"";
534 if ($this->colspan > 1) {
535 $param .= " colspan=\"{$this->colspan}\"";
536 unset($this->style['width']);
538 if (! empty($this->style)) {
539 $param .= ' style="' . join(' ', $this->style) . '"';
542 return $this->wrap(parent::toString(), $this->tag, $param, FALSE);
546 // | title1 | title2 | title3 |
547 // | cell1 | cell2 | cell3 |
548 // | cell4 | cell5 | cell6 |
549 class Table extends Element
553 var $col; // number of column
559 $cells = explode('|', $out[1]);
560 $this->col = count($cells);
561 $this->type = strtolower($out[2]);
562 $this->types = array($this->type);
563 $is_template = ($this->type == 'c');
565 foreach ($cells as $cell) {
566 $row[] = & new TableCell($cell, $is_template);
568 $this->elements[] = $row;
571 function canContain(& $obj)
573 return is_a($obj, 'Table') && ($obj->col == $this->col);
576 function & insert(& $obj)
578 $this->elements[] = $obj->elements[0];
579 $this->types[] = $obj->type;
585 static $parts = array('h'=>'thead', 'f'=>'tfoot', ''=>'tbody');
587 // rowspan¤òÀßÄê(²¼¤«¤é¾å¤Ø)
588 for ($ncol = 0; $ncol < $this->col; $ncol++) {
590 foreach (array_reverse(array_keys($this->elements)) as $nrow) {
591 $row = & $this->elements[$nrow];
592 if ($row[$ncol]->rowspan == 0) {
596 $row[$ncol]->rowspan = $rowspan;
597 while (--$rowspan) { // ¹Ô¼ïÊ̤ò·Ñ¾µ¤¹¤ë
598 $this->types[$nrow + $rowspan] = $this->types[$nrow];
604 // colspan,style¤òÀßÄê
606 foreach (array_keys($this->elements) as $nrow) {
607 $row = & $this->elements[$nrow];
608 if ($this->types[$nrow] == 'c') {
612 foreach (array_keys($row) as $ncol) {
613 if ($row[$ncol]->colspan == 0) {
617 $row[$ncol]->colspan = $colspan;
618 if ($stylerow !== NULL) {
619 $row[$ncol]->setStyle($stylerow[$ncol]->style);
620 while (--$colspan) { // Îó¥¹¥¿¥¤¥ë¤ò·Ñ¾µ¤¹¤ë
621 $row[$ncol - $colspan]->setStyle($stylerow[$ncol]->style);
630 foreach ($parts as $type => $part)
633 foreach (array_keys($this->elements) as $nrow) {
634 if ($this->types[$nrow] != $type)
636 $row = & $this->elements[$nrow];
638 foreach (array_keys($row) as $ncol) {
639 $row_string .= $row[$ncol]->toString();
641 $part_string .= $this->wrap($row_string, 'tr');
643 $string .= $this->wrap($part_string, $part);
645 $string = $this->wrap($string, 'table', ' class="style_table" cellspacing="1" border="0"');
647 return $this->wrap($string, 'div', ' class="ie5"');
651 // , title1 , title2 , title3
652 // , cell1 , cell2 , cell3
653 // , cell4 , cell5 , cell6
654 class YTable extends Element
658 function YTable($_value)
662 $align = $value = $matches = array();
663 foreach($_value as $val) {
664 if (preg_match('/^(\s+)?(.+?)(\s+)?$/', $val, $matches)) {
665 $align[] =($matches[1] != '') ?
666 ((isset($matches[3]) && $matches[3] != '') ?
667 ' style="text-align:center"' :
668 ' style="text-align:right"'
670 $value[] = $matches[2];
676 $this->col = count($value);
678 foreach ($value as $val) {
679 $colspan[] = ($val == '==') ? 0 : 1;
682 for ($i = 0; $i < count($value); $i++) {
684 while ($i + $colspan[$i] < count($value) && $value[$i + $colspan[$i]] == '==') {
687 $colspan[$i] = ($colspan[$i] > 1) ? " colspan=\"{$colspan[$i]}\"" : '';
688 $str .= "<td class=\"style_td\"{$align[$i]}{$colspan[$i]}>".make_link($value[$i]).'</td>';
691 $this->elements[] = $str;
694 function canContain(& $obj)
696 return is_a($obj, 'YTable') && ($obj->col == $this->col);
699 function & insert(& $obj)
701 $this->elements[] = $obj->elements[0];
708 foreach ($this->elements as $str) {
709 $rows .= "\n<tr class=\"style_tr\">$str</tr>\n";
711 $rows = $this->wrap($rows, 'table', ' class="style_table" cellspacing="1" border="0"');
712 return $this->wrap($rows, 'div', ' class="ie5"');
716 // ' 'Space-beginning sentence
717 // ' 'Space-beginning sentence
718 // ' 'Space-beginning sentence
719 class Pre extends Element
721 function Pre(& $root, $text)
723 global $preformat_ltrim;
726 $this->elements[] = htmlspecialchars(
727 (! $preformat_ltrim || $text == '' || $text{0} != ' ') ? $text : substr($text, 1)
731 function canContain(& $obj)
733 return is_a($obj, 'Pre');
736 function & insert(& $obj)
738 $this->elements[] = $obj->elements[0];
744 return $this->wrap(join("\n", $this->elements), 'pre');
748 // #someting(started with '#')
749 class Div extends Element
757 list(, $this->name, $this->param) = array_pad($out, 3, '');
760 function canContain(& $obj)
767 return do_plugin_convert($this->name, $this->param);
771 // LEFT:/CENTER:/RIGHT:
772 class Align extends Element
776 function Align($align)
779 $this->align = $align;
782 function canContain(& $obj)
784 return is_a($obj, 'Inline');
789 return $this->wrap(parent::toString(), 'div', ' style="text-align:' . $this->align . '"');
794 class Body extends Element
800 var $classes = array(
806 var $factories = array(
816 $this->contents = & new Element();
817 $this->contents_last = & $this->contents;
821 function parse(& $lines)
823 $this->last = & $this;
826 while (! empty($lines)) {
827 $line = array_shift($lines);
830 if (substr($line, 0, 2) == '//') continue;
832 if (preg_match('/^(LEFT|CENTER|RIGHT):(.*)$/', $line, $matches)) {
833 // <div style="text-align:...">
834 $this->last = & $this->last->add(new Align(strtolower($matches[1])));
836 if ($matches[2] == '') continue;
841 $line = preg_replace("/[\r\n]*$/", '', $line);
845 $this->last = & $this;
850 if (substr($line, 0, 4) == '----') {
851 $this->insert(new HRule($this, $line));
855 // The first character
860 $this->insert(new Heading($this, $line));
865 if ($head == ' ' || $head == "\t") {
866 $this->last = & $this->last->add(new Pre($this, $line));
871 if (substr($line, -1) == '~') {
872 $line = substr($line,0,-1)."\r";
876 if (isset($this->classes[$head])) {
877 $classname = $this->classes[$head];
878 $this->last = & $this->last->add(new $classname($this, $line));
883 if (isset($this->factories[$head])) {
884 $factoryname = 'Factory_' . $this->factories[$head];
885 $this->last = & $this->last->add($factoryname($this, $line));
890 $this->last = & $this->last->add(Factory_Inline($line));
894 function getAnchor($text, $level)
896 global $top, $_symbol_anchor;
898 $anchor = (($id = make_heading($text, FALSE)) == '') ?
899 '' : " &aname($id,super,full)\{$_symbol_anchor};";
901 $id = "content_{$this->id}_{$this->count}";
903 $this->contents_last = & $this->contents_last->add(new Contents_UList($text, $level, $id));
905 return array($text. $anchor, $this->count > 1 ? "\n$top" : '', $id);
908 function & insert(& $obj)
910 if (is_a($obj, 'Inline')) $obj = & $obj->toPara();
911 return parent::insert($obj);
918 $text = parent::toString();
921 $text = preg_replace_callback('/(<p[^>]*>)<del>#contents<\/del>(\s*)(<\/p>)/', array(& $this, 'replace_contents'), $text);
924 // <p>¤Î¤È¤¤Ï¹ÔƬ¤«¤é¡¢<del>¤Î¤È¤¤Ï¾¤ÎÍ×ÁǤλÒÍ×ÁǤȤ·¤Æ¸ºß
925 $text = preg_replace_callback('/(<p[^>]*>)<del>#related<\/del>(\s*)(<\/p>)/', array(& $this, 'replace_related'), $text);
926 $text = preg_replace('/<del>#related<\/del>/', make_related($vars['page'], 'del'), $text);
930 function replace_contents($arr)
932 $contents = "<div class=\"contents\">\n";
933 $contents .= "<a id=\"contents_{$this->id}\"></a>";
934 $contents .= $this->contents->toString();
935 $contents .= "</div>\n";
938 return ($arr[1] != '') ? $contents . join('', $arr) : $contents;
941 function replace_related($arr)
944 static $related = NULL;
946 if (is_null($related))
947 $related = make_related($vars['page'],'p');
951 return ($arr[1] != '') ? $related . join('', $arr) : $related;
955 class Contents_UList extends ListContainer
957 function Contents_UList($text, $level, $id)
959 // ¥Æ¥¥¹¥È¤Î¥ê¥Õ¥©¡¼¥à
960 // ¹ÔƬ\n¤ÇÀ°·ÁºÑ¤ß¤òɽ¤¹ ... X(
962 $text = "\n<a href=\"#$id\">$text</a>\n";
963 parent::ListContainer('ul', 'li', '-', str_repeat('-', $level));
964 $this->insert(Factory_Inline($text));
967 function setParent(& $parent)
969 global $_list_pad_str;
971 parent::setParent($parent);
972 $step = $this->level;
973 $margin = $this->left_margin;
974 if (isset($parent->parent) && is_a($parent->parent, 'ListContainer')) {
975 $step -= $parent->parent->level;
978 $margin += $this->margin * ($step == $this->level ? 1 : $step);
979 $this->style = sprintf($_list_pad_str, $this->level, $margin, $margin);