Advertisement
Guest User

Untitled

a guest
Jun 26th, 2017
69
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.45 KB | None | 0 0
  1. <?php
  2.  
  3. class Parsedown
  4. {
  5.  
  6. const version = '1.5.1';
  7.  
  8.  
  9. function text($text)
  10. {
  11. $this->DefinitionData = array();
  12.  
  13. $text = str_replace(array("\r\n", "\r"), "\n", $text);
  14.  
  15. $text = trim($text, "\n");
  16.  
  17. $lines = explode("\n", $text);
  18.  
  19. $markup = $this->lines($lines);
  20.  
  21. $markup = trim($markup, "\n");
  22.  
  23. return $markup;
  24. }
  25.  
  26. function setBreaksEnabled($breaksEnabled)
  27. {
  28. $this->breaksEnabled = $breaksEnabled;
  29.  
  30. return $this;
  31. }
  32.  
  33. protected $breaksEnabled;
  34.  
  35. function setMarkupEscaped($markupEscaped)
  36. {
  37. $this->markupEscaped = $markupEscaped;
  38.  
  39. return $this;
  40. }
  41.  
  42. protected $markupEscaped;
  43.  
  44. function setUrlsLinked($urlsLinked)
  45. {
  46. $this->urlsLinked = $urlsLinked;
  47.  
  48. return $this;
  49. }
  50.  
  51. protected $urlsLinked = true;
  52.  
  53.  
  54. protected $BlockTypes = array(
  55. '#' => array('Header'),
  56. '*' => array('Rule', 'List'),
  57. '+' => array('List'),
  58. '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
  59. '0' => array('List'),
  60. '1' => array('List'),
  61. '2' => array('List'),
  62. '3' => array('List'),
  63. '4' => array('List'),
  64. '5' => array('List'),
  65. '6' => array('List'),
  66. '7' => array('List'),
  67. '8' => array('List'),
  68. '9' => array('List'),
  69. ':' => array('Table'),
  70. '<' => array('Comment', 'Markup'),
  71. '=' => array('SetextHeader'),
  72. '>' => array('Quote'),
  73. '[' => array('Reference'),
  74. '_' => array('Rule'),
  75. '`' => array('FencedCode'),
  76. '|' => array('Table'),
  77. '~' => array('FencedCode'),
  78. );
  79.  
  80.  
  81. protected $DefinitionTypes = array(
  82. '[' => array('Reference'),
  83. );
  84.  
  85.  
  86. protected $unmarkedBlockTypes = array(
  87. 'Code',
  88. );
  89.  
  90. private function lines(array $lines)
  91. {
  92. $CurrentBlock = null;
  93.  
  94. foreach ($lines as $line)
  95. {
  96. if (chop($line) === '')
  97. {
  98. if (isset($CurrentBlock))
  99. {
  100. $CurrentBlock['interrupted'] = true;
  101. }
  102.  
  103. continue;
  104. }
  105.  
  106. if (strpos($line, "\t") !== false)
  107. {
  108. $parts = explode("\t", $line);
  109.  
  110. $line = $parts[0];
  111.  
  112. unset($parts[0]);
  113.  
  114. foreach ($parts as $part)
  115. {
  116. $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
  117.  
  118. $line .= str_repeat(' ', $shortage);
  119. $line .= $part;
  120. }
  121. }
  122.  
  123. $indent = 0;
  124.  
  125. while (isset($line[$indent]) and $line[$indent] === ' ')
  126. {
  127. $indent ++;
  128. }
  129.  
  130. $text = $indent > 0 ? substr($line, $indent) : $line;
  131.  
  132.  
  133. $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
  134.  
  135.  
  136. if (isset($CurrentBlock['incomplete']))
  137. {
  138. $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
  139.  
  140. if (isset($Block))
  141. {
  142. $CurrentBlock = $Block;
  143.  
  144. continue;
  145. }
  146. else
  147. {
  148. if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
  149. {
  150. $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
  151. }
  152.  
  153. unset($CurrentBlock['incomplete']);
  154. }
  155. }
  156.  
  157.  
  158. $marker = $text[0];
  159.  
  160.  
  161. $blockTypes = $this->unmarkedBlockTypes;
  162.  
  163. if (isset($this->BlockTypes[$marker]))
  164. {
  165. foreach ($this->BlockTypes[$marker] as $blockType)
  166. {
  167. $blockTypes []= $blockType;
  168. }
  169. }
  170.  
  171.  
  172. foreach ($blockTypes as $blockType)
  173. {
  174. $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
  175.  
  176. if (isset($Block))
  177. {
  178. $Block['type'] = $blockType;
  179.  
  180. if ( ! isset($Block['identified']))
  181. {
  182. $Blocks []= $CurrentBlock;
  183.  
  184. $Block['identified'] = true;
  185. }
  186.  
  187. if (method_exists($this, 'block'.$blockType.'Continue'))
  188. {
  189. $Block['incomplete'] = true;
  190. }
  191.  
  192. $CurrentBlock = $Block;
  193.  
  194. continue 2;
  195. }
  196. }
  197.  
  198.  
  199. if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
  200. {
  201. $CurrentBlock['element']['text'] .= "\n".$text;
  202. }
  203. else
  204. {
  205. $Blocks []= $CurrentBlock;
  206.  
  207. $CurrentBlock = $this->paragraph($Line);
  208.  
  209. $CurrentBlock['identified'] = true;
  210. }
  211. }
  212.  
  213.  
  214. if (isset($CurrentBlock['incomplete']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
  215. {
  216. $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
  217. }
  218.  
  219.  
  220. $Blocks []= $CurrentBlock;
  221.  
  222. unset($Blocks[0]);
  223.  
  224.  
  225. $markup = '';
  226.  
  227. foreach ($Blocks as $Block)
  228. {
  229. if (isset($Block['hidden']))
  230. {
  231. continue;
  232. }
  233.  
  234. $markup .= "\n";
  235. $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
  236. }
  237.  
  238. $markup .= "\n";
  239.  
  240.  
  241. return $markup;
  242. }
  243.  
  244. protected function blockCode($Line, $Block = null)
  245. {
  246. if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
  247. {
  248. return;
  249. }
  250.  
  251. if ($Line['indent'] >= 4)
  252. {
  253. $text = substr($Line['body'], 4);
  254.  
  255. $Block = array(
  256. 'element' => array(
  257. 'name' => 'pre',
  258. 'handler' => 'element',
  259. 'text' => array(
  260. 'name' => 'code',
  261. 'text' => $text,
  262. ),
  263. ),
  264. );
  265.  
  266. return $Block;
  267. }
  268. }
  269.  
  270. protected function blockCodeContinue($Line, $Block)
  271. {
  272. if ($Line['indent'] >= 4)
  273. {
  274. if (isset($Block['interrupted']))
  275. {
  276. $Block['element']['text']['text'] .= "\n";
  277.  
  278. unset($Block['interrupted']);
  279. }
  280.  
  281. $Block['element']['text']['text'] .= "\n";
  282.  
  283. $text = substr($Line['body'], 4);
  284.  
  285. $Block['element']['text']['text'] .= $text;
  286.  
  287. return $Block;
  288. }
  289. }
  290.  
  291. protected function blockCodeComplete($Block)
  292. {
  293. $text = $Block['element']['text']['text'];
  294.  
  295. $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
  296.  
  297. $Block['element']['text']['text'] = $text;
  298.  
  299. return $Block;
  300. }
  301.  
  302.  
  303. protected function blockComment($Line)
  304. {
  305. if ($this->markupEscaped)
  306. {
  307. return;
  308. }
  309.  
  310. if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
  311. {
  312. $Block = array(
  313. 'markup' => $Line['body'],
  314. );
  315.  
  316. if (preg_match('/-->$/', $Line['text']))
  317. {
  318. $Block['closed'] = true;
  319. }
  320.  
  321. return $Block;
  322. }
  323. }
  324.  
  325. protected function blockCommentContinue($Line, array $Block)
  326. {
  327. if (isset($Block['closed']))
  328. {
  329. return;
  330. }
  331.  
  332. $Block['markup'] .= "\n" . $Line['body'];
  333.  
  334. if (preg_match('/-->$/', $Line['text']))
  335. {
  336. $Block['closed'] = true;
  337. }
  338.  
  339. return $Block;
  340. }
  341.  
  342. protected function blockFencedCode($Line)
  343. {
  344. if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
  345. {
  346. $Element = array(
  347. 'name' => 'code',
  348. 'text' => '',
  349. );
  350.  
  351. if (isset($matches[2]))
  352. {
  353. $class = 'language-'.$matches[2];
  354.  
  355. $Element['attributes'] = array(
  356. 'class' => $class,
  357. );
  358. }
  359.  
  360. $Block = array(
  361. 'char' => $Line['text'][0],
  362. 'element' => array(
  363. 'name' => 'pre',
  364. 'handler' => 'element',
  365. 'text' => $Element,
  366. ),
  367. );
  368.  
  369. return $Block;
  370. }
  371. }
  372.  
  373. protected function blockFencedCodeContinue($Line, $Block)
  374. {
  375. if (isset($Block['complete']))
  376. {
  377. return;
  378. }
  379.  
  380. if (isset($Block['interrupted']))
  381. {
  382. $Block['element']['text']['text'] .= "\n";
  383.  
  384. unset($Block['interrupted']);
  385. }
  386.  
  387. if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
  388. {
  389. $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
  390.  
  391. $Block['complete'] = true;
  392.  
  393. return $Block;
  394. }
  395.  
  396. $Block['element']['text']['text'] .= "\n".$Line['body'];;
  397.  
  398. return $Block;
  399. }
  400.  
  401. protected function blockFencedCodeComplete($Block)
  402. {
  403. $text = $Block['element']['text']['text'];
  404.  
  405. $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
  406.  
  407. $Block['element']['text']['text'] = $text;
  408.  
  409. return $Block;
  410. }
  411.  
  412. protected function blockHeader($Line)
  413. {
  414. if (isset($Line['text'][1]))
  415. {
  416. $level = 1;
  417.  
  418. while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
  419. {
  420. $level ++;
  421. }
  422.  
  423. if ($level > 6)
  424. {
  425. return;
  426. }
  427.  
  428. $text = trim($Line['text'], '# ');
  429.  
  430. $Block = array(
  431. 'element' => array(
  432. 'name' => 'h' . min(6, $level),
  433. 'text' => $text,
  434. 'handler' => 'line',
  435. ),
  436. );
  437.  
  438. return $Block;
  439. }
  440. }
  441.  
  442. protected function blockList($Line)
  443. {
  444. list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
  445.  
  446. if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
  447. {
  448. $Block = array(
  449. 'indent' => $Line['indent'],
  450. 'pattern' => $pattern,
  451. 'element' => array(
  452. 'name' => $name,
  453. 'handler' => 'elements',
  454. ),
  455. );
  456.  
  457. $Block['li'] = array(
  458. 'name' => 'li',
  459. 'handler' => 'li',
  460. 'text' => array(
  461. $matches[2],
  462. ),
  463. );
  464.  
  465. $Block['element']['text'] []= & $Block['li'];
  466.  
  467. return $Block;
  468. }
  469. }
  470.  
  471. protected function blockListContinue($Line, array $Block)
  472. {
  473. if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
  474. {
  475. if (isset($Block['interrupted']))
  476. {
  477. $Block['li']['text'] []= '';
  478.  
  479. unset($Block['interrupted']);
  480. }
  481.  
  482. unset($Block['li']);
  483.  
  484. $text = isset($matches[1]) ? $matches[1] : '';
  485.  
  486. $Block['li'] = array(
  487. 'name' => 'li',
  488. 'handler' => 'li',
  489. 'text' => array(
  490. $text,
  491. ),
  492. );
  493.  
  494. $Block['element']['text'] []= & $Block['li'];
  495.  
  496. return $Block;
  497. }
  498.  
  499. if ($Line['text'][0] === '[' and $this->blockReference($Line))
  500. {
  501. return $Block;
  502. }
  503.  
  504. if ( ! isset($Block['interrupted']))
  505. {
  506. $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
  507.  
  508. $Block['li']['text'] []= $text;
  509.  
  510. return $Block;
  511. }
  512.  
  513. if ($Line['indent'] > 0)
  514. {
  515. $Block['li']['text'] []= '';
  516.  
  517. $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
  518.  
  519. $Block['li']['text'] []= $text;
  520.  
  521. unset($Block['interrupted']);
  522.  
  523. return $Block;
  524. }
  525. }
  526.  
  527. protected function blockQuote($Line)
  528. {
  529. if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
  530. {
  531. $Block = array(
  532. 'element' => array(
  533. 'name' => 'blockquote',
  534. 'handler' => 'lines',
  535. 'text' => (array) $matches[1],
  536. ),
  537. );
  538.  
  539. return $Block;
  540. }
  541. }
  542.  
  543. protected function blockQuoteContinue($Line, array $Block)
  544. {
  545. if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
  546. {
  547. if (isset($Block['interrupted']))
  548. {
  549. $Block['element']['text'] []= '';
  550.  
  551. unset($Block['interrupted']);
  552. }
  553.  
  554. $Block['element']['text'] []= $matches[1];
  555.  
  556. return $Block;
  557. }
  558.  
  559. if ( ! isset($Block['interrupted']))
  560. {
  561. $Block['element']['text'] []= $Line['text'];
  562.  
  563. return $Block;
  564. }
  565. }
  566.  
  567. protected function blockRule($Line)
  568. {
  569. if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
  570. {
  571. $Block = array(
  572. 'element' => array(
  573. 'name' => 'hr'
  574. ),
  575. );
  576.  
  577. return $Block;
  578. }
  579. }
  580.  
  581. protected function blockSetextHeader($Line, array $Block = null)
  582. {
  583. if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
  584. {
  585. return;
  586. }
  587.  
  588. if (chop($Line['text'], $Line['text'][0]) === '')
  589. {
  590. $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
  591.  
  592. return $Block;
  593. }
  594. }
  595.  
  596. protected function blockMarkup($Line)
  597. {
  598. if ($this->markupEscaped)
  599. {
  600. return;
  601. }
  602.  
  603. if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
  604. {
  605. if (in_array($matches[1], $this->textLevelElements))
  606. {
  607. return;
  608. }
  609.  
  610. $Block = array(
  611. 'name' => $matches[1],
  612. 'depth' => 0,
  613. 'markup' => $Line['text'],
  614. );
  615.  
  616. $length = strlen($matches[0]);
  617.  
  618. $remainder = substr($Line['text'], $length);
  619.  
  620. if (trim($remainder) === '')
  621. {
  622. if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
  623. {
  624. $Block['closed'] = true;
  625.  
  626. $Block['void'] = true;
  627. }
  628. }
  629. else
  630. {
  631. if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
  632. {
  633. return;
  634. }
  635.  
  636. if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
  637. {
  638. $Block['closed'] = true;
  639. }
  640. }
  641.  
  642. return $Block;
  643. }
  644. }
  645.  
  646. protected function blockMarkupContinue($Line, array $Block)
  647. {
  648. if (isset($Block['closed']))
  649. {
  650. return;
  651. }
  652.  
  653. if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text']))
  654. {
  655. $Block['depth'] ++;
  656. }
  657.  
  658. if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches))
  659. {
  660. if ($Block['depth'] > 0)
  661. {
  662. $Block['depth'] --;
  663. }
  664. else
  665. {
  666. $Block['closed'] = true;
  667. }
  668. }
  669.  
  670. if (isset($Block['interrupted']))
  671. {
  672. $Block['markup'] .= "\n";
  673.  
  674. unset($Block['interrupted']);
  675. }
  676.  
  677. $Block['markup'] .= "\n".$Line['body'];
  678.  
  679. return $Block;
  680. }
  681.  
  682. protected function blockReference($Line)
  683. {
  684. if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
  685. {
  686. $id = strtolower($matches[1]);
  687.  
  688. $Data = array(
  689. 'url' => $matches[2],
  690. 'title' => null,
  691. );
  692.  
  693. if (isset($matches[3]))
  694. {
  695. $Data['title'] = $matches[3];
  696. }
  697.  
  698. $this->DefinitionData['Reference'][$id] = $Data;
  699.  
  700. $Block = array(
  701. 'hidden' => true,
  702. );
  703.  
  704. return $Block;
  705. }
  706. }
  707.  
  708. protected function blockTable($Line, array $Block = null)
  709. {
  710. if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
  711. {
  712. return;
  713. }
  714.  
  715. if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
  716. {
  717. $alignments = array();
  718.  
  719. $divider = $Line['text'];
  720.  
  721. $divider = trim($divider);
  722. $divider = trim($divider, '|');
  723.  
  724. $dividerCells = explode('|', $divider);
  725.  
  726. foreach ($dividerCells as $dividerCell)
  727. {
  728. $dividerCell = trim($dividerCell);
  729.  
  730. if ($dividerCell === '')
  731. {
  732. continue;
  733. }
  734.  
  735. $alignment = null;
  736.  
  737. if ($dividerCell[0] === ':')
  738. {
  739. $alignment = 'left';
  740. }
  741.  
  742. if (substr($dividerCell, - 1) === ':')
  743. {
  744. $alignment = $alignment === 'left' ? 'center' : 'right';
  745. }
  746.  
  747. $alignments []= $alignment;
  748. }
  749.  
  750. $HeaderElements = array();
  751.  
  752. $header = $Block['element']['text'];
  753.  
  754. $header = trim($header);
  755. $header = trim($header, '|');
  756.  
  757. $headerCells = explode('|', $header);
  758.  
  759. foreach ($headerCells as $index => $headerCell)
  760. {
  761. $headerCell = trim($headerCell);
  762.  
  763. $HeaderElement = array(
  764. 'name' => 'th',
  765. 'text' => $headerCell,
  766. 'handler' => 'line',
  767. );
  768.  
  769. if (isset($alignments[$index]))
  770. {
  771. $alignment = $alignments[$index];
  772.  
  773. $HeaderElement['attributes'] = array(
  774. 'style' => 'text-align: '.$alignment.';',
  775. );
  776. }
  777.  
  778. $HeaderElements []= $HeaderElement;
  779. }
  780.  
  781. $Block = array(
  782. 'alignments' => $alignments,
  783. 'identified' => true,
  784. 'element' => array(
  785. 'name' => 'table',
  786. 'handler' => 'elements',
  787. ),
  788. );
  789.  
  790. $Block['element']['text'] []= array(
  791. 'name' => 'thead',
  792. 'handler' => 'elements',
  793. );
  794.  
  795. $Block['element']['text'] []= array(
  796. 'name' => 'tbody',
  797. 'handler' => 'elements',
  798. 'text' => array(),
  799. );
  800.  
  801. $Block['element']['text'][0]['text'] []= array(
  802. 'name' => 'tr',
  803. 'handler' => 'elements',
  804. 'text' => $HeaderElements,
  805. );
  806.  
  807. return $Block;
  808. }
  809. }
  810.  
  811. protected function blockTableContinue($Line, array $Block)
  812. {
  813. if (isset($Block['interrupted']))
  814. {
  815. return;
  816. }
  817.  
  818. if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
  819. {
  820. $Elements = array();
  821.  
  822. $row = $Line['text'];
  823.  
  824. $row = trim($row);
  825. $row = trim($row, '|');
  826.  
  827. preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
  828.  
  829. foreach ($matches[0] as $index => $cell)
  830. {
  831. $cell = trim($cell);
  832.  
  833. $Element = array(
  834. 'name' => 'td',
  835. 'handler' => 'line',
  836. 'text' => $cell,
  837. );
  838.  
  839. if (isset($Block['alignments'][$index]))
  840. {
  841. $Element['attributes'] = array(
  842. 'style' => 'text-align: '.$Block['alignments'][$index].';',
  843. );
  844. }
  845.  
  846. $Elements []= $Element;
  847. }
  848.  
  849. $Element = array(
  850. 'name' => 'tr',
  851. 'handler' => 'elements',
  852. 'text' => $Elements,
  853. );
  854.  
  855. $Block['element']['text'][1]['text'] []= $Element;
  856.  
  857. return $Block;
  858. }
  859. }
  860.  
  861. protected function paragraph($Line)
  862. {
  863. $Block = array(
  864. 'element' => array(
  865. 'name' => 'p',
  866. 'text' => $Line['text'],
  867. 'handler' => 'line',
  868. ),
  869. );
  870.  
  871. return $Block;
  872. }
  873.  
  874. protected $InlineTypes = array(
  875. '"' => array('SpecialCharacter'),
  876. '!' => array('Image'),
  877. '&' => array('SpecialCharacter'),
  878. '*' => array('Emphasis'),
  879. ':' => array('Url'),
  880. '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
  881. '>' => array('SpecialCharacter'),
  882. '[' => array('Link'),
  883. '_' => array('Emphasis'),
  884. '`' => array('Code'),
  885. '~' => array('Strikethrough'),
  886. '\\' => array('EscapeSequence'),
  887. );
  888.  
  889. protected $inlineMarkerList = '!"*_&[:<>`~\\';
  890.  
  891. public function line($text)
  892. {
  893. $markup = '';
  894.  
  895. $unexaminedText = $text;
  896.  
  897. $markerPosition = 0;
  898.  
  899. while ($excerpt = strpbrk($unexaminedText, $this->inlineMarkerList))
  900. {
  901. $marker = $excerpt[0];
  902.  
  903. $markerPosition += strpos($unexaminedText, $marker);
  904.  
  905. $Excerpt = array('text' => $excerpt, 'context' => $text);
  906.  
  907. foreach ($this->InlineTypes[$marker] as $inlineType)
  908. {
  909. $Inline = $this->{'inline'.$inlineType}($Excerpt);
  910.  
  911. if ( ! isset($Inline))
  912. {
  913. continue;
  914. }
  915.  
  916. if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
  917. {
  918. continue;
  919. }
  920.  
  921. if ( ! isset($Inline['position']))
  922. {
  923. $Inline['position'] = $markerPosition;
  924. }
  925.  
  926. $unmarkedText = substr($text, 0, $Inline['position']);
  927.  
  928. $markup .= $this->unmarkedText($unmarkedText);
  929.  
  930. $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
  931.  
  932. $text = substr($text, $Inline['position'] + $Inline['extent']);
  933.  
  934. $unexaminedText = $text;
  935.  
  936. $markerPosition = 0;
  937.  
  938. continue 2;
  939. }
  940.  
  941. $unexaminedText = substr($excerpt, 1);
  942.  
  943. $markerPosition ++;
  944. }
  945.  
  946. $markup .= $this->unmarkedText($text);
  947.  
  948. return $markup;
  949. }
  950.  
  951. protected function inlineCode($Excerpt)
  952. {
  953. $marker = $Excerpt['text'][0];
  954.  
  955. if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
  956. {
  957. $text = $matches[2];
  958. $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
  959. $text = preg_replace("/[ ]*\n/", ' ', $text);
  960.  
  961. return array(
  962. 'extent' => strlen($matches[0]),
  963. 'element' => array(
  964. 'name' => 'code',
  965. 'text' => $text,
  966. ),
  967. );
  968. }
  969. }
  970.  
  971. protected function inlineEmailTag($Excerpt)
  972. {
  973. if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
  974. {
  975. $url = $matches[1];
  976.  
  977. if ( ! isset($matches[2]))
  978. {
  979. $url = 'mailto:' . $url;
  980. }
  981.  
  982. return array(
  983. 'extent' => strlen($matches[0]),
  984. 'element' => array(
  985. 'name' => 'a',
  986. 'text' => $matches[1],
  987. 'attributes' => array(
  988. 'href' => $url,
  989. ),
  990. ),
  991. );
  992. }
  993. }
  994.  
  995. protected function inlineEmphasis($Excerpt)
  996. {
  997. if ( ! isset($Excerpt['text'][1]))
  998. {
  999. return;
  1000. }
  1001.  
  1002. $marker = $Excerpt['text'][0];
  1003.  
  1004. if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
  1005. {
  1006. $emphasis = 'strong';
  1007. }
  1008. elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
  1009. {
  1010. $emphasis = 'em';
  1011. }
  1012. else
  1013. {
  1014. return;
  1015. }
  1016.  
  1017. return array(
  1018. 'extent' => strlen($matches[0]),
  1019. 'element' => array(
  1020. 'name' => $emphasis,
  1021. 'handler' => 'line',
  1022. 'text' => $matches[1],
  1023. ),
  1024. );
  1025. }
  1026.  
  1027. protected function inlineEscapeSequence($Excerpt)
  1028. {
  1029. if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
  1030. {
  1031. return array(
  1032. 'markup' => $Excerpt['text'][1],
  1033. 'extent' => 2,
  1034. );
  1035. }
  1036. }
  1037.  
  1038. protected function inlineImage($Excerpt)
  1039. {
  1040. if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
  1041. {
  1042. return;
  1043. }
  1044.  
  1045. $Excerpt['text']= substr($Excerpt['text'], 1);
  1046.  
  1047. $Link = $this->inlineLink($Excerpt);
  1048.  
  1049. if ($Link === null)
  1050. {
  1051. return;
  1052. }
  1053.  
  1054. $Inline = array(
  1055. 'extent' => $Link['extent'] + 1,
  1056. 'element' => array(
  1057. 'name' => 'img',
  1058. 'attributes' => array(
  1059. 'src' => $Link['element']['attributes']['href'],
  1060. 'alt' => $Link['element']['text'],
  1061. ),
  1062. ),
  1063. );
  1064.  
  1065. $Inline['element']['attributes'] += $Link['element']['attributes'];
  1066.  
  1067. unset($Inline['element']['attributes']['href']);
  1068.  
  1069. return $Inline;
  1070. }
  1071.  
  1072. protected function inlineLink($Excerpt)
  1073. {
  1074. $Element = array(
  1075. 'name' => 'a',
  1076. 'handler' => 'line',
  1077. 'text' => null,
  1078. 'attributes' => array(
  1079. 'href' => null,
  1080. 'title' => null,
  1081. ),
  1082. );
  1083.  
  1084. $extent = 0;
  1085.  
  1086. $remainder = $Excerpt['text'];
  1087.  
  1088. if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
  1089. {
  1090. $Element['text'] = $matches[1];
  1091.  
  1092. $extent += strlen($matches[0]);
  1093.  
  1094. $remainder = substr($remainder, $extent);
  1095. }
  1096. else
  1097. {
  1098. return;
  1099. }
  1100.  
  1101. if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches))
  1102. {
  1103. $Element['attributes']['href'] = $matches[1];
  1104.  
  1105. if (isset($matches[2]))
  1106. {
  1107. $Element['attributes']['title'] = substr($matches[2], 1, - 1);
  1108. }
  1109.  
  1110. $extent += strlen($matches[0]);
  1111. }
  1112. else
  1113. {
  1114. if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
  1115. {
  1116. $definition = $matches[1] ? $matches[1] : $Element['text'];
  1117. $definition = strtolower($definition);
  1118.  
  1119. $extent += strlen($matches[0]);
  1120. }
  1121. else
  1122. {
  1123. $definition = strtolower($Element['text']);
  1124. }
  1125.  
  1126. if ( ! isset($this->DefinitionData['Reference'][$definition]))
  1127. {
  1128. return;
  1129. }
  1130.  
  1131. $Definition = $this->DefinitionData['Reference'][$definition];
  1132.  
  1133. $Element['attributes']['href'] = $Definition['url'];
  1134. $Element['attributes']['title'] = $Definition['title'];
  1135. }
  1136.  
  1137. $Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
  1138.  
  1139. return array(
  1140. 'extent' => $extent,
  1141. 'element' => $Element,
  1142. );
  1143. }
  1144.  
  1145. protected function inlineMarkup($Excerpt)
  1146. {
  1147. if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
  1148. {
  1149. return;
  1150. }
  1151.  
  1152. if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
  1153. {
  1154. return array(
  1155. 'markup' => $matches[0],
  1156. 'extent' => strlen($matches[0]),
  1157. );
  1158. }
  1159.  
  1160. if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
  1161. {
  1162. return array(
  1163. 'markup' => $matches[0],
  1164. 'extent' => strlen($matches[0]),
  1165. );
  1166. }
  1167.  
  1168. if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
  1169. {
  1170. return array(
  1171. 'markup' => $matches[0],
  1172. 'extent' => strlen($matches[0]),
  1173. );
  1174. }
  1175. }
  1176.  
  1177. protected function inlineSpecialCharacter($Excerpt)
  1178. {
  1179. if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
  1180. {
  1181. return array(
  1182. 'markup' => '&amp;',
  1183. 'extent' => 1,
  1184. );
  1185. }
  1186.  
  1187. $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
  1188.  
  1189. if (isset($SpecialCharacter[$Excerpt['text'][0]]))
  1190. {
  1191. return array(
  1192. 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
  1193. 'extent' => 1,
  1194. );
  1195. }
  1196. }
  1197.  
  1198. protected function inlineStrikethrough($Excerpt)
  1199. {
  1200. if ( ! isset($Excerpt['text'][1]))
  1201. {
  1202. return;
  1203. }
  1204.  
  1205. if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
  1206. {
  1207. return array(
  1208. 'extent' => strlen($matches[0]),
  1209. 'element' => array(
  1210. 'name' => 'del',
  1211. 'text' => $matches[1],
  1212. 'handler' => 'line',
  1213. ),
  1214. );
  1215. }
  1216. }
  1217.  
  1218. protected function inlineUrl($Excerpt)
  1219. {
  1220. if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
  1221. {
  1222. return;
  1223. }
  1224.  
  1225. if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
  1226. {
  1227. $Inline = array(
  1228. 'extent' => strlen($matches[0][0]),
  1229. 'position' => $matches[0][1],
  1230. 'element' => array(
  1231. 'name' => 'a',
  1232. 'text' => $matches[0][0],
  1233. 'attributes' => array(
  1234. 'href' => $matches[0][0],
  1235. ),
  1236. ),
  1237. );
  1238.  
  1239. return $Inline;
  1240. }
  1241. }
  1242.  
  1243. protected function inlineUrlTag($Excerpt)
  1244. {
  1245. if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
  1246. {
  1247. $url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
  1248.  
  1249. return array(
  1250. 'extent' => strlen($matches[0]),
  1251. 'element' => array(
  1252. 'name' => 'a',
  1253. 'text' => $url,
  1254. 'attributes' => array(
  1255. 'href' => $url,
  1256. ),
  1257. ),
  1258. );
  1259. }
  1260. }
  1261.  
  1262. protected function unmarkedText($text)
  1263. {
  1264. if ($this->breaksEnabled)
  1265. {
  1266. $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
  1267. }
  1268. else
  1269. {
  1270. $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
  1271. $text = str_replace(" \n", "\n", $text);
  1272. }
  1273.  
  1274. return $text;
  1275. }
  1276.  
  1277. protected function element(array $Element)
  1278. {
  1279. $markup = '<'.$Element['name'];
  1280.  
  1281. if (isset($Element['attributes']))
  1282. {
  1283. foreach ($Element['attributes'] as $name => $value)
  1284. {
  1285. if ($value === null)
  1286. {
  1287. continue;
  1288. }
  1289.  
  1290. $markup .= ' '.$name.'="'.$value.'"';
  1291. }
  1292. }
  1293.  
  1294. if (isset($Element['text']))
  1295. {
  1296. $markup .= '>';
  1297.  
  1298. if (isset($Element['handler']))
  1299. {
  1300. $markup .= $this->{$Element['handler']}($Element['text']);
  1301. }
  1302. else
  1303. {
  1304. $markup .= $Element['text'];
  1305. }
  1306.  
  1307. $markup .= '</'.$Element['name'].'>';
  1308. }
  1309. else
  1310. {
  1311. $markup .= ' />';
  1312. }
  1313.  
  1314. return $markup;
  1315. }
  1316.  
  1317. protected function elements(array $Elements)
  1318. {
  1319. $markup = '';
  1320.  
  1321. foreach ($Elements as $Element)
  1322. {
  1323. $markup .= "\n" . $this->element($Element);
  1324. }
  1325.  
  1326. $markup .= "\n";
  1327.  
  1328. return $markup;
  1329. }
  1330.  
  1331. protected function li($lines)
  1332. {
  1333. $markup = $this->lines($lines);
  1334.  
  1335. $trimmedMarkup = trim($markup);
  1336.  
  1337. if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
  1338. {
  1339. $markup = $trimmedMarkup;
  1340. $markup = substr($markup, 3);
  1341.  
  1342. $position = strpos($markup, "</p>");
  1343.  
  1344. $markup = substr_replace($markup, '', $position, 4);
  1345. }
  1346.  
  1347. return $markup;
  1348. }
  1349.  
  1350.  
  1351. function parse($text)
  1352. {
  1353. $markup = $this->text($text);
  1354.  
  1355. return $markup;
  1356. }
  1357.  
  1358.  
  1359. static function instance($name = 'default')
  1360. {
  1361. if (isset(self::$instances[$name]))
  1362. {
  1363. return self::$instances[$name];
  1364. }
  1365.  
  1366. $instance = new self();
  1367.  
  1368. self::$instances[$name] = $instance;
  1369.  
  1370. return $instance;
  1371. }
  1372.  
  1373. private static $instances = array();
  1374.  
  1375.  
  1376.  
  1377. protected $DefinitionData;
  1378.  
  1379.  
  1380. protected $specialCharacters = array(
  1381. '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
  1382. );
  1383.  
  1384. protected $StrongRegex = array(
  1385. '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
  1386. '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
  1387. );
  1388.  
  1389. protected $EmRegex = array(
  1390. '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
  1391. '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
  1392. );
  1393.  
  1394. protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
  1395.  
  1396. protected $voidElements = array(
  1397. 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
  1398. );
  1399.  
  1400. protected $textLevelElements = array(
  1401. 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
  1402. 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
  1403. 'i', 'rp', 'del', 'code', 'strike', 'marquee',
  1404. 'q', 'rt', 'ins', 'font', 'strong',
  1405. 's', 'tt', 'sub', 'mark',
  1406. 'u', 'xm', 'sup', 'nobr',
  1407. 'var', 'ruby',
  1408. 'wbr', 'span',
  1409. 'time',
  1410. );
  1411. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement