fmt_md.php 66 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338
  1. <?php
  2. /*
  3. * plugins/fmt_md.php
  4. *
  5. * Copyright (C) 2022 bzt (bztsrc@gitlab)
  6. *
  7. * This program is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20. *
  21. * @brief MarkDown Input File Format plugin for gendoc
  22. *
  23. * This file is an amalgamation. The first part is from parsedown.org with the following modifications:
  24. * 1. I've inlined the license into the comment
  25. * 3. Commented out the deprecated parse method because it conflicted with the gendoc interface
  26. *
  27. * The second part is just the gendoc_md class which extends Parsedown and provides the gendoc interface.
  28. *
  29. */
  30. /*----------Parsedown.php starts----------*/
  31. #
  32. #
  33. # Parsedown
  34. # http://parsedown.org
  35. #
  36. # (c) Emanuil Rusev
  37. # http://erusev.com
  38. #
  39. # The MIT License (MIT)
  40. #
  41. # Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
  42. #
  43. # Permission is hereby granted, free of charge, to any person obtaining a copy of
  44. # this software and associated documentation files (the "Software"), to deal in
  45. # the Software without restriction, including without limitation the rights to
  46. # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  47. # the Software, and to permit persons to whom the Software is furnished to do so,
  48. # subject to the following conditions:
  49. #
  50. # The above copyright notice and this permission notice shall be included in all
  51. # copies or substantial portions of the Software.
  52. #
  53. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  54. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  55. # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  56. # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  57. # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  58. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  59. #
  60. #
  61. class Parsedown
  62. {
  63. # ~
  64. const version = '1.8.0-beta-7';
  65. # ~
  66. function text($text)
  67. {
  68. $Elements = $this->textElements($text);
  69. # convert to markup
  70. $markup = $this->elements($Elements);
  71. # trim line breaks
  72. $markup = trim($markup, "\n");
  73. return $markup;
  74. }
  75. protected function textElements($text)
  76. {
  77. # make sure no definitions are set
  78. $this->DefinitionData = array();
  79. # standardize line breaks
  80. $text = str_replace(array("\r\n", "\r"), "\n", $text);
  81. # remove surrounding line breaks
  82. $text = trim($text, "\n");
  83. # split text into lines
  84. $lines = explode("\n", $text);
  85. # iterate through lines to identify blocks
  86. return $this->linesElements($lines);
  87. }
  88. #
  89. # Setters
  90. #
  91. function setBreaksEnabled($breaksEnabled)
  92. {
  93. $this->breaksEnabled = $breaksEnabled;
  94. return $this;
  95. }
  96. protected $breaksEnabled;
  97. function setMarkupEscaped($markupEscaped)
  98. {
  99. $this->markupEscaped = $markupEscaped;
  100. return $this;
  101. }
  102. protected $markupEscaped;
  103. function setUrlsLinked($urlsLinked)
  104. {
  105. $this->urlsLinked = $urlsLinked;
  106. return $this;
  107. }
  108. protected $urlsLinked = true;
  109. function setSafeMode($safeMode)
  110. {
  111. $this->safeMode = (bool) $safeMode;
  112. return $this;
  113. }
  114. protected $safeMode;
  115. function setStrictMode($strictMode)
  116. {
  117. $this->strictMode = (bool) $strictMode;
  118. return $this;
  119. }
  120. protected $strictMode;
  121. protected $safeLinksWhitelist = array(
  122. 'http://',
  123. 'https://',
  124. 'ftp://',
  125. 'ftps://',
  126. 'mailto:',
  127. 'tel:',
  128. 'data:image/png;base64,',
  129. 'data:image/gif;base64,',
  130. 'data:image/jpeg;base64,',
  131. 'irc:',
  132. 'ircs:',
  133. 'git:',
  134. 'ssh:',
  135. 'news:',
  136. 'steam:',
  137. );
  138. #
  139. # Lines
  140. #
  141. protected $BlockTypes = array(
  142. '#' => array('Header'),
  143. '*' => array('Rule', 'List'),
  144. '+' => array('List'),
  145. '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
  146. '0' => array('List'),
  147. '1' => array('List'),
  148. '2' => array('List'),
  149. '3' => array('List'),
  150. '4' => array('List'),
  151. '5' => array('List'),
  152. '6' => array('List'),
  153. '7' => array('List'),
  154. '8' => array('List'),
  155. '9' => array('List'),
  156. ':' => array('Table'),
  157. '<' => array('Comment', 'Markup'),
  158. '=' => array('SetextHeader'),
  159. '>' => array('Quote'),
  160. '[' => array('Reference'),
  161. '_' => array('Rule'),
  162. '`' => array('FencedCode'),
  163. '|' => array('Table'),
  164. '~' => array('FencedCode'),
  165. );
  166. # ~
  167. protected $unmarkedBlockTypes = array(
  168. 'Code',
  169. );
  170. #
  171. # Blocks
  172. #
  173. protected function lines(array $lines)
  174. {
  175. return $this->elements($this->linesElements($lines));
  176. }
  177. protected function linesElements(array $lines)
  178. {
  179. $Elements = array();
  180. $CurrentBlock = null;
  181. foreach ($lines as $line)
  182. {
  183. if (chop($line) === '')
  184. {
  185. if (isset($CurrentBlock))
  186. {
  187. $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
  188. ? $CurrentBlock['interrupted'] + 1 : 1
  189. );
  190. }
  191. continue;
  192. }
  193. while (($beforeTab = strstr($line, "\t", true)) !== false)
  194. {
  195. $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
  196. $line = $beforeTab
  197. . str_repeat(' ', $shortage)
  198. . substr($line, strlen($beforeTab) + 1)
  199. ;
  200. }
  201. $indent = strspn($line, ' ');
  202. $text = $indent > 0 ? substr($line, $indent) : $line;
  203. # ~
  204. $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
  205. # ~
  206. if (isset($CurrentBlock['continuable']))
  207. {
  208. $methodName = 'block' . $CurrentBlock['type'] . 'Continue';
  209. $Block = $this->$methodName($Line, $CurrentBlock);
  210. if (isset($Block))
  211. {
  212. $CurrentBlock = $Block;
  213. continue;
  214. }
  215. else
  216. {
  217. if ($this->isBlockCompletable($CurrentBlock['type']))
  218. {
  219. $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
  220. $CurrentBlock = $this->$methodName($CurrentBlock);
  221. }
  222. }
  223. }
  224. # ~
  225. $marker = $text[0];
  226. # ~
  227. $blockTypes = $this->unmarkedBlockTypes;
  228. if (isset($this->BlockTypes[$marker]))
  229. {
  230. foreach ($this->BlockTypes[$marker] as $blockType)
  231. {
  232. $blockTypes []= $blockType;
  233. }
  234. }
  235. #
  236. # ~
  237. foreach ($blockTypes as $blockType)
  238. {
  239. $Block = $this->{"block$blockType"}($Line, $CurrentBlock);
  240. if (isset($Block))
  241. {
  242. $Block['type'] = $blockType;
  243. if ( ! isset($Block['identified']))
  244. {
  245. if (isset($CurrentBlock))
  246. {
  247. $Elements[] = $this->extractElement($CurrentBlock);
  248. }
  249. $Block['identified'] = true;
  250. }
  251. if ($this->isBlockContinuable($blockType))
  252. {
  253. $Block['continuable'] = true;
  254. }
  255. $CurrentBlock = $Block;
  256. continue 2;
  257. }
  258. }
  259. # ~
  260. if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
  261. {
  262. $Block = $this->paragraphContinue($Line, $CurrentBlock);
  263. }
  264. if (isset($Block))
  265. {
  266. $CurrentBlock = $Block;
  267. }
  268. else
  269. {
  270. if (isset($CurrentBlock))
  271. {
  272. $Elements[] = $this->extractElement($CurrentBlock);
  273. }
  274. $CurrentBlock = $this->paragraph($Line);
  275. $CurrentBlock['identified'] = true;
  276. }
  277. }
  278. # ~
  279. if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
  280. {
  281. $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
  282. $CurrentBlock = $this->$methodName($CurrentBlock);
  283. }
  284. # ~
  285. if (isset($CurrentBlock))
  286. {
  287. $Elements[] = $this->extractElement($CurrentBlock);
  288. }
  289. # ~
  290. return $Elements;
  291. }
  292. protected function extractElement(array $Component)
  293. {
  294. if ( ! isset($Component['element']))
  295. {
  296. if (isset($Component['markup']))
  297. {
  298. $Component['element'] = array('rawHtml' => $Component['markup']);
  299. }
  300. elseif (isset($Component['hidden']))
  301. {
  302. $Component['element'] = array();
  303. }
  304. }
  305. return $Component['element'];
  306. }
  307. protected function isBlockContinuable($Type)
  308. {
  309. return method_exists($this, 'block' . $Type . 'Continue');
  310. }
  311. protected function isBlockCompletable($Type)
  312. {
  313. return method_exists($this, 'block' . $Type . 'Complete');
  314. }
  315. #
  316. # Code
  317. protected function blockCode($Line, $Block = null)
  318. {
  319. if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
  320. {
  321. return;
  322. }
  323. if ($Line['indent'] >= 4)
  324. {
  325. $text = substr($Line['body'], 4);
  326. $Block = array(
  327. 'element' => array(
  328. 'name' => 'pre',
  329. 'element' => array(
  330. 'name' => 'code',
  331. 'text' => $text,
  332. ),
  333. ),
  334. );
  335. return $Block;
  336. }
  337. }
  338. protected function blockCodeContinue($Line, $Block)
  339. {
  340. if ($Line['indent'] >= 4)
  341. {
  342. if (isset($Block['interrupted']))
  343. {
  344. $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
  345. unset($Block['interrupted']);
  346. }
  347. $Block['element']['element']['text'] .= "\n";
  348. $text = substr($Line['body'], 4);
  349. $Block['element']['element']['text'] .= $text;
  350. return $Block;
  351. }
  352. }
  353. protected function blockCodeComplete($Block)
  354. {
  355. return $Block;
  356. }
  357. #
  358. # Comment
  359. protected function blockComment($Line)
  360. {
  361. if ($this->markupEscaped or $this->safeMode)
  362. {
  363. return;
  364. }
  365. if (strpos($Line['text'], '<!--') === 0)
  366. {
  367. $Block = array(
  368. 'element' => array(
  369. 'rawHtml' => $Line['body'],
  370. 'autobreak' => true,
  371. ),
  372. );
  373. if (strpos($Line['text'], '-->') !== false)
  374. {
  375. $Block['closed'] = true;
  376. }
  377. return $Block;
  378. }
  379. }
  380. protected function blockCommentContinue($Line, array $Block)
  381. {
  382. if (isset($Block['closed']))
  383. {
  384. return;
  385. }
  386. $Block['element']['rawHtml'] .= "\n" . $Line['body'];
  387. if (strpos($Line['text'], '-->') !== false)
  388. {
  389. $Block['closed'] = true;
  390. }
  391. return $Block;
  392. }
  393. #
  394. # Fenced Code
  395. protected function blockFencedCode($Line)
  396. {
  397. $marker = $Line['text'][0];
  398. $openerLength = strspn($Line['text'], $marker);
  399. if ($openerLength < 3)
  400. {
  401. return;
  402. }
  403. $infostring = trim(substr($Line['text'], $openerLength), "\t ");
  404. if (strpos($infostring, '`') !== false)
  405. {
  406. return;
  407. }
  408. $Element = array(
  409. 'name' => 'code',
  410. 'text' => '',
  411. );
  412. if ($infostring !== '')
  413. {
  414. /**
  415. * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
  416. * Every HTML element may have a class attribute specified.
  417. * The attribute, if specified, must have a value that is a set
  418. * of space-separated tokens representing the various classes
  419. * that the element belongs to.
  420. * [...]
  421. * The space characters, for the purposes of this specification,
  422. * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
  423. * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
  424. * U+000D CARRIAGE RETURN (CR).
  425. */
  426. $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));
  427. $Element['attributes'] = array('class' => "language-$language");
  428. }
  429. $Block = array(
  430. 'char' => $marker,
  431. 'openerLength' => $openerLength,
  432. 'element' => array(
  433. 'name' => 'pre',
  434. 'element' => $Element,
  435. ),
  436. );
  437. return $Block;
  438. }
  439. protected function blockFencedCodeContinue($Line, $Block)
  440. {
  441. if (isset($Block['complete']))
  442. {
  443. return;
  444. }
  445. if (isset($Block['interrupted']))
  446. {
  447. $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
  448. unset($Block['interrupted']);
  449. }
  450. if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
  451. and chop(substr($Line['text'], $len), ' ') === ''
  452. ) {
  453. $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
  454. $Block['complete'] = true;
  455. return $Block;
  456. }
  457. $Block['element']['element']['text'] .= "\n" . $Line['body'];
  458. return $Block;
  459. }
  460. protected function blockFencedCodeComplete($Block)
  461. {
  462. return $Block;
  463. }
  464. #
  465. # Header
  466. protected function blockHeader($Line)
  467. {
  468. $level = strspn($Line['text'], '#');
  469. if ($level > 6)
  470. {
  471. return;
  472. }
  473. $text = trim($Line['text'], '#');
  474. if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
  475. {
  476. return;
  477. }
  478. $text = trim($text, ' ');
  479. $Block = array(
  480. 'element' => array(
  481. 'name' => 'h' . $level,
  482. 'handler' => array(
  483. 'function' => 'lineElements',
  484. 'argument' => $text,
  485. 'destination' => 'elements',
  486. )
  487. ),
  488. );
  489. return $Block;
  490. }
  491. #
  492. # List
  493. protected function blockList($Line, array $CurrentBlock = null)
  494. {
  495. list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
  496. if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
  497. {
  498. $contentIndent = strlen($matches[2]);
  499. if ($contentIndent >= 5)
  500. {
  501. $contentIndent -= 1;
  502. $matches[1] = substr($matches[1], 0, -$contentIndent);
  503. $matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
  504. }
  505. elseif ($contentIndent === 0)
  506. {
  507. $matches[1] .= ' ';
  508. }
  509. $markerWithoutWhitespace = strstr($matches[1], ' ', true);
  510. $Block = array(
  511. 'indent' => $Line['indent'],
  512. 'pattern' => $pattern,
  513. 'data' => array(
  514. 'type' => $name,
  515. 'marker' => $matches[1],
  516. 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
  517. ),
  518. 'element' => array(
  519. 'name' => $name,
  520. 'elements' => array(),
  521. ),
  522. );
  523. $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
  524. if ($name === 'ol')
  525. {
  526. $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
  527. if ($listStart !== '1')
  528. {
  529. if (
  530. isset($CurrentBlock)
  531. and $CurrentBlock['type'] === 'Paragraph'
  532. and ! isset($CurrentBlock['interrupted'])
  533. ) {
  534. return;
  535. }
  536. $Block['element']['attributes'] = array('start' => $listStart);
  537. }
  538. }
  539. $Block['li'] = array(
  540. 'name' => 'li',
  541. 'handler' => array(
  542. 'function' => 'li',
  543. 'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
  544. 'destination' => 'elements'
  545. )
  546. );
  547. $Block['element']['elements'] []= & $Block['li'];
  548. return $Block;
  549. }
  550. }
  551. protected function blockListContinue($Line, array $Block)
  552. {
  553. if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
  554. {
  555. return null;
  556. }
  557. $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));
  558. if ($Line['indent'] < $requiredIndent
  559. and (
  560. (
  561. $Block['data']['type'] === 'ol'
  562. and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
  563. ) or (
  564. $Block['data']['type'] === 'ul'
  565. and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
  566. )
  567. )
  568. ) {
  569. if (isset($Block['interrupted']))
  570. {
  571. $Block['li']['handler']['argument'] []= '';
  572. $Block['loose'] = true;
  573. unset($Block['interrupted']);
  574. }
  575. unset($Block['li']);
  576. $text = isset($matches[1]) ? $matches[1] : '';
  577. $Block['indent'] = $Line['indent'];
  578. $Block['li'] = array(
  579. 'name' => 'li',
  580. 'handler' => array(
  581. 'function' => 'li',
  582. 'argument' => array($text),
  583. 'destination' => 'elements'
  584. )
  585. );
  586. $Block['element']['elements'] []= & $Block['li'];
  587. return $Block;
  588. }
  589. elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
  590. {
  591. return null;
  592. }
  593. if ($Line['text'][0] === '[' and $this->blockReference($Line))
  594. {
  595. return $Block;
  596. }
  597. if ($Line['indent'] >= $requiredIndent)
  598. {
  599. if (isset($Block['interrupted']))
  600. {
  601. $Block['li']['handler']['argument'] []= '';
  602. $Block['loose'] = true;
  603. unset($Block['interrupted']);
  604. }
  605. $text = substr($Line['body'], $requiredIndent);
  606. $Block['li']['handler']['argument'] []= $text;
  607. return $Block;
  608. }
  609. if ( ! isset($Block['interrupted']))
  610. {
  611. $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
  612. $Block['li']['handler']['argument'] []= $text;
  613. return $Block;
  614. }
  615. }
  616. protected function blockListComplete(array $Block)
  617. {
  618. if (isset($Block['loose']))
  619. {
  620. foreach ($Block['element']['elements'] as &$li)
  621. {
  622. if (end($li['handler']['argument']) !== '')
  623. {
  624. $li['handler']['argument'] []= '';
  625. }
  626. }
  627. }
  628. return $Block;
  629. }
  630. #
  631. # Quote
  632. protected function blockQuote($Line)
  633. {
  634. if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
  635. {
  636. $Block = array(
  637. 'element' => array(
  638. 'name' => 'blockquote',
  639. 'handler' => array(
  640. 'function' => 'linesElements',
  641. 'argument' => (array) $matches[1],
  642. 'destination' => 'elements',
  643. )
  644. ),
  645. );
  646. return $Block;
  647. }
  648. }
  649. protected function blockQuoteContinue($Line, array $Block)
  650. {
  651. if (isset($Block['interrupted']))
  652. {
  653. return;
  654. }
  655. if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
  656. {
  657. $Block['element']['handler']['argument'] []= $matches[1];
  658. return $Block;
  659. }
  660. if ( ! isset($Block['interrupted']))
  661. {
  662. $Block['element']['handler']['argument'] []= $Line['text'];
  663. return $Block;
  664. }
  665. }
  666. #
  667. # Rule
  668. protected function blockRule($Line)
  669. {
  670. $marker = $Line['text'][0];
  671. if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
  672. {
  673. $Block = array(
  674. 'element' => array(
  675. 'name' => 'hr',
  676. ),
  677. );
  678. return $Block;
  679. }
  680. }
  681. #
  682. # Setext
  683. protected function blockSetextHeader($Line, array $Block = null)
  684. {
  685. if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
  686. {
  687. return;
  688. }
  689. if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
  690. {
  691. $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
  692. return $Block;
  693. }
  694. }
  695. #
  696. # Markup
  697. protected function blockMarkup($Line)
  698. {
  699. if ($this->markupEscaped or $this->safeMode)
  700. {
  701. return;
  702. }
  703. if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
  704. {
  705. $element = strtolower($matches[1]);
  706. if (in_array($element, $this->textLevelElements))
  707. {
  708. return;
  709. }
  710. $Block = array(
  711. 'name' => $matches[1],
  712. 'element' => array(
  713. 'rawHtml' => $Line['text'],
  714. 'autobreak' => true,
  715. ),
  716. );
  717. return $Block;
  718. }
  719. }
  720. protected function blockMarkupContinue($Line, array $Block)
  721. {
  722. if (isset($Block['closed']) or isset($Block['interrupted']))
  723. {
  724. return;
  725. }
  726. $Block['element']['rawHtml'] .= "\n" . $Line['body'];
  727. return $Block;
  728. }
  729. #
  730. # Reference
  731. protected function blockReference($Line)
  732. {
  733. if (strpos($Line['text'], ']') !== false
  734. and preg_match('/^\[(.+?)\]:[ ]*+<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
  735. ) {
  736. $id = strtolower($matches[1]);
  737. $Data = array(
  738. 'url' => $matches[2],
  739. 'title' => isset($matches[3]) ? $matches[3] : null,
  740. );
  741. $this->DefinitionData['Reference'][$id] = $Data;
  742. $Block = array(
  743. 'element' => array(),
  744. );
  745. return $Block;
  746. }
  747. }
  748. #
  749. # Table
  750. protected function blockTable($Line, array $Block = null)
  751. {
  752. if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
  753. {
  754. return;
  755. }
  756. if (
  757. strpos($Block['element']['handler']['argument'], '|') === false
  758. and strpos($Line['text'], '|') === false
  759. and strpos($Line['text'], ':') === false
  760. or strpos($Block['element']['handler']['argument'], "\n") !== false
  761. ) {
  762. return;
  763. }
  764. if (chop($Line['text'], ' -:|') !== '')
  765. {
  766. return;
  767. }
  768. $alignments = array();
  769. $divider = $Line['text'];
  770. $divider = trim($divider);
  771. $divider = trim($divider, '|');
  772. $dividerCells = explode('|', $divider);
  773. foreach ($dividerCells as $dividerCell)
  774. {
  775. $dividerCell = trim($dividerCell);
  776. if ($dividerCell === '')
  777. {
  778. return;
  779. }
  780. $alignment = null;
  781. if ($dividerCell[0] === ':')
  782. {
  783. $alignment = 'left';
  784. }
  785. if (substr($dividerCell, - 1) === ':')
  786. {
  787. $alignment = $alignment === 'left' ? 'center' : 'right';
  788. }
  789. $alignments []= $alignment;
  790. }
  791. # ~
  792. $HeaderElements = array();
  793. $header = $Block['element']['handler']['argument'];
  794. $header = trim($header);
  795. $header = trim($header, '|');
  796. $headerCells = explode('|', $header);
  797. if (count($headerCells) !== count($alignments))
  798. {
  799. return;
  800. }
  801. foreach ($headerCells as $index => $headerCell)
  802. {
  803. $headerCell = trim($headerCell);
  804. $HeaderElement = array(
  805. 'name' => 'th',
  806. 'handler' => array(
  807. 'function' => 'lineElements',
  808. 'argument' => $headerCell,
  809. 'destination' => 'elements',
  810. )
  811. );
  812. if (isset($alignments[$index]))
  813. {
  814. $alignment = $alignments[$index];
  815. $HeaderElement['attributes'] = array(
  816. 'style' => "text-align: $alignment;",
  817. );
  818. }
  819. $HeaderElements []= $HeaderElement;
  820. }
  821. # ~
  822. $Block = array(
  823. 'alignments' => $alignments,
  824. 'identified' => true,
  825. 'element' => array(
  826. 'name' => 'table',
  827. 'elements' => array(),
  828. ),
  829. );
  830. $Block['element']['elements'] []= array(
  831. 'name' => 'thead',
  832. );
  833. $Block['element']['elements'] []= array(
  834. 'name' => 'tbody',
  835. 'elements' => array(),
  836. );
  837. $Block['element']['elements'][0]['elements'] []= array(
  838. 'name' => 'tr',
  839. 'elements' => $HeaderElements,
  840. );
  841. return $Block;
  842. }
  843. protected function blockTableContinue($Line, array $Block)
  844. {
  845. if (isset($Block['interrupted']))
  846. {
  847. return;
  848. }
  849. if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|'))
  850. {
  851. $Elements = array();
  852. $row = $Line['text'];
  853. $row = trim($row);
  854. $row = trim($row, '|');
  855. preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches);
  856. $cells = array_slice($matches[0], 0, count($Block['alignments']));
  857. foreach ($cells as $index => $cell)
  858. {
  859. $cell = trim($cell);
  860. $Element = array(
  861. 'name' => 'td',
  862. 'handler' => array(
  863. 'function' => 'lineElements',
  864. 'argument' => $cell,
  865. 'destination' => 'elements',
  866. )
  867. );
  868. if (isset($Block['alignments'][$index]))
  869. {
  870. $Element['attributes'] = array(
  871. 'style' => 'text-align: ' . $Block['alignments'][$index] . ';',
  872. );
  873. }
  874. $Elements []= $Element;
  875. }
  876. $Element = array(
  877. 'name' => 'tr',
  878. 'elements' => $Elements,
  879. );
  880. $Block['element']['elements'][1]['elements'] []= $Element;
  881. return $Block;
  882. }
  883. }
  884. #
  885. # ~
  886. #
  887. protected function paragraph($Line)
  888. {
  889. return array(
  890. 'type' => 'Paragraph',
  891. 'element' => array(
  892. 'name' => 'p',
  893. 'handler' => array(
  894. 'function' => 'lineElements',
  895. 'argument' => $Line['text'],
  896. 'destination' => 'elements',
  897. ),
  898. ),
  899. );
  900. }
  901. protected function paragraphContinue($Line, array $Block)
  902. {
  903. if (isset($Block['interrupted']))
  904. {
  905. return;
  906. }
  907. $Block['element']['handler']['argument'] .= "\n".$Line['text'];
  908. return $Block;
  909. }
  910. #
  911. # Inline Elements
  912. #
  913. protected $InlineTypes = array(
  914. '!' => array('Image'),
  915. '&' => array('SpecialCharacter'),
  916. '*' => array('Emphasis'),
  917. ':' => array('Url'),
  918. '<' => array('UrlTag', 'EmailTag', 'Markup'),
  919. '[' => array('Link'),
  920. '_' => array('Emphasis'),
  921. '`' => array('Code'),
  922. '~' => array('Strikethrough'),
  923. '\\' => array('EscapeSequence'),
  924. );
  925. # ~
  926. protected $inlineMarkerList = '!*_&[:<`~\\';
  927. #
  928. # ~
  929. #
  930. public function line($text, $nonNestables = array())
  931. {
  932. return $this->elements($this->lineElements($text, $nonNestables));
  933. }
  934. protected function lineElements($text, $nonNestables = array())
  935. {
  936. # standardize line breaks
  937. $text = str_replace(array("\r\n", "\r"), "\n", $text);
  938. $Elements = array();
  939. $nonNestables = (empty($nonNestables)
  940. ? array()
  941. : array_combine($nonNestables, $nonNestables)
  942. );
  943. # $excerpt is based on the first occurrence of a marker
  944. while ($excerpt = strpbrk($text, $this->inlineMarkerList))
  945. {
  946. $marker = $excerpt[0];
  947. $markerPosition = strlen($text) - strlen($excerpt);
  948. $Excerpt = array('text' => $excerpt, 'context' => $text);
  949. foreach ($this->InlineTypes[$marker] as $inlineType)
  950. {
  951. # check to see if the current inline type is nestable in the current context
  952. if (isset($nonNestables[$inlineType]))
  953. {
  954. continue;
  955. }
  956. $Inline = $this->{"inline$inlineType"}($Excerpt);
  957. if ( ! isset($Inline))
  958. {
  959. continue;
  960. }
  961. # makes sure that the inline belongs to "our" marker
  962. if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
  963. {
  964. continue;
  965. }
  966. # sets a default inline position
  967. if ( ! isset($Inline['position']))
  968. {
  969. $Inline['position'] = $markerPosition;
  970. }
  971. # cause the new element to 'inherit' our non nestables
  972. $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
  973. ? array_merge($Inline['element']['nonNestables'], $nonNestables)
  974. : $nonNestables
  975. ;
  976. # the text that comes before the inline
  977. $unmarkedText = substr($text, 0, $Inline['position']);
  978. # compile the unmarked text
  979. $InlineText = $this->inlineText($unmarkedText);
  980. $Elements[] = $InlineText['element'];
  981. # compile the inline
  982. $Elements[] = $this->extractElement($Inline);
  983. # remove the examined text
  984. $text = substr($text, $Inline['position'] + $Inline['extent']);
  985. continue 2;
  986. }
  987. # the marker does not belong to an inline
  988. $unmarkedText = substr($text, 0, $markerPosition + 1);
  989. $InlineText = $this->inlineText($unmarkedText);
  990. $Elements[] = $InlineText['element'];
  991. $text = substr($text, $markerPosition + 1);
  992. }
  993. $InlineText = $this->inlineText($text);
  994. $Elements[] = $InlineText['element'];
  995. foreach ($Elements as &$Element)
  996. {
  997. if ( ! isset($Element['autobreak']))
  998. {
  999. $Element['autobreak'] = false;
  1000. }
  1001. }
  1002. return $Elements;
  1003. }
  1004. #
  1005. # ~
  1006. #
  1007. protected function inlineText($text)
  1008. {
  1009. $Inline = array(
  1010. 'extent' => strlen($text),
  1011. 'element' => array(),
  1012. );
  1013. $Inline['element']['elements'] = self::pregReplaceElements(
  1014. $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
  1015. array(
  1016. array('name' => 'br'),
  1017. array('text' => "\n"),
  1018. ),
  1019. $text
  1020. );
  1021. return $Inline;
  1022. }
  1023. protected function inlineCode($Excerpt)
  1024. {
  1025. $marker = $Excerpt['text'][0];
  1026. if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(?<!['.$marker.'])\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
  1027. {
  1028. $text = $matches[2];
  1029. $text = preg_replace('/[ ]*+\n/', ' ', $text);
  1030. return array(
  1031. 'extent' => strlen($matches[0]),
  1032. 'element' => array(
  1033. 'name' => 'code',
  1034. 'text' => $text,
  1035. ),
  1036. );
  1037. }
  1038. }
  1039. protected function inlineEmailTag($Excerpt)
  1040. {
  1041. $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
  1042. $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@'
  1043. . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*';
  1044. if (strpos($Excerpt['text'], '>') !== false
  1045. and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches)
  1046. ){
  1047. $url = $matches[1];
  1048. if ( ! isset($matches[2]))
  1049. {
  1050. $url = "mailto:$url";
  1051. }
  1052. return array(
  1053. 'extent' => strlen($matches[0]),
  1054. 'element' => array(
  1055. 'name' => 'a',
  1056. 'text' => $matches[1],
  1057. 'attributes' => array(
  1058. 'href' => $url,
  1059. ),
  1060. ),
  1061. );
  1062. }
  1063. }
  1064. protected function inlineEmphasis($Excerpt)
  1065. {
  1066. if ( ! isset($Excerpt['text'][1]))
  1067. {
  1068. return;
  1069. }
  1070. $marker = $Excerpt['text'][0];
  1071. if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
  1072. {
  1073. $emphasis = 'strong';
  1074. }
  1075. elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
  1076. {
  1077. $emphasis = 'em';
  1078. }
  1079. else
  1080. {
  1081. return;
  1082. }
  1083. return array(
  1084. 'extent' => strlen($matches[0]),
  1085. 'element' => array(
  1086. 'name' => $emphasis,
  1087. 'handler' => array(
  1088. 'function' => 'lineElements',
  1089. 'argument' => $matches[1],
  1090. 'destination' => 'elements',
  1091. )
  1092. ),
  1093. );
  1094. }
  1095. protected function inlineEscapeSequence($Excerpt)
  1096. {
  1097. if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
  1098. {
  1099. return array(
  1100. 'element' => array('rawHtml' => $Excerpt['text'][1]),
  1101. 'extent' => 2,
  1102. );
  1103. }
  1104. }
  1105. protected function inlineImage($Excerpt)
  1106. {
  1107. if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
  1108. {
  1109. return;
  1110. }
  1111. $Excerpt['text']= substr($Excerpt['text'], 1);
  1112. $Link = $this->inlineLink($Excerpt);
  1113. if ($Link === null)
  1114. {
  1115. return;
  1116. }
  1117. $Inline = array(
  1118. 'extent' => $Link['extent'] + 1,
  1119. 'element' => array(
  1120. 'name' => 'img',
  1121. 'attributes' => array(
  1122. 'src' => $Link['element']['attributes']['href'],
  1123. 'alt' => $Link['element']['handler']['argument'],
  1124. ),
  1125. 'autobreak' => true,
  1126. ),
  1127. );
  1128. $Inline['element']['attributes'] += $Link['element']['attributes'];
  1129. unset($Inline['element']['attributes']['href']);
  1130. return $Inline;
  1131. }
  1132. protected function inlineLink($Excerpt)
  1133. {
  1134. $Element = array(
  1135. 'name' => 'a',
  1136. 'handler' => array(
  1137. 'function' => 'lineElements',
  1138. 'argument' => null,
  1139. 'destination' => 'elements',
  1140. ),
  1141. 'nonNestables' => array('Url', 'Link'),
  1142. 'attributes' => array(
  1143. 'href' => null,
  1144. 'title' => null,
  1145. ),
  1146. );
  1147. $extent = 0;
  1148. $remainder = $Excerpt['text'];
  1149. if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
  1150. {
  1151. $Element['handler']['argument'] = $matches[1];
  1152. $extent += strlen($matches[0]);
  1153. $remainder = substr($remainder, $extent);
  1154. }
  1155. else
  1156. {
  1157. return;
  1158. }
  1159. if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
  1160. {
  1161. $Element['attributes']['href'] = $matches[1];
  1162. if (isset($matches[2]))
  1163. {
  1164. $Element['attributes']['title'] = substr($matches[2], 1, - 1);
  1165. }
  1166. $extent += strlen($matches[0]);
  1167. }
  1168. else
  1169. {
  1170. if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
  1171. {
  1172. $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument'];
  1173. $definition = strtolower($definition);
  1174. $extent += strlen($matches[0]);
  1175. }
  1176. else
  1177. {
  1178. $definition = strtolower($Element['handler']['argument']);
  1179. }
  1180. if ( ! isset($this->DefinitionData['Reference'][$definition]))
  1181. {
  1182. return;
  1183. }
  1184. $Definition = $this->DefinitionData['Reference'][$definition];
  1185. $Element['attributes']['href'] = $Definition['url'];
  1186. $Element['attributes']['title'] = $Definition['title'];
  1187. }
  1188. return array(
  1189. 'extent' => $extent,
  1190. 'element' => $Element,
  1191. );
  1192. }
  1193. protected function inlineMarkup($Excerpt)
  1194. {
  1195. if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
  1196. {
  1197. return;
  1198. }
  1199. if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
  1200. {
  1201. return array(
  1202. 'element' => array('rawHtml' => $matches[0]),
  1203. 'extent' => strlen($matches[0]),
  1204. );
  1205. }
  1206. if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?+[^-])*-->/s', $Excerpt['text'], $matches))
  1207. {
  1208. return array(
  1209. 'element' => array('rawHtml' => $matches[0]),
  1210. 'extent' => strlen($matches[0]),
  1211. );
  1212. }
  1213. if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
  1214. {
  1215. return array(
  1216. 'element' => array('rawHtml' => $matches[0]),
  1217. 'extent' => strlen($matches[0]),
  1218. );
  1219. }
  1220. }
  1221. protected function inlineSpecialCharacter($Excerpt)
  1222. {
  1223. if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
  1224. and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
  1225. ) {
  1226. return array(
  1227. 'element' => array('rawHtml' => '&' . $matches[1] . ';'),
  1228. 'extent' => strlen($matches[0]),
  1229. );
  1230. }
  1231. return;
  1232. }
  1233. protected function inlineStrikethrough($Excerpt)
  1234. {
  1235. if ( ! isset($Excerpt['text'][1]))
  1236. {
  1237. return;
  1238. }
  1239. if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
  1240. {
  1241. return array(
  1242. 'extent' => strlen($matches[0]),
  1243. 'element' => array(
  1244. 'name' => 'del',
  1245. 'handler' => array(
  1246. 'function' => 'lineElements',
  1247. 'argument' => $matches[1],
  1248. 'destination' => 'elements',
  1249. )
  1250. ),
  1251. );
  1252. }
  1253. }
  1254. protected function inlineUrl($Excerpt)
  1255. {
  1256. if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
  1257. {
  1258. return;
  1259. }
  1260. if (strpos($Excerpt['context'], 'http') !== false
  1261. and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)
  1262. ) {
  1263. $url = $matches[0][0];
  1264. $Inline = array(
  1265. 'extent' => strlen($matches[0][0]),
  1266. 'position' => $matches[0][1],
  1267. 'element' => array(
  1268. 'name' => 'a',
  1269. 'text' => $url,
  1270. 'attributes' => array(
  1271. 'href' => $url,
  1272. ),
  1273. ),
  1274. );
  1275. return $Inline;
  1276. }
  1277. }
  1278. protected function inlineUrlTag($Excerpt)
  1279. {
  1280. if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
  1281. {
  1282. $url = $matches[1];
  1283. return array(
  1284. 'extent' => strlen($matches[0]),
  1285. 'element' => array(
  1286. 'name' => 'a',
  1287. 'text' => $url,
  1288. 'attributes' => array(
  1289. 'href' => $url,
  1290. ),
  1291. ),
  1292. );
  1293. }
  1294. }
  1295. # ~
  1296. protected function unmarkedText($text)
  1297. {
  1298. $Inline = $this->inlineText($text);
  1299. return $this->element($Inline['element']);
  1300. }
  1301. #
  1302. # Handlers
  1303. #
  1304. protected function handle(array $Element)
  1305. {
  1306. if (isset($Element['handler']))
  1307. {
  1308. if (!isset($Element['nonNestables']))
  1309. {
  1310. $Element['nonNestables'] = array();
  1311. }
  1312. if (is_string($Element['handler']))
  1313. {
  1314. $function = $Element['handler'];
  1315. $argument = $Element['text'];
  1316. unset($Element['text']);
  1317. $destination = 'rawHtml';
  1318. }
  1319. else
  1320. {
  1321. $function = $Element['handler']['function'];
  1322. $argument = $Element['handler']['argument'];
  1323. $destination = $Element['handler']['destination'];
  1324. }
  1325. $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']);
  1326. if ($destination === 'handler')
  1327. {
  1328. $Element = $this->handle($Element);
  1329. }
  1330. unset($Element['handler']);
  1331. }
  1332. return $Element;
  1333. }
  1334. protected function handleElementRecursive(array $Element)
  1335. {
  1336. return $this->elementApplyRecursive(array($this, 'handle'), $Element);
  1337. }
  1338. protected function handleElementsRecursive(array $Elements)
  1339. {
  1340. return $this->elementsApplyRecursive(array($this, 'handle'), $Elements);
  1341. }
  1342. protected function elementApplyRecursive($closure, array $Element)
  1343. {
  1344. $Element = call_user_func($closure, $Element);
  1345. if (isset($Element['elements']))
  1346. {
  1347. $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
  1348. }
  1349. elseif (isset($Element['element']))
  1350. {
  1351. $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']);
  1352. }
  1353. return $Element;
  1354. }
  1355. protected function elementApplyRecursiveDepthFirst($closure, array $Element)
  1356. {
  1357. if (isset($Element['elements']))
  1358. {
  1359. $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']);
  1360. }
  1361. elseif (isset($Element['element']))
  1362. {
  1363. $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']);
  1364. }
  1365. $Element = call_user_func($closure, $Element);
  1366. return $Element;
  1367. }
  1368. protected function elementsApplyRecursive($closure, array $Elements)
  1369. {
  1370. foreach ($Elements as &$Element)
  1371. {
  1372. $Element = $this->elementApplyRecursive($closure, $Element);
  1373. }
  1374. return $Elements;
  1375. }
  1376. protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)
  1377. {
  1378. foreach ($Elements as &$Element)
  1379. {
  1380. $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);
  1381. }
  1382. return $Elements;
  1383. }
  1384. protected function element(array $Element)
  1385. {
  1386. if ($this->safeMode)
  1387. {
  1388. $Element = $this->sanitiseElement($Element);
  1389. }
  1390. # identity map if element has no handler
  1391. $Element = $this->handle($Element);
  1392. $hasName = isset($Element['name']);
  1393. $markup = '';
  1394. if ($hasName)
  1395. {
  1396. $markup .= '<' . $Element['name'];
  1397. if (isset($Element['attributes']))
  1398. {
  1399. foreach ($Element['attributes'] as $name => $value)
  1400. {
  1401. if ($value === null)
  1402. {
  1403. continue;
  1404. }
  1405. $markup .= " $name=\"".self::escape($value).'"';
  1406. }
  1407. }
  1408. }
  1409. $permitRawHtml = false;
  1410. if (isset($Element['text']))
  1411. {
  1412. $text = $Element['text'];
  1413. }
  1414. // very strongly consider an alternative if you're writing an
  1415. // extension
  1416. elseif (isset($Element['rawHtml']))
  1417. {
  1418. $text = $Element['rawHtml'];
  1419. $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
  1420. $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
  1421. }
  1422. $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']);
  1423. if ($hasContent)
  1424. {
  1425. $markup .= $hasName ? '>' : '';
  1426. if (isset($Element['elements']))
  1427. {
  1428. $markup .= $this->elements($Element['elements']);
  1429. }
  1430. elseif (isset($Element['element']))
  1431. {
  1432. $markup .= $this->element($Element['element']);
  1433. }
  1434. else
  1435. {
  1436. if (!$permitRawHtml)
  1437. {
  1438. $markup .= self::escape($text, true);
  1439. }
  1440. else
  1441. {
  1442. $markup .= $text;
  1443. }
  1444. }
  1445. $markup .= $hasName ? '</' . $Element['name'] . '>' : '';
  1446. }
  1447. elseif ($hasName)
  1448. {
  1449. $markup .= ' />';
  1450. }
  1451. return $markup;
  1452. }
  1453. protected function elements(array $Elements)
  1454. {
  1455. $markup = '';
  1456. $autoBreak = true;
  1457. foreach ($Elements as $Element)
  1458. {
  1459. if (empty($Element))
  1460. {
  1461. continue;
  1462. }
  1463. $autoBreakNext = (isset($Element['autobreak'])
  1464. ? $Element['autobreak'] : isset($Element['name'])
  1465. );
  1466. // (autobreak === false) covers both sides of an element
  1467. $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
  1468. $markup .= ($autoBreak ? "\n" : '') . $this->element($Element);
  1469. $autoBreak = $autoBreakNext;
  1470. }
  1471. $markup .= $autoBreak ? "\n" : '';
  1472. return $markup;
  1473. }
  1474. # ~
  1475. protected function li($lines)
  1476. {
  1477. $Elements = $this->linesElements($lines);
  1478. if ( ! in_array('', $lines)
  1479. and isset($Elements[0]) and isset($Elements[0]['name'])
  1480. and $Elements[0]['name'] === 'p'
  1481. ) {
  1482. unset($Elements[0]['name']);
  1483. }
  1484. return $Elements;
  1485. }
  1486. #
  1487. # AST Convenience
  1488. #
  1489. /**
  1490. * Replace occurrences $regexp with $Elements in $text. Return an array of
  1491. * elements representing the replacement.
  1492. */
  1493. protected static function pregReplaceElements($regexp, $Elements, $text)
  1494. {
  1495. $newElements = array();
  1496. while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))
  1497. {
  1498. $offset = $matches[0][1];
  1499. $before = substr($text, 0, $offset);
  1500. $after = substr($text, $offset + strlen($matches[0][0]));
  1501. $newElements[] = array('text' => $before);
  1502. foreach ($Elements as $Element)
  1503. {
  1504. $newElements[] = $Element;
  1505. }
  1506. $text = $after;
  1507. }
  1508. $newElements[] = array('text' => $text);
  1509. return $newElements;
  1510. }
  1511. #
  1512. # Deprecated Methods
  1513. #
  1514. /*
  1515. function parse($text)
  1516. {
  1517. $markup = $this->text($text);
  1518. return $markup;
  1519. }
  1520. */
  1521. protected function sanitiseElement(array $Element)
  1522. {
  1523. static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
  1524. static $safeUrlNameToAtt = array(
  1525. 'a' => 'href',
  1526. 'img' => 'src',
  1527. );
  1528. if ( ! isset($Element['name']))
  1529. {
  1530. unset($Element['attributes']);
  1531. return $Element;
  1532. }
  1533. if (isset($safeUrlNameToAtt[$Element['name']]))
  1534. {
  1535. $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
  1536. }
  1537. if ( ! empty($Element['attributes']))
  1538. {
  1539. foreach ($Element['attributes'] as $att => $val)
  1540. {
  1541. # filter out badly parsed attribute
  1542. if ( ! preg_match($goodAttribute, $att))
  1543. {
  1544. unset($Element['attributes'][$att]);
  1545. }
  1546. # dump onevent attribute
  1547. elseif (self::striAtStart($att, 'on'))
  1548. {
  1549. unset($Element['attributes'][$att]);
  1550. }
  1551. }
  1552. }
  1553. return $Element;
  1554. }
  1555. protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
  1556. {
  1557. foreach ($this->safeLinksWhitelist as $scheme)
  1558. {
  1559. if (self::striAtStart($Element['attributes'][$attribute], $scheme))
  1560. {
  1561. return $Element;
  1562. }
  1563. }
  1564. $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
  1565. return $Element;
  1566. }
  1567. #
  1568. # Static Methods
  1569. #
  1570. protected static function escape($text, $allowQuotes = false)
  1571. {
  1572. return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
  1573. }
  1574. protected static function striAtStart($string, $needle)
  1575. {
  1576. $len = strlen($needle);
  1577. if ($len > strlen($string))
  1578. {
  1579. return false;
  1580. }
  1581. else
  1582. {
  1583. return strtolower(substr($string, 0, $len)) === strtolower($needle);
  1584. }
  1585. }
  1586. static function instance($name = 'default')
  1587. {
  1588. if (isset(self::$instances[$name]))
  1589. {
  1590. return self::$instances[$name];
  1591. }
  1592. $instance = new static();
  1593. self::$instances[$name] = $instance;
  1594. return $instance;
  1595. }
  1596. private static $instances = array();
  1597. #
  1598. # Fields
  1599. #
  1600. protected $DefinitionData;
  1601. #
  1602. # Read-Only
  1603. protected $specialCharacters = array(
  1604. '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
  1605. );
  1606. protected $StrongRegex = array(
  1607. '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
  1608. '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
  1609. );
  1610. protected $EmRegex = array(
  1611. '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
  1612. '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
  1613. );
  1614. protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
  1615. protected $voidElements = array(
  1616. 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
  1617. );
  1618. protected $textLevelElements = array(
  1619. 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
  1620. 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
  1621. 'i', 'rp', 'del', 'code', 'strike', 'marquee',
  1622. 'q', 'rt', 'ins', 'font', 'strong',
  1623. 's', 'tt', 'kbd', 'mark',
  1624. 'u', 'xm', 'sub', 'nobr',
  1625. 'sup', 'ruby',
  1626. 'var', 'span',
  1627. 'wbr', 'time',
  1628. );
  1629. }
  1630. /*----------Parsedown.php ends----------*/
  1631. class gendoc_md extends Parsedown
  1632. {
  1633. protected $gendocTags = array( 'imgt', 'imgl', 'imgr', 'imgw', 'mbl', 'mbr', 'mbw', 'include', 'api' );
  1634. /* add superscript, subscript and gendoc tags */
  1635. function __construct()
  1636. {
  1637. $this->InlineTypes['^'][] = 'SuperScript';
  1638. $this->InlineTypes[','][] = 'SubScript';
  1639. }
  1640. protected function inlineSuperScript($excerpt)
  1641. {
  1642. if(preg_match('/^\^\^(.*?)\^\^/', $excerpt['text'], $matches)) {
  1643. return array(
  1644. 'extent' => strlen($matches[0]),
  1645. 'element' => array(
  1646. 'name' => 'sup',
  1647. 'text' => $matches[1]
  1648. )
  1649. );
  1650. }
  1651. }
  1652. protected function inlineSubScript($excerpt)
  1653. {
  1654. if(preg_match('/^,,(.*?),,/', $excerpt['text'], $matches)) {
  1655. return array(
  1656. 'extent' => strlen($matches[0]),
  1657. 'element' => array(
  1658. 'name' => 'sub',
  1659. 'text' => $matches[1]
  1660. )
  1661. );
  1662. }
  1663. }
  1664. protected function inlineMarkup($Excerpt)
  1665. {
  1666. if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
  1667. {
  1668. return;
  1669. }
  1670. if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
  1671. {
  1672. return array(
  1673. 'element' => array('rawHtml' => $matches[0]),
  1674. 'extent' => strlen($matches[0]),
  1675. );
  1676. }
  1677. if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?+[^-])*-->/s', $Excerpt['text'], $matches))
  1678. {
  1679. return array(
  1680. 'element' => array('rawHtml' => ""),
  1681. 'extent' => strlen($matches[0]),
  1682. );
  1683. }
  1684. if ($Excerpt['text'][1] !== ' ' and preg_match('/^<(\w[\w-]*+)([^>]*)>/s', $Excerpt['text'], $matches))
  1685. {
  1686. if(in_array($matches[1], $this->gendocTags))
  1687. return array(
  1688. 'extent' => strlen($matches[0]),
  1689. 'element' => array(
  1690. 'name' => $matches[1],
  1691. 'attributes' => trim(@$matches[2])
  1692. )
  1693. );
  1694. return array(
  1695. 'element' => array('rawHtml' => $matches[0]),
  1696. 'extent' => strlen($matches[0]),
  1697. );
  1698. }
  1699. }
  1700. protected function inlineUrlTag($Excerpt)
  1701. {
  1702. if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
  1703. {
  1704. $url = $matches[1];
  1705. return array(
  1706. 'extent' => strlen($matches[0]),
  1707. 'element' => array(
  1708. 'name' => 'a',
  1709. 'text' => $url,
  1710. 'attributes' => array(
  1711. 'href' => $url,
  1712. ),
  1713. ),
  1714. );
  1715. }
  1716. if (preg_match('/^<a>([^<]*)<\/a>/i', $Excerpt['text'], $matches))
  1717. return array(
  1718. 'extent' => strlen($matches[0]),
  1719. 'element' => array(
  1720. 'name' => 'a',
  1721. 'handler' => array( 'argument' => $matches[1]),
  1722. 'attributes' => array(
  1723. 'href' => "#".gendoc::safeid($matches[1]),
  1724. ),
  1725. ),
  1726. );
  1727. }
  1728. /* provide the gendoc interface */
  1729. protected function element(array $Element)
  1730. {
  1731. if ($this->safeMode)
  1732. {
  1733. $Element = $this->sanitiseElement($Element);
  1734. }
  1735. $hasName = isset($Element['name']);
  1736. if($hasName)
  1737. {
  1738. if($Element['name'] == "p" && !empty($Element['handler']['argument'])) {
  1739. if(substr($Element['handler']['argument'], 0, 5) == "INFO:") {
  1740. $Element['name'] = 'info';
  1741. $Element['handler']['argument'] = trim(substr($Element['handler']['argument'], 5));
  1742. } else
  1743. if(substr($Element['handler']['argument'], 0, 5) == "HINT:") {
  1744. $Element['name'] = 'hint';
  1745. $Element['handler']['argument'] = trim(substr($Element['handler']['argument'], 5));
  1746. } else
  1747. if(substr($Element['handler']['argument'], 0, 5) == "NOTE:") {
  1748. $Element['name'] = 'note';
  1749. $Element['handler']['argument'] = trim(substr($Element['handler']['argument'], 5));
  1750. } else
  1751. if(substr($Element['handler']['argument'], 0, 9) == "SEE ALSO:") {
  1752. $Element['name'] = 'also';
  1753. $Element['handler']['argument'] = trim(substr($Element['handler']['argument'], 9));
  1754. } else
  1755. if(substr($Element['handler']['argument'], 0, 5) == "ALSO:") {
  1756. $Element['name'] = 'also';
  1757. $Element['handler']['argument'] = trim(substr($Element['handler']['argument'], 5));
  1758. } else
  1759. if(substr($Element['handler']['argument'], 0, 5) == "TODO:") {
  1760. $Element['name'] = 'hint';
  1761. $Element['handler']['argument'] = trim(substr($Element['handler']['argument'], 5));
  1762. } else
  1763. if(substr($Element['handler']['argument'], 0, 8) == "WARNING:") {
  1764. $Element['name'] = 'warn';
  1765. $Element['handler']['argument'] = trim(substr($Element['handler']['argument'], 8));
  1766. } else
  1767. if(substr($Element['handler']['argument'], 0, 5) == "WARN:") {
  1768. $Element['name'] = 'warn';
  1769. $Element['handler']['argument'] = trim(substr($Element['handler']['argument'], 5));
  1770. }
  1771. }
  1772. if($Element['name'] == "a" && @$Element['attributes']['href'][0] == '#') {
  1773. gendoc::internal_link($Element['handler']['argument'], substr($Element['attributes']['href'], 1));
  1774. return;
  1775. } else
  1776. switch($Element['name']) {
  1777. case "h1": case "h2": case "h3": case "h4": case "h5": case "h6":
  1778. if($Element['name'] == "h1") {
  1779. if(empty(gendoc::$toc)) gendoc::hello_open();
  1780. else gendoc::hello_close();
  1781. }
  1782. gendoc::heading($Element['name'][1], $Element['handler']['argument']);
  1783. return;
  1784. case "tt": case "code": gendoc::teletype($Element['text']); return;
  1785. case "pre":
  1786. $lang = substr(@$Element['element']['attributes']['class'], 9);
  1787. if(substr(@$Element['element']['attributes']['class'], 0, 9) == "language-" && !empty(gendoc::$rules[$lang]))
  1788. gendoc::source_code($Element['element']['text'], $lang);
  1789. else
  1790. gendoc::preformatted($Element['element']['text']);
  1791. return;
  1792. case "br": gendoc::line_break(); return;
  1793. case "img":
  1794. gendoc::image(strpos(@$Element['attributes']['style'], "right") ? "r" : "l", $Element['attributes']['src']);
  1795. return;
  1796. case "imgt": case "imgl": case "imgr": case "imgw":
  1797. gendoc::image($Element['name'][3], $Element['attributes']);
  1798. return;
  1799. case "mbl": case "mbr": case "mbw": gendoc::mouse_button($Element['name'][2]); return;
  1800. case "include": gendoc::include($Element['attributes']); return;
  1801. case "api": $m = explode(" ", $Element['attributes']); gendoc::api($m[0], $m[1]); return;
  1802. }
  1803. }
  1804. # identity map if element has no handler
  1805. $Element = $this->handle($Element);
  1806. if ($hasName)
  1807. {
  1808. switch($Element['name']) {
  1809. case "a": gendoc::external_link_open($Element['attributes']['href']); break;
  1810. case "b": case "strong": gendoc::bold_open(); break;
  1811. case "i": case "em": gendoc::italic_open(); break;
  1812. case "u": gendoc::underline_open(); break;
  1813. case "s": case "del": case "strike": gendoc::strike_open(); break;
  1814. case "sup": gendoc::superscript_open(); break;
  1815. case "sub": gendoc::subscript_open(); break;
  1816. case "blockquote": gendoc::blockquote_close(); break;
  1817. case "p": gendoc::paragraph_open(); break;
  1818. case "ol": gendoc::ordered_list_open(); break;
  1819. case "ul": gendoc::unordered_list_open(); break;
  1820. case "li": gendoc::list_item_open(); break;
  1821. case "dl": gendoc::data_list_open(); break;
  1822. case "dt": gendoc::data_topic_open(); break;
  1823. case "dd": gendoc::data_description_open(); break;
  1824. case "table": gendoc::table_open(); break;
  1825. case "tr": gendoc::table_row_open(); break;
  1826. case "th": gendoc::table_header_open(); break;
  1827. case "td":
  1828. if(strpos(@$Element['attributes']['style'], "right"))
  1829. gendoc::table_number_open();
  1830. else
  1831. gendoc::table_cell_open();
  1832. break;
  1833. case "info": case "hint": case "note": case "also": case "todo": case "warn":
  1834. gendoc::alert_box_open($Element['name']);
  1835. break;
  1836. }
  1837. }
  1838. $permitRawHtml = false;
  1839. if (isset($Element['text']))
  1840. {
  1841. $text = $Element['text'];
  1842. }
  1843. // very strongly consider an alternative if you're writing an
  1844. // extension
  1845. elseif (isset($Element['rawHtml']))
  1846. {
  1847. $text = $Element['rawHtml'];
  1848. if(substr($text, 0, 5) == "<doc>") { gendoc::doc(substr($text, 5, strlen($text) - 11)); return; }
  1849. if(substr($text, 0, 5) == "<cap>") { gendoc::caption(substr($text, 5, strlen($text) - 11)); return; }
  1850. if(substr($text, 0, 5) == "<fig>") { gendoc::figure(substr($text, 5, strlen($text) - 11)); return; }
  1851. if(substr($text, 0, 4) == "<!--") { return; }
  1852. $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
  1853. $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
  1854. }
  1855. $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']);
  1856. if ($hasContent)
  1857. {
  1858. if (isset($Element['elements']))
  1859. {
  1860. $this->elements($Element['elements']);
  1861. }
  1862. elseif (isset($Element['element']))
  1863. {
  1864. $this->element($Element['element']);
  1865. }
  1866. else
  1867. {
  1868. if (!$permitRawHtml)
  1869. {
  1870. gendoc::text(self::escape($text, true));
  1871. }
  1872. else
  1873. {
  1874. gendoc::text($text);
  1875. }
  1876. }
  1877. if($hasName)
  1878. {
  1879. switch($Element['name']) {
  1880. case "a": gendoc::external_link_close(); break;
  1881. case "b": case "strong": gendoc::bold_close(); break;
  1882. case "i": case "em": gendoc::italic_close(); break;
  1883. case "u": gendoc::underline_close(); break;
  1884. case "s": case "del": case "strike": gendoc::strike_close(); break;
  1885. case "sup": gendoc::superscript_close(); break;
  1886. case "sub": gendoc::subscript_close(); break;
  1887. case "blockquote": gendoc::quote_close(); break;
  1888. case "p": gendoc::paragraph_close(); break;
  1889. case "ol": gendoc::ordered_list_close(); break;
  1890. case "ul": gendoc::unordered_list_close(); break;
  1891. case "li": gendoc::list_item_close(); break;
  1892. case "dl": gendoc::data_list_close(); break;
  1893. case "dt": gendoc::data_topic_close(); break;
  1894. case "dd": gendoc::data_description_close(); break;
  1895. case "table": gendoc::table_close(); break;
  1896. case "tr": gendoc::table_row_close(); break;
  1897. case "th": gendoc::table_header_close(); break;
  1898. case "td": gendoc::table_cell_close(); break;
  1899. case "info": case "hint": case "note": case "also": case "todo": case "warn": gendoc::alert_box_close(); break;
  1900. }
  1901. }
  1902. }
  1903. }
  1904. static function parse($str)
  1905. {
  1906. self::instance()->text($str);
  1907. }
  1908. }