OSDN Git Service

初回コミット(v2.6.17.1)
[magic3/magic3.git] / widgets / wiki_main / include / lib / convert_html.php
1 <?php
2 /**
3  * HTML変換ライブラリ
4  *
5  * PHP versions 5
6  *
7  * LICENSE: This source file is licensed under the terms of the GNU General Public License.
8  *
9  * @package    Magic3 Framework
10  * @author     平田直毅(Naoki Hirata) <naoki@aplo.co.jp>
11  * @copyright  Copyright 2006-2012 Magic3 Project.
12  * @license    http://www.gnu.org/copyleft/gpl.html  GPL License
13  * @version    SVN: $Id: convert_html.php 4951 2012-06-09 09:55:41Z fishbone $
14  * @link       http://www.magic3.org
15  */
16 // Copyright (C)
17 //   2002-2005 PukiWiki Developers Team
18 //   2001-2002 Originally written by yu-ji
19 // License: GPL v2 or (at your option) any later version
20 //
21 // function 'convert_html()', wiki text parser
22 // and related classes-and-functions
23
24 function convert_html($lines)
25 {
26         global $vars, $digest;
27         static $contents_id = 0;
28
29         // comment for Magic3 by naoki on 2008/9/30
30         // get_sourceが2度実行されている
31         // Set digest
32         //$digest = md5(join('', get_source($vars['page'])));
33         $digest = md5(get_source(WikiParam::getPage(), true));
34         WikiParam::setDigest($digest);          // ダイジェストの設定はここで行う
35
36         if (! is_array($lines)) $lines = explode("\n", $lines);
37 //global $gErrorManager;
38 //$gErrorManager->writeDebug(__METHOD__, 'convert_html start');         // 時間計測用
39         $body = new Body(++$contents_id);
40         $body->parse($lines);
41 //$gErrorManager->writeDebug(__METHOD__, 'convert_html end');           // 時間計測用
42         return $body->toString();
43 }
44
45 // Block elements
46 class Element
47 {
48         var $parent;
49         var $elements; // References of childs
50         var $last;     // Insert new one at the back of the $last
51
52         function Element()
53         {
54                 $this->elements = array();
55                 $this->last     = $this;
56         }
57
58         function setParent($parent)
59         {
60                 $this->parent = $parent;
61         }
62
63         function add($obj)
64         {
65                 if ($this->canContain($obj)) {
66                         return $this->insert($obj);
67                 } else {
68                         return $this->parent->add($obj);
69                 }
70         }
71
72         function insert($obj)
73         {
74                 $obj->setParent($this);
75                 $this->elements[] = $obj;
76
77                 return $this->last = $obj->last;
78         }
79
80         function canContain($obj)
81         {
82                 return TRUE;
83         }
84
85         function wrap($string, $tag, $param = '', $canomit = TRUE)
86         {
87                 return ($canomit && $string == '') ? '' :
88                         '<' . $tag . $param . '>' . $string . '</' . $tag . '>';
89         }
90
91         function toString()
92         {
93                 $ret = array();
94                 foreach (array_keys($this->elements) as $key)
95                         $ret[] = $this->elements[$key]->toString();
96                 return join("\n", $ret);
97         }
98
99         function dump($indent = 0)
100         {
101                 $ret = str_repeat(' ', $indent) . get_class($this) . "\n";
102                 $indent += 2;
103                 foreach (array_keys($this->elements) as $key) {
104                         $ret .= is_object($this->elements[$key]) ?
105                                 $this->elements[$key]->dump($indent) : '';
106                                 //str_repeat(' ', $indent) . $this->elements[$key];
107                 }
108                 return $ret;
109         }
110 }
111
112 // Returns inline-related object
113 function Factory_Inline($text)
114 {
115         // Check the first letter of the line
116         if (substr($text, 0, 1) == '~') {
117                 return new Paragraph(' ' . substr($text, 1));
118         } else {
119                 return new Inline($text);
120         }
121 }
122
123 function Factory_DList($root, $text)
124 {
125         $out = explode('|', ltrim($text), 2);
126         if (count($out) < 2) {
127                 return Factory_Inline($text);
128         } else {
129                 return new DList($out);
130         }
131 }
132
133 // '|'-separated table
134 function Factory_Table($root, $text)
135 {
136         if (! preg_match('/^\|(.+)\|([hHfFcC]?)$/', $text, $out)) {
137                 return Factory_Inline($text);
138         } else {
139                 return new Table($out);
140         }
141 }
142
143 // Comma-separated table
144 function Factory_YTable($root, $text)
145 {
146         if ($text == ',') {
147                 return Factory_Inline($text);
148         } else {
149                 return new YTable(csv_explode(',', substr($text, 1)));
150         }
151 }
152
153 function Factory_Div($root, $text)
154 {
155         $matches = array();
156
157         // Seems block plugin?
158         if (PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK) {
159                 // Usual code
160                 if (preg_match('/^\#([^\(]+)(?:\((.*)\))?/', $text, $matches) &&
161                     exist_plugin_convert($matches[1])) {
162                         return new Div($matches);
163                 }
164         } else {
165                 // Hack code
166                 if(preg_match('/^#([^\(\{]+)(?:\(([^\r]*)\))?(\{*)/', $text, $matches) &&
167                    exist_plugin_convert($matches[1])) {
168                         $len  = strlen($matches[3]);
169                         $body = array();
170                         if ($len == 0) {
171                                 return new Div($matches); // Seems legacy block plugin
172                         } else if (preg_match('/\{{' . $len . '}\s*\r(.*)\r\}{' . $len . '}/', $text, $body)) { 
173                                 $matches[2] .= "\r" . $body[1] . "\r";
174                                 return new Div($matches); // Seems multiline-enabled block plugin
175                         }
176                 }
177         }
178
179         return new Paragraph($text);
180 }
181
182 // Inline elements
183 class Inline extends Element
184 {
185         function Inline($text)
186         {
187                 parent::Element();
188                 $this->elements[] = trim((substr($text, 0, 1) == "\n") ?
189                         $text : make_link($text));
190         }
191
192         function insert($obj)
193         {
194                 $this->elements[] = $obj->elements[0];
195                 return $this;
196         }
197
198         function canContain($obj)
199         {
200                 return is_a($obj, 'Inline');
201         }
202
203         function toString()
204         {
205                 global $line_break;
206                 return join(($line_break ? '<br />' . "\n" : "\n"), $this->elements);
207         }
208
209         function toPara($class = '')
210         {
211                 $obj = new Paragraph('', $class);
212                 $obj->insert($this);
213                 return $obj;
214         }
215 }
216
217 // Paragraph: blank-line-separated sentences
218 class Paragraph extends Element
219 {
220         var $param;
221
222         function Paragraph($text, $param = '')
223         {
224                 parent::Element();
225                 $this->param = $param;
226                 if ($text == '') return;
227
228                 if (substr($text, 0, 1) == '~')
229                         $text = ' ' . substr($text, 1);
230
231                 $this->insert(Factory_Inline($text));
232         }
233
234         function canContain($obj)
235         {
236                 return is_a($obj, 'Inline');
237         }
238
239         function toString()
240         {
241                 return $this->wrap(parent::toString(), 'p', $this->param);
242         }
243 }
244
245 // * Heading1
246 // ** Heading2
247 // *** Heading3
248 class Heading extends Element
249 {
250         var $level;
251         var $id;
252         var $msg_top;
253
254         function Heading($root, $text)
255         {
256                 parent::Element();
257
258                 $this->level = min(3, strspn($text, '*'));
259                 list($text, $this->msg_top, $this->id) = $root->getAnchor($text, $this->level);
260                 $this->insert(Factory_Inline($text));
261                 $this->level++; // h2,h3,h4
262         }
263
264         function insert($obj)
265         {
266                 parent::insert($obj);
267                 return $this->last = $this;
268         }
269
270         function canContain($obj)
271         {
272                 return FALSE;
273         }
274
275         function toString()
276         {
277                 return $this->msg_top .  $this->wrap(parent::toString(),
278                         'h' . $this->level, ' id="' . $this->id . '"');
279         }
280 }
281
282 // ----
283 // Horizontal Rule
284 class HRule extends Element
285 {
286         function HRule($root, $text)
287         {
288                 parent::Element();
289         }
290
291         function canContain($obj)
292         {
293                 return FALSE;
294         }
295
296         function toString()
297         {
298                 global $hr;
299                 return $hr;
300         }
301 }
302
303 // Lists (UL, OL, DL)
304 class ListContainer extends Element
305 {
306         var $tag;
307         var $tag2;
308         var $level;
309         var $style;
310         var $margin;
311         var $left_margin;
312
313         function ListContainer($tag, $tag2, $head, $text)
314         {
315                 parent::Element();
316
317                 $var_margin      = '_' . $tag . '_margin';
318                 $var_left_margin = '_' . $tag . '_left_margin';
319                 global $$var_margin, $$var_left_margin;
320
321                 $this->margin      = $$var_margin;
322                 $this->left_margin = $$var_left_margin;
323
324                 $this->tag   = $tag;
325                 $this->tag2  = $tag2;
326                 $this->level = min(3, strspn($text, $head));
327                 $text = ltrim(substr($text, $this->level));
328
329                 parent::insert(new ListElement($this->level, $tag2));
330                 if ($text != '')
331                         $this->last = $this->last->insert(Factory_Inline($text));
332         }
333
334         function canContain($obj)
335         {
336                 return (! is_a($obj, 'ListContainer')
337                         || ($this->tag == $obj->tag && $this->level == $obj->level));
338         }
339
340         function setParent($parent)
341         {
342                 global $_list_pad_str;
343
344                 parent::setParent($parent);
345
346                 $step = $this->level;
347                 if (isset($parent->parent) && is_a($parent->parent, 'ListContainer'))
348                         $step -= $parent->parent->level;
349
350                 $margin = $this->margin * $step;
351                 if ($step == $this->level)
352                         $margin += $this->left_margin;
353
354                 $this->style = sprintf($_list_pad_str, $this->level, $margin, $margin);
355         }
356
357         function insert($obj)
358         {
359                 if (! is_a($obj, get_class($this)))
360                         return $this->last = $this->last->insert($obj);
361
362                 // Break if no elements found (BugTrack/524)
363                 if (count($obj->elements) == 1 && empty($obj->elements[0]->elements))
364                         return $this->last->parent; // up to ListElement
365
366                 // Move elements
367                 foreach(array_keys($obj->elements) as $key)
368                         parent::insert($obj->elements[$key]);
369
370                 return $this->last;
371         }
372
373         function toString()
374         {
375                 return $this->wrap(parent::toString(), $this->tag, $this->style);
376         }
377 }
378
379 class ListElement extends Element
380 {
381         function ListElement($level, $head)
382         {
383                 parent::Element();
384                 $this->level = $level;
385                 $this->head  = $head;
386         }
387
388         function canContain($obj)
389         {
390                 return (! is_a($obj, 'ListContainer') || ($obj->level > $this->level));
391         }
392
393         function toString()
394         {
395                 return $this->wrap(parent::toString(), $this->head);
396         }
397 }
398
399 // - One
400 // - Two
401 // - Three
402 class UList extends ListContainer
403 {
404         function UList($root, $text)
405         {
406                 parent::ListContainer('ul', 'li', '-', $text);
407         }
408 }
409
410 // + One
411 // + Two
412 // + Three
413 class OList extends ListContainer
414 {
415         function OList($root, $text)
416         {
417                 parent::ListContainer('ol', 'li', '+', $text);
418         }
419 }
420
421 // : definition1 | description1
422 // : definition2 | description2
423 // : definition3 | description3
424 class DList extends ListContainer
425 {
426         function DList($out)
427         {
428                 parent::ListContainer('dl', 'dt', ':', $out[0]);
429                 $this->last = Element::insert(new ListElement($this->level, 'dd'));
430                 if ($out[1] != '')
431                         $this->last = $this->last->insert(Factory_Inline($out[1]));
432         }
433 }
434
435 // > Someting cited
436 // > like E-mail text
437 class BQuote extends Element
438 {
439         var $level;
440
441         function BQuote($root, $text)
442         {
443                 parent::Element();
444
445                 $head = substr($text, 0, 1);
446                 $this->level = min(3, strspn($text, $head));
447                 $text = ltrim(substr($text, $this->level));
448
449                 if ($head == '<') { // Blockquote close
450                         $level       = $this->level;
451                         $this->level = 0;
452                         $this->last  = $this->end($root, $level);
453                         if ($text != '')
454                                 $this->last = $this->last->insert(Factory_Inline($text));
455                 } else {
456                         $this->insert(Factory_Inline($text));
457                 }
458         }
459
460         function canContain($obj)
461         {
462                 return (! is_a($obj, get_class($this)) || $obj->level >= $this->level);
463         }
464
465         function insert($obj)
466         {
467                 // BugTrack/521, BugTrack/545
468                 if (is_a($obj, 'inline'))
469                         return parent::insert($obj->toPara(' class="quotation"'));
470
471                 if (is_a($obj, 'BQuote') && $obj->level == $this->level && count($obj->elements)) {
472                         $obj = $obj->elements[0];
473                         if (is_a($this->last, 'Paragraph') && count($obj->elements))
474                                 $obj = $obj->elements[0];
475                 }
476                 return parent::insert($obj);
477         }
478
479         function toString()
480         {
481                 return $this->wrap(parent::toString(), 'blockquote');
482         }
483
484         function end($root, $level)
485         {
486                 $parent = $root->last;
487
488                 while (is_object($parent)) {
489                         if (is_a($parent, 'BQuote') && $parent->level == $level)
490                                 return $parent->parent;
491                         $parent = $parent->parent;
492                 }
493                 return $this;
494         }
495 }
496
497 class TableCell extends Element
498 {
499         var $tag = 'td'; // {td|th}
500         var $colspan = 1;
501         var $rowspan = 1;
502         var $style; // is array('width'=>, 'align'=>...);
503
504         function TableCell($text, $is_template = FALSE)
505         {
506                 parent::Element();
507                 $this->style = $matches = array();
508
509                 while (preg_match('/^(?:(LEFT|CENTER|RIGHT)|(BG)?COLOR\(([#\w]+)\)|SIZE\((\d+)\)):(.*)$/',
510                     $text, $matches)) {
511                         if ($matches[1]) {
512                                 $this->style['align'] = 'text-align:' . strtolower($matches[1]) . ';';
513                                 $text = $matches[5];
514                         } else if ($matches[3]) {
515                                 $name = $matches[2] ? 'background-color' : 'color';
516                                 $this->style[$name] = $name . ':' . htmlspecialchars($matches[3]) . ';';
517                                 $text = $matches[5];
518                         } else if ($matches[4]) {
519                                 $this->style['size'] = 'font-size:' . htmlspecialchars($matches[4]) . 'px;';
520                                 $text = $matches[5];
521                         }
522                 }
523                 if ($is_template && is_numeric($text))
524                         $this->style['width'] = 'width:' . $text . 'px;';
525
526                 if ($text == '>') {
527                         $this->colspan = 0;
528                 } else if ($text == '~') {
529                         $this->rowspan = 0;
530                 } else if (substr($text, 0, 1) == '~') {
531                         $this->tag = 'th';
532                         $text      = substr($text, 1);
533                 }
534
535                 if ($text != '' && $text{0} == '#') {
536                         // Try using Div class for this $text
537                         $obj = Factory_Div($this, $text);
538                         if (is_a($obj, 'Paragraph'))
539                                 $obj = $obj->elements[0];
540                 } else {
541                         $obj = Factory_Inline($text);
542                 }
543
544                 $this->insert($obj);
545         }
546
547         function setStyle($style)
548         {
549                 foreach ($style as $key=>$value)
550                         if (! isset($this->style[$key]))
551                                 $this->style[$key] = $value;
552         }
553
554         function toString()
555         {
556                 if ($this->rowspan == 0 || $this->colspan == 0) return '';
557
558                 $param = ' class="style_' . $this->tag . '"';
559                 if ($this->rowspan > 1)
560                         $param .= ' rowspan="' . $this->rowspan . '"';
561                 if ($this->colspan > 1) {
562                         $param .= ' colspan="' . $this->colspan . '"';
563                         unset($this->style['width']);
564                 }
565                 if (! empty($this->style))
566                         $param .= ' style="' . join(' ', $this->style) . '"';
567
568                 return $this->wrap(parent::toString(), $this->tag, $param, FALSE);
569         }
570 }
571
572 // | title1 | title2 | title3 |
573 // | cell1  | cell2  | cell3  |
574 // | cell4  | cell5  | cell6  |
575 class Table extends Element
576 {
577         var $type;
578         var $types;
579         var $col; // number of column
580
581         function Table($out)
582         {
583                 parent::Element();
584
585                 $cells       = explode('|', $out[1]);
586                 $this->col   = count($cells);
587                 $this->type  = strtolower($out[2]);
588                 $this->types = array($this->type);
589                 $is_template = ($this->type == 'c');
590                 $row = array();
591                 foreach ($cells as $cell)
592                         $row[] = new TableCell($cell, $is_template);
593                 $this->elements[] = $row;
594         }
595
596         function canContain($obj)
597         {
598                 return is_a($obj, 'Table') && ($obj->col == $this->col);
599         }
600
601         function insert($obj)
602         {
603                 $this->elements[] = $obj->elements[0];
604                 $this->types[]    = $obj->type;
605                 return $this;
606         }
607
608         function toString()
609         {
610                 static $parts = array('h'=>'thead', 'f'=>'tfoot', ''=>'tbody');
611
612                 // Set rowspan (from bottom, to top)
613                 for ($ncol = 0; $ncol < $this->col; $ncol++) {
614                         $rowspan = 1;
615                         foreach (array_reverse(array_keys($this->elements)) as $nrow) {
616                                 $row = $this->elements[$nrow];
617                                 if ($row[$ncol]->rowspan == 0) {
618                                         ++$rowspan;
619                                         continue;
620                                 }
621                                 $row[$ncol]->rowspan = $rowspan;
622                                 // Inherits row type
623                                 while (--$rowspan)
624                                         $this->types[$nrow + $rowspan] = $this->types[$nrow];
625                                 $rowspan = 1;
626                         }
627                 }
628
629                 // Set colspan and style
630                 $stylerow = NULL;
631                 foreach (array_keys($this->elements) as $nrow) {
632                         $row = $this->elements[$nrow];
633                         if ($this->types[$nrow] == 'c')
634                                 $stylerow = $row;
635                         $colspan = 1;
636                         foreach (array_keys($row) as $ncol) {
637                                 if ($row[$ncol]->colspan == 0) {
638                                         ++$colspan;
639                                         continue;
640                                 }
641                                 $row[$ncol]->colspan = $colspan;
642                                 if ($stylerow !== NULL) {
643                                         $row[$ncol]->setStyle($stylerow[$ncol]->style);
644                                         // Inherits column style
645                                         while (--$colspan)
646                                                 $row[$ncol - $colspan]->setStyle($stylerow[$ncol]->style);
647                                 }
648                                 $colspan = 1;
649                         }
650                 }
651
652                 // toString
653                 $string = '';
654                 foreach ($parts as $type => $part)
655                 {
656                         $part_string = '';
657                         foreach (array_keys($this->elements) as $nrow) {
658                                 if ($this->types[$nrow] != $type)
659                                         continue;
660                                 $row        = $this->elements[$nrow];
661                                 $row_string = '';
662                                 foreach (array_keys($row) as $ncol)
663                                         $row_string .= $row[$ncol]->toString();
664                                 $part_string .= $this->wrap($row_string, 'tr');
665                         }
666                         $string .= $this->wrap($part_string, $part);
667                 }
668                 $string = $this->wrap($string, 'table', ' class="style_table" cellspacing="1" border="0"');
669
670                 return $this->wrap($string, 'div', ' class="ie5"');
671         }
672 }
673
674 // , title1 , title2 , title3
675 // , cell1  , cell2  , cell3
676 // , cell4  , cell5  , cell6
677 class YTable extends Element
678 {
679         var $col;
680
681         function YTable($_value)
682         {
683                 parent::Element();
684
685                 $align = $value = $matches = array();
686                 foreach($_value as $val) {
687                         if (preg_match('/^(\s+)?(.+?)(\s+)?$/', $val, $matches)) {
688                                 $align[] =($matches[1] != '') ?
689                                         ((isset($matches[3]) && $matches[3] != '') ?
690                                                 ' style="text-align:center"' :
691                                                 ' style="text-align:right"'
692                                         ) : '';
693                                 $value[] = $matches[2];
694                         } else {
695                                 $align[] = '';
696                                 $value[] = $val;
697                         }
698                 }
699                 $this->col = count($value);
700                 $colspan = array();
701                 foreach ($value as $val)
702                         $colspan[] = ($val == '==') ? 0 : 1;
703                 $str = '';
704                 $count = count($value);
705                 for ($i = 0; $i < $count; $i++) {
706                         if ($colspan[$i]) {
707                                 while ($i + $colspan[$i] < $count && $value[$i + $colspan[$i]] == '==')
708                                         $colspan[$i]++;
709                                 $colspan[$i] = ($colspan[$i] > 1) ? ' colspan="' . $colspan[$i] . '"' : '';
710                                 $str .= '<td class="style_td"' . $align[$i] . $colspan[$i] . '>' . make_link($value[$i]) . '</td>';
711                         }
712                 }
713                 $this->elements[] = $str;
714         }
715
716         function canContain($obj)
717         {
718                 return is_a($obj, 'YTable') && ($obj->col == $this->col);
719         }
720
721         function insert($obj)
722         {
723                 $this->elements[] = $obj->elements[0];
724                 return $this;
725         }
726
727         function toString()
728         {
729                 $rows = '';
730                 foreach ($this->elements as $str)
731                         $rows .= "\n" . '<tr class="style_tr">' . $str . '</tr>' . "\n";
732                 $rows = $this->wrap($rows, 'table', ' class="style_table" cellspacing="1" border="0"');
733                 return $this->wrap($rows, 'div', ' class="ie5"');
734         }
735 }
736
737 // ' 'Space-beginning sentence
738 // ' 'Space-beginning sentence
739 // ' 'Space-beginning sentence
740 class Pre extends Element
741 {
742         function Pre($root, $text)
743         {
744                 global $preformat_ltrim;
745                 parent::Element();
746                 $this->elements[] = htmlspecialchars(
747                         (! $preformat_ltrim || $text == '' || $text{0} != ' ') ? $text : substr($text, 1));
748         }
749
750         function canContain($obj)
751         {
752                 return is_a($obj, 'Pre');
753         }
754
755         function insert($obj)
756         {
757                 $this->elements[] = $obj->elements[0];
758                 return $this;
759         }
760
761         function toString()
762         {
763                 //return $this->wrap(join("\n", $this->elements), 'pre');
764                 return $this->wrap(join("\n", $this->elements), 'pre', ' class="wiki_pre"');            // modified for magic3
765         }
766 }
767
768 // Block plugin: #something (started with '#')
769 class Div extends Element
770 {
771         var $name;
772         var $param;
773
774         function Div($out)
775         {
776                 parent::Element();
777                 list(, $this->name, $this->param) = array_pad($out, 3, '');
778         }
779
780         function canContain($obj)
781         {
782                 return FALSE;
783         }
784
785         function toString()
786         {
787                 // Call #plugin
788                 return do_plugin_convert($this->name, $this->param);
789         }
790 }
791
792 // LEFT:/CENTER:/RIGHT:
793 class Align extends Element
794 {
795         var $align;
796
797         function Align($align)
798         {
799                 parent::Element();
800                 $this->align = $align;
801         }
802
803         function canContain($obj)
804         {
805                 return is_a($obj, 'Inline');
806         }
807
808         function toString()
809         {
810                 return $this->wrap(parent::toString(), 'div', ' style="text-align:' . $this->align . '"');
811         }
812 }
813
814 // Body
815 class Body extends Element
816 {
817         var $id;
818         var $count = 0;
819         var $contents;
820         var $contents_last;
821         var $classes = array(
822                 '-' => 'UList',
823                 '+' => 'OList',
824                 '>' => 'BQuote',
825                 '<' => 'BQuote');
826         var $factories = array(
827                 ':' => 'DList',
828                 '|' => 'Table',
829                 ',' => 'YTable',
830                 '#' => 'Div');
831
832         function Body($id)
833         {
834                 $this->id            = $id;
835                 $this->contents      = new Element();
836                 $this->contents_last = $this->contents;
837                 parent::Element();
838         }
839
840         function parse($lines)
841         {
842                 $this->last = $this;
843                 $matches = array();
844
845                 while (! empty($lines)) {
846                         $line = array_shift($lines);
847
848                         // Escape comments
849                         if (substr($line, 0, 2) == '//') continue;
850
851                         if (preg_match('/^(LEFT|CENTER|RIGHT):(.*)$/', $line, $matches)) {
852                                 // <div style="text-align:...">
853                                 $this->last = $this->last->add(new Align(strtolower($matches[1])));
854                                 if ($matches[2] == '') continue;
855                                 $line = $matches[2];
856                         }
857
858                         $line = rtrim($line, "\r\n");
859
860                         // Empty
861                         if ($line == '') {
862                                 $this->last = $this;
863                                 continue;
864                         }
865
866                         // Horizontal Rule
867                         if (substr($line, 0, 4) == '----') {
868                                 $this->insert(new HRule($this, $line));
869                                 continue;
870                         }
871
872                         // Multiline-enabled block plugin
873                         if (! PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK &&
874                             preg_match('/^#[^{]+(\{\{+)\s*$/', $line, $matches)) {
875                                 $len = strlen($matches[1]);
876                                 $line .= "\r"; // Delimiter
877                                 while (! empty($lines)) {
878                                         $next_line = preg_replace("/[\r\n]*$/", '', array_shift($lines));
879                                         if (preg_match('/\}{' . $len . '}/', $next_line)) {
880                                                 $line .= $next_line;
881                                                 break;
882                                         } else {
883                                                 $line .= $next_line .= "\r"; // Delimiter
884                                         }
885                                 }
886                         }
887
888                         // The first character
889                         $head = $line{0};
890
891                         // Heading
892                         if ($head == '*') {
893                                 $this->insert(new Heading($this, $line));
894                                 continue;
895                         }
896
897                         // Pre
898                         if ($head == ' ' || $head == "\t") {
899                                 $this->last = $this->last->add(new Pre($this, $line));
900                                 continue;
901                         }
902
903                         // Line Break
904                         if (substr($line, -1) == '~')
905                                 $line = substr($line, 0, -1) . "\r";
906                         
907                         // Other Character
908                         if (isset($this->classes[$head])) {
909                                 $classname  = $this->classes[$head];
910                                 $this->last = $this->last->add(new $classname($this, $line));
911                                 continue;
912                         }
913
914                         // Other Character
915                         if (isset($this->factories[$head])) {
916                                 $factoryname = 'Factory_' . $this->factories[$head];
917                                 $this->last  = $this->last->add($factoryname($this, $line));
918                                 continue;
919                         }
920
921                         // Default
922                         $this->last = $this->last->add(Factory_Inline($line));
923                 }
924         }
925
926         function getAnchor($text, $level)
927         {
928                 global $top, $_symbol_anchor;
929
930                 // Heading id (auto-generated)
931                 $autoid = 'content_' . $this->id . '_' . $this->count;
932                 $this->count++;
933
934                 // Heading id (specified by users)
935                 $id = make_heading($text, FALSE); // Cut fixed-anchor from $text
936                 if ($id == '') {
937                         // Not specified
938                         $id     = $autoid;
939                         $anchor = '';
940                 } else {
941                         $anchor = ' &aname(' . $id . ',super,full){' . $_symbol_anchor . '};';
942                 }
943
944                 $text = ' ' . $text;
945
946                 // Add 'page contents' link to its heading
947                 $this->contents_last = $this->contents_last->add(new Contents_UList($text, $level, $id));
948
949                 // Add heding
950                 return array($text . $anchor, $this->count > 1 ? "\n" . $top : '', $autoid);
951         }
952
953         function insert($obj)
954         {
955                 if (is_a($obj, 'Inline')) $obj = $obj->toPara();
956                 return parent::insert($obj);
957         }
958
959         function toString()
960         {
961                 global $vars;
962
963                 $text = parent::toString();
964
965                 // #contents
966                 $text = preg_replace_callback('/<#_contents_>/',
967                         array($this, 'replace_contents'), $text);
968
969                 return $text . "\n";
970         }
971
972         function replace_contents($arr)
973         {
974                 //$contents  = '<div class="contents">' . "\n" . '<a id="contents_' . $this->id . '"></a>' . "\n" . $this->contents->toString() . "\n" . '</div>' . "\n";
975                 $contents  = '<div class="wiki_content">' . "\n" . '<a id="wiki_content_' . $this->id . '"></a>' . "\n" . $this->contents->toString() . "\n" . '</div>' . "\n";
976                 return $contents;
977         }
978 }
979
980 class Contents_UList extends ListContainer
981 {
982         function Contents_UList($text, $level, $id)
983         {
984                 // Reformatting $text
985                 // A line started with "\n" means "preformatted" ... X(
986                 make_heading($text);
987                 $text = "\n" . '<a href="#' . $id . '">' . $text . '</a>' . "\n";
988                 parent::ListContainer('ul', 'li', '-', str_repeat('-', $level));
989                 $this->insert(Factory_Inline($text));
990         }
991
992         function setParent($parent)
993         {
994                 global $_list_pad_str;
995
996                 parent::setParent($parent);
997                 $step   = $this->level;
998                 $margin = $this->left_margin;
999                 if (isset($parent->parent) && is_a($parent->parent, 'ListContainer')) {
1000                         $step  -= $parent->parent->level;
1001                         $margin = 0;
1002                 }
1003                 $margin += $this->margin * ($step == $this->level ? 1 : $step);
1004                 $this->style = sprintf($_list_pad_str, $this->level, $margin, $margin);
1005         }
1006 }
1007 ?>