PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/convert_html.php

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