/** * @typedef {import('micromark-util-types').Construct} Construct * @typedef {import('micromark-util-types').Tokenizer} Tokenizer * @typedef {import('micromark-util-types').State} State * @typedef {import('micromark-util-types').Code} Code */ import {factorySpace} from 'micromark-factory-space' import { asciiAlpha, asciiAlphanumeric, markdownLineEnding, markdownLineEndingOrSpace, markdownSpace } from 'micromark-util-character' /** @type {Construct} */ export const htmlText = { name: 'htmlText', tokenize: tokenizeHtmlText } /** @type {Tokenizer} */ function tokenizeHtmlText(effects, ok, nok) { const self = this /** @type {NonNullable|undefined} */ let marker /** @type {string} */ let buffer /** @type {number} */ let index /** @type {State} */ let returnState return start /** @type {State} */ function start(code) { effects.enter('htmlText') effects.enter('htmlTextData') effects.consume(code) return open } /** @type {State} */ function open(code) { if (code === 33) { effects.consume(code) return declarationOpen } if (code === 47) { effects.consume(code) return tagCloseStart } if (code === 63) { effects.consume(code) return instruction } if (asciiAlpha(code)) { effects.consume(code) return tagOpen } return nok(code) } /** @type {State} */ function declarationOpen(code) { if (code === 45) { effects.consume(code) return commentOpen } if (code === 91) { effects.consume(code) buffer = 'CDATA[' index = 0 return cdataOpen } if (asciiAlpha(code)) { effects.consume(code) return declaration } return nok(code) } /** @type {State} */ function commentOpen(code) { if (code === 45) { effects.consume(code) return commentStart } return nok(code) } /** @type {State} */ function commentStart(code) { if (code === null || code === 62) { return nok(code) } if (code === 45) { effects.consume(code) return commentStartDash } return comment(code) } /** @type {State} */ function commentStartDash(code) { if (code === null || code === 62) { return nok(code) } return comment(code) } /** @type {State} */ function comment(code) { if (code === null) { return nok(code) } if (code === 45) { effects.consume(code) return commentClose } if (markdownLineEnding(code)) { returnState = comment return atLineEnding(code) } effects.consume(code) return comment } /** @type {State} */ function commentClose(code) { if (code === 45) { effects.consume(code) return end } return comment(code) } /** @type {State} */ function cdataOpen(code) { if (code === buffer.charCodeAt(index++)) { effects.consume(code) return index === buffer.length ? cdata : cdataOpen } return nok(code) } /** @type {State} */ function cdata(code) { if (code === null) { return nok(code) } if (code === 93) { effects.consume(code) return cdataClose } if (markdownLineEnding(code)) { returnState = cdata return atLineEnding(code) } effects.consume(code) return cdata } /** @type {State} */ function cdataClose(code) { if (code === 93) { effects.consume(code) return cdataEnd } return cdata(code) } /** @type {State} */ function cdataEnd(code) { if (code === 62) { return end(code) } if (code === 93) { effects.consume(code) return cdataEnd } return cdata(code) } /** @type {State} */ function declaration(code) { if (code === null || code === 62) { return end(code) } if (markdownLineEnding(code)) { returnState = declaration return atLineEnding(code) } effects.consume(code) return declaration } /** @type {State} */ function instruction(code) { if (code === null) { return nok(code) } if (code === 63) { effects.consume(code) return instructionClose } if (markdownLineEnding(code)) { returnState = instruction return atLineEnding(code) } effects.consume(code) return instruction } /** @type {State} */ function instructionClose(code) { return code === 62 ? end(code) : instruction(code) } /** @type {State} */ function tagCloseStart(code) { if (asciiAlpha(code)) { effects.consume(code) return tagClose } return nok(code) } /** @type {State} */ function tagClose(code) { if (code === 45 || asciiAlphanumeric(code)) { effects.consume(code) return tagClose } return tagCloseBetween(code) } /** @type {State} */ function tagCloseBetween(code) { if (markdownLineEnding(code)) { returnState = tagCloseBetween return atLineEnding(code) } if (markdownSpace(code)) { effects.consume(code) return tagCloseBetween } return end(code) } /** @type {State} */ function tagOpen(code) { if (code === 45 || asciiAlphanumeric(code)) { effects.consume(code) return tagOpen } if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) { return tagOpenBetween(code) } return nok(code) } /** @type {State} */ function tagOpenBetween(code) { if (code === 47) { effects.consume(code) return end } if (code === 58 || code === 95 || asciiAlpha(code)) { effects.consume(code) return tagOpenAttributeName } if (markdownLineEnding(code)) { returnState = tagOpenBetween return atLineEnding(code) } if (markdownSpace(code)) { effects.consume(code) return tagOpenBetween } return end(code) } /** @type {State} */ function tagOpenAttributeName(code) { if ( code === 45 || code === 46 || code === 58 || code === 95 || asciiAlphanumeric(code) ) { effects.consume(code) return tagOpenAttributeName } return tagOpenAttributeNameAfter(code) } /** @type {State} */ function tagOpenAttributeNameAfter(code) { if (code === 61) { effects.consume(code) return tagOpenAttributeValueBefore } if (markdownLineEnding(code)) { returnState = tagOpenAttributeNameAfter return atLineEnding(code) } if (markdownSpace(code)) { effects.consume(code) return tagOpenAttributeNameAfter } return tagOpenBetween(code) } /** @type {State} */ function tagOpenAttributeValueBefore(code) { if ( code === null || code === 60 || code === 61 || code === 62 || code === 96 ) { return nok(code) } if (code === 34 || code === 39) { effects.consume(code) marker = code return tagOpenAttributeValueQuoted } if (markdownLineEnding(code)) { returnState = tagOpenAttributeValueBefore return atLineEnding(code) } if (markdownSpace(code)) { effects.consume(code) return tagOpenAttributeValueBefore } effects.consume(code) marker = undefined return tagOpenAttributeValueUnquoted } /** @type {State} */ function tagOpenAttributeValueQuoted(code) { if (code === marker) { effects.consume(code) return tagOpenAttributeValueQuotedAfter } if (code === null) { return nok(code) } if (markdownLineEnding(code)) { returnState = tagOpenAttributeValueQuoted return atLineEnding(code) } effects.consume(code) return tagOpenAttributeValueQuoted } /** @type {State} */ function tagOpenAttributeValueQuotedAfter(code) { if (code === 62 || code === 47 || markdownLineEndingOrSpace(code)) { return tagOpenBetween(code) } return nok(code) } /** @type {State} */ function tagOpenAttributeValueUnquoted(code) { if ( code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 96 ) { return nok(code) } if (code === 62 || markdownLineEndingOrSpace(code)) { return tagOpenBetween(code) } effects.consume(code) return tagOpenAttributeValueUnquoted } // We can’t have blank lines in content, so no need to worry about empty // tokens. /** @type {State} */ function atLineEnding(code) { effects.exit('htmlTextData') effects.enter('lineEnding') effects.consume(code) effects.exit('lineEnding') return factorySpace( effects, afterPrefix, 'linePrefix', self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4 ) } /** @type {State} */ function afterPrefix(code) { effects.enter('htmlTextData') return returnState(code) } /** @type {State} */ function end(code) { if (code === 62) { effects.consume(code) effects.exit('htmlTextData') effects.exit('htmlText') return ok } return nok(code) } }