| var htmlFlow = { |
| name: 'htmlFlow', |
| tokenize: tokenizeHtmlFlow, |
| resolveTo: resolveToHtmlFlow, |
| concrete: true |
| } |
| export default htmlFlow |
| |
| import assert from 'assert' |
| import asciiAlpha from '../character/ascii-alpha.mjs' |
| import asciiAlphanumeric from '../character/ascii-alphanumeric.mjs' |
| import codes from '../character/codes.mjs' |
| import markdownLineEnding from '../character/markdown-line-ending.mjs' |
| import markdownLineEndingOrSpace from '../character/markdown-line-ending-or-space.mjs' |
| import markdownSpace from '../character/markdown-space.mjs' |
| import constants from '../constant/constants.mjs' |
| import fromCharCode from '../constant/from-char-code.mjs' |
| import basics from '../constant/html-block-names.mjs' |
| import raws from '../constant/html-raw-names.mjs' |
| import types from '../constant/types.mjs' |
| import blank from './partial-blank-line.mjs' |
| |
| var nextBlankConstruct = {tokenize: tokenizeNextBlank, partial: true} |
| |
| function resolveToHtmlFlow(events) { |
| var index = events.length |
| |
| while (index--) { |
| if ( |
| events[index][0] === 'enter' && |
| events[index][1].type === types.htmlFlow |
| ) { |
| break |
| } |
| } |
| |
| if (index > 1 && events[index - 2][1].type === types.linePrefix) { |
| // Add the prefix start to the HTML token. |
| events[index][1].start = events[index - 2][1].start |
| // Add the prefix start to the HTML line token. |
| events[index + 1][1].start = events[index - 2][1].start |
| // Remove the line prefix. |
| events.splice(index - 2, 2) |
| } |
| |
| return events |
| } |
| |
| function tokenizeHtmlFlow(effects, ok, nok) { |
| var self = this |
| var kind |
| var startTag |
| var buffer |
| var index |
| var marker |
| |
| return start |
| |
| function start(code) { |
| assert(code === codes.lessThan, 'expected `<`') |
| effects.enter(types.htmlFlow) |
| effects.enter(types.htmlFlowData) |
| effects.consume(code) |
| return open |
| } |
| |
| function open(code) { |
| if (code === codes.exclamationMark) { |
| effects.consume(code) |
| return declarationStart |
| } |
| |
| if (code === codes.slash) { |
| effects.consume(code) |
| return tagCloseStart |
| } |
| |
| if (code === codes.questionMark) { |
| effects.consume(code) |
| kind = constants.htmlInstruction |
| // While we’re in an instruction instead of a declaration, we’re on a `?` |
| // right now, so we do need to search for `>`, similar to declarations. |
| return self.interrupt ? ok : continuationDeclarationInside |
| } |
| |
| if (asciiAlpha(code)) { |
| effects.consume(code) |
| buffer = fromCharCode(code) |
| startTag = true |
| return tagName |
| } |
| |
| return nok(code) |
| } |
| |
| function declarationStart(code) { |
| if (code === codes.dash) { |
| effects.consume(code) |
| kind = constants.htmlComment |
| return commentOpenInside |
| } |
| |
| if (code === codes.leftSquareBracket) { |
| effects.consume(code) |
| kind = constants.htmlCdata |
| buffer = constants.cdataOpeningString |
| index = 0 |
| return cdataOpenInside |
| } |
| |
| if (asciiAlpha(code)) { |
| effects.consume(code) |
| kind = constants.htmlDeclaration |
| return self.interrupt ? ok : continuationDeclarationInside |
| } |
| |
| return nok(code) |
| } |
| |
| function commentOpenInside(code) { |
| if (code === codes.dash) { |
| effects.consume(code) |
| return self.interrupt ? ok : continuationDeclarationInside |
| } |
| |
| return nok(code) |
| } |
| |
| function cdataOpenInside(code) { |
| if (code === buffer.charCodeAt(index++)) { |
| effects.consume(code) |
| return index === buffer.length |
| ? self.interrupt |
| ? ok |
| : continuation |
| : cdataOpenInside |
| } |
| |
| return nok(code) |
| } |
| |
| function tagCloseStart(code) { |
| if (asciiAlpha(code)) { |
| effects.consume(code) |
| buffer = fromCharCode(code) |
| return tagName |
| } |
| |
| return nok(code) |
| } |
| |
| function tagName(code) { |
| if ( |
| code === codes.eof || |
| code === codes.slash || |
| code === codes.greaterThan || |
| markdownLineEndingOrSpace(code) |
| ) { |
| if ( |
| code !== codes.slash && |
| startTag && |
| raws.indexOf(buffer.toLowerCase()) > -1 |
| ) { |
| kind = constants.htmlRaw |
| return self.interrupt ? ok(code) : continuation(code) |
| } |
| |
| if (basics.indexOf(buffer.toLowerCase()) > -1) { |
| kind = constants.htmlBasic |
| |
| if (code === codes.slash) { |
| effects.consume(code) |
| return basicSelfClosing |
| } |
| |
| return self.interrupt ? ok(code) : continuation(code) |
| } |
| |
| kind = constants.htmlComplete |
| // Do not support complete HTML when interrupting. |
| return self.interrupt |
| ? nok(code) |
| : startTag |
| ? completeAttributeNameBefore(code) |
| : completeClosingTagAfter(code) |
| } |
| |
| if (code === codes.dash || asciiAlphanumeric(code)) { |
| effects.consume(code) |
| buffer += fromCharCode(code) |
| return tagName |
| } |
| |
| return nok(code) |
| } |
| |
| function basicSelfClosing(code) { |
| if (code === codes.greaterThan) { |
| effects.consume(code) |
| return self.interrupt ? ok : continuation |
| } |
| |
| return nok(code) |
| } |
| |
| function completeClosingTagAfter(code) { |
| if (markdownSpace(code)) { |
| effects.consume(code) |
| return completeClosingTagAfter |
| } |
| |
| return completeEnd(code) |
| } |
| |
| function completeAttributeNameBefore(code) { |
| if (code === codes.slash) { |
| effects.consume(code) |
| return completeEnd |
| } |
| |
| if (code === codes.colon || code === codes.underscore || asciiAlpha(code)) { |
| effects.consume(code) |
| return completeAttributeName |
| } |
| |
| if (markdownSpace(code)) { |
| effects.consume(code) |
| return completeAttributeNameBefore |
| } |
| |
| return completeEnd(code) |
| } |
| |
| function completeAttributeName(code) { |
| if ( |
| code === codes.dash || |
| code === codes.dot || |
| code === codes.colon || |
| code === codes.underscore || |
| asciiAlphanumeric(code) |
| ) { |
| effects.consume(code) |
| return completeAttributeName |
| } |
| |
| return completeAttributeNameAfter(code) |
| } |
| |
| function completeAttributeNameAfter(code) { |
| if (code === codes.equalsTo) { |
| effects.consume(code) |
| return completeAttributeValueBefore |
| } |
| |
| if (markdownSpace(code)) { |
| effects.consume(code) |
| return completeAttributeNameAfter |
| } |
| |
| return completeAttributeNameBefore(code) |
| } |
| |
| function completeAttributeValueBefore(code) { |
| if ( |
| code === codes.eof || |
| code === codes.lessThan || |
| code === codes.equalsTo || |
| code === codes.greaterThan || |
| code === codes.graveAccent |
| ) { |
| return nok(code) |
| } |
| |
| if (code === codes.quotationMark || code === codes.apostrophe) { |
| effects.consume(code) |
| marker = code |
| return completeAttributeValueQuoted |
| } |
| |
| if (markdownSpace(code)) { |
| effects.consume(code) |
| return completeAttributeValueBefore |
| } |
| |
| marker = undefined |
| return completeAttributeValueUnquoted(code) |
| } |
| |
| function completeAttributeValueQuoted(code) { |
| if (code === marker) { |
| effects.consume(code) |
| return completeAttributeValueQuotedAfter |
| } |
| |
| if (code === codes.eof || markdownLineEnding(code)) { |
| return nok(code) |
| } |
| |
| effects.consume(code) |
| return completeAttributeValueQuoted |
| } |
| |
| function completeAttributeValueUnquoted(code) { |
| if ( |
| code === codes.eof || |
| code === codes.quotationMark || |
| code === codes.apostrophe || |
| code === codes.lessThan || |
| code === codes.equalsTo || |
| code === codes.greaterThan || |
| code === codes.graveAccent || |
| markdownLineEndingOrSpace(code) |
| ) { |
| return completeAttributeNameAfter(code) |
| } |
| |
| effects.consume(code) |
| return completeAttributeValueUnquoted |
| } |
| |
| function completeAttributeValueQuotedAfter(code) { |
| if ( |
| code === codes.slash || |
| code === codes.greaterThan || |
| markdownSpace(code) |
| ) { |
| return completeAttributeNameBefore(code) |
| } |
| |
| return nok(code) |
| } |
| |
| function completeEnd(code) { |
| if (code === codes.greaterThan) { |
| effects.consume(code) |
| return completeAfter |
| } |
| |
| return nok(code) |
| } |
| |
| function completeAfter(code) { |
| if (markdownSpace(code)) { |
| effects.consume(code) |
| return completeAfter |
| } |
| |
| return code === codes.eof || markdownLineEnding(code) |
| ? continuation(code) |
| : nok(code) |
| } |
| |
| function continuation(code) { |
| if (code === codes.dash && kind === constants.htmlComment) { |
| effects.consume(code) |
| return continuationCommentInside |
| } |
| |
| if (code === codes.lessThan && kind === constants.htmlRaw) { |
| effects.consume(code) |
| return continuationRawTagOpen |
| } |
| |
| if (code === codes.greaterThan && kind === constants.htmlDeclaration) { |
| effects.consume(code) |
| return continuationClose |
| } |
| |
| if (code === codes.questionMark && kind === constants.htmlInstruction) { |
| effects.consume(code) |
| return continuationDeclarationInside |
| } |
| |
| if (code === codes.rightSquareBracket && kind === constants.htmlCdata) { |
| effects.consume(code) |
| return continuationCharacterDataInside |
| } |
| |
| if ( |
| markdownLineEnding(code) && |
| (kind === constants.htmlBasic || kind === constants.htmlComplete) |
| ) { |
| return effects.check( |
| nextBlankConstruct, |
| continuationClose, |
| continuationAtLineEnding |
| )(code) |
| } |
| |
| if (code === codes.eof || markdownLineEnding(code)) { |
| return continuationAtLineEnding(code) |
| } |
| |
| effects.consume(code) |
| return continuation |
| } |
| |
| function continuationAtLineEnding(code) { |
| effects.exit(types.htmlFlowData) |
| return htmlContinueStart(code) |
| } |
| |
| function htmlContinueStart(code) { |
| if (code === codes.eof) { |
| return done(code) |
| } |
| |
| if (markdownLineEnding(code)) { |
| effects.enter(types.lineEnding) |
| effects.consume(code) |
| effects.exit(types.lineEnding) |
| return htmlContinueStart |
| } |
| |
| effects.enter(types.htmlFlowData) |
| return continuation(code) |
| } |
| |
| function continuationCommentInside(code) { |
| if (code === codes.dash) { |
| effects.consume(code) |
| return continuationDeclarationInside |
| } |
| |
| return continuation(code) |
| } |
| |
| function continuationRawTagOpen(code) { |
| if (code === codes.slash) { |
| effects.consume(code) |
| buffer = '' |
| return continuationRawEndTag |
| } |
| |
| return continuation(code) |
| } |
| |
| function continuationRawEndTag(code) { |
| if (code === codes.greaterThan && raws.indexOf(buffer.toLowerCase()) > -1) { |
| effects.consume(code) |
| return continuationClose |
| } |
| |
| if (asciiAlpha(code) && buffer.length < constants.htmlRawSizeMax) { |
| effects.consume(code) |
| buffer += fromCharCode(code) |
| return continuationRawEndTag |
| } |
| |
| return continuation(code) |
| } |
| |
| function continuationCharacterDataInside(code) { |
| if (code === codes.rightSquareBracket) { |
| effects.consume(code) |
| return continuationDeclarationInside |
| } |
| |
| return continuation(code) |
| } |
| |
| function continuationDeclarationInside(code) { |
| if (code === codes.greaterThan) { |
| effects.consume(code) |
| return continuationClose |
| } |
| |
| return continuation(code) |
| } |
| |
| function continuationClose(code) { |
| if (code === codes.eof || markdownLineEnding(code)) { |
| effects.exit(types.htmlFlowData) |
| return done(code) |
| } |
| |
| effects.consume(code) |
| return continuationClose |
| } |
| |
| function done(code) { |
| effects.exit(types.htmlFlow) |
| return ok(code) |
| } |
| } |
| |
| function tokenizeNextBlank(effects, ok, nok) { |
| return start |
| |
| function start(code) { |
| assert(markdownLineEnding(code), 'expected a line ending') |
| effects.exit(types.htmlFlowData) |
| effects.enter(types.lineEndingBlank) |
| effects.consume(code) |
| effects.exit(types.lineEndingBlank) |
| return effects.attempt(blank, ok, nok) |
| } |
| } |