2 /////////////////////////////////////////////////
3 // PukiWiki - Yet another WikiWikiWeb clone.
5 // $Id: convert_html.php,v 1.21 2003/02/20 12:22:51 panda Exp $
7 function &convert_html(&$lines)
9 global $script,$vars,$digest;
10 static $contents_id = 0;
12 $digest = md5(join('',get_source($vars['page'])));
14 $body = new Body(++$contents_id);
16 $ret = $body->toString();
25 function setParent(&$parent)
27 $this->parent =& $parent;
29 function debug($indent = 0)
31 return str_repeat(' ',$indent).get_class($this)."({$this->text})\n";
35 class Inline extends Element
39 function Inline($text)
41 if (substr($text,0,1) == '~') { // ¹ÔƬ~¡£¥Ñ¥é¥°¥é¥Õ³«»Ï
42 $parent =& $this->parent;
43 $this = new Paragraph(' '.substr($text,1));
44 $this->setParent($parent);
47 $this->text = (substr($text,0,1) == "\n") ? $text : inline2(inline($text));
52 return $this->insert($obj);
54 function &insert(&$obj)
56 return $this->parent->add($obj);
62 function toPara($class = '')
64 $obj = new Paragraph('',$class);
66 $this->setParent($obj);
70 class Block extends Element
72 var $elements; // Í×ÁǤÎÇÛÎó
75 $this->elements = array();
78 function &add(&$obj) // ¥¨¥ì¥á¥ó¥È¤òÄɲÃ
80 if ($this->canContain($obj)) {
81 return $this->insert($obj);
83 return $this->parent->add($obj);
85 function &insert(&$obj)
87 $obj->setParent($this);
88 $this->elements[] =& $obj;
89 if (isset($obj->last) and is_object($obj->last)) {
94 function canContain($obj)
101 if (isset($this->elements) and count($this->elements) > 0) {
102 foreach ($this->elements as $obj) {
103 $ret .= $obj->toString();
108 function wrap($string, $tag, $param = '')
110 return ($string == '') ? '' : "\n<$tag$param>$string</$tag>\n";
112 function debug($indent = 0)
114 $ret = parent::debug($indent);
115 foreach (array_keys($this->elements) as $key) {
116 if (is_object($this->elements[$key]))
117 $ret .= $this->elements[$key]->debug($indent + 2);
119 $ret .= str_repeat(' ',$indent + 2).$this->elements[$key];
124 class Paragraph extends Block
128 function Paragraph($text,$class='')
131 $this->class = $class;
135 if (substr($text,0,1) == '~') {
136 $text = ' '.substr($text,1);
138 $this->elements[] =& new Inline($text);
140 function canContain($obj)
142 return is_a($obj,'Inline');
146 return $this->wrap(parent::toString(), 'p', $this->class);
150 class Heading extends Block
154 function Heading(&$root,$text)
157 if (($level = strspn($text,'*')) > 3) {
160 $text = ltrim(substr($text,$level));
161 $this->level = ++$level;
162 list($this->top,$this->id) = $root->getAnchor($text,$level);
163 $this->last =& $this->insert(new Inline($text));
165 function canContain(&$obj)
171 return $this->wrap(parent::toString().$this->top,'h'.$this->level," id=\"{$this->id}\"");
174 class HRule extends Block
176 function HRule(&$root,$text) {
179 function canContain(&$obj)
190 class ListContainer extends Block
192 var $tag,$tag2,$level,$style;
194 function ListContainer($tag,$tag2,$level,$text)
198 $var_margin = "_{$tag}_margin";
199 $var_left_margin = "_{$tag}_left_margin";
200 global $$var_margin, $$var_left_margin;
201 $this->margin = $$var_margin;
202 $this->left_margin = $$var_left_margin;
207 $this->level = $level;
210 $this->insert(new Inline($text));
214 function canContain(&$obj)
216 return is_a($obj, 'ListContainer') ? ($this->tag == $obj->tag and $this->level == $obj->level) : TRUE;
218 function setParent(&$parent)
220 global $_list_left_margin, $_list_margin, $_list_pad_str;
222 parent::setParent($parent);
223 $step = $this->level;
224 if (isset($parent->parent) and is_a($parent->parent,'ListContainer')) {
225 $step -= $parent->parent->level;
227 $margin = $_list_margin * $step;
228 if ($step == $this->level) {
229 $margin += $_list_left_margin;
231 $this->style = sprintf($_list_pad_str,$this->level,$margin,$margin);
233 function &insert(&$obj)
235 if (is_a($obj, get_class($this))) {
236 for ($n = 0; $n < count($obj->elements); $n++) {
237 $this->last =& parent::insert($obj->elements[$n]);
241 $obj =& new ListElement($obj, $this->level, $this->tag2); // wrap
243 return parent::insert($obj);
245 function toString($param='')
247 return $this->wrap(parent::toString(),$this->tag,$this->style.$param);
250 class ListElement extends Block
252 function ListElement(&$obj,$level,$head)
255 $this->level = $level;
259 if (isset($obj->last) and is_object($obj->last)) {
260 $this->last =& $obj->last;
263 function canContain(&$obj)
265 return !(is_a($obj, 'ListContainer') and ($obj->level <= $this->level));
269 return $this->wrap(parent::toString(), $this->head);
272 class UList extends ListContainer
274 function UList(&$root,$text)
276 if (($level = strspn($text,'-')) > 3) {
277 $level = 3; // limitation ;(
279 $text = ltrim(substr($text,$level));
280 parent::ListContainer('ul','li',$level,$text);
283 class OList extends ListContainer
285 function OList(&$root,$text)
287 if (($level = strspn($text,'+')) > 3) {
288 $level = 3; // limitation ;(
290 $text = ltrim(substr($text,$level));
291 parent::ListContainer('ol','li',$level,$text);
294 class DList extends ListContainer
296 function DList(&$root,$text)
298 if (($level = strspn($text,':')) > 3) {
299 $level = 3; // limitation ;(
301 $out = explode('|',ltrim(substr($text,$level)),2);
302 if (count($out) < 2) {
303 $this = new Inline($text);
306 parent::ListContainer('dl','dd',$level,$out[1]);
308 array_unshift($this->elements,new Inline("\n<dt>".inline2(inline($out[0]))."</dt>\n"));
312 class BQuote extends Block
316 function BQuote(&$root,$text)
319 $head = substr($text,0,1);
320 if (($level = strspn($text,$head)) > 3) {
321 $level = 3; // limitation ;(
323 $this->level = $level;
324 $text = ltrim(substr($text,$level));
325 if ($head == '<') { //blockquote close
327 $this->last =& $this->end($root,$level,$text);
330 $this->last =& $this->insert(new Paragraph($text, ' class="quotation"'));
333 function canContain(&$obj)
335 if (!is_a($obj, get_class($this))) {
338 return ($obj->level >= $this->level);
340 function &insert(&$obj)
342 if (is_a($obj, 'BQuote') and $obj->level == $this->level) {
343 if (is_a($this->last,'Paragraph')
344 and array_key_exists(0,$obj->elements[0])
345 and is_object($obj->elements[0]->elements[0])) {
346 $this->last->insert($obj->elements[0]->elements[0]);
348 $this->last =& $this->insert($obj->elements[0]);
353 return parent::insert($obj);
357 return $this->wrap(parent::toString(),'blockquote');
359 function &end(&$root,$level,$text)
361 $parent =& $root->last;
362 while (is_object($parent)) {
363 if (is_a($parent,'BQuote') and $parent->level == $level) {
364 return $parent->parent->insert(new Inline($text));
366 $parent =& $parent->parent;
368 return $this->insert(new Inline($text));
371 class TableCell extends Block
373 var $tag = 'td'; // {td|th}
376 var $style; // is array('width'=>, 'align'=>...);
378 function TableCell($text,$is_template=FALSE) {
380 $this->style = array();
382 if (preg_match("/^(LEFT|CENTER|RIGHT):(.*)$/",$text,$out)) {
383 $this->style['align'] = 'text-align:'.strtolower($out[1]).';';
387 if (is_numeric($text)) {
388 $this->style['width'] = "width:{$text}px;";
394 else if ($text == '~') {
397 else if (substr($text,0,1) == '~') {
399 $text = substr($text,1);
401 $this->last =& $this->insert(new Inline($text));
403 function setStyle(&$style) {
404 foreach ($style as $key=>$value) {
405 if (!array_key_exists($key,$this->style)) {
406 $this->style[$key] = $value;
410 function toString() {
411 if ($this->rowspan == 0 or $this->colspan == 0) {
414 $param = " class=\"style_{$this->tag}\"";
415 if ($this->rowspan > 1) {
416 $param .= " rowspan=\"{$this->rowspan}\"";
418 if ($this->colspan > 1) {
419 $param .= " colspan=\"{$this->colspan}\"";
420 unset($this->style['width']);
422 if (count($this->style)) {
423 $param .= ' style="'.join(' ',$this->style).'"';
425 return "\n<{$this->tag}$param>".parent::toString()."</{$this->tag}>\n";
428 class Table extends Block
431 var $col; // number of column
433 function Table(&$root,$text)
435 if (!preg_match("/^\|(.+)\|([hHfFcC]?)$/",$text,$out)) {
436 $this = new Inline($text);
440 $cells = explode('|',$out[1]);
441 $this->col = count($cells);
442 $this->type = strtolower($out[2]);
443 $this->types = array($this->type);
444 $is_template = ($this->type == 'c');
446 foreach ($cells as $cell) {
447 $row[] = new TableCell($cell,$is_template);
449 $this->elements[] = $row;
450 $this->last =& $this;
452 function canContain(&$obj)
454 return is_a($obj, 'Table') and ($obj->col == $this->col);
456 function &insert(&$obj)
458 $this->elements[] = $obj->elements[0];
459 $this->types[] = $obj->type;
464 // rowspan¤òÀßÄê(²¼¤«¤é¾å¤Ø)
465 for ($ncol = 0; $ncol < $this->col; $ncol++) {
467 foreach (array_reverse(array_keys($this->elements)) as $nrow) {
468 $row =& $this->elements[$nrow];
469 if ($row[$ncol]->rowspan == 0) {
473 $row[$ncol]->rowspan = $rowspan;
474 while (--$rowspan) { // ¹Ô¼ïÊ̤ò·Ñ¾µ¤¹¤ë
475 $this->types[$nrow + $rowspan] = $this->types[$nrow];
481 // colspan,style¤òÀßÄê
483 foreach (array_keys($this->elements) as $nrow) {
484 $row =& $this->elements[$nrow];
485 if ($this->types[$nrow] == 'c') {
489 foreach (array_keys($row) as $ncol) {
490 if ($row[$ncol]->colspan == 0) {
494 $row[$ncol]->colspan = $colspan;
495 if ($stylerow !== NULL) {
496 $row[$ncol]->setStyle($stylerow[$ncol]->style);
497 while (--$colspan) { // Îó¥¹¥¿¥¤¥ë¤ò·Ñ¾µ¤¹¤ë
498 $row[$ncol - $colspan]->setStyle($stylerow[$ncol]->style);
507 $parts = array('h'=>'thead',''=>'tbody','f'=>'tfoot');
508 foreach ($parts as $type=>$part) {
510 foreach (array_keys($this->elements) as $nrow) {
511 if ($this->types[$nrow] != $type) {
514 $row =& $this->elements[$nrow];
516 foreach (array_keys($row) as $ncol) {
517 $row_string .= $row[$ncol]->toString();
519 $part_string .= $this->wrap($row_string,'tr');
521 $string .= $this->wrap($part_string,$part);
525 <table class="style_table" cellspacing="1" border="0">
532 class YTable extends Block
536 function YTable(&$root,$text)
539 if (!preg_match_all('/("[^"]*(?:""[^"]*)*"|[^,]*),/',"$text,",$out)) {
540 $this = new Inline($text);
543 array_shift($out[1]);
545 foreach ($out[1] as $val) {
546 $_value[] = preg_match('/^"(.*)"$/',$val,$matches) ? str_replace('""','"',$matches[1]) : $val;
550 foreach($_value as $val) {
551 if (preg_match('/^(\s+)?(.+?)(\s+)?$/',$val,$matches)) {
552 $align[] =($matches[1] != '') ?
553 ((array_key_exists(3,$matches) and $matches[3] != '') ? ' style="text-align:center"' : ' style="text-align:right"') : '';
554 $value[] = $matches[2];
561 $this->col = count($value);
563 foreach ($value as $val) {
564 $colspan[] = ($val == '==') ? 0 : 1;
567 for ($i = 0; $i < count($value); $i++) {
569 while ($i + $colspan[$i] < count($value) and $value[$i + $colspan[$i]] == '==') {
572 $colspan[$i] = ($colspan[$i] > 1) ? " colspan=\"{$colspan[$i]}\"" : '';
573 $str .= "<td class=\"style_td\"{$align[$i]}{$colspan[$i]}>".inline2(inline($value[$i])).'</td>';
576 $this->elements[] = $str;
578 function canContain(&$obj)
580 return is_a($obj, 'YTable') and ($obj->col == $this->col);
582 function &insert(&$obj)
584 $this->elements[] = $obj->elements[0];
590 foreach ($this->elements as $str) {
591 $rows .= "\n<tr class=\"style_tr\">$str</tr>\n";
596 <table class="style_table" cellspacing="1" border="0">
604 class Pre extends Block
607 function Pre(&$root,$text)
610 $this->elements[] = htmlspecialchars($text,ENT_NOQUOTES);
612 function canContain(&$obj)
614 return is_a($obj, 'Pre');
616 function &insert(&$obj)
618 $this->elements[] = $obj->elements[0];
623 return $this->wrap(join("\n",$this->elements),'pre');
626 class Div extends Block
630 function Div(&$root,$text)
632 if (!preg_match("/^\#([^\(]+)(?:\((.*)\))?/",$text,$out) or !exist_plugin_convert($out[1])) {
633 $this = new Inline($text);
637 $this->name = $out[1];
638 $this->param = array_key_exists(2,$out) ? $out[2] : '';
640 function canContain(&$obj)
646 return do_plugin_convert($this->name,$this->param);
649 class Align extends Block
650 { // LEFT:/CENTER:/RIGHT:
653 function Align($align)
655 $this->align = $align;
657 function &insert(&$obj)
659 if (is_a($obj,'Inline')) {
660 $obj =& $obj->toPara();
662 return parent::insert($obj);
666 $string = parent::toString();
668 if (preg_match('/^(\s*<[^>]+style=")(.+)$"/',$string,$matches)) {
669 $string = $matches[1]."text-align:{$this->align};".$matches[2];
672 $string = preg_replace('/^(\s*<[a-z]+)/', '$1 style="text-align:'.$this->align.';"',$string);
678 class Body extends Block
680 var $id,$count,$top,$contents,$contents_last;
681 var $classes = array('HRule','Heading','Pre','UList','OList','DList','Table','YTable','BQuote','BQuoteEnd','Div');
689 $this->top = $top ? "<a href=\"#contents_$id\">$top</a>" : '';
690 $this->contents = new Block();
691 $this->contents_last =& $this->contents;
694 function parse(&$lines)
696 $this->last =& $this;
698 foreach ($lines as $line) {
699 if (substr($line,0,2) == '//') { //¥³¥á¥ó¥È¤Ï½èÍý¤·¤Ê¤¤
704 if (preg_match('/^(LEFT|CENTER|RIGHT):(.*)$/',$line,$matches)) {
705 $this->last =& $this->last->add(new Align(strtolower($matches[1]))); // <div style="text-align:...">
706 if ($matches[2] == '') {
712 $line = preg_replace("/[\r\n]*$/",'',$line);
715 $head = substr($line,0,1);
717 if ($line == '') { // ¶õ¹Ô
718 $this->last =& $this;
720 else if (substr($line,0,4) == '----') { // HRule
721 $this->last =& $this->insert(new HRule($this,$line));
723 else if ($head == '*') { // Heading
724 $this->last =& $this->insert(new Heading($this,$line));
726 else if ($head == ' ' or $head == "\t") { // Pre
727 $this->last =& $this->last->add(new Pre($this,$line));
730 if (substr($line,-1) == '~') {
731 $line = substr($line,0,-1)."\r";
733 if ($head == '-') { // UList
734 $this->last =& $this->last->add(new UList($this,$line)); // inline
736 else if ($head == '+') { // OList
737 $this->last =& $this->last->add(new OList($this,$line)); // inline
739 else if ($head == ':') { // DList
740 $this->last =& $this->last->add(new DList($this,$line)); // inline
742 else if ($head == '|') { // Table
743 $this->last =& $this->last->add(new Table($this,$line));
745 else if ($head == ',') { // Table(YukiWiki¸ß´¹)
746 $this->last =& $this->last->add(new YTable($this,$line));
748 else if ($head == '>' or $head == '<') { // BrockQuote
749 $this->last =& $this->last->add(new BQuote($this,$line));
751 else if ($head == '#') { // Div
752 $this->last =& $this->last->add(new Div($this,$line));
755 $this->last =& $this->last->add(new Inline($line));
760 function getAnchor($text,$level)
762 $id = "content_{$this->id}_{$this->count}";
764 $this->contents_last =& $this->contents_last->add(new Contents_UList($text,$this->id,$level,$id));
765 return array(&$this->top,$id);
767 function getContents()
769 $contents = "<a id=\"contents_{$this->id}\"></a>";
770 $contents .= $this->contents->toString();
773 function &insert(&$obj)
775 if (is_a($obj,'Inline')) {
776 $obj =& $obj->toPara();
778 return parent::insert($obj);
784 $text = parent::toString();
787 $text = preg_replace('/<p[^>]*>#contents<\/p>/',$this->getContents(),$text);
790 // <p>¤Î¤È¤¤Ï¹ÔƬ¤«¤é¡¢<del>¤Î¤È¤¤Ï¾¤ÎÍ×ÁǤλÒÍ×ÁǤȤ·¤Æ¸ºß
791 $text = preg_replace('/<(p|del)>#related<\/\1>/e','make_related($vars[\'page\'],\'$1\')',$text);
795 class Contents_UList extends ListContainer
797 function Contents_UList($text,$id,$level,$id)
799 // ¥Æ¥¥¹¥È¤Î¥ê¥Õ¥©¡¼¥à
800 // ¹ÔƬ\n¤ÇÀ°·ÁºÑ¤ß¤òɽ¤¹ ... X(
801 $text = "\n<a href=\"#$id\">".strip_htmltag(inline2(inline($text,TRUE)))."</a>\n";
802 parent::ListContainer('ul', 'li', --$level, $text);