html-flow.mjs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. var htmlFlow = {
  2. name: 'htmlFlow',
  3. tokenize: tokenizeHtmlFlow,
  4. resolveTo: resolveToHtmlFlow,
  5. concrete: true
  6. }
  7. export default htmlFlow
  8. import assert from 'assert'
  9. import asciiAlpha from '../character/ascii-alpha.mjs'
  10. import asciiAlphanumeric from '../character/ascii-alphanumeric.mjs'
  11. import codes from '../character/codes.mjs'
  12. import markdownLineEnding from '../character/markdown-line-ending.mjs'
  13. import markdownLineEndingOrSpace from '../character/markdown-line-ending-or-space.mjs'
  14. import markdownSpace from '../character/markdown-space.mjs'
  15. import constants from '../constant/constants.mjs'
  16. import fromCharCode from '../constant/from-char-code.mjs'
  17. import basics from '../constant/html-block-names.mjs'
  18. import raws from '../constant/html-raw-names.mjs'
  19. import types from '../constant/types.mjs'
  20. import blank from './partial-blank-line.mjs'
  21. var nextBlankConstruct = {tokenize: tokenizeNextBlank, partial: true}
  22. function resolveToHtmlFlow(events) {
  23. var index = events.length
  24. while (index--) {
  25. if (
  26. events[index][0] === 'enter' &&
  27. events[index][1].type === types.htmlFlow
  28. ) {
  29. break
  30. }
  31. }
  32. if (index > 1 && events[index - 2][1].type === types.linePrefix) {
  33. // Add the prefix start to the HTML token.
  34. events[index][1].start = events[index - 2][1].start
  35. // Add the prefix start to the HTML line token.
  36. events[index + 1][1].start = events[index - 2][1].start
  37. // Remove the line prefix.
  38. events.splice(index - 2, 2)
  39. }
  40. return events
  41. }
  42. function tokenizeHtmlFlow(effects, ok, nok) {
  43. var self = this
  44. var kind
  45. var startTag
  46. var buffer
  47. var index
  48. var marker
  49. return start
  50. function start(code) {
  51. assert(code === codes.lessThan, 'expected `<`')
  52. effects.enter(types.htmlFlow)
  53. effects.enter(types.htmlFlowData)
  54. effects.consume(code)
  55. return open
  56. }
  57. function open(code) {
  58. if (code === codes.exclamationMark) {
  59. effects.consume(code)
  60. return declarationStart
  61. }
  62. if (code === codes.slash) {
  63. effects.consume(code)
  64. return tagCloseStart
  65. }
  66. if (code === codes.questionMark) {
  67. effects.consume(code)
  68. kind = constants.htmlInstruction
  69. // While we’re in an instruction instead of a declaration, we’re on a `?`
  70. // right now, so we do need to search for `>`, similar to declarations.
  71. return self.interrupt ? ok : continuationDeclarationInside
  72. }
  73. if (asciiAlpha(code)) {
  74. effects.consume(code)
  75. buffer = fromCharCode(code)
  76. startTag = true
  77. return tagName
  78. }
  79. return nok(code)
  80. }
  81. function declarationStart(code) {
  82. if (code === codes.dash) {
  83. effects.consume(code)
  84. kind = constants.htmlComment
  85. return commentOpenInside
  86. }
  87. if (code === codes.leftSquareBracket) {
  88. effects.consume(code)
  89. kind = constants.htmlCdata
  90. buffer = constants.cdataOpeningString
  91. index = 0
  92. return cdataOpenInside
  93. }
  94. if (asciiAlpha(code)) {
  95. effects.consume(code)
  96. kind = constants.htmlDeclaration
  97. return self.interrupt ? ok : continuationDeclarationInside
  98. }
  99. return nok(code)
  100. }
  101. function commentOpenInside(code) {
  102. if (code === codes.dash) {
  103. effects.consume(code)
  104. return self.interrupt ? ok : continuationDeclarationInside
  105. }
  106. return nok(code)
  107. }
  108. function cdataOpenInside(code) {
  109. if (code === buffer.charCodeAt(index++)) {
  110. effects.consume(code)
  111. return index === buffer.length
  112. ? self.interrupt
  113. ? ok
  114. : continuation
  115. : cdataOpenInside
  116. }
  117. return nok(code)
  118. }
  119. function tagCloseStart(code) {
  120. if (asciiAlpha(code)) {
  121. effects.consume(code)
  122. buffer = fromCharCode(code)
  123. return tagName
  124. }
  125. return nok(code)
  126. }
  127. function tagName(code) {
  128. if (
  129. code === codes.eof ||
  130. code === codes.slash ||
  131. code === codes.greaterThan ||
  132. markdownLineEndingOrSpace(code)
  133. ) {
  134. if (
  135. code !== codes.slash &&
  136. startTag &&
  137. raws.indexOf(buffer.toLowerCase()) > -1
  138. ) {
  139. kind = constants.htmlRaw
  140. return self.interrupt ? ok(code) : continuation(code)
  141. }
  142. if (basics.indexOf(buffer.toLowerCase()) > -1) {
  143. kind = constants.htmlBasic
  144. if (code === codes.slash) {
  145. effects.consume(code)
  146. return basicSelfClosing
  147. }
  148. return self.interrupt ? ok(code) : continuation(code)
  149. }
  150. kind = constants.htmlComplete
  151. // Do not support complete HTML when interrupting.
  152. return self.interrupt
  153. ? nok(code)
  154. : startTag
  155. ? completeAttributeNameBefore(code)
  156. : completeClosingTagAfter(code)
  157. }
  158. if (code === codes.dash || asciiAlphanumeric(code)) {
  159. effects.consume(code)
  160. buffer += fromCharCode(code)
  161. return tagName
  162. }
  163. return nok(code)
  164. }
  165. function basicSelfClosing(code) {
  166. if (code === codes.greaterThan) {
  167. effects.consume(code)
  168. return self.interrupt ? ok : continuation
  169. }
  170. return nok(code)
  171. }
  172. function completeClosingTagAfter(code) {
  173. if (markdownSpace(code)) {
  174. effects.consume(code)
  175. return completeClosingTagAfter
  176. }
  177. return completeEnd(code)
  178. }
  179. function completeAttributeNameBefore(code) {
  180. if (code === codes.slash) {
  181. effects.consume(code)
  182. return completeEnd
  183. }
  184. if (code === codes.colon || code === codes.underscore || asciiAlpha(code)) {
  185. effects.consume(code)
  186. return completeAttributeName
  187. }
  188. if (markdownSpace(code)) {
  189. effects.consume(code)
  190. return completeAttributeNameBefore
  191. }
  192. return completeEnd(code)
  193. }
  194. function completeAttributeName(code) {
  195. if (
  196. code === codes.dash ||
  197. code === codes.dot ||
  198. code === codes.colon ||
  199. code === codes.underscore ||
  200. asciiAlphanumeric(code)
  201. ) {
  202. effects.consume(code)
  203. return completeAttributeName
  204. }
  205. return completeAttributeNameAfter(code)
  206. }
  207. function completeAttributeNameAfter(code) {
  208. if (code === codes.equalsTo) {
  209. effects.consume(code)
  210. return completeAttributeValueBefore
  211. }
  212. if (markdownSpace(code)) {
  213. effects.consume(code)
  214. return completeAttributeNameAfter
  215. }
  216. return completeAttributeNameBefore(code)
  217. }
  218. function completeAttributeValueBefore(code) {
  219. if (
  220. code === codes.eof ||
  221. code === codes.lessThan ||
  222. code === codes.equalsTo ||
  223. code === codes.greaterThan ||
  224. code === codes.graveAccent
  225. ) {
  226. return nok(code)
  227. }
  228. if (code === codes.quotationMark || code === codes.apostrophe) {
  229. effects.consume(code)
  230. marker = code
  231. return completeAttributeValueQuoted
  232. }
  233. if (markdownSpace(code)) {
  234. effects.consume(code)
  235. return completeAttributeValueBefore
  236. }
  237. marker = undefined
  238. return completeAttributeValueUnquoted(code)
  239. }
  240. function completeAttributeValueQuoted(code) {
  241. if (code === marker) {
  242. effects.consume(code)
  243. return completeAttributeValueQuotedAfter
  244. }
  245. if (code === codes.eof || markdownLineEnding(code)) {
  246. return nok(code)
  247. }
  248. effects.consume(code)
  249. return completeAttributeValueQuoted
  250. }
  251. function completeAttributeValueUnquoted(code) {
  252. if (
  253. code === codes.eof ||
  254. code === codes.quotationMark ||
  255. code === codes.apostrophe ||
  256. code === codes.lessThan ||
  257. code === codes.equalsTo ||
  258. code === codes.greaterThan ||
  259. code === codes.graveAccent ||
  260. markdownLineEndingOrSpace(code)
  261. ) {
  262. return completeAttributeNameAfter(code)
  263. }
  264. effects.consume(code)
  265. return completeAttributeValueUnquoted
  266. }
  267. function completeAttributeValueQuotedAfter(code) {
  268. if (
  269. code === codes.slash ||
  270. code === codes.greaterThan ||
  271. markdownSpace(code)
  272. ) {
  273. return completeAttributeNameBefore(code)
  274. }
  275. return nok(code)
  276. }
  277. function completeEnd(code) {
  278. if (code === codes.greaterThan) {
  279. effects.consume(code)
  280. return completeAfter
  281. }
  282. return nok(code)
  283. }
  284. function completeAfter(code) {
  285. if (markdownSpace(code)) {
  286. effects.consume(code)
  287. return completeAfter
  288. }
  289. return code === codes.eof || markdownLineEnding(code)
  290. ? continuation(code)
  291. : nok(code)
  292. }
  293. function continuation(code) {
  294. if (code === codes.dash && kind === constants.htmlComment) {
  295. effects.consume(code)
  296. return continuationCommentInside
  297. }
  298. if (code === codes.lessThan && kind === constants.htmlRaw) {
  299. effects.consume(code)
  300. return continuationRawTagOpen
  301. }
  302. if (code === codes.greaterThan && kind === constants.htmlDeclaration) {
  303. effects.consume(code)
  304. return continuationClose
  305. }
  306. if (code === codes.questionMark && kind === constants.htmlInstruction) {
  307. effects.consume(code)
  308. return continuationDeclarationInside
  309. }
  310. if (code === codes.rightSquareBracket && kind === constants.htmlCdata) {
  311. effects.consume(code)
  312. return continuationCharacterDataInside
  313. }
  314. if (
  315. markdownLineEnding(code) &&
  316. (kind === constants.htmlBasic || kind === constants.htmlComplete)
  317. ) {
  318. return effects.check(
  319. nextBlankConstruct,
  320. continuationClose,
  321. continuationAtLineEnding
  322. )(code)
  323. }
  324. if (code === codes.eof || markdownLineEnding(code)) {
  325. return continuationAtLineEnding(code)
  326. }
  327. effects.consume(code)
  328. return continuation
  329. }
  330. function continuationAtLineEnding(code) {
  331. effects.exit(types.htmlFlowData)
  332. return htmlContinueStart(code)
  333. }
  334. function htmlContinueStart(code) {
  335. if (code === codes.eof) {
  336. return done(code)
  337. }
  338. if (markdownLineEnding(code)) {
  339. effects.enter(types.lineEnding)
  340. effects.consume(code)
  341. effects.exit(types.lineEnding)
  342. return htmlContinueStart
  343. }
  344. effects.enter(types.htmlFlowData)
  345. return continuation(code)
  346. }
  347. function continuationCommentInside(code) {
  348. if (code === codes.dash) {
  349. effects.consume(code)
  350. return continuationDeclarationInside
  351. }
  352. return continuation(code)
  353. }
  354. function continuationRawTagOpen(code) {
  355. if (code === codes.slash) {
  356. effects.consume(code)
  357. buffer = ''
  358. return continuationRawEndTag
  359. }
  360. return continuation(code)
  361. }
  362. function continuationRawEndTag(code) {
  363. if (code === codes.greaterThan && raws.indexOf(buffer.toLowerCase()) > -1) {
  364. effects.consume(code)
  365. return continuationClose
  366. }
  367. if (asciiAlpha(code) && buffer.length < constants.htmlRawSizeMax) {
  368. effects.consume(code)
  369. buffer += fromCharCode(code)
  370. return continuationRawEndTag
  371. }
  372. return continuation(code)
  373. }
  374. function continuationCharacterDataInside(code) {
  375. if (code === codes.rightSquareBracket) {
  376. effects.consume(code)
  377. return continuationDeclarationInside
  378. }
  379. return continuation(code)
  380. }
  381. function continuationDeclarationInside(code) {
  382. if (code === codes.greaterThan) {
  383. effects.consume(code)
  384. return continuationClose
  385. }
  386. return continuation(code)
  387. }
  388. function continuationClose(code) {
  389. if (code === codes.eof || markdownLineEnding(code)) {
  390. effects.exit(types.htmlFlowData)
  391. return done(code)
  392. }
  393. effects.consume(code)
  394. return continuationClose
  395. }
  396. function done(code) {
  397. effects.exit(types.htmlFlow)
  398. return ok(code)
  399. }
  400. }
  401. function tokenizeNextBlank(effects, ok, nok) {
  402. return start
  403. function start(code) {
  404. assert(markdownLineEnding(code), 'expected a line ending')
  405. effects.exit(types.htmlFlowData)
  406. effects.enter(types.lineEndingBlank)
  407. effects.consume(code)
  408. effects.exit(types.lineEndingBlank)
  409. return effects.attempt(blank, ok, nok)
  410. }
  411. }