2 /////////////////////////////////////////////////
3 // PukiWiki - Yet another WikiWikiWeb clone.
5 // $Id: convert_html.php,v 1.58 2003/12/03 12:17:26 arino Exp $
7 function convert_html($lines)
9 global $script, $vars, $digest;
10 static $contents_id = 0;
12 if (!is_array($lines))
14 $lines = explode("\n", $lines);
17 $digest = md5(join('', get_source($vars['page'])));
19 $body = new Body(++$contents_id);
21 $ret = $body->toString();
28 var $parent; // ¿ÆÍ×ÁÇ
29 var $last; // ¼¡¤ËÍ×ÁǤòÁÞÆþ¤¹¤ëÀè
30 var $elements; // Í×ÁǤÎÇÛÎó
34 $this->elements = array();
37 function setParent(&$parent)
39 $this->parent = &$parent;
43 if ($this->canContain($obj))
45 return $this->insert($obj);
48 return $this->parent->add($obj);
50 function &insert(&$obj)
52 $obj->setParent($this);
53 $this->elements[] = &$obj;
55 return $this->last = &$obj->last;
57 function canContain($obj)
61 function wrap($string, $tag, $param = '')
63 return ($string == '') ? '' : "<$tag$param>$string</$tag>";
68 foreach (array_keys($this->elements) as $key)
70 $ret[] = $this->elements[$key]->toString();
73 return join("\n",$ret);
75 function dump($indent = 0)
77 $ret = str_repeat(' ', $indent).get_class($this)."\n";
81 foreach (array_keys($this->elements) as $key)
83 $ret .= is_object($this->elements[$key]) ?
84 $this->elements[$key]->dump($indent) : '';
85 //str_repeat(' ',$indent).$this->elements[$key];
91 class Inline extends Element
93 function Inline($text)
97 if (substr($text,0,1) == '~') // ¹ÔƬ~¡£¥Ñ¥é¥°¥é¥Õ³«»Ï
99 $parent = &$this->parent;
100 $this = &new Paragraph(' '.substr($text,1));
101 $this->setParent($parent);
105 $this->elements[] = trim((substr($text, 0, 1) == "\n") ? $text : make_link($text));
108 function &insert(&$obj)
110 $this->elements[] = $obj->elements[0];
113 function canContain($obj)
115 return is_a($obj,'Inline');
121 return join($line_break ? "<br />\n" : "\n",$this->elements);
123 function &toPara($class = '')
125 $obj = &new Paragraph('', $class);
130 class Paragraph extends Element
134 function Paragraph($text, $param = '')
138 $this->param = $param;
143 if (substr($text,0,1) == '~')
145 $text = ' '.substr($text, 1);
147 $this->insert(new Inline($text));
149 function canContain($obj)
151 return is_a($obj,'Inline');
155 return $this->wrap(parent::toString(), 'p', $this->param);
159 class Heading extends Element
165 function Heading(&$root, $text)
169 $this->level = min(3, strspn($text, '*'));
170 list($text, $this->msg_top, $this->id) = $root->getAnchor($text, $this->level);
171 $this->insert(new Inline($text));
172 $this->level++; // h2,h3,h4
174 function &insert(&$obj)
176 parent::insert($obj);
177 return $this->last = &$this;
179 function canContain(&$obj)
185 return $this->msg_top.$this->wrap(parent::toString(), 'h'.$this->level, " id=\"{$this->id}\"");
188 class HRule extends Element
190 function HRule(&$root, $text)
194 function canContain(&$obj)
205 class ListContainer extends Element
214 function ListContainer($tag, $tag2, $head, $text)
219 $var_margin = "_{$tag}_margin";
220 $var_left_margin = "_{$tag}_left_margin";
221 global $$var_margin, $$var_left_margin;
222 $this->margin = $$var_margin;
223 $this->left_margin = $$var_left_margin;
228 $this->level = min(3, strspn($text, $head));
229 $text = ltrim(substr($text, $this->level));
231 parent::insert(new ListElement($this->level, $tag2));
234 $this->last = &$this->last->insert(new Inline($text));
238 function canContain(&$obj)
240 return (!is_a($obj, 'ListContainer')
241 or ($this->tag == $obj->tag and $this->level == $obj->level));
243 function setParent(&$parent)
245 global $_list_pad_str;
247 parent::setParent($parent);
249 $step = $this->level;
250 if (isset($parent->parent) and is_a($parent->parent, 'ListContainer'))
252 $step -= $parent->parent->level;
254 $margin = $this->margin * $step;
255 if ($step == $this->level)
257 $margin += $this->left_margin;
259 $this->style = sprintf($_list_pad_str, $this->level, $margin, $margin);
261 function &insert(&$obj)
263 if (!is_a($obj, get_class($this)))
265 return $this->last = &$this->last->insert($obj);
267 if (count($obj->elements[0]->elements) == 0)
269 return $this->last->parent; // up to ListElement.
272 foreach(array_keys($obj->elements) as $key)
274 parent::insert($obj->elements[$key]);
281 return $this->wrap(parent::toString(), $this->tag, $this->style);
284 class ListElement extends Element
286 function ListElement($level, $head)
289 $this->level = $level;
292 function canContain(&$obj)
294 return (!is_a($obj, 'ListContainer') or ($obj->level > $this->level));
298 return $this->wrap(parent::toString(), $this->head);
301 class UList extends ListContainer
303 function UList(&$root, $text)
305 parent::ListContainer('ul', 'li', '-', $text);
308 class OList extends ListContainer
310 function OList(&$root, $text)
312 parent::ListContainer('ol', 'li', '+', $text);
315 class DList extends ListContainer
317 function DList(&$root, $text)
319 $out = explode('|', $text, 2);
322 $this = new Inline($text);
325 parent::ListContainer('dl', 'dt', ':', $out[0]);
327 $this->last = &Element::insert(new ListElement($this->level, 'dd'));
330 $this->last = &$this->last->insert(new Inline($out[1]));
334 class BQuote extends Element
338 function BQuote(&$root, $text)
342 $head = substr($text, 0, 1);
343 $this->level = min(3, strspn($text, $head));
344 $text = ltrim(substr($text, $this->level));
346 if ($head == '<') //blockquote close
348 $level = $this->level;
350 $this->last = &$this->end($root, $level);
353 $this->last = &$this->last->insert(new Inline($text));
358 parent::insert(new Paragraph($text, ' class="quotation"'));
361 function canContain(&$obj)
363 return (!is_a($obj, get_class($this)) or $obj->level >= $this->level);
365 function &insert(&$obj)
367 if (is_a($obj, 'BQuote') and $obj->level == $this->level and count($obj->elements))
369 $obj = &$obj->elements[0];
370 if (is_a($this->last,'Paragraph') and count($obj->elements))
372 $obj = &$obj->elements[0];
375 return parent::insert($obj);
379 return $this->wrap(parent::toString(), 'blockquote');
381 function &end(&$root, $level)
383 $parent = &$root->last;
385 while (is_object($parent))
387 if (is_a($parent,'BQuote') and $parent->level == $level)
389 return $parent->parent;
391 $parent = &$parent->parent;
396 class TableCell extends Element
398 var $tag = 'td'; // {td|th}
401 var $style; // is array('width'=>, 'align'=>...);
403 function TableCell($text, $is_template = FALSE)
406 $this->style = array();
408 while (preg_match('/^(?:(LEFT|CENTER|RIGHT)|(BG)?COLOR\(([#\w]+)\)|SIZE\((\d+)\)):(.*)$/',$text,$matches))
412 $this->style['align'] = 'text-align:'.strtolower($matches[1]).';';
415 else if ($matches[3])
417 $name = $matches[2] ? 'background-color' : 'color';
418 $this->style[$name] = $name.':'.htmlspecialchars($matches[3]).';';
421 else if ($matches[4])
423 $this->style['size'] = 'font-size:'.htmlspecialchars($matches[4]).'px;';
427 if ($is_template and is_numeric($text))
429 $this->style['width'] = "width:{$text}px;";
435 else if ($text == '~')
439 else if (substr($text, 0, 1) == '~')
442 $text = substr($text, 1);
444 if ($text != '' and $text{0} == '#')
446 // ¥»¥ëÆâÍƤ¬'#'¤Ç»Ï¤Þ¤ë¤È¤¤ÏDiv¥¯¥é¥¹¤òÄ̤·¤Æ¤ß¤ë
447 $obj = &new Div($this, $text);
448 if (is_a($obj, 'Paragraph'))
450 $obj = &$obj->elements[0];
455 $obj = new Inline($text);
459 function setStyle(&$style)
461 foreach ($style as $key=>$value)
463 if (!array_key_exists($key, $this->style))
465 $this->style[$key] = $value;
471 if ($this->rowspan == 0 or $this->colspan == 0)
475 $param = " class=\"style_{$this->tag}\"";
476 if ($this->rowspan > 1)
478 $param .= " rowspan=\"{$this->rowspan}\"";
480 if ($this->colspan > 1)
482 $param .= " colspan=\"{$this->colspan}\"";
483 unset($this->style['width']);
485 if (count($this->style))
487 $param .= ' style="'.join(' ', $this->style).'"';
490 return $this->wrap(parent::toString(), $this->tag, $param);
493 class Table extends Element
497 var $col; // number of column
499 function Table(&$root, $text)
503 if (!preg_match("/^\|(.+)\|([hHfFcC]?)$/", $text, $out))
505 $this = new Inline($text);
508 $cells = explode('|', $out[1]);
509 $this->col = count($cells);
510 $this->type = strtolower($out[2]);
511 $this->types = array($this->type);
512 $is_template = ($this->type == 'c');
514 foreach ($cells as $cell)
516 $row[] = new TableCell($cell, $is_template);
518 $this->elements[] = $row;
520 function canContain(&$obj)
522 return is_a($obj, 'Table') and ($obj->col == $this->col);
524 function &insert(&$obj)
526 $this->elements[] = $obj->elements[0];
527 $this->types[] = $obj->type;
533 static $parts = array('h'=>'thead', 'f'=>'tfoot', ''=>'tbody');
535 // rowspan¤òÀßÄê(²¼¤«¤é¾å¤Ø)
536 for ($ncol = 0; $ncol < $this->col; $ncol++)
539 foreach (array_reverse(array_keys($this->elements)) as $nrow)
541 $row = &$this->elements[$nrow];
542 if ($row[$ncol]->rowspan == 0)
547 $row[$ncol]->rowspan = $rowspan;
548 while (--$rowspan) // ¹Ô¼ïÊ̤ò·Ñ¾µ¤¹¤ë
550 $this->types[$nrow + $rowspan] = $this->types[$nrow];
555 // colspan,style¤òÀßÄê
557 foreach (array_keys($this->elements) as $nrow)
559 $row = &$this->elements[$nrow];
560 if ($this->types[$nrow] == 'c')
565 foreach (array_keys($row) as $ncol)
567 if ($row[$ncol]->colspan == 0)
572 $row[$ncol]->colspan = $colspan;
573 if ($stylerow !== NULL)
575 $row[$ncol]->setStyle($stylerow[$ncol]->style);
576 while (--$colspan) // Îó¥¹¥¿¥¤¥ë¤ò·Ñ¾µ¤¹¤ë
578 $row[$ncol - $colspan]->setStyle($stylerow[$ncol]->style);
586 foreach ($parts as $type => $part)
589 foreach (array_keys($this->elements) as $nrow)
591 if ($this->types[$nrow] != $type)
595 $row = &$this->elements[$nrow];
597 foreach (array_keys($row) as $ncol)
599 $row_string .= $row[$ncol]->toString();
601 $part_string .= $this->wrap($row_string, 'tr');
603 $string .= $this->wrap($part_string, $part);
605 $string = $this->wrap($string, 'table', ' class="style_table" cellspacing="1" border="0"');
606 return $this->wrap($string, 'div', ' class="ie5"');
609 class YTable extends Element
613 function YTable(&$root, $text)
617 if (!preg_match_all('/("[^"]*(?:""[^"]*)*"|[^,]*),/', "$text,", $out))
619 $this = new Inline($text);
622 array_shift($out[1]);
624 foreach ($out[1] as $val)
626 $_value[] = preg_match('/^"(.*)"$/', $val, $matches) ? str_replace('""', '"', $matches[1]) : $val;
630 foreach($_value as $val)
632 if (preg_match('/^(\s+)?(.+?)(\s+)?$/', $val, $matches))
634 $align[] =($matches[1] != '') ?
635 ((array_key_exists(3,$matches) and $matches[3] != '') ?
636 ' style="text-align:center"' : ' style="text-align:right"'
638 $value[] = $matches[2];
646 $this->col = count($value);
648 foreach ($value as $val)
650 $colspan[] = ($val == '==') ? 0 : 1;
653 for ($i = 0; $i < count($value); $i++)
657 while ($i + $colspan[$i] < count($value) and $value[$i + $colspan[$i]] == '==')
661 $colspan[$i] = ($colspan[$i] > 1) ? " colspan=\"{$colspan[$i]}\"" : '';
662 $str .= "<td class=\"style_td\"{$align[$i]}{$colspan[$i]}>".make_link($value[$i]).'</td>';
665 $this->elements[] = $str;
667 function canContain(&$obj)
669 return is_a($obj, 'YTable') and ($obj->col == $this->col);
671 function &insert(&$obj)
673 $this->elements[] = $obj->elements[0];
680 foreach ($this->elements as $str)
682 $rows .= "\n<tr class=\"style_tr\">$str</tr>\n";
684 $rows = $this->wrap($rows, 'table', ' class="style_table" cellspacing="1" border="0"');
685 return $this->wrap($rows, 'div', ' class="ie5"');
688 class Pre extends Element
690 function Pre(&$root,$text)
692 global $preformat_ltrim;
695 $this->elements[] = htmlspecialchars(
696 (!$preformat_ltrim or $text == '' or $text{0} != ' ') ? $text : substr($text, 1)
699 function canContain(&$obj)
701 return is_a($obj, 'Pre');
703 function &insert(&$obj)
705 $this->elements[] = $obj->elements[0];
711 return $this->wrap(join("\n", $this->elements), 'pre');
714 class Div extends Element
719 function Div(&$root, $text)
723 if (!preg_match("/^\#([^\(]+)(?:\((.*)\))?/", $text, $out) or !exist_plugin_convert($out[1]))
725 $this = new Paragraph($text);
728 $this->name = $out[1];
729 $this->param = array_key_exists(2, $out) ? $out[2] : '';
731 function canContain(&$obj)
737 return do_plugin_convert($this->name,$this->param);
740 class Align extends Element
741 { // LEFT:/CENTER:/RIGHT:
744 function Align($align)
748 $this->align = $align;
750 function canContain(&$obj)
752 return is_a($obj, 'Inline');
756 return $this->wrap(parent::toString(), 'div', ' style="text-align:'.$this->align.'"');
759 class Body extends Element
765 var $classes = array(
779 $this->contents = new Element();
780 $this->contents_last = &$this->contents;
783 function parse(&$lines)
785 $this->last = &$this;
787 while (count($lines))
789 $line = array_shift($lines);
791 if (substr($line,0,2) == '//') //¥³¥á¥ó¥È¤Ï½èÍý¤·¤Ê¤¤
797 if (preg_match('/^(LEFT|CENTER|RIGHT):(.*)$/',$line,$matches))
799 $this->last = &$this->last->add(new Align(strtolower($matches[1]))); // <div style="text-align:...">
800 if ($matches[2] == '')
807 $line = preg_replace("/[\r\n]*$/",'',$line);
812 $this->last = &$this;
816 if (substr($line,0,4) == '----')
818 $this->insert(new HRule($this,$line));
827 $this->insert(new Heading($this,$line));
831 if ($head == ' ' or $head == "\t")
833 $this->last = &$this->last->add(new Pre($this,$line));
837 if (substr($line,-1) == '~')
839 $line = substr($line,0,-1)."\r";
842 if (array_key_exists($head, $this->classes))
844 $classname = $this->classes[$head];
845 $this->last = &$this->last->add(new $classname($this,$line));
850 $this->last = &$this->last->add(new Inline($line));
853 function getAnchor($text,$level)
855 global $top,$_symbol_anchor;
857 $anchor = (($id = make_heading($text,FALSE)) == '') ?
858 '' : " &aname($id,super,full)\{$_symbol_anchor};";
860 $id = "content_{$this->id}_{$this->count}";
862 $this->contents_last = &$this->contents_last->add(new Contents_UList($text,$level,$id));
864 return array($text. $anchor, $this->count > 1 ? "\n$top" : '', $id);
866 function &insert(&$obj)
868 if (is_a($obj, 'Inline'))
870 $obj = &$obj->toPara();
872 return parent::insert($obj);
878 $text = parent::toString();
881 $text = preg_replace_callback('/(<p[^>]*>)<del>#contents<\/del>(\s*)(<\/p>)/', array(&$this,'replace_contents'),$text);
884 // <p>¤Î¤È¤¤Ï¹ÔƬ¤«¤é¡¢<del>¤Î¤È¤¤Ï¾¤ÎÍ×ÁǤλÒÍ×ÁǤȤ·¤Æ¸ºß
885 $text = preg_replace_callback('/(<p[^>]*>)<del>#related<\/del>(\s*)(<\/p>)/', array(&$this, 'replace_related'), $text);
886 $text = preg_replace('/<del>#related<\/del>/',make_related($vars['page'],'del'),$text);
889 function replace_contents($arr)
891 $contents = "<div class=\"contents\">\n";
892 $contents .= "<a id=\"contents_{$this->id}\"></a>";
893 $contents .= $this->contents->toString();
894 $contents .= "</div>\n";
897 return ($arr[1] != '') ? $contents.join('',$arr) : $contents;
899 function replace_related($arr)
902 static $related = NULL;
904 if (is_null($related))
906 $related = make_related($vars['page'],'p');
910 return ($arr[1] != '') ? $related.join('',$arr) : $related;
913 class Contents_UList extends ListContainer
915 function Contents_UList($text,$level,$id)
917 // ¥Æ¥¥¹¥È¤Î¥ê¥Õ¥©¡¼¥à
918 // ¹ÔƬ\n¤ÇÀ°·ÁºÑ¤ß¤òɽ¤¹ ... X(
920 $text = "\n<a href=\"#$id\">$text</a>\n";
921 parent::ListContainer('ul', 'li', '-', str_repeat('-',$level));
922 $this->insert(new Inline($text));
924 function setParent(&$parent)
926 global $_list_pad_str;
928 parent::setParent($parent);
929 $step = $this->level;
930 $margin = $this->left_margin;
931 if (isset($parent->parent) and is_a($parent->parent,'ListContainer'))
933 $step -= $parent->parent->level;
936 $margin += $this->margin * ($step == $this->level ? 1 : $step);
937 $this->style = sprintf($_list_pad_str,$this->level,$margin,$margin);