'use strict' var asciiAlpha = require('../character/ascii-alpha.js') var asciiAlphanumeric = require('../character/ascii-alphanumeric.js') var markdownLineEnding = require('../character/markdown-line-ending.js') var markdownLineEndingOrSpace = require('../character/markdown-line-ending-or-space.js') var markdownSpace = require('../character/markdown-space.js') var fromCharCode = require('../constant/from-char-code.js') var htmlBlockNames = require('../constant/html-block-names.js') var htmlRawNames = require('../constant/html-raw-names.js') var partialBlankLine = require('./partial-blank-line.js') var htmlFlow = { name: 'htmlFlow', tokenize: tokenizeHtmlFlow, resolveTo: resolveToHtmlFlow, concrete: true } var nextBlankConstruct = { tokenize: tokenizeNextBlank, partial: true } function resolveToHtmlFlow(events) { var index = events.length while (index--) { if (events[index][0] === 'enter' && events[index][1].type === 'htmlFlow') { break } } if (index > 1 && events[index - 2][1].type === '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) { effects.enter('htmlFlow') effects.enter('htmlFlowData') effects.consume(code) return open } function open(code) { if (code === 33) { effects.consume(code) return declarationStart } if (code === 47) { effects.consume(code) return tagCloseStart } if (code === 63) { effects.consume(code) kind = 3 // 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 === 45) { effects.consume(code) kind = 2 return commentOpenInside } if (code === 91) { effects.consume(code) kind = 5 buffer = 'CDATA[' index = 0 return cdataOpenInside } if (asciiAlpha(code)) { effects.consume(code) kind = 4 return self.interrupt ? ok : continuationDeclarationInside } return nok(code) } function commentOpenInside(code) { if (code === 45) { 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 === null || code === 47 || code === 62 || markdownLineEndingOrSpace(code) ) { if ( code !== 47 && startTag && htmlRawNames.indexOf(buffer.toLowerCase()) > -1 ) { kind = 1 return self.interrupt ? ok(code) : continuation(code) } if (htmlBlockNames.indexOf(buffer.toLowerCase()) > -1) { kind = 6 if (code === 47) { effects.consume(code) return basicSelfClosing } return self.interrupt ? ok(code) : continuation(code) } kind = 7 // Do not support complete HTML when interrupting. return self.interrupt ? nok(code) : startTag ? completeAttributeNameBefore(code) : completeClosingTagAfter(code) } if (code === 45 || asciiAlphanumeric(code)) { effects.consume(code) buffer += fromCharCode(code) return tagName } return nok(code) } function basicSelfClosing(code) { if (code === 62) { 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 === 47) { effects.consume(code) return completeEnd } if (code === 58 || code === 95 || asciiAlpha(code)) { effects.consume(code) return completeAttributeName } if (markdownSpace(code)) { effects.consume(code) return completeAttributeNameBefore } return completeEnd(code) } function completeAttributeName(code) { if ( code === 45 || code === 46 || code === 58 || code === 95 || asciiAlphanumeric(code) ) { effects.consume(code) return completeAttributeName } return completeAttributeNameAfter(code) } function completeAttributeNameAfter(code) { if (code === 61) { effects.consume(code) return completeAttributeValueBefore } if (markdownSpace(code)) { effects.consume(code) return completeAttributeNameAfter } return completeAttributeNameBefore(code) } function completeAttributeValueBefore(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 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 === null || markdownLineEnding(code)) { return nok(code) } effects.consume(code) return completeAttributeValueQuoted } function completeAttributeValueUnquoted(code) { if ( code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 62 || code === 96 || markdownLineEndingOrSpace(code) ) { return completeAttributeNameAfter(code) } effects.consume(code) return completeAttributeValueUnquoted } function completeAttributeValueQuotedAfter(code) { if (code === 47 || code === 62 || markdownSpace(code)) { return completeAttributeNameBefore(code) } return nok(code) } function completeEnd(code) { if (code === 62) { effects.consume(code) return completeAfter } return nok(code) } function completeAfter(code) { if (markdownSpace(code)) { effects.consume(code) return completeAfter } return code === null || markdownLineEnding(code) ? continuation(code) : nok(code) } function continuation(code) { if (code === 45 && kind === 2) { effects.consume(code) return continuationCommentInside } if (code === 60 && kind === 1) { effects.consume(code) return continuationRawTagOpen } if (code === 62 && kind === 4) { effects.consume(code) return continuationClose } if (code === 63 && kind === 3) { effects.consume(code) return continuationDeclarationInside } if (code === 93 && kind === 5) { effects.consume(code) return continuationCharacterDataInside } if (markdownLineEnding(code) && (kind === 6 || kind === 7)) { return effects.check( nextBlankConstruct, continuationClose, continuationAtLineEnding )(code) } if (code === null || markdownLineEnding(code)) { return continuationAtLineEnding(code) } effects.consume(code) return continuation } function continuationAtLineEnding(code) { effects.exit('htmlFlowData') return htmlContinueStart(code) } function htmlContinueStart(code) { if (code === null) { return done(code) } if (markdownLineEnding(code)) { effects.enter('lineEnding') effects.consume(code) effects.exit('lineEnding') return htmlContinueStart } effects.enter('htmlFlowData') return continuation(code) } function continuationCommentInside(code) { if (code === 45) { effects.consume(code) return continuationDeclarationInside } return continuation(code) } function continuationRawTagOpen(code) { if (code === 47) { effects.consume(code) buffer = '' return continuationRawEndTag } return continuation(code) } function continuationRawEndTag(code) { if (code === 62 && htmlRawNames.indexOf(buffer.toLowerCase()) > -1) { effects.consume(code) return continuationClose } if (asciiAlpha(code) && buffer.length < 8) { effects.consume(code) buffer += fromCharCode(code) return continuationRawEndTag } return continuation(code) } function continuationCharacterDataInside(code) { if (code === 93) { effects.consume(code) return continuationDeclarationInside } return continuation(code) } function continuationDeclarationInside(code) { if (code === 62) { effects.consume(code) return continuationClose } return continuation(code) } function continuationClose(code) { if (code === null || markdownLineEnding(code)) { effects.exit('htmlFlowData') return done(code) } effects.consume(code) return continuationClose } function done(code) { effects.exit('htmlFlow') return ok(code) } } function tokenizeNextBlank(effects, ok, nok) { return start function start(code) { effects.exit('htmlFlowData') effects.enter('lineEndingBlank') effects.consume(code) effects.exit('lineEndingBlank') return effects.attempt(partialBlankLine, ok, nok) } } module.exports = htmlFlow