PHP and Javascript implementations of a simple markdown parser
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

markdown.js 35KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493
  1. // Blocks
  2. // - Paragraph
  3. // - Header 1-6 # ## ### #### ##### ###### or === ---
  4. // - Blockquote (nestable) >
  5. // - Unordered list (nestable) *_
  6. // - Ordered list (nestable) 1._
  7. // - Code block ```\ncode\n``` or 4 spaces/tab indent
  8. // - Horizontal rule --- - - - * * * etc
  9. // - Table -|-
  10. // - Definition list term\n: definition\n: alternate definition
  11. // - Footnote (bottom) citation[^1]
  12. // - Abbreviation (definition) *[ABC]: Abbrev Blah Cat
  13. // Inline
  14. // - Link [text](https://url)
  15. // - Emphasis *emphasized*
  16. // - Strong **bold**
  17. // - Inline code `code`
  18. // - Strikethrough ~strike~
  19. // - Image ![alt text](https://image){.cssclass}
  20. // - Footnote (inline) [^1]: footnote text
  21. // - Abbreviation (inline)
  22. class _MDHAlign {
  23. static Left = new _MDHAlign('Left');
  24. static Center = new _MDHAlign('Center');
  25. static Right = new _MDHAlign('Right');
  26. /** @var {String} */
  27. name;
  28. constructor(name) {
  29. this.name = name;
  30. }
  31. toString() {
  32. return `_MDHAlign.${this.name}`;
  33. }
  34. static toHTMLAttribute(align) {
  35. switch (align) {
  36. case _MDHAlign.Left: return ' align="left"';
  37. case _MDHAlign.Center: return ' align="center"';
  38. case _MDHAlign.Right: return ' align="right"';
  39. }
  40. return '';
  41. }
  42. }
  43. class _MDTokenType {
  44. static Text = new _MDTokenType('Text');
  45. static Whitespace = new _MDTokenType('Whitespace');
  46. static Underscore = new _MDTokenType('Underscore');
  47. static Asterisk = new _MDTokenType('Asterisk');
  48. static Slash = new _MDTokenType('Slash');
  49. static Tilde = new _MDTokenType('Tilde');
  50. static Bang = new _MDTokenType('Bang');
  51. static Backtick = new _MDTokenType('Backtick');
  52. static Label = new _MDTokenType('Label'); // content=label
  53. static URL = new _MDTokenType('URL'); // content=URL, extra=title
  54. static Email = new _MDTokenType('Email'); // content=email address, extra=title
  55. static SimpleLink = new _MDTokenType('SimpleLink'); // content=URL
  56. static SimpleEmail = new _MDTokenType('SimpleEmail'); // content=email address
  57. static Footnote = new _MDTokenType('Footnote'); // content=symbol
  58. static HTMLTag = new _MDTokenType('HTMLTag'); // content=tag string, tag=_MDHTMLTag
  59. #name;
  60. get name() {
  61. return this.#name;
  62. }
  63. constructor(name) {
  64. this.#name = name;
  65. }
  66. toString() {
  67. return this.constructor.name + `.${this.#name}`;
  68. }
  69. }
  70. class _MDToken {
  71. /** @var {String} */
  72. original;
  73. /** @var {_MDTokenType} */
  74. type;
  75. /** @var {String|null} */
  76. content;
  77. /** @var {String|null} */
  78. extra;
  79. /** @var {_MDHTMLTag|null} */
  80. tag;
  81. constructor(original, type, content=null, extra=null, tag=null) {
  82. this.original = original;
  83. this.type = type;
  84. this.content = content;
  85. this.extra = extra;
  86. this.tag = tag;
  87. }
  88. }
  89. class _MDSpan {
  90. toHTML(config) {
  91. throw new Error(self.constructor.name + ".toHTML not implemented");
  92. }
  93. static toHTML(spans, config) {
  94. return spans.map((span) => span.toHTML(config)).join("");
  95. }
  96. }
  97. class _MDMultiSpan extends _MDSpan {
  98. /** @var {_MDSpan[]} */
  99. content;
  100. /**
  101. * @param {_MDSpan[]} content
  102. */
  103. constructor(content) {
  104. super();
  105. this.content = content;
  106. }
  107. toHTML() {
  108. return _MDSpan.toHTML(this.content);
  109. }
  110. }
  111. class _MDTextSpan extends _MDSpan {
  112. /** @param {String} text */
  113. text;
  114. /**
  115. * @param {String} text
  116. */
  117. constructor(text) {
  118. super();
  119. this.text = text;
  120. }
  121. toHTML(config) {
  122. return this.text.replace('<', '&lt;');
  123. }
  124. }
  125. class _MDHTMLSpan extends _MDSpan {
  126. /** @param {String} html */
  127. html;
  128. /**
  129. * @param {String} html
  130. */
  131. constructor(html) {
  132. super();
  133. this.html = html;
  134. }
  135. toHTML(config) {
  136. return this.html;
  137. }
  138. }
  139. class _MDLink extends _MDSpan {
  140. /** @var {String} */
  141. link;
  142. /** @var {String|null} */
  143. target = null;
  144. /** @var {_MDSpan} */
  145. content;
  146. /**
  147. * @param {String} link
  148. * @param {_MDSpan} content
  149. */
  150. constructor(link, content) {
  151. super();
  152. this.link = link;
  153. this.content = content;
  154. }
  155. toHTML(config) {
  156. let escapedLink = this.link.replace('"', '&quot;');
  157. var html = `<a href="${escapedLink}"`;
  158. if (target) {
  159. let escapedTarget = this.target.replace('"', '&quot;');
  160. html += ` target="${escapedTarget}"`;
  161. }
  162. html += '>' + this.content.toHTML(config) + '</a>';
  163. return html;
  164. }
  165. }
  166. class _MDReferencedLink extends _MDLink {
  167. /** @var {String} id */
  168. id;
  169. constructor(id, content) {
  170. super(null, content);
  171. this.id = id;
  172. }
  173. toHTML(config) {
  174. if (this.link) {
  175. return super.toHTML(config);
  176. } else {
  177. let contentHTML = this.content.toHTML(config);
  178. return `[${contentHTML}][${this.id}]`;
  179. }
  180. }
  181. }
  182. class _MDEmphasis extends _MDSpan {
  183. /** @var {_MDSpan} content */
  184. #content;
  185. /**
  186. * @param {_MDSpan} content
  187. */
  188. constructor(content) {
  189. super();
  190. this.#content = content;
  191. }
  192. toHTML(config) {
  193. let contentHTML = this.#content.toHTML(config);
  194. return `<em>${contentHTML}</em>`;
  195. }
  196. }
  197. class _MDStrong extends _MDSpan {
  198. /** @var {_MDSpan} content */
  199. #content;
  200. /**
  201. * @param {_MDSpan} content
  202. */
  203. constructor(content) {
  204. super();
  205. this.#content = content;
  206. }
  207. toHTML(config) {
  208. let contentHTML = this.#content.toHTML(config);
  209. return `<strong>${contentHTML}</strong>`;
  210. }
  211. }
  212. class _MDStrikethrough extends _MDSpan {
  213. /** @var {_MDSpan} content */
  214. #content;
  215. /**
  216. * @param {_MDSpan} content
  217. */
  218. constructor(content) {
  219. super();
  220. this.#content = content;
  221. }
  222. toHTML(config) {
  223. let contentHTML = this.#content.toHTML(config);
  224. return `<strike>${contentHTML}</strike>`;
  225. }
  226. }
  227. class _MDInlineCode extends _MDSpan {
  228. /** @var {_MDSpan} content */
  229. #content;
  230. /**
  231. * @param {_MDSpan} content
  232. */
  233. constructor(content) {
  234. super();
  235. this.#content = content;
  236. }
  237. toHTML(config) {
  238. let contentHTML = this.#content.toHTML(config);
  239. return `<code>${contentHTML}</code>`;
  240. }
  241. }
  242. class _MDImage extends _MDSpan {
  243. /** @var {String} */
  244. source;
  245. /** @var {String|null} */
  246. alt;
  247. /**
  248. * @param {String} source
  249. */
  250. constructor(source, alt) {
  251. super();
  252. this.source = source;
  253. this.alt = alt;
  254. }
  255. toHTML(config) {
  256. let escapedSource = this.source.replace('"', '&quot;');
  257. let html = `<img src="${escapedSource}"`;
  258. if (this.alt) {
  259. let altEscaped = this.alt.replace('"', '&quot');
  260. html += ` alt="${altEscaped}"`;
  261. }
  262. html += '>';
  263. return html;
  264. }
  265. }
  266. class _MDReferencedImage extends _MDImage {
  267. /** @var {String} */
  268. id;
  269. /**
  270. * @param {String} id
  271. */
  272. constructor(id, alt) {
  273. super(null, alt);
  274. this.id = id;
  275. }
  276. toHTML(config) {
  277. if (this.source) {
  278. return super.toHTML(config);
  279. } else {
  280. let altEscaped = this.alt.replace('"', '&quot;');
  281. let idEscaped = this.id.replace('"', '&quot;');
  282. return `![${altEscaped}][${idEscaped}]`;
  283. }
  284. }
  285. }
  286. class _MDFootnoteReference extends _MDSpan {
  287. /** @var {String} */
  288. symbol;
  289. /** @var {Number} */
  290. differentiator = 0;
  291. /**
  292. * @param {String} symbol
  293. */
  294. constructor(symbol) {
  295. super();
  296. this.symbol = symbol;
  297. }
  298. toHTML(config) {
  299. return `<sup id="fnref-${this.symbol}-${this.differentiator}"><a href="#fndef-${this.symbol}">${this.symbol}</a></sup>`;
  300. }
  301. }
  302. class _MDAbbreviationReference extends _MDSpan {
  303. /** @var {_MDSpan} content */
  304. #content;
  305. /** @var {String} definition */
  306. #definition;
  307. /**
  308. * @param {_MDSpan} content
  309. */
  310. constructor(content, definition) {
  311. super();
  312. this.#content = content;
  313. this.#definition = definition;
  314. }
  315. toHTML(config) {
  316. let contentHTML = this.#content.toHTML(config);
  317. let definitionEscaped = this.#definition.replace('"', '&quot;');
  318. return `<abbr title="${definitionEscaped}">${contentHTML}</em>`;
  319. }
  320. }
  321. class _MDBlock {
  322. toHTML(config) {
  323. throw new Error(self.constructor.name + ".toHTML not implemented");
  324. }
  325. /**
  326. * @param {_MDBlock[]} blocks
  327. * @returns {String}
  328. */
  329. static toHTML(blocks, config) {
  330. return blocks.map((block) => block.toHTML(config)).join("\n");
  331. }
  332. }
  333. class _MDMultiBlock extends _MDBlock {
  334. /** @var {_MDBlock[]} */
  335. #blocks;
  336. /**
  337. * @param {_MDBlock[]} blocks
  338. */
  339. constructor(blocks) {
  340. super();
  341. this.#blocks = blocks;
  342. }
  343. toHTML(config) {
  344. return _MDBlock.toHTML(this.#blocks, config);
  345. }
  346. }
  347. class _MDParagraph extends _MDBlock {
  348. /** @var {_MDBlock} */
  349. content;
  350. /**
  351. * @param {_MDBlock} content
  352. */
  353. constructor(content) {
  354. super();
  355. this.content = content;
  356. }
  357. toHTML(config) {
  358. let contentHTML = this.content.toHTML(config);
  359. return `<p>${contentHTML}</p>\n`;
  360. }
  361. }
  362. class _MDHeader extends _MDBlock {
  363. /** @var {number} */
  364. level;
  365. /** @var {_MDBlock} */
  366. content;
  367. /**
  368. * @param {number} level
  369. * @param {_MDBlock} content
  370. */
  371. constructor(level, content) {
  372. super();
  373. this.level = level;
  374. this.content = content;
  375. }
  376. toHTML(config) {
  377. let contentHTML = this.content.toHTML(config);
  378. return `<h${this.level}>${contentHTML}</h${this.level}>\n`;
  379. }
  380. }
  381. class _MDBlockquote extends _MDBlock {
  382. /** @var {_MDBlock[]} */
  383. content;
  384. /**
  385. * @param {_MDBlock[]} content
  386. */
  387. constructor(content) {
  388. super();
  389. this.content = content;
  390. }
  391. toHTML(config) {
  392. let contentHTML = _MDBlock.toHTML(this.content, config);
  393. return `<blockquote>\n${contentHTML}\n</blockquote>`;
  394. }
  395. }
  396. class _MDUnorderedList extends _MDBlock {
  397. /** @var {_MDListItem[]} */
  398. items;
  399. /**
  400. * @param {_MDListItem[]} items
  401. */
  402. constructor(items) {
  403. super();
  404. this.items = items;
  405. }
  406. toHTML(config) {
  407. let contentHTML = _MDBlock.toHTML(this.items);
  408. return `<ul>\n${contentHTML}\n</ul>`;
  409. }
  410. }
  411. class _MDOrderedList extends _MDBlock {
  412. /** @var {_MDListItem[]} */
  413. items;
  414. /**
  415. * @param {_MDListItem[]} items
  416. */
  417. constructor(items) {
  418. super();
  419. this.items = items;
  420. }
  421. toHTML(config) {
  422. let contentHTML = _MDBlock.toHTML(this.items);
  423. return `<ol>\n${contentHTML}\n</ol>`;
  424. }
  425. }
  426. class _MDListItem extends _MDBlock {
  427. /** @var {_MDBlock} */
  428. content;
  429. /**
  430. * @param {_MDBlock} content
  431. */
  432. constructor(content) {
  433. super();
  434. this.content = content;
  435. }
  436. toHTML(config) {
  437. let contentHTML = this.content.toHTML(config);
  438. return `<li>${contentHTML}</li>`;
  439. }
  440. }
  441. class _MDCodeBlock extends _MDBlock {
  442. /** @var {String} */
  443. #code;
  444. /**
  445. * @param {String} code
  446. */
  447. constructor(code) {
  448. super();
  449. this.#code = code;
  450. }
  451. toHTML(config) {
  452. return `<pre><code>${this.#code}</code></pre>`;
  453. }
  454. }
  455. class _MDHorizontalRule extends _MDBlock {
  456. toHTML(config) {
  457. return "<hr>\n";
  458. }
  459. }
  460. class _MDTableCell extends _MDBlock {
  461. /** @var {_MDBlock} */
  462. #content;
  463. /** @var {_MDHAlign|null} */
  464. align = null;
  465. /**
  466. * @param {_MDBlock} content
  467. */
  468. constructor(content) {
  469. super();
  470. this.#content = content;
  471. }
  472. toHTML(config) {
  473. let contentHTML = this.#content.toHTML(config);
  474. let alignAttribute = _MDHAlign.toHTMLAttribute(this.align);
  475. return `<td${alignAttribute}>${contentHTML}</td>`;
  476. }
  477. }
  478. class _MDTableHeaderCell extends _MDTableCell {
  479. toHTML(config) {
  480. let html = super.toHTML(config);
  481. let groups = /^<td(.*)td>$/.exec(html);
  482. return `<th${groups[1]}th>`;
  483. }
  484. }
  485. class _MDTableRow extends _MDBlock {
  486. /** @var {_MDTableCell[]|_MDTableHeaderCell[]} */
  487. #cells;
  488. /**
  489. * @param {_MDTableCell[]|_MDTableHeaderCell[]} cells
  490. */
  491. constructor(cells) {
  492. super();
  493. this.#cells = cells;
  494. }
  495. /**
  496. * @param {_MDHAlign[]} alignments
  497. */
  498. applyAlignments(alignments) {
  499. for (var i = 0; i < this.#cells.length; i++) {
  500. let cell = this.#cells[i];
  501. let align = i < alignments.length ? alignments[i] : null;
  502. cell.align = align;
  503. }
  504. }
  505. toHTML(config) {
  506. let cellsHTML = _MDBlock.toHTML(this.#cells, config);
  507. return `<tr>\n${cellsHTML}\n</tr>`;
  508. }
  509. }
  510. class _MDTable extends _MDBlock {
  511. /** @var {_MDTableRow} */
  512. #headerRow;
  513. /** @var {_MDTableRow[]} */
  514. #bodyRows;
  515. /**
  516. * @param {_MDTableRow} headerRow
  517. * @param {_MDTableRow[]} bodyRows
  518. */
  519. constructor(headerRow, bodyRows) {
  520. super();
  521. this.#headerRow = headerRow;
  522. this.#bodyRows = bodyRows;
  523. }
  524. toHTML(config) {
  525. let headerRowHTML = this.#headerRow.toHTML(config);
  526. let bodyRowsHTML = _MDBlock.toHTML(this.#bodyRows);
  527. return `<table>\n<thead>\n${headerRowHTML}\n</thead>\n<tbody>\n${bodyRowsHTML}\n</tbody>\n</table>`;
  528. }
  529. }
  530. class _MDDefinitionList extends _MDBlock {
  531. /** @var {_MDBlock[]} */
  532. #content;
  533. /**
  534. * @param {_MDBlock[]} content
  535. */
  536. constructor(content) {
  537. super();
  538. this.#content = content;
  539. }
  540. toHTML(config) {
  541. let contentHTML = _MDBlock.toHTML(this.#content);
  542. return `<dl>\n${contentHTML}\n</dl>`;
  543. }
  544. }
  545. class _MDDefinitionTerm extends _MDBlock {
  546. /** @var {_MDBlock} */
  547. #content;
  548. /**
  549. * @param {_MDBlock} content
  550. */
  551. constructor(content) {
  552. super();
  553. this.#content = content;
  554. }
  555. toHTML(config) {
  556. let contentHTML = this.#content.toHTML(config);
  557. return `<dt>${contentHTML}</dt>`;
  558. }
  559. }
  560. class _MDDefinitionDefinition extends _MDBlock {
  561. /** @var {_MDBlock} */
  562. #content;
  563. /**
  564. * @param {_MDBlock} content
  565. */
  566. constructor(content) {
  567. super();
  568. this.#content = content;
  569. }
  570. toHTML(config) {
  571. let contentHTML = this.#content.toHTML(config);
  572. return `<dd>${contentHTML}</dd>`;
  573. }
  574. }
  575. class _MDFootnoteContent extends _MDBlock {
  576. /** @var {String} */
  577. #id;
  578. /** @var {_MDBlock} */
  579. #content;
  580. /**
  581. * @param {String} id
  582. * @param {_MDBlock} content
  583. */
  584. constructor(id, content) {
  585. super();
  586. this.#id = id;
  587. this.#content = content;
  588. }
  589. toHTML(config) {
  590. // TODO: Forward and back links
  591. // TODO: Deferring footnotes to end of document
  592. //<ol>
  593. //<li id="fn:1" role="doc-endnote">
  594. //<p>Footnote&nbsp;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
  595. //</li>
  596. //</ol>
  597. return '';
  598. }
  599. }
  600. class _MDAbbreviationOccurrence extends _MDBlock {
  601. /** @var {String} */
  602. #label;
  603. /** @var {String} */
  604. #definition;
  605. /**
  606. * @param {String} label
  607. * @param {String} definition
  608. */
  609. constructor(label, definition) {
  610. super();
  611. this.#label = label;
  612. this.#definition = definition;
  613. }
  614. toHTML(config) {
  615. return `<abbr title="${this.#definition.replace('"', '&quot;')}">${this.#label}</abbr>`;
  616. }
  617. }
  618. class _MDInline extends _MDBlock {
  619. /** @var {String} */
  620. #raw;
  621. /**
  622. * @param {String} raw
  623. */
  624. constructor(raw) {
  625. super();
  626. this.#raw = raw;
  627. }
  628. toHTML(config) {
  629. return this.#raw;
  630. }
  631. }
  632. class _MDHTMLTag {
  633. /** @var {String} */
  634. fullTag;
  635. /** @var {String} */
  636. tagName;
  637. /** @var {Boolean} */
  638. isCloser;
  639. /** @var {Object} */
  640. attributes;
  641. /**
  642. * @param {String} fullTag
  643. * @param {String} tagName
  644. * @param {Boolean} isCloser
  645. * @param {Object} attributes
  646. */
  647. constructor(fullTag, tagName, isCloser, attributes) {
  648. this.fullTag = fullTag;
  649. this.tagName = tagName;
  650. this.isCloser = isCloser;
  651. this.attributes = attributes;
  652. }
  653. }
  654. class _MDState {
  655. /** @var {String[]} */
  656. lines = [];
  657. /** @var {object} */
  658. abbreviations = {};
  659. /** @var {object} */
  660. footnotes = {};
  661. /** @var {number} */
  662. p = 0;
  663. copy() {
  664. let cp = new _MDState();
  665. cp.abbreviations = this.abbreviations;
  666. cp.footnotes = this.footnotes;
  667. cp.p = this.p;
  668. return cp;
  669. }
  670. /** @param {_MDState} other */
  671. apply(other) {
  672. this.abbreviations = other.abbreviations;
  673. this.footnotes = other.footnotes;
  674. this.p = other.p;
  675. }
  676. hasLines(minCount, p=-1) {
  677. let relativeTo = (p < 0) ? this.p : p;
  678. return relativeTo + minCount <= this.lines.length;
  679. }
  680. }
  681. class MDConfig {
  682. }
  683. class Markdown {
  684. /**
  685. * @param {String} line
  686. */
  687. static #stripIndent(line, count=1) {
  688. let regex = new RegExp(`^(: {1,4}|\\t){${count}}`);
  689. return line.replace(regex, '');
  690. }
  691. /**
  692. * @param {String} line
  693. * @param {Boolean} fullIndentsOnly
  694. * @returns {Number} indent count
  695. */
  696. static #countIndents(line, fullIndentsOnly=false) {
  697. var count = 0;
  698. var lastLine = line;
  699. while (line.length > 0) {
  700. line = (fullIndentsOnly)
  701. ? line.replace(/^(?: {4}|\t)/, '')
  702. : line.replace(/^(?: {1,4}|\t)/, '');
  703. if (line != lastLine) {
  704. count++;
  705. } else {
  706. break;
  707. }
  708. lastLine = line;
  709. }
  710. return count;
  711. }
  712. /**
  713. * @param {_MDState} state
  714. * @returns {_MDBlock[]}
  715. */
  716. static #readBlocks(state) {
  717. var blocks = [];
  718. while (state.hasLines(1)) {
  719. let block = this.#readNextBlock(state);
  720. if (block) {
  721. blocks.push(block);
  722. } else {
  723. break;
  724. }
  725. }
  726. return blocks;
  727. }
  728. /**
  729. * @param {_MDState} state
  730. * @returns {_MDBlock}
  731. */
  732. static #readNextBlock(state) {
  733. while (state.hasLines(1) && state.lines[state.p].trim().length == 0) {
  734. console.info("Skipping blank line " + state.p);
  735. state.p++;
  736. }
  737. var block;
  738. block = this.#readUnderlineHeader(state); if (block) return block;
  739. block = this.#readHashHeader(state); if (block) return block;
  740. block = this.#readBlockQuote(state); if (block) return block;
  741. block = this.#readUnorderedList(state); if (block) return block;
  742. block = this.#readOrderedList(state); if (block) return block;
  743. block = this.#readFencedCodeBlock(state); if (block) return block;
  744. block = this.#readIndentedCodeBlock(state); if (block) return block;
  745. block = this.#readHorizontalRule(state); if (block) return block;
  746. block = this.#readTable(state); if (block) return block;
  747. block = this.#readDefinitionList(state); if (block) return block;
  748. block = this.#readFootnoteDef(state); if (block) return block;
  749. block = this.#readAbbreviationDef(state); if (block) return block;
  750. block = this.#readParagraph(state); if (block) return block;
  751. return null;
  752. }
  753. static #baseURLRegex = /(?:(?:(?:[a-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[a-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[a-z0-9\.\-]+)(?:(?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/i;
  754. static #baseEmailRegex = /(?:(?:[^<>()\[\]\\.,;:\s@"]+(?:\.[^<>()\[\]\\.,;:\s@"]+)*)|(?:".+"))@(?:(?:\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(?:(?:[a-z\-0-9]+\.)+[a-z]{2,}))/i;
  755. static #footnoteWithTitleRegex = /^\[\^\s*([^\]"]+?)\s+"(.*?)"\s*\]/;
  756. static #footnoteRegex = /^\[\^\s*([^\]]+?)\s*\]/;
  757. static #labelRegex = /^\[(.*?)\]/;
  758. static #urlWithTitleRegex = /^\((\S+?)\s+"(.*?)"\)/i;
  759. static #urlRegex = /^\((\S+?)\)/i;
  760. static #emailWithTitleRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s+\"(.*?)\"\\s*\\)", "i");
  761. static #emailRegex = new RegExp("^\\(\\s*(" + this.#baseEmailRegex.source + ")\\s*\\)", "i");
  762. static #simpleURLRegex = new RegExp("^<" + this.#baseURLRegex.source + ">", "i");
  763. static #simpleEmailRegex = new RegExp("^<" + this.#baseEmailRegex.source + ">", "i");
  764. /**
  765. * @param {String} line
  766. * @returns {_MDHTMLTag|null} HTML tag if possible
  767. */
  768. static #htmlTag(line) {
  769. let expectOpenBracket = 0;
  770. let expectCloserOrName = 1;
  771. let expectName = 2;
  772. let expectAttributeNameOrEnd = 3;
  773. let expectEqualsOrAttributeOrEnd = 4;
  774. let expectAttributeValue = 5;
  775. let expectCloseBracket = 6;
  776. var isCloser = false;
  777. var tagName = '';
  778. var attributeName = '';
  779. var attributeValue = '';
  780. var attributeQuote = null;
  781. var attributes = {};
  782. var fullTag = null;
  783. let endAttribute = function() {
  784. if (attributeName.length > 0) {
  785. if (attributeValue.length > 0 || attributeQuote) {
  786. attributes[attributeName] = attributeValue;
  787. } else {
  788. attributes[attributeName] = true;
  789. }
  790. }
  791. attributeName = '';
  792. attributeValue = '';
  793. attributeQuote = null;
  794. };
  795. var expect = expectOpenBracket;
  796. for (var p = 0; p < line.length && fullTag === null; p++) {
  797. let ch = line.substring(p, p + 1);
  798. switch (expect) {
  799. case expectOpenBracket:
  800. if (ch != '<') return null;
  801. expect = expectCloserOrName;
  802. break;
  803. case expectCloserOrName:
  804. if (ch == '/') {
  805. isCloser = true;
  806. } else {
  807. p--;
  808. }
  809. expect = expectName;
  810. break;
  811. case expectName:
  812. if (tagName.length == 0) {
  813. if (/[a-z]/i.exec(ch) === null) return null;
  814. tagName += ch;
  815. } else {
  816. if (/[a-z0-9]/i.exec(ch)) {
  817. tagName += ch;
  818. } else {
  819. p--;
  820. expect = (isCloser) ? expectCloseBracket : expectAttributeNameOrEnd;
  821. }
  822. }
  823. break;
  824. case expectAttributeNameOrEnd:
  825. if (attributeName.length == 0) {
  826. if (/\s/.exec(ch)) {
  827. // skip whitespace
  828. } else if (ch == '/') {
  829. expect = expectCloseBracket;
  830. } else if (ch == '>') {
  831. fullTag = line.substring(0, p + 1);
  832. break;
  833. } else if (/[a-z0-9-]/i.exec(ch)) {
  834. attributeName += ch;
  835. } else {
  836. return null;
  837. }
  838. } else if (/\s/.exec(ch)) {
  839. expect = expectEqualsOrAttributeOrEnd;
  840. } else if (ch == '/') {
  841. endAttribute();
  842. expect = expectCloseBracket;
  843. } else if (ch == '>') {
  844. endAttribute();
  845. fullTag = line.substring(0, p + 1);
  846. break;
  847. } else if (ch == '=') {
  848. expect = expectAttributeValue;
  849. } else if (/[a-z0-9-]/i.exec(ch)) {
  850. attributeName += ch;
  851. } else {
  852. return null;
  853. }
  854. break;
  855. case expectEqualsOrAttributeOrEnd:
  856. if (ch == '=') {
  857. expect = expectAttributeValue;
  858. } else if (/\s/.exec(ch)) {
  859. // skip whitespace
  860. } else if (ch == '/') {
  861. expect = expectCloseBracket;
  862. } else if (ch == '>') {
  863. fullTag = line.substring(0, p + 1);
  864. break;
  865. } else if (/[a-z]/i.exec(ch)) {
  866. endAttribute();
  867. expect = expectAttributeNameOrEnd;
  868. p--;
  869. }
  870. break;
  871. case expectAttributeValue:
  872. if (attributeValue.length == 0) {
  873. if (attributeQuote === null) {
  874. if (/\s/.exec(ch)) {
  875. // skip whitespace
  876. } else if (ch == '"' || ch == "'") {
  877. attributeQuote = ch;
  878. } else {
  879. attributeQuote = '';
  880. p--;
  881. }
  882. } else {
  883. if (ch === attributeQuote) {
  884. // Empty string
  885. endAttribute();
  886. expect = expectAttributeNameOrEnd;
  887. } else if (attributeQuote === '' && (ch == '/' || ch == '>')) {
  888. return null;
  889. } else {
  890. attributeValue += ch;
  891. }
  892. }
  893. } else {
  894. if (ch === attributeQuote) {
  895. endAttribute();
  896. expect = expectAttributeNameOrEnd;
  897. } else if (attributeQuote === '' && /\s/.exec(ch)) {
  898. endAttribute();
  899. expect = expectAttributeNameOrEnd;
  900. } else {
  901. attributeValue += ch;
  902. }
  903. }
  904. break;
  905. case expectCloseBracket:
  906. if (/\s/.exec(ch)) {
  907. // ignore whitespace
  908. } else if (ch == '>') {
  909. fullTag = line.substring(0, p + 1);
  910. break;
  911. }
  912. break;
  913. }
  914. }
  915. if (fullTag === null) return null;
  916. endAttribute();
  917. return new _MDHTMLTag(fullTag, tagName, isCloser, attributes);
  918. }
  919. /**
  920. * @param {String} line
  921. * @returns {_MDToken[]} tokens
  922. */
  923. static #tokenize(line) {
  924. var tokens = [];
  925. var text = '';
  926. var expectLiteral = false;
  927. var groups = null;
  928. var tag = null;
  929. const endText = function() {
  930. if (text.length == 0) return;
  931. let textGroups = /^(\s*)(?:(\S|\S.*\S)(\s*?))?$/.exec(text);
  932. if (textGroups !== null) {
  933. if (textGroups[1].length > 0) {
  934. tokens.push(new _MDToken(textGroups[1], _MDTokenType.Whitespace, textGroups[1]));
  935. }
  936. if (textGroups[2] !== undefined && textGroups[2].length > 0) {
  937. tokens.push(new _MDToken(textGroups[2], _MDTokenType.Text, textGroups[2]));
  938. }
  939. if (textGroups[3] !== undefined && textGroups[3].length > 0) {
  940. tokens.push(new _MDToken(textGroups[3], _MDTokenType.Whitespace, textGroups[3]));
  941. }
  942. } else {
  943. tokens.push(new _MDToken(text, _MDTokenType.Text, text));
  944. }
  945. text = '';
  946. }
  947. for (var p = 0; p < line.length; p++) {
  948. let ch = line.substring(p, p + 1);
  949. let remainder = line.substring(p);
  950. if (expectLiteral) {
  951. // TODO: Check for only allowable escapable characters
  952. text += ch;
  953. continue;
  954. }
  955. if (ch == '\\') {
  956. expectLiteral = true;
  957. } else if (ch == '*') {
  958. endText();
  959. tokens.push(new _MDToken(ch, _MDTokenType.Asterisk));
  960. } else if (ch == '_') {
  961. endText();
  962. tokens.push(new _MDToken(ch, _MDTokenType.Underscore));
  963. } else if (ch == '`') {
  964. endText();
  965. tokens.push(new _MDToken(ch, _MDTokenType.Backtick));
  966. } else if (ch == '~') {
  967. endText();
  968. tokens.push(new _MDToken(ch, _MDTokenType.Tilde));
  969. } else if (ch == '!') {
  970. endText();
  971. tokens.push(new _MDToken(ch, _MDTokenType.Bang));
  972. } else if (groups = this.#footnoteWithTitleRegex.exec(remainder)) {
  973. // Footnote with title [^1 "Foo"]
  974. endText();
  975. tokens.push(new _MDToken(groups[0], _MDTokenType.Footnote, groups[1], groups[2]));
  976. p += groups[0].length - 1;
  977. } else if (groups = this.#footnoteRegex.exec(remainder)) {
  978. // Footnote without title [^1]
  979. endText();
  980. tokens.push(new _MDToken(groups[0], _MDTokenType.Footnote, groups[1]));
  981. p += groups[0].length - 1;
  982. } else if (groups = this.#labelRegex.exec(remainder)) {
  983. // Label/ref for link/image [Foo]
  984. endText();
  985. tokens.push(new _MDToken(groups[0], _MDTokenType.Label, groups[1]));
  986. p += groups[0].length - 1;
  987. } else if (groups = this.#urlWithTitleRegex.exec(remainder)) {
  988. // URL with title (https://foo "Bar")
  989. endText();
  990. tokens.push(new _MDToken(groups[0], _MDTokenType.URL, groups[1], groups[2]));
  991. p += groups[0].length - 1;
  992. } else if (groups = this.#emailWithTitleRegex.exec(remainder)) {
  993. // Email address with title (user@example.com "Foo")
  994. endText();
  995. tokens.push(new _MDToken(groups[0], _MDTokenType.Email, groups[1]));
  996. p += groups[0].length - 1;
  997. } else if (groups = this.#urlRegex.exec(remainder)) {
  998. // URL (https://example.com)
  999. endText();
  1000. tokens.push(new _MDToken(groups[0], _MDTokenType.URL, groups[1]));
  1001. p += groups[0].length - 1;
  1002. } else if (groups = this.#emailRegex.exec(remainder)) {
  1003. // Email (user@example.com)
  1004. endText();
  1005. tokens.push(new _MDToken(groups[0], _MDTokenType.Email, groups[1]));
  1006. p += groups[0].length - 1;
  1007. } else if (groups = this.#simpleURLRegex.exec(remainder)) {
  1008. // Simple URL <https://example.com>
  1009. endText();
  1010. tokens.push(new _MDToken(groups[0], _MDTokenType.SimpleLink, groups[1]));
  1011. p += groups[0].length - 1;
  1012. } else if (groups = this.#simpleEmailRegex.exec(remainder)) {
  1013. // Simple email <user@example.com>
  1014. endText();
  1015. tokens.push(new _MDToken(groups[0], _MDTokenType.SimpleEmail, groups[1]));
  1016. p += groups[0].length - 1;
  1017. } else if (tag = this.#htmlTag(remainder)) {
  1018. endText();
  1019. tokens.push(new _MDToken(tag.fullTag, _MDTokenType.HTMLTag, tag.fullTag, null, tag));
  1020. p += tag.fullTag.length - 1;
  1021. } else {
  1022. text += ch;
  1023. }
  1024. }
  1025. endText();
  1026. return tokens;
  1027. }
  1028. /**
  1029. * @param {_MDState} state
  1030. * @param {String} line
  1031. * @returns {_MDBlock|null}
  1032. */
  1033. static #readInline(state, line) {
  1034. let tokens = this.#tokenize(line);
  1035. var spans = [];
  1036. // - Link [text](https://url)
  1037. // - Emphasis *emphasized* or _emphasized_ or /emphasized/
  1038. // - Strong **bold** or __bold__
  1039. // - Inline code `code` or ``code``
  1040. // - Strikethrough ~strike~ or ~~strike~~
  1041. // - Image ![alt text](https://image){.cssclass}
  1042. // - Footnote (inline) [^1]: footnote text
  1043. // - Abbreviation (inline)
  1044. //
  1045. // Tokens:
  1046. // _
  1047. // *
  1048. // /
  1049. // ~
  1050. // [label] or []
  1051. // (url)
  1052. // !
  1053. // <>
  1054. // `
  1055. return new _MDInline(line);
  1056. }
  1057. /**
  1058. * Reads the contents of something like a list item
  1059. * @param {_MDState} state
  1060. * @param {number} firstLineStartPos
  1061. * @param {RegExp} stopRegex
  1062. * @returns {_MDBlock}
  1063. */
  1064. static #readInteriorContent(state, firstLineStartPos, stopRegex) {
  1065. // FIXME: When reading <li> content need to detect nested list without
  1066. // a blank line
  1067. var p = state.p;
  1068. var seenBlankLine = false;
  1069. var needsBlocks = false;
  1070. var lines = [];
  1071. while (p < state.lines.length) {
  1072. let line = state.lines[p++];
  1073. if (p == state.p + 1) {
  1074. line = line.substring(firstLineStartPos);
  1075. }
  1076. let isBlank = line.trim().length == 0;
  1077. let isIndented = /^\s+/.exec(line) !== null;
  1078. if (isBlank) {
  1079. seenBlankLine = true;
  1080. lines.push(line.trim());
  1081. } else if (stopRegex && stopRegex.exec(line)) {
  1082. p--;
  1083. break;
  1084. } else if (isIndented) {
  1085. if (seenBlankLine) {
  1086. needsBlocks = true;
  1087. }
  1088. lines.push(this.#stripIndent(line));
  1089. } else {
  1090. if (seenBlankLine) {
  1091. p--;
  1092. break;
  1093. }
  1094. lines.push(this.#stripIndent(line));
  1095. }
  1096. }
  1097. while (lines.length > 0 && lines[lines.length - 1].trim().length == 0) {
  1098. lines.pop();
  1099. }
  1100. if (needsBlocks) {
  1101. let substate = new _MDState();
  1102. substate.lines = lines;
  1103. substate.abbreviations = state.abbreviations;
  1104. substate.footnotes = state.footnotes;
  1105. let blocks = this.#readBlocks(substate);
  1106. state.p = p;
  1107. return new _MDMultiBlock(blocks);
  1108. } else {
  1109. state.p = p;
  1110. return this.#readInline(state, lines.join("\n"));
  1111. }
  1112. }
  1113. /**
  1114. * @param {_MDState} state
  1115. * @returns {_MDBlock|null}
  1116. */
  1117. static #readUnderlineHeader(state) {
  1118. var p = state.p;
  1119. if (!state.hasLines(2)) return null;
  1120. let contentLine = state.lines[p++].trim();
  1121. let underLine = state.lines[p++].trim();
  1122. if (contentLine == '') return null;
  1123. if (/^=+$/.exec(underLine)) {
  1124. state.p = p;
  1125. return new _MDHeader(1, this.#readInline(state, contentLine));
  1126. }
  1127. if (/^\-+$/.exec(underLine)) {
  1128. state.p = p;
  1129. return new _MDHeader(2, this.#readInline(state, contentLine));
  1130. }
  1131. return null;
  1132. }
  1133. /**
  1134. * @param {_MDState} state
  1135. * @returns {_MDBlock|null}
  1136. */
  1137. static #readHashHeader(state) {
  1138. var p = state.p;
  1139. var groups = /^(#{1,6})\s*([^#].*)\s*$/.exec(state.lines[p++]);
  1140. if (groups === null) return null;
  1141. state.p = p;
  1142. return new _MDHeader(groups[1].length, this.#readInline(state, groups[2]));
  1143. }
  1144. /**
  1145. * @param {_MDState} state
  1146. * @returns {_MDBlock|null}
  1147. */
  1148. static #readBlockQuote(state) {
  1149. var blockquoteLines = [];
  1150. var p = state.p;
  1151. while (p < state.lines.length) {
  1152. let line = state.lines[p++];
  1153. if (line.startsWith(">")) {
  1154. blockquoteLines.push(line);
  1155. } else {
  1156. break;
  1157. }
  1158. }
  1159. if (blockquoteLines.length > 0) {
  1160. let contentLines = blockquoteLines.map(function(line) {
  1161. return line.substring(1).replace(/^ {0,3}\t?/, '');
  1162. });
  1163. let substate = new _MDState();
  1164. substate.lines = contentLines;
  1165. substate.abbreviations = state.abbreviations;
  1166. substate.footnotes = state.footnotes;
  1167. let quotedBlocks = this.#readBlocks(substate);
  1168. state.p = p;
  1169. return new _MDBlockquote(quotedBlocks);
  1170. }
  1171. return null;
  1172. }
  1173. /**
  1174. * @param {_MDState} state
  1175. * @returns {_MDListItem|null}
  1176. */
  1177. static #readUnorderedListItem(state) {
  1178. var p = state.p;
  1179. let line = state.lines[p];
  1180. let groups = /^([\*\+\-]\s+)(.*)$/.exec(line);
  1181. if (groups === null) return null;
  1182. return new _MDListItem(this.#readInteriorContent(state, groups[1].length, /^[\*\+\-]\s+/));
  1183. }
  1184. /**
  1185. * @param {_MDState} state
  1186. * @returns {_MDBlock|null}
  1187. */
  1188. static #readUnorderedList(state) {
  1189. var items = [];
  1190. var item = null;
  1191. do {
  1192. item = this.#readUnorderedListItem(state);
  1193. if (item) items.push(item);
  1194. } while (item);
  1195. if (items.length == 0) return null;
  1196. return new _MDUnorderedList(items);
  1197. }
  1198. /**
  1199. * @param {_MDState} state
  1200. * @returns {_MDListItem|null}
  1201. */
  1202. static #readOrderedListItem(state) {
  1203. var p = state.p;
  1204. let line = state.lines[p];
  1205. let groups = /^(\d+\.\s+)(.*)$/.exec(line);
  1206. if (groups === null) return null;
  1207. return new _MDListItem(this.#readInteriorContent(state, groups[1].length, /^\d+\.\s+/));
  1208. }
  1209. /**
  1210. * @param {_MDState} state
  1211. * @returns {_MDBlock|null}
  1212. */
  1213. static #readOrderedList(state) {
  1214. var items = [];
  1215. var item = null;
  1216. do {
  1217. item = this.#readOrderedListItem(state);
  1218. if (item) items.push(item);
  1219. } while (item);
  1220. if (items.length == 0) return null;
  1221. return new _MDOrderedList(items);
  1222. }
  1223. /**
  1224. * @param {_MDState} state
  1225. * @returns {_MDBlock|null}
  1226. */
  1227. static #readFencedCodeBlock(state) {
  1228. var p = state.p;
  1229. if (state.lines[p++].trim() != '```') return null;
  1230. var codeLines = [];
  1231. while (state.hasLines(1, p)) {
  1232. let line = state.lines[p++];
  1233. if (line.trim() == '```') {
  1234. state.p = p;
  1235. return new _MDCodeBlock(codeLines.join("\n"));
  1236. }
  1237. codeLines.push(line);
  1238. }
  1239. return null;
  1240. }
  1241. /**
  1242. * @param {_MDState} state
  1243. * @returns {_MDBlock|null}
  1244. */
  1245. static #readIndentedCodeBlock(state) {
  1246. var p = state.p;
  1247. var codeLines = [];
  1248. while (state.hasLines(1, p)) {
  1249. let line = state.lines[p++];
  1250. if (this.#countIndents(line, true) < 1) {
  1251. p--;
  1252. break;
  1253. }
  1254. codeLines.push(this.#stripIndent(line));
  1255. }
  1256. if (codeLines.length == 0) return null;
  1257. state.p = p;
  1258. return new _MDCodeBlock(codeLines.join("\n"));
  1259. }
  1260. /**
  1261. * @param {_MDState} state
  1262. * @returns {_MDBlock|null}
  1263. */
  1264. static #readHorizontalRule(state) {
  1265. var p = state.p;
  1266. let line = state.lines[p++];
  1267. if (/^\s*(?:\-(?:\s*\-){2,}|\*(?:\s*\*){2,})\s*$/.exec(line)) {
  1268. state.p = p;
  1269. return new _MDHorizontalRule();
  1270. }
  1271. return null;
  1272. }
  1273. /**
  1274. * @param {_MDState} state
  1275. * @param {Boolean} isHeader
  1276. * @return {_MDTableRow|null}
  1277. */
  1278. static #readTableRow(state, isHeader) {
  1279. if (!state.hasLines(1)) return null;
  1280. var p = state.p;
  1281. let line = state.lines[p++].trim();
  1282. if (/.*\|.*/.exec(line) === null) return null;
  1283. if (line.startsWith('|')) line = line.substring(1);
  1284. if (line.endsWith('|')) line = line.substring(0, line.length - 1);
  1285. let cellTokens = line.split('|');
  1286. let cells = cellTokens.map(function(token) {
  1287. let content = Markdown.#readInline(state, token);
  1288. return isHeader ? new _MDTableHeaderCell(content) : new _MDTableCell(content);
  1289. });
  1290. state.p = p;
  1291. return new _MDTableRow(cells);
  1292. }
  1293. /**
  1294. * @param {String} line
  1295. * @returns {_MDHAlign[]}
  1296. */
  1297. static #parseColumnAlignments(line) {
  1298. line = line.trim();
  1299. if (line.startsWith('|')) line = line.substring(1);
  1300. if (line.endsWith('|')) line = line.substring(0, line.length - 1);
  1301. return line.split('|').map(function(token) {
  1302. token = token.trim();
  1303. if (token.startsWith(':')) {
  1304. if (token.endsWith(':')) {
  1305. return _MDHAlign.Center;
  1306. }
  1307. return _MDHAlign.Left;
  1308. } else if (token.endsWith(':')) {
  1309. return _MDHAlign.Right;
  1310. }
  1311. return null;
  1312. });
  1313. }
  1314. /**
  1315. * @param {_MDState} state
  1316. * @returns {_MDBlock|null}
  1317. */
  1318. static #readTable(state) {
  1319. if (!state.hasLines(2)) return null;
  1320. let startP = state.p;
  1321. let headerRow = this.#readTableRow(state, true);
  1322. if (headerRow === null) {
  1323. state.p = startP;
  1324. return null;
  1325. }
  1326. let dividerLine = state.lines[state.p++];
  1327. let dividerGroups = /^\s*[|]?(?:\s*[:]?-+[:]?\s*\|)(?:\s*[:]?-+[:]?\s*)[|]?\s*$/.exec(dividerLine);
  1328. if (dividerGroups === null) {
  1329. state.p = startP;
  1330. return null;
  1331. }
  1332. let columnAlignments = this.#parseColumnAlignments(dividerLine);
  1333. headerRow.applyAlignments(columnAlignments);
  1334. var bodyRows = [];
  1335. while (state.hasLines(1)) {
  1336. let row = this.#readTableRow(state, false);
  1337. if (row === null) break;
  1338. row.applyAlignments(columnAlignments);
  1339. bodyRows.push(row);
  1340. }
  1341. return new _MDTable(headerRow, bodyRows);
  1342. }
  1343. /**
  1344. * @param {_MDState} state
  1345. * @returns {_MDBlock|null}
  1346. */
  1347. static #readDefinitionList(state) {
  1348. // TODO
  1349. return null;
  1350. }
  1351. /**
  1352. * @param {_MDState} state
  1353. * @returns {_MDBlock|null}
  1354. */
  1355. static #readFootnoteDef(state) {
  1356. // TODO
  1357. return null;
  1358. }
  1359. /**
  1360. * @param {_MDState} state
  1361. * @returns {_MDBlock|null}
  1362. */
  1363. static #readAbbreviationDef(state) {
  1364. // TODO
  1365. return null;
  1366. }
  1367. /**
  1368. * @param {_MDState} state
  1369. * @returns {_MDBlock|null}
  1370. */
  1371. static #readParagraph(state) {
  1372. if (!state.hasLines(1)) return null;
  1373. var paragraphLines = [];
  1374. var p = state.p;
  1375. while (p < state.lines.length) {
  1376. let line = state.lines[p++];
  1377. if (line.trim().length == 0) {
  1378. break;
  1379. }
  1380. paragraphLines.push(line);
  1381. }
  1382. if (paragraphLines.length > 0) {
  1383. state.p = p;
  1384. let content = paragraphLines.join("\n");
  1385. return new _MDParagraph(this.#readInline(state, content));
  1386. }
  1387. return null;
  1388. }
  1389. /**
  1390. * @param {String} markdown
  1391. * @returns {String} HTML
  1392. */
  1393. static toHTML(markdown, config=new MDConfig()) {
  1394. var state = new _MDState();
  1395. let lines = markdown.replace("\r", "").split("\n");
  1396. state.lines = lines;
  1397. let blocks = this.#readBlocks(state);
  1398. let html = _MDBlock.toHTML(blocks);
  1399. return html;
  1400. }
  1401. }