| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126 |
- // Blocks
- // - Paragraph
- // - Header 1-6 # ## ### #### ##### ###### or === ---
- // - Blockquote (nestable) >
- // - Unordered list (nestable) *_
- // - Ordered list (nestable) 1._
- // - Code block ```\ncode\n``` or 4 spaces/tab indent
- // - Horizontal rule --- - - - * * * etc
- // - Table -|-
- // - Definition list term\n: definition\n: alternate definition
- // - Footnote (bottom) citation[^1]
- // - Abbreviation (definition) *[ABC]: Abbrev Blah Cat
- // Inline
- // - Link [text](https://url)
- // - Emphasis *emphasized*
- // - Strong **bold**
- // - Inline code `code`
- // - Strikethrough ~strike~
- // - Image {.cssclass}
- // - Footnote (inline) [^1]: footnote text
- // - Abbreviation (inline)
-
- class _MDHAlign {
- static Left = new _MDHAlign('Left');
- static Center = new _MDHAlign('Center');
- static Right = new _MDHAlign('Right');
-
- constructor(name) {
- this.name = name;
- }
-
- toString() {
- return `_MDHAlign.${this.name}`;
- }
-
- static toHTMLAttribute(align) {
- switch (align) {
- case _MDHAlign.Left: return ' align="left"';
- case _MDHAlign.Center: return ' align="center"';
- case _MDHAlign.Right: return ' align="right"';
- }
- return '';
- }
- }
-
- class _MDSpan {
- toHTML(config) {
- throw new Error(self.constructor.name + ".toHTML not implemented");
- }
-
- static toHTML(spans, config) {
- return spans.map((span) => span.toHTML(config)).join("");
- }
- }
- class _MDMultiSpan extends _MDSpan {
- /** @var {_MDSpan[]} */
- content;
- /**
- * @param {_MDSpan[]} content
- */
- constructor(content) {
- super();
- this.content = content;
- }
- toHTML() {
- return _MDSpan.toHTML(this.content);
- }
- }
- class _MDTextSpan extends _MDSpan {
- /** @param {String} text */
- text;
- /**
- * @param {String} text
- */
- constructor(text) {
- super();
- this.text = text;
- }
- toHTML(config) {
- return this.text.replace('<', '<');
- }
- }
- class _MDHTMLSpan extends _MDSpan {
- /** @param {String} html */
- html;
- /**
- * @param {String} html
- */
- constructor(html) {
- super();
- this.html = html;
- }
- toHTML(config) {
- return this.html;
- }
- }
- class _MDLink extends _MDSpan {
- /** @var {String} */
- link;
- /** @var {String|null} */
- target = null;
- /** @var {_MDSpan} */
- content;
-
- /**
- * @param {String} link
- * @param {_MDSpan} content
- */
- constructor(link, content) {
- super();
- this.link = link;
- this.content = content;
- }
-
- toHTML(config) {
- let escapedLink = this.link.replace('"', '"');
- var html = `<a href="${escapedLink}"`;
- if (target) {
- let escapedTarget = this.target.replace('"', '"');
- html += ` target="${escapedTarget}"`;
- }
- html += '>' + this.content.toHTML(config) + '</a>';
- return html;
- }
- }
- class _MDReferencedLink extends _MDLink {
- /** @var {String} id */
- id;
- constructor(id, content) {
- super(null, content);
- this.id = id;
- }
- toHTML(config) {
- if (this.link) {
- return super.toHTML(config);
- } else {
- let contentHTML = this.content.toHTML(config);
- return `[${contentHTML}][${this.id}]`;
- }
- }
- }
- class _MDEmphasis extends _MDSpan {
- /** @var {_MDSpan} content */
- #content;
- /**
- * @param {_MDSpan} content
- */
- constructor(content) {
- super();
- this.#content = content;
- }
- toHTML(config) {
- let contentHTML = this.#content.toHTML(config);
- return `<em>${contentHTML}</em>`;
- }
- }
- class _MDStrong extends _MDSpan {
- /** @var {_MDSpan} content */
- #content;
- /**
- * @param {_MDSpan} content
- */
- constructor(content) {
- super();
- this.#content = content;
- }
- toHTML(config) {
- let contentHTML = this.#content.toHTML(config);
- return `<strong>${contentHTML}</strong>`;
- }
- }
- class _MDStrikethrough extends _MDSpan {
- /** @var {_MDSpan} content */
- #content;
- /**
- * @param {_MDSpan} content
- */
- constructor(content) {
- super();
- this.#content = content;
- }
- toHTML(config) {
- let contentHTML = this.#content.toHTML(config);
- return `<strike>${contentHTML}</strike>`;
- }
- }
- class _MDInlineCode extends _MDSpan {
- /** @var {_MDSpan} content */
- #content;
- /**
- * @param {_MDSpan} content
- */
- constructor(content) {
- super();
- this.#content = content;
- }
- toHTML(config) {
- let contentHTML = this.#content.toHTML(config);
- return `<code>${contentHTML}</code>`;
- }
- }
- class _MDImage extends _MDSpan {
- /** @var {String} */
- source;
- /** @var {String|null} */
- alt;
- /**
- * @param {String} source
- */
- constructor(source, alt) {
- super();
- this.source = source;
- this.alt = alt;
- }
- toHTML(config) {
- let escapedSource = this.source.replace('"', '"');
- let html = `<img src="${escapedSource}"`;
- if (this.alt) {
- let altEscaped = this.alt.replace('"', '"');
- html += ` alt="${altEscaped}"`;
- }
- html += '>';
- return html;
- }
- }
- class _MDReferencedImage extends _MDImage {
- /** @var {String} */
- id;
- /**
- * @param {String} id
- */
- constructor(id, alt) {
- super(null, alt);
- this.id = id;
- }
- toHTML(config) {
- if (this.source) {
- return super.toHTML(config);
- } else {
- let altEscaped = this.alt.replace('"', '"');
- let idEscaped = this.id.replace('"', '"');
- return `![${altEscaped}][${idEscaped}]`;
- }
- }
- }
- class _MDFootnoteReference extends _MDSpan {
- /** @var {String} */
- symbol;
- /** @var {Number} */
- differentiator = 0;
- /**
- * @param {String} symbol
- */
- constructor(symbol) {
- super();
- this.symbol = symbol;
- }
- toHTML(config) {
- return `<sup id="fnref-${this.symbol}-${this.differentiator}"><a href="#fndef-${this.symbol}">${this.symbol}</a></sup>`;
- }
- }
- class _MDAbbreviationReference extends _MDSpan {
- /** @var {_MDSpan} content */
- #content;
- /** @var {String} definition */
- #definition;
- /**
- * @param {_MDSpan} content
- */
- constructor(content, definition) {
- super();
- this.#content = content;
- this.#definition = definition;
- }
- toHTML(config) {
- let contentHTML = this.#content.toHTML(config);
- let definitionEscaped = this.#definition.replace('"', '"');
- return `<abbr title="${definitionEscaped}">${contentHTML}</em>`;
- }
- }
-
- class _MDBlock {
- toHTML(config) {
- throw new Error(self.constructor.name + ".toHTML not implemented");
- }
-
- /**
- * @param {_MDBlock[]} blocks
- * @returns {String}
- */
- static toHTML(blocks, config) {
- return blocks.map((block) => block.toHTML(config)).join("\n");
- }
- }
-
- class _MDMultiBlock extends _MDBlock {
- /** @var {_MDBlock[]} */
- #blocks;
- /**
- * @param {_MDBlock[]} blocks
- */
- constructor(blocks) {
- super();
- this.#blocks = blocks;
- }
- toHTML(config) {
- return _MDBlock.toHTML(this.#blocks, config);
- }
- }
-
- class _MDParagraph extends _MDBlock {
- /** @var {_MDBlock} */
- content;
-
- /**
- * @param {_MDBlock} content
- */
- constructor(content) {
- super();
- this.content = content;
- }
-
- toHTML(config) {
- let contentHTML = this.content.toHTML(config);
- return `<p>${contentHTML}</p>\n`;
- }
- }
-
- class _MDHeader extends _MDBlock {
- /** @var {number} */
- level;
- /** @var {_MDBlock} */
- content;
-
- /**
- * @param {number} level
- * @param {_MDBlock} content
- */
- constructor(level, content) {
- super();
- this.level = level;
- this.content = content;
- }
-
- toHTML(config) {
- let contentHTML = this.content.toHTML(config);
- return `<h${this.level}>${contentHTML}</h${this.level}>\n`;
- }
- }
-
- class _MDBlockquote extends _MDBlock {
- /** @var {_MDBlock[]} */
- content;
- /**
- * @param {_MDBlock[]} content
- */
- constructor(content) {
- super();
- this.content = content;
- }
- toHTML(config) {
- let contentHTML = _MDBlock.toHTML(this.content, config);
- return `<blockquote>\n${contentHTML}\n</blockquote>`;
- }
- }
-
- class _MDUnorderedList extends _MDBlock {
- /** @var {_MDListItem[]} */
- items;
- /**
- * @param {_MDListItem[]} items
- */
- constructor(items) {
- super();
- this.items = items;
- }
- toHTML(config) {
- let contentHTML = _MDBlock.toHTML(this.items);
- return `<ul>\n${contentHTML}\n</ul>`;
- }
- }
-
- class _MDOrderedList extends _MDBlock {
- /** @var {_MDListItem[]} */
- items;
- /**
- * @param {_MDListItem[]} items
- */
- constructor(items) {
- super();
- this.items = items;
- }
- toHTML(config) {
- let contentHTML = _MDBlock.toHTML(this.items);
- return `<ol>\n${contentHTML}\n</ol>`;
- }
- }
-
- class _MDListItem extends _MDBlock {
- /** @var {_MDBlock} */
- content;
-
- /**
- * @param {_MDBlock} content
- */
- constructor(content) {
- super();
- this.content = content;
- }
-
- toHTML(config) {
- let contentHTML = this.content.toHTML(config);
- return `<li>${contentHTML}</li>`;
- }
- }
-
- class _MDCodeBlock extends _MDBlock {
- /** @var {String} */
- #code;
- /**
- * @param {String} code
- */
- constructor(code) {
- super();
- this.#code = code;
- }
- toHTML(config) {
- return `<pre><code>${this.#code}</code></pre>`;
- }
- }
-
- class _MDHorizontalRule extends _MDBlock {
- toHTML(config) {
- return "<hr>\n";
- }
- }
-
- class _MDTableCell extends _MDBlock {
- /** @var {_MDBlock} */
- #content;
- /** @var {_MDHAlign|null} */
- align = null;
- /**
- * @param {_MDBlock} content
- */
- constructor(content) {
- super();
- this.#content = content;
- }
- toHTML(config) {
- let contentHTML = this.#content.toHTML(config);
- let alignAttribute = _MDHAlign.toHTMLAttribute(this.align);
- return `<td${alignAttribute}>${contentHTML}</td>`;
- }
- }
-
- class _MDTableHeaderCell extends _MDTableCell {
- toHTML(config) {
- let html = super.toHTML(config);
- let groups = /^<td(.*)td>$/.exec(html);
- return `<th${groups[1]}th>`;
- }
- }
-
- class _MDTableRow extends _MDBlock {
- /** @var {_MDTableCell[]|_MDTableHeaderCell[]} */
- #cells;
- /**
- * @param {_MDTableCell[]|_MDTableHeaderCell[]} cells
- */
- constructor(cells) {
- super();
- this.#cells = cells;
- }
- /**
- * @param {_MDHAlign[]} alignments
- */
- applyAlignments(alignments) {
- for (var i = 0; i < this.#cells.length; i++) {
- let cell = this.#cells[i];
- let align = i < alignments.length ? alignments[i] : null;
- cell.align = align;
- }
- }
- toHTML(config) {
- let cellsHTML = _MDBlock.toHTML(this.#cells, config);
- return `<tr>\n${cellsHTML}\n</tr>`;
- }
- }
-
- class _MDTable extends _MDBlock {
- /** @var {_MDTableRow} */
- #headerRow;
- /** @var {_MDTableRow[]} */
- #bodyRows;
- /**
- * @param {_MDTableRow} headerRow
- * @param {_MDTableRow[]} bodyRows
- */
- constructor(headerRow, bodyRows) {
- super();
- this.#headerRow = headerRow;
- this.#bodyRows = bodyRows;
- }
- toHTML(config) {
- let headerRowHTML = this.#headerRow.toHTML(config);
- let bodyRowsHTML = _MDBlock.toHTML(this.#bodyRows);
- return `<table>\n<thead>\n${headerRowHTML}\n</thead>\n<tbody>\n${bodyRowsHTML}\n</tbody>\n</table>`;
- }
- }
-
- class _MDDefinitionList extends _MDBlock {
- /** @var {_MDBlock[]} */
- #content;
- /**
- * @param {_MDBlock[]} content
- */
- constructor(content) {
- super();
- this.#content = content;
- }
- toHTML(config) {
- let contentHTML = _MDBlock.toHTML(this.#content);
- return `<dl>\n${contentHTML}\n</dl>`;
- }
- }
-
- class _MDDefinitionTerm extends _MDBlock {
- /** @var {_MDBlock} */
- #content;
- /**
- * @param {_MDBlock} content
- */
- constructor(content) {
- super();
- this.#content = content;
- }
- toHTML(config) {
- let contentHTML = this.#content.toHTML(config);
- return `<dt>${contentHTML}</dt>`;
- }
- }
-
- class _MDDefinitionDefinition extends _MDBlock {
- /** @var {_MDBlock} */
- #content;
- /**
- * @param {_MDBlock} content
- */
- constructor(content) {
- super();
- this.#content = content;
- }
- toHTML(config) {
- let contentHTML = this.#content.toHTML(config);
- return `<dd>${contentHTML}</dd>`;
- }
- }
-
- class _MDFootnoteContent extends _MDBlock {
- /** @var {String} */
- #id;
- /** @var {_MDBlock} */
- #content;
- /**
- * @param {String} id
- * @param {_MDBlock} content
- */
- constructor(id, content) {
- super();
- this.#id = id;
- this.#content = content;
- }
- toHTML(config) {
- // TODO: Forward and back links
- // TODO: Deferring footnotes to end of document
- //<ol>
- //<li id="fn:1" role="doc-endnote">
- //<p>Footnote <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p>
- //</li>
- //</ol>
- return '';
- }
- }
-
- class _MDAbbreviationOccurrence extends _MDBlock {
- /** @var {String} */
- #label;
- /** @var {String} */
- #definition;
-
- /**
- * @param {String} label
- * @param {String} definition
- */
- constructor(label, definition) {
- super();
- this.#label = label;
- this.#definition = definition;
- }
-
- toHTML(config) {
- return `<abbr title="${this.#definition.replace('"', '"')}">${this.#label}</abbr>`;
- }
- }
-
- class _MDInline extends _MDBlock {
- /** @var {String} */
- #raw;
- /**
- * @param {String} raw
- */
- constructor(raw) {
- super();
- this.#raw = raw;
- }
-
- toHTML(config) {
- return this.#raw;
- }
- }
-
- // Blocks that immediately start a new block
- // - Headers
- // - Blockquote
- // - Code block ```\ncode\n```
- // Blocks that need blank line first
- // - HR --- - - - *** * * * * * *
- // - Lists
- // - Table
- // - Code block [4+spaces]code
- // - Definition list term\n: definition\n: alternate def
- // Unknown blocks
- // - Footnotes some text[^1] [^1]: first footnote content
- // - Abbreviations *[HTML]: Hyper Text
- // Inline styles
- // - Links
- // - Italic
- // - Bold
- // - `code`
- // - Strikethrough
- // - Images {.cssclass}
- // - Literals \*
- class _MDState {
- /** @var {String[]} */
- lines = [];
-
- /** @var {object} */
- abbreviations = {};
-
- /** @var {object} */
- footnotes = {};
-
- /** @var {number} */
- p = 0;
-
- copy() {
- let cp = new _MDState();
- cp.abbreviations = this.abbreviations;
- cp.footnotes = this.footnotes;
- cp.p = this.p;
- return cp;
- }
-
- /** @param {_MDState} other */
- apply(other) {
- this.abbreviations = other.abbreviations;
- this.footnotes = other.footnotes;
- this.p = other.p;
- }
-
- hasLines(minCount, p=-1) {
- let relativeTo = (p < 0) ? this.p : p;
- return relativeTo + minCount <= this.lines.length;
- }
- }
-
- class MDConfig {
-
- }
-
- class Markdown {
- /**
- * @param {String} line
- */
- static #stripIndent(line, count=1) {
- let regex = new RegExp(`^(: {1,4}|\\t){${count}}`);
- return line.replace(regex, '');
- }
-
- /**
- * @param {String} line
- * @param {Boolean} fullIndentsOnly
- * @returns {Number} indent count
- */
- static #countIndents(line, fullIndentsOnly=false) {
- var count = 0;
- var lastLine = line;
- while (line.length > 0) {
- line = (fullIndentsOnly)
- ? line.replace(/^(?: {4}|\t)/, '')
- : line.replace(/^(?: {1,4}|\t)/, '');
- if (line != lastLine) {
- count++;
- } else {
- break;
- }
- lastLine = line;
- }
- return count;
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock[]}
- */
- static #readBlocks(state) {
- var blocks = [];
- while (state.hasLines(1)) {
- let block = this.#readNextBlock(state);
- if (block) {
- blocks.push(block);
- } else {
- break;
- }
- }
- return blocks;
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock}
- */
- static #readNextBlock(state) {
- while (state.hasLines(1) && state.lines[state.p].trim().length == 0) {
- console.info("Skipping blank line " + state.p);
- state.p++;
- }
- var block;
- block = this.#readUnderlineHeader(state); if (block) return block;
- block = this.#readHashHeader(state); if (block) return block;
- block = this.#readBlockQuote(state); if (block) return block;
- block = this.#readUnorderedList(state); if (block) return block;
- block = this.#readOrderedList(state); if (block) return block;
- block = this.#readFencedCodeBlock(state); if (block) return block;
- block = this.#readIndentedCodeBlock(state); if (block) return block;
- block = this.#readHorizontalRule(state); if (block) return block;
- block = this.#readTable(state); if (block) return block;
- block = this.#readDefinitionList(state); if (block) return block;
- block = this.#readFootnoteDef(state); if (block) return block;
- block = this.#readAbbreviationDef(state); if (block) return block;
- block = this.#readParagraph(state); if (block) return block;
- return null;
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readInline(state, line) {
- return new _MDInline(line);
- }
-
- /**
- * Reads the contents of something like a list item
- * @param {_MDState} state
- * @param {number} firstLineStartPos
- * @param {RegExp} stopRegex
- * @returns {_MDBlock}
- */
- static #readInteriorContent(state, firstLineStartPos, stopRegex) {
- // FIXME: When reading <li> content need to detect nested list without
- // a blank line
- var p = state.p;
- var seenBlankLine = false;
- var needsBlocks = false;
- var lines = [];
- while (p < state.lines.length) {
- let line = state.lines[p++];
- if (p == state.p + 1) {
- line = line.substring(firstLineStartPos);
- }
- let isBlank = line.trim().length == 0;
- let isIndented = /^\s+/.exec(line) !== null;
- if (isBlank) {
- seenBlankLine = true;
- lines.push(line.trim());
- } else if (stopRegex && stopRegex.exec(line)) {
- p--;
- break;
- } else if (isIndented) {
- if (seenBlankLine) {
- needsBlocks = true;
- }
- lines.push(this.#stripIndent(line));
- } else {
- if (seenBlankLine) {
- p--;
- break;
- }
- lines.push(this.#stripIndent(line));
- }
- }
- while (lines.length > 0 && lines[lines.length - 1].trim().length == 0) {
- lines.pop();
- }
- if (needsBlocks) {
- let substate = new _MDState();
- substate.lines = lines;
- substate.abbreviations = state.abbreviations;
- substate.footnotes = state.footnotes;
- let blocks = this.#readBlocks(substate);
- state.p = p;
- return new _MDMultiBlock(blocks);
- } else {
- state.p = p;
- return this.#readInline(state, lines.join("\n"));
- }
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readUnderlineHeader(state) {
- var p = state.p;
- if (!state.hasLines(2)) return null;
- let contentLine = state.lines[p++].trim();
- let underLine = state.lines[p++].trim();
- if (contentLine == '') return null;
- if (/^=+$/.exec(underLine)) {
- state.p = p;
- return new _MDHeader(1, this.#readInline(state, contentLine));
- }
- if (/^\-+$/.exec(underLine)) {
- state.p = p;
- return new _MDHeader(2, this.#readInline(state, contentLine));
- }
- return null;
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readHashHeader(state) {
- var p = state.p;
- var groups = /^(#{1,6})\s*([^#].*)\s*$/.exec(state.lines[p++]);
- if (groups === null) return null;
- state.p = p;
- return new _MDHeader(groups[1].length, this.#readInline(state, groups[2]));
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readBlockQuote(state) {
- var blockquoteLines = [];
- var p = state.p;
- while (p < state.lines.length) {
- let line = state.lines[p++];
- if (line.startsWith(">")) {
- blockquoteLines.push(line);
- } else {
- break;
- }
- }
- if (blockquoteLines.length > 0) {
- let contentLines = blockquoteLines.map(function(line) {
- return line.substring(1).replace(/^ {0,3}\t?/, '');
- });
- let substate = new _MDState();
- substate.lines = contentLines;
- substate.abbreviations = state.abbreviations;
- substate.footnotes = state.footnotes;
- let quotedBlocks = this.#readBlocks(substate);
- state.p = p;
- return new _MDBlockquote(quotedBlocks);
- }
- return null;
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDListItem|null}
- */
- static #readUnorderedListItem(state) {
- var p = state.p;
- let line = state.lines[p];
- let groups = /^([\*\+\-]\s+)(.*)$/.exec(line);
- if (groups === null) return null;
- return new _MDListItem(this.#readInteriorContent(state, groups[1].length, /^[\*\+\-]\s+/));
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readUnorderedList(state) {
- var items = [];
- var item = null;
- do {
- item = this.#readUnorderedListItem(state);
- if (item) items.push(item);
- } while (item);
- if (items.length == 0) return null;
- return new _MDUnorderedList(items);
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDListItem|null}
- */
- static #readOrderedListItem(state) {
- var p = state.p;
- let line = state.lines[p];
- let groups = /^(\d+\.\s+)(.*)$/.exec(line);
- if (groups === null) return null;
- return new _MDListItem(this.#readInteriorContent(state, groups[1].length, /^\d+\.\s+/));
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readOrderedList(state) {
- var items = [];
- var item = null;
- do {
- item = this.#readOrderedListItem(state);
- if (item) items.push(item);
- } while (item);
- if (items.length == 0) return null;
- return new _MDOrderedList(items);
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readFencedCodeBlock(state) {
- var p = state.p;
- if (state.lines[p++].trim() != '```') return null;
- var codeLines = [];
- while (state.hasLines(1, p)) {
- let line = state.lines[p++];
- if (line.trim() == '```') {
- state.p = p;
- return new _MDCodeBlock(codeLines.join("\n"));
- }
- codeLines.push(line);
- }
- return null;
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readIndentedCodeBlock(state) {
- var p = state.p;
- var codeLines = [];
- while (state.hasLines(1, p)) {
- let line = state.lines[p++];
- if (this.#countIndents(line, true) < 1) {
- p--;
- break;
- }
- codeLines.push(this.#stripIndent(line));
- }
- if (codeLines.length == 0) return null;
- state.p = p;
- return new _MDCodeBlock(codeLines.join("\n"));
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readHorizontalRule(state) {
- var p = state.p;
- let line = state.lines[p++];
- if (/^\s*(?:\-(?:\s*\-){2,}|\*(?:\s*\*){2,})\s*$/.exec(line)) {
- state.p = p;
- return new _MDHorizontalRule();
- }
- return null;
- }
-
- /**
- * @param {_MDState} state
- * @param {Boolean} isHeader
- * @return {_MDTableRow|null}
- */
- static #readTableRow(state, isHeader) {
- if (!state.hasLines(1)) return null;
- var p = state.p;
- let line = state.lines[p++].trim();
- if (/.*\|.*/.exec(line) === null) return null;
- if (line.startsWith('|')) line = line.substring(1);
- if (line.endsWith('|')) line = line.substring(0, line.length - 1);
- let cellTokens = line.split('|');
- let cells = cellTokens.map(function(token) {
- let content = Markdown.#readInline(state, token);
- return isHeader ? new _MDTableHeaderCell(content) : new _MDTableCell(content);
- });
- state.p = p;
- return new _MDTableRow(cells);
- }
-
- /**
- * @param {String} line
- * @returns {_MDHAlign[]}
- */
- static #parseColumnAlignments(line) {
- line = line.trim();
- if (line.startsWith('|')) line = line.substring(1);
- if (line.endsWith('|')) line = line.substring(0, line.length - 1);
- return line.split('|').map(function(token) {
- token = token.trim();
- if (token.startsWith(':')) {
- if (token.endsWith(':')) {
- return _MDHAlign.Center;
- }
- return _MDHAlign.Left;
- } else if (token.endsWith(':')) {
- return _MDHAlign.Right;
- }
- return null;
- });
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readTable(state) {
- if (!state.hasLines(2)) return null;
- let startP = state.p;
- let headerRow = this.#readTableRow(state, true);
- if (headerRow === null) {
- state.p = startP;
- return null;
- }
- let dividerLine = state.lines[state.p++];
- let dividerGroups = /^\s*[|]?(?:\s*[:]?-+[:]?\s*\|)(?:\s*[:]?-+[:]?\s*)[|]?\s*$/.exec(dividerLine);
- if (dividerGroups === null) {
- state.p = startP;
- return null;
- }
- let columnAlignments = this.#parseColumnAlignments(dividerLine);
- headerRow.applyAlignments(columnAlignments);
- var bodyRows = [];
- while (state.hasLines(1)) {
- let row = this.#readTableRow(state, false);
- if (row === null) break;
- row.applyAlignments(columnAlignments);
- bodyRows.push(row);
- }
- return new _MDTable(headerRow, bodyRows);
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readDefinitionList(state) {
- // TODO
- return null;
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readFootnoteDef(state) {
- // TODO
- return null;
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readAbbreviationDef(state) {
- // TODO
- return null;
- }
-
- /**
- * @param {_MDState} state
- * @returns {_MDBlock|null}
- */
- static #readParagraph(state) {
- if (!state.hasLines(1)) return null;
- var paragraphLines = [];
- var p = state.p;
- while (p < state.lines.length) {
- let line = state.lines[p++];
- if (line.trim().length == 0) {
- break;
- }
- paragraphLines.push(line);
- }
- if (paragraphLines.length > 0) {
- state.p = p;
- let content = paragraphLines.join("\n");
- return new _MDParagraph(this.#readInline(state, content));
- }
- return null;
- }
-
- /**
- * @param {String} markdown
- * @returns {String} HTML
- */
- static toHTML(markdown, config=new MDConfig()) {
- var state = new _MDState();
- let lines = markdown.replace("\r", "").split("\n");
- state.lines = lines;
- let blocks = this.#readBlocks(state);
- let html = _MDBlock.toHTML(blocks);
- return html;
- }
- }
|