OSDN Git Service

Cleanup only
[pukiwiki/pukiwiki.git] / lib / convert_html.php
1 <?php
2 /////////////////////////////////////////////////
3 // PukiWiki - Yet another WikiWikiWeb clone.
4 //
5 // $Id: convert_html.php,v 1.5 2004/11/23 11:27:25 henoheno Exp $
6 //
7
8 function convert_html($lines)
9 {
10         global $vars, $digest;
11         static $contents_id = 0;
12
13         // Set digest
14         $digest = md5(join('', get_source($vars['page'])));
15
16         if (! is_array($lines)) $lines = explode("\n", $lines);
17
18         $body = & new Body(++$contents_id);
19         $body->parse($lines);
20
21         return $body->toString();
22 }
23
24 class Element
25 { // ¥Ö¥í¥Ã¥¯Í×ÁÇ
26         var $parent;   // ¿ÆÍ×ÁÇ
27         var $last;     // ¼¡¤ËÍ×ÁǤòÁÞÆþ¤¹¤ëÀè
28         var $elements; // Í×ÁǤÎÇÛÎó
29
30         function Element()
31         {
32                 $this->elements = array();
33                 $this->last     = & $this;
34         }
35
36         function setParent(& $parent)
37         {
38                 $this->parent = & $parent;
39         }
40
41         function & add(& $obj)
42         {
43                 if ($this->canContain($obj)) {
44                         return $this->insert($obj);
45                 } else {
46                         return $this->parent->add($obj);
47                 }
48         }
49
50         function & insert(& $obj)
51         {
52                 $obj->setParent($this);
53                 $this->elements[] = & $obj;
54
55                 return $this->last = & $obj->last;
56         }
57
58         function canContain($obj)
59         {
60                 return TRUE;
61         }
62
63         function wrap($string, $tag, $param = '', $canomit = TRUE)
64         {
65                 return ($canomit && $string == '') ? '' : "<$tag$param>$string</$tag>";
66         }
67
68         function toString()
69         {
70                 $ret = array();
71                 foreach (array_keys($this->elements) as $key) {
72                         $ret[] = $this->elements[$key]->toString();
73                 }
74
75                 return join("\n", $ret);
76         }
77
78         function dump($indent = 0)
79         {
80                 $ret = str_repeat(' ', $indent) . get_class($this) . "\n";
81
82                 $indent += 2;
83
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];
88                 }
89
90                 return $ret;
91         }
92 }
93
94 function & Factory_Inline($text)
95 {
96         if (substr($text, 0, 1) == '~') {
97                 // ¹ÔƬ '~' ¡£¥Ñ¥é¥°¥é¥Õ³«»Ï
98                 return new Paragraph(' ' . substr($text, 1));
99         } else {
100                 return new Inline($text);
101         }
102 }
103
104 function & Factory_DList(& $root, $text)
105 {
106         $out = explode('|', ltrim($text), 2);
107         if (count($out) < 2) {
108                 return Factory_Inline($text);
109         } else {
110                 return new DList($out);
111         }
112 }
113
114 // '|'-separated table
115 function & Factory_Table(& $root, $text)
116 {
117         if (! preg_match("/^\|(.+)\|([hHfFcC]?)$/", $text, $out)) {
118                 return Factory_Inline($text);
119         } else {
120                 return new Table($out);
121         }
122 }
123
124 // Comma-separated table
125 function & Factory_YTable(& $root, $text)
126 {
127         if ($text == ',') {
128                 return Factory_Inline($text);
129         } else {
130                 return new YTable(csv_explode(',', substr($text, 1)));
131         }
132 }
133
134 function & Factory_Div(& $root, $text)
135 {
136         if (! preg_match("/^\#([^\(]+)(?:\((.*)\))?/", $text, $out) || ! exist_plugin_convert($out[1])) {
137                 return new Paragraph($text);
138         } else {
139                 return new Div($out);
140         }
141 }
142
143 // ¥¤¥ó¥é¥¤¥óÍ×ÁÇ
144 class Inline extends Element
145 {
146         function Inline($text)
147         {
148                 parent::Element();
149                 $this->elements[] = trim((substr($text, 0, 1) == "\n") ? $text : make_link($text));
150         }
151
152         function & insert(& $obj)
153         {
154                 $this->elements[] = $obj->elements[0];
155                 return $this;
156         }
157
158         function canContain($obj)
159         {
160                 return is_a($obj, 'Inline');
161         }
162
163         function toString()
164         {
165                 global $line_break;
166                 return join($line_break ? "<br />\n" : "\n", $this->elements);
167         }
168
169         function & toPara($class = '')
170         {
171                 $obj = & new Paragraph('', $class);
172                 $obj->insert($this);
173                 return $obj;
174         }
175 }
176
177 // Paragraph: blank-line-separated sentences
178 class Paragraph extends Element
179 {
180         var $param;
181
182         function Paragraph($text, $param = '')
183         {
184                 parent::Element();
185
186                 $this->param = $param;
187
188                 if ($text == '') return;
189
190                 if (substr($text, 0, 1) == '~')
191                         $text = ' ' . substr($text, 1);
192
193                 $this->insert(Factory_Inline($text));
194         }
195
196         function canContain($obj)
197         {
198                 return is_a($obj, 'Inline');
199         }
200
201         function toString()
202         {
203                 return $this->wrap(parent::toString(), 'p', $this->param);
204         }
205 }
206
207 // * Heading1
208 // ** Heading2
209 // *** Heading3
210 class Heading extends Element
211 {
212         var $level;
213         var $id;
214         var $msg_top;
215
216         function Heading(& $root, $text)
217         {
218                 parent::Element();
219
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
224         }
225
226         function & insert(& $obj)
227         {
228                 parent::insert($obj);
229                 return $this->last = & $this;
230         }
231
232         function canContain(& $obj)
233         {
234                 return FALSE;
235         }
236
237         function toString()
238         {
239                 return $this->msg_top . $this->wrap(parent::toString(), 'h' . $this->level, " id=\"{$this->id}\"");
240         }
241 }
242
243 // ----
244 // Horizontal Rule
245 class HRule extends Element
246 {
247         function HRule(& $root, $text)
248         {
249                 parent::Element();
250         }
251
252         function canContain(& $obj)
253         {
254                 return FALSE;
255         }
256
257         function toString()
258         {
259                 global $hr;
260                 return $hr;
261         }
262 }
263
264 class ListContainer extends Element
265 {
266         var $tag;
267         var $tag2;
268         var $level;
269         var $style;
270         var $margin;
271         var $left_margin;
272
273         function ListContainer($tag, $tag2, $head, $text)
274         {
275                 parent::Element();
276
277                 //¥Þ¡¼¥¸¥ó¤ò¼èÆÀ
278                 $var_margin      = "_{$tag}_margin";
279                 $var_left_margin = "_{$tag}_left_margin";
280
281                 global $$var_margin, $$var_left_margin;
282
283                 $this->margin      = $$var_margin;
284                 $this->left_margin = $$var_left_margin;
285
286                 //½é´ü²½
287                 $this->tag   = $tag;
288                 $this->tag2  = $tag2;
289                 $this->level = min(3, strspn($text, $head));
290                 $text = ltrim(substr($text, $this->level));
291
292                 parent::insert(new ListElement($this->level, $tag2));
293                 if ($text != '')
294                         $this->last = & $this->last->insert(Factory_Inline($text));
295         }
296
297         function canContain(& $obj)
298         {
299                 return (! is_a($obj, 'ListContainer')
300                         || ($this->tag == $obj->tag && $this->level == $obj->level));
301         }
302
303         function setParent(& $parent)
304         {
305                 global $_list_pad_str;
306
307                 parent::setParent($parent);
308
309                 $step = $this->level;
310                 if (isset($parent->parent) && is_a($parent->parent, 'ListContainer')) {
311                         $step -= $parent->parent->level;
312                 }
313                 $margin = $this->margin * $step;
314                 if ($step == $this->level) {
315                         $margin += $this->left_margin;
316                 }
317                 $this->style = sprintf($_list_pad_str, $this->level, $margin, $margin);
318         }
319
320         function & insert(& $obj)
321         {
322                 if (! is_a($obj, get_class($this)))
323                         return $this->last = & $this->last->insert($obj);
324
325                 // ¹ÔƬʸ»ú¤Î¤ß¤Î»ØÄê»þ¤ÏUL/OL¥Ö¥í¥Ã¥¯¤òæ½Ð
326                 // BugTrack/524
327                 if (count($obj->elements) == 1 && empty($obj->elements[0]->elements))
328                         return $this->last->parent; // up to ListElement.
329
330                 // move elements.
331                 foreach(array_keys($obj->elements) as $key)
332                         parent::insert($obj->elements[$key]);
333
334                 return $this->last;
335         }
336
337         function toString()
338         {
339                 return $this->wrap(parent::toString(), $this->tag, $this->style);
340         }
341 }
342
343 class ListElement extends Element
344 {
345         function ListElement($level, $head)
346         {
347                 parent::Element();
348                 $this->level = $level;
349                 $this->head  = $head;
350         }
351
352         function canContain(& $obj)
353         {
354                 return (! is_a($obj, 'ListContainer') || ($obj->level > $this->level));
355         }
356
357         function toString()
358         {
359                 return $this->wrap(parent::toString(), $this->head);
360         }
361 }
362
363 // - One
364 // - Two
365 // - Three
366 class UList extends ListContainer
367 {
368         function UList(& $root, $text)
369         {
370                 parent::ListContainer('ul', 'li', '-', $text);
371         }
372 }
373
374 // + One
375 // + Two
376 // + Three
377 class OList extends ListContainer
378 {
379         function OList(& $root, $text)
380         {
381                 parent::ListContainer('ol', 'li', '+', $text);
382         }
383 }
384
385 // : definition1 | description1
386 // : definition2 | description2
387 // : definition3 | description3
388 class DList extends ListContainer
389 {
390         function DList($out)
391         {
392                 parent::ListContainer('dl', 'dt', ':', $out[0]);
393
394                 $this->last = & Element::insert(new ListElement($this->level, 'dd'));
395                 if ($out[1] != '') {
396                         $this->last = & $this->last->insert(Factory_Inline($out[1]));
397                 }
398         }
399 }
400
401 // > Someting cited
402 // > like E-mail text
403 class BQuote extends Element
404 {
405         var $level;
406
407         function BQuote(& $root, $text)
408         {
409                 parent::Element();
410
411                 $head = substr($text, 0, 1);
412                 $this->level = min(3, strspn($text, $head));
413                 $text = ltrim(substr($text, $this->level));
414
415                 if ($head == '<') { // Blockquote close
416                         $level       = $this->level;
417                         $this->level = 0;
418                         $this->last  = & $this->end($root, $level);
419                         if ($text != '')
420                                 $this->last = & $this->last->insert(Factory_Inline($text));
421                 } else {
422                         $this->insert(Factory_Inline($text));
423                 }
424         }
425
426         function canContain(& $obj)
427         {
428                 return (! is_a($obj, get_class($this)) || $obj->level >= $this->level);
429         }
430
431         function & insert(& $obj)
432         {
433                 // BugTrack/521, BugTrack/545
434                 if (is_a($obj, 'inline'))
435                         return parent::insert($obj->toPara(' class="quotation"'));
436
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];
441                 }
442                 return parent::insert($obj);
443         }
444
445         function toString()
446         {
447                 return $this->wrap(parent::toString(), 'blockquote');
448         }
449
450         function & end(& $root, $level)
451         {
452                 $parent = & $root->last;
453
454                 while (is_object($parent)) {
455                         if (is_a($parent, 'BQuote') && $parent->level == $level)
456                                 return $parent->parent;
457                         $parent = & $parent->parent;
458                 }
459                 return $this;
460         }
461 }
462
463 class TableCell extends Element
464 {
465         var $tag = 'td'; // {td|th}
466         var $colspan = 1;
467         var $rowspan = 1;
468         var $style; // is array('width'=>, 'align'=>...);
469
470         function TableCell($text, $is_template = FALSE)
471         {
472                 parent::Element();
473                 $this->style = $matches = array();
474
475                 while (preg_match('/^(?:(LEFT|CENTER|RIGHT)|(BG)?COLOR\(([#\w]+)\)|SIZE\((\d+)\)):(.*)$/',
476                     $text, $matches)) {
477                         if ($matches[1]) {
478                                 $this->style['align'] = 'text-align:' . strtolower($matches[1]) . ';';
479                                 $text = $matches[5];
480                         } else if ($matches[3]) {
481                                 $name = $matches[2] ? 'background-color' : 'color';
482                                 $this->style[$name] = $name . ':' . htmlspecialchars($matches[3]) . ';';
483                                 $text = $matches[5];
484                         } else if ($matches[4]) {
485                                 $this->style['size'] = 'font-size:' . htmlspecialchars($matches[4]) . 'px;';
486                                 $text = $matches[5];
487                         }
488                 }
489                 if ($is_template && is_numeric($text)) {
490                         $this->style['width'] = "width:{$text}px;";
491                 }
492
493                 if ($text == '>') {
494                         $this->colspan = 0;
495                 } else if ($text == '~') {
496                         $this->rowspan = 0;
497                 } else if (substr($text, 0, 1) == '~') {
498                         $this->tag = 'th';
499                         $text      = substr($text, 1);
500                 }
501
502                 if ($text != '' && $text{0} == '#') {
503                         // ¥»¥ëÆâÍƤ¬'#'¤Ç»Ï¤Þ¤ë¤È¤­¤ÏDiv¥¯¥é¥¹¤òÄ̤·¤Æ¤ß¤ë
504                         $obj = & Factory_Div($this, $text);
505                         if (is_a($obj, 'Paragraph')) {
506                                 $obj = & $obj->elements[0];
507                         }
508                 } else {
509                         $obj = & Factory_Inline($text);
510                 }
511
512                 $this->insert($obj);
513         }
514
515         function setStyle(& $style)
516         {
517                 foreach ($style as $key=>$value) {
518                         if (! isset($this->style[$key]))
519                                 $this->style[$key] = $value;
520                 }
521         }
522
523         function toString()
524         {
525                 if ($this->rowspan == 0 || $this->colspan == 0) return '';
526
527                 $param = " class=\"style_{$this->tag}\"";
528                 if ($this->rowspan > 1) {
529                         $param .= " rowspan=\"{$this->rowspan}\"";
530                 }
531                 if ($this->colspan > 1) {
532                         $param .= " colspan=\"{$this->colspan}\"";
533                         unset($this->style['width']);
534                 }
535                 if (! empty($this->style)) {
536                         $param .= ' style="' . join(' ', $this->style) . '"';
537                 }
538
539                 return $this->wrap(parent::toString(), $this->tag, $param, FALSE);
540         }
541 }
542
543 // | title1 | title2 | title3 |
544 // | cell1  | cell2  | cell3  |
545 // | cell4  | cell5  | cell6  |
546 class Table extends Element
547 {
548         var $type;
549         var $types;
550         var $col; // number of column
551
552         function Table($out)
553         {
554                 parent::Element();
555
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');
561                 $row = array();
562                 foreach ($cells as $cell) {
563                         $row[] = & new TableCell($cell, $is_template);
564                 }
565                 $this->elements[] = $row;
566         }
567
568         function canContain(& $obj)
569         {
570                 return is_a($obj, 'Table') && ($obj->col == $this->col);
571         }
572
573         function & insert(& $obj)
574         {
575                 $this->elements[] = $obj->elements[0];
576                 $this->types[]    = $obj->type;
577                 return $this;
578         }
579
580         function toString()
581         {
582                 static $parts = array('h'=>'thead', 'f'=>'tfoot', ''=>'tbody');
583
584                 // rowspan¤òÀßÄê(²¼¤«¤é¾å¤Ø)
585                 for ($ncol = 0; $ncol < $this->col; $ncol++) {
586                         $rowspan = 1;
587                         foreach (array_reverse(array_keys($this->elements)) as $nrow) {
588                                 $row = & $this->elements[$nrow];
589                                 if ($row[$ncol]->rowspan == 0) {
590                                         ++$rowspan;
591                                         continue;
592                                 }
593                                 $row[$ncol]->rowspan = $rowspan;
594                                 while (--$rowspan) { // ¹Ô¼ïÊ̤ò·Ñ¾µ¤¹¤ë
595                                         $this->types[$nrow + $rowspan] = $this->types[$nrow];
596                                 }
597                                 $rowspan = 1;
598                         }
599                 }
600
601                 // colspan,style¤òÀßÄê
602                 $stylerow = NULL;
603                 foreach (array_keys($this->elements) as $nrow) {
604                         $row = & $this->elements[$nrow];
605                         if ($this->types[$nrow] == 'c') {
606                                 $stylerow = & $row;
607                         }
608                         $colspan = 1;
609                         foreach (array_keys($row) as $ncol) {
610                                 if ($row[$ncol]->colspan == 0) {
611                                         ++$colspan;
612                                         continue;
613                                 }
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);
619                                         }
620                                 }
621                                 $colspan = 1;
622                         }
623                 }
624
625                 // ¥Æ¥­¥¹¥È²½
626                 $string = '';
627                 foreach ($parts as $type => $part)
628                 {
629                         $part_string = '';
630                         foreach (array_keys($this->elements) as $nrow) {
631                                 if ($this->types[$nrow] != $type)
632                                         continue;
633                                 $row        = & $this->elements[$nrow];
634                                 $row_string = '';
635                                 foreach (array_keys($row) as $ncol) {
636                                         $row_string .= $row[$ncol]->toString();
637                                 }
638                                 $part_string .= $this->wrap($row_string, 'tr');
639                         }
640                         $string .= $this->wrap($part_string, $part);
641                 }
642                 $string = $this->wrap($string, 'table', ' class="style_table" cellspacing="1" border="0"');
643
644                 return $this->wrap($string, 'div', ' class="ie5"');
645         }
646 }
647
648 // , title1 , title2 , title3
649 // , cell1  , cell2  , cell3
650 // , cell4  , cell5  , cell6
651 class YTable extends Element
652 {
653         var $col;
654
655         function YTable($_value)
656         {
657                 parent::Element();
658
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"'
666                                         ) : '';
667                                 $value[] = $matches[2];
668                         } else {
669                                 $align[] = '';
670                                 $value[] = $val;
671                         }
672                 }
673                 $this->col = count($value);
674                 $colspan = array();
675                 foreach ($value as $val) {
676                         $colspan[] = ($val == '==') ? 0 : 1;
677                 }
678                 $str = '';
679                 for ($i = 0; $i < count($value); $i++) {
680                         if ($colspan[$i]) {
681                                 while ($i + $colspan[$i] < count($value) && $value[$i + $colspan[$i]] == '==') {
682                                         $colspan[$i]++;
683                                 }
684                                 $colspan[$i] = ($colspan[$i] > 1) ? " colspan=\"{$colspan[$i]}\"" : '';
685                                 $str .= "<td class=\"style_td\"{$align[$i]}{$colspan[$i]}>".make_link($value[$i]).'</td>';
686                         }
687                 }
688                 $this->elements[] = $str;
689         }
690
691         function canContain(& $obj)
692         {
693                 return is_a($obj, 'YTable') && ($obj->col == $this->col);
694         }
695
696         function & insert(& $obj)
697         {
698                 $this->elements[] = $obj->elements[0];
699                 return $this;
700         }
701
702         function toString()
703         {
704                 $rows = '';
705                 foreach ($this->elements as $str) {
706                         $rows .= "\n<tr class=\"style_tr\">$str</tr>\n";
707                 }
708                 $rows = $this->wrap($rows, 'table', ' class="style_table" cellspacing="1" border="0"');
709                 return $this->wrap($rows, 'div', ' class="ie5"');
710         }
711 }
712
713 // ' 'Space-beginning sentence
714 // ' 'Space-beginning sentence
715 // ' 'Space-beginning sentence
716 class Pre extends Element
717 {
718         function Pre(& $root, $text)
719         {
720                 global $preformat_ltrim;
721
722                 parent::Element();
723                 $this->elements[] = htmlspecialchars(
724                         (! $preformat_ltrim || $text == '' || $text{0} != ' ') ? $text : substr($text, 1)
725                 );
726         }
727
728         function canContain(& $obj)
729         {
730                 return is_a($obj, 'Pre');
731         }
732
733         function & insert(& $obj)
734         {
735                 $this->elements[] = $obj->elements[0];
736                 return $this;
737         }
738
739         function toString()
740         {
741                 return $this->wrap(join("\n", $this->elements), 'pre');
742         }
743 }
744
745 // #someting(started with '#')
746 class Div extends Element
747 {
748         var $name;
749         var $param;
750
751         function Div($out)
752         {
753                 parent::Element();
754                 list(, $this->name, $this->param) = array_pad($out, 3, '');
755         }
756
757         function canContain(& $obj)
758         {
759                 return FALSE;
760         }
761
762         function toString()
763         {
764                 return do_plugin_convert($this->name, $this->param);
765         }
766 }
767
768 // LEFT:/CENTER:/RIGHT:
769 class Align extends Element
770 {
771         var $align;
772
773         function Align($align)
774         {
775                 parent::Element();
776                 $this->align = $align;
777         }
778
779         function canContain(& $obj)
780         {
781                 return is_a($obj, 'Inline');
782         }
783
784         function toString()
785         {
786                 return $this->wrap(parent::toString(), 'div', ' style="text-align:' . $this->align . '"');
787         }
788 }
789
790 // Body
791 class Body extends Element
792 {
793         var $id;
794         var $count = 0;
795         var $contents;
796         var $contents_last;
797         var $classes = array(
798                 '-' => 'UList',
799                 '+' => 'OList',
800                 '>' => 'BQuote',
801                 '<' => 'BQuote'
802         );
803         var $factories = array(
804                 ':' => 'DList',
805                 '|' => 'Table',
806                 ',' => 'YTable',
807                 '#' => 'Div'
808         );
809
810         function Body($id)
811         {
812                 $this->id            = $id;
813                 $this->contents      = & new Element();
814                 $this->contents_last = & $this->contents;
815                 parent::Element();
816         }
817
818         function parse(& $lines)
819         {
820                 $this->last = & $this;
821                 $matches = array();
822
823                 while (! empty($lines)) {
824                         $line = array_shift($lines);
825
826                         // Escape comments
827                         if (substr($line, 0, 2) == '//') continue;
828
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])));
832
833                                 if ($matches[2] == '') continue;
834
835                                 $line = $matches[2];
836                         }
837
838                         $line = preg_replace("/[\r\n]*$/", '', $line);
839
840                         // Empty
841                         if ($line == '') {
842                                 $this->last = & $this;
843                                 continue;
844                         }
845
846                         // Horizontal Rule
847                         if (substr($line, 0, 4) == '----') {
848                                 $this->insert(new HRule($this, $line));
849                                 continue;
850                         }
851
852                         // The first character
853                         $head = $line{0};
854
855                         // Heading
856                         if ($head == '*') {
857                                 $this->insert(new Heading($this, $line));
858                                 continue;
859                         }
860
861                         // Pre
862                         if ($head == ' ' || $head == "\t") {
863                                 $this->last = & $this->last->add(new Pre($this, $line));
864                                 continue;
865                         }
866
867                         // Line Break
868                         if (substr($line, -1) == '~') {
869                                 $line = substr($line,0,-1)."\r";
870                         }
871                         
872                         // Other Character
873                         if (isset($this->classes[$head])) {
874                                 $classname  = $this->classes[$head];
875                                 $this->last = & $this->last->add(new $classname($this, $line));
876                                 continue;
877                         }
878
879                         // Other Character
880                         if (isset($this->factories[$head])) {
881                                 $factoryname = 'Factory_' . $this->factories[$head];
882                                 $this->last  = & $this->last->add($factoryname($this, $line));
883                                 continue;
884                         }
885
886                         // Default
887                         $this->last = & $this->last->add(Factory_Inline($line));
888                 }
889         }
890
891         function getAnchor($text, $level)
892         {
893                 global $top, $_symbol_anchor;
894
895                 $id = make_heading($text, FALSE); // Cut fixed-anchor from $text
896                 $anchor = ($id == '') ?  '' : " &aname($id,super,full)\{$_symbol_anchor};";
897
898                 $text = ' ' . $text;
899                 $id = "content_{$this->id}_{$this->count}";
900                 $this->count++;
901                 $this->contents_last = & $this->contents_last->add(new Contents_UList($text, $level, $id));
902
903                 return array($text . $anchor, $this->count > 1 ? "\n$top" : '', $id);
904         }
905
906         function & insert(& $obj)
907         {
908                 if (is_a($obj, 'Inline')) $obj = & $obj->toPara();
909                 return parent::insert($obj);
910         }
911
912         function toString()
913         {
914                 global $vars;
915
916                 $text = parent::toString();
917
918                 // #contents
919                 $text = preg_replace_callback('/(<p[^>]*>)<del>#contents<\/del>(\s*)(<\/p>)/', array(& $this, 'replace_contents'), $text);
920
921                 // ´ØÏ¢¤¹¤ë¥Ú¡¼¥¸
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);
925                 return "$text\n";
926         }
927
928         function replace_contents($arr)
929         {
930                 $contents  = "<div class=\"contents\">\n";
931                 $contents .= "<a id=\"contents_{$this->id}\"></a>";
932                 $contents .= $this->contents->toString();
933                 $contents .= "</div>\n";
934                 array_shift($arr);
935
936                 return ($arr[1] != '') ? $contents . join('', $arr) : $contents;
937         }
938
939         function replace_related($arr)
940         {
941                 global $vars;
942                 static $related = NULL;
943
944                 if (is_null($related))
945                         $related = make_related($vars['page'],'p');
946
947                 array_shift($arr);
948
949                 return ($arr[1] != '') ? $related . join('', $arr) : $related;
950         }
951 }
952
953 class Contents_UList extends ListContainer
954 {
955         function Contents_UList($text, $level, $id)
956         {
957                 // ¥Æ¥­¥¹¥È¤Î¥ê¥Õ¥©¡¼¥à
958                 // ¹ÔƬ\n¤ÇÀ°·ÁºÑ¤ß¤òɽ¤¹ ... X(
959                 make_heading($text);
960                 $text = "\n<a href=\"#$id\">$text</a>\n";
961                 parent::ListContainer('ul', 'li', '-', str_repeat('-', $level));
962                 $this->insert(Factory_Inline($text));
963         }
964
965         function setParent(& $parent)
966         {
967                 global $_list_pad_str;
968
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;
974                         $margin = 0;
975                 }
976                 $margin += $this->margin * ($step == $this->level ? 1 : $step);
977                 $this->style = sprintf($_list_pad_str, $this->level, $margin, $margin);
978         }
979 }
980 ?>