html-text.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  1. /**
  2. * @import {
  3. * Code,
  4. * Construct,
  5. * State,
  6. * TokenizeContext,
  7. * Tokenizer
  8. * } from 'micromark-util-types'
  9. */
  10. import { factorySpace } from 'micromark-factory-space';
  11. import { asciiAlphanumeric, asciiAlpha, markdownLineEndingOrSpace, markdownLineEnding, markdownSpace } from 'micromark-util-character';
  12. /** @type {Construct} */
  13. export const htmlText = {
  14. name: 'htmlText',
  15. tokenize: tokenizeHtmlText
  16. };
  17. /**
  18. * @this {TokenizeContext}
  19. * Context.
  20. * @type {Tokenizer}
  21. */
  22. function tokenizeHtmlText(effects, ok, nok) {
  23. const self = this;
  24. /** @type {NonNullable<Code> | undefined} */
  25. let marker;
  26. /** @type {number} */
  27. let index;
  28. /** @type {State} */
  29. let returnState;
  30. return start;
  31. /**
  32. * Start of HTML (text).
  33. *
  34. * ```markdown
  35. * > | a <b> c
  36. * ^
  37. * ```
  38. *
  39. * @type {State}
  40. */
  41. function start(code) {
  42. effects.enter("htmlText");
  43. effects.enter("htmlTextData");
  44. effects.consume(code);
  45. return open;
  46. }
  47. /**
  48. * After `<`, at tag name or other stuff.
  49. *
  50. * ```markdown
  51. * > | a <b> c
  52. * ^
  53. * > | a <!doctype> c
  54. * ^
  55. * > | a <!--b--> c
  56. * ^
  57. * ```
  58. *
  59. * @type {State}
  60. */
  61. function open(code) {
  62. if (code === 33) {
  63. effects.consume(code);
  64. return declarationOpen;
  65. }
  66. if (code === 47) {
  67. effects.consume(code);
  68. return tagCloseStart;
  69. }
  70. if (code === 63) {
  71. effects.consume(code);
  72. return instruction;
  73. }
  74. // ASCII alphabetical.
  75. if (asciiAlpha(code)) {
  76. effects.consume(code);
  77. return tagOpen;
  78. }
  79. return nok(code);
  80. }
  81. /**
  82. * After `<!`, at declaration, comment, or CDATA.
  83. *
  84. * ```markdown
  85. * > | a <!doctype> c
  86. * ^
  87. * > | a <!--b--> c
  88. * ^
  89. * > | a <![CDATA[>&<]]> c
  90. * ^
  91. * ```
  92. *
  93. * @type {State}
  94. */
  95. function declarationOpen(code) {
  96. if (code === 45) {
  97. effects.consume(code);
  98. return commentOpenInside;
  99. }
  100. if (code === 91) {
  101. effects.consume(code);
  102. index = 0;
  103. return cdataOpenInside;
  104. }
  105. if (asciiAlpha(code)) {
  106. effects.consume(code);
  107. return declaration;
  108. }
  109. return nok(code);
  110. }
  111. /**
  112. * In a comment, after `<!-`, at another `-`.
  113. *
  114. * ```markdown
  115. * > | a <!--b--> c
  116. * ^
  117. * ```
  118. *
  119. * @type {State}
  120. */
  121. function commentOpenInside(code) {
  122. if (code === 45) {
  123. effects.consume(code);
  124. return commentEnd;
  125. }
  126. return nok(code);
  127. }
  128. /**
  129. * In comment.
  130. *
  131. * ```markdown
  132. * > | a <!--b--> c
  133. * ^
  134. * ```
  135. *
  136. * @type {State}
  137. */
  138. function comment(code) {
  139. if (code === null) {
  140. return nok(code);
  141. }
  142. if (code === 45) {
  143. effects.consume(code);
  144. return commentClose;
  145. }
  146. if (markdownLineEnding(code)) {
  147. returnState = comment;
  148. return lineEndingBefore(code);
  149. }
  150. effects.consume(code);
  151. return comment;
  152. }
  153. /**
  154. * In comment, after `-`.
  155. *
  156. * ```markdown
  157. * > | a <!--b--> c
  158. * ^
  159. * ```
  160. *
  161. * @type {State}
  162. */
  163. function commentClose(code) {
  164. if (code === 45) {
  165. effects.consume(code);
  166. return commentEnd;
  167. }
  168. return comment(code);
  169. }
  170. /**
  171. * In comment, after `--`.
  172. *
  173. * ```markdown
  174. * > | a <!--b--> c
  175. * ^
  176. * ```
  177. *
  178. * @type {State}
  179. */
  180. function commentEnd(code) {
  181. return code === 62 ? end(code) : code === 45 ? commentClose(code) : comment(code);
  182. }
  183. /**
  184. * After `<![`, in CDATA, expecting `CDATA[`.
  185. *
  186. * ```markdown
  187. * > | a <![CDATA[>&<]]> b
  188. * ^^^^^^
  189. * ```
  190. *
  191. * @type {State}
  192. */
  193. function cdataOpenInside(code) {
  194. const value = "CDATA[";
  195. if (code === value.charCodeAt(index++)) {
  196. effects.consume(code);
  197. return index === value.length ? cdata : cdataOpenInside;
  198. }
  199. return nok(code);
  200. }
  201. /**
  202. * In CDATA.
  203. *
  204. * ```markdown
  205. * > | a <![CDATA[>&<]]> b
  206. * ^^^
  207. * ```
  208. *
  209. * @type {State}
  210. */
  211. function cdata(code) {
  212. if (code === null) {
  213. return nok(code);
  214. }
  215. if (code === 93) {
  216. effects.consume(code);
  217. return cdataClose;
  218. }
  219. if (markdownLineEnding(code)) {
  220. returnState = cdata;
  221. return lineEndingBefore(code);
  222. }
  223. effects.consume(code);
  224. return cdata;
  225. }
  226. /**
  227. * In CDATA, after `]`, at another `]`.
  228. *
  229. * ```markdown
  230. * > | a <![CDATA[>&<]]> b
  231. * ^
  232. * ```
  233. *
  234. * @type {State}
  235. */
  236. function cdataClose(code) {
  237. if (code === 93) {
  238. effects.consume(code);
  239. return cdataEnd;
  240. }
  241. return cdata(code);
  242. }
  243. /**
  244. * In CDATA, after `]]`, at `>`.
  245. *
  246. * ```markdown
  247. * > | a <![CDATA[>&<]]> b
  248. * ^
  249. * ```
  250. *
  251. * @type {State}
  252. */
  253. function cdataEnd(code) {
  254. if (code === 62) {
  255. return end(code);
  256. }
  257. if (code === 93) {
  258. effects.consume(code);
  259. return cdataEnd;
  260. }
  261. return cdata(code);
  262. }
  263. /**
  264. * In declaration.
  265. *
  266. * ```markdown
  267. * > | a <!b> c
  268. * ^
  269. * ```
  270. *
  271. * @type {State}
  272. */
  273. function declaration(code) {
  274. if (code === null || code === 62) {
  275. return end(code);
  276. }
  277. if (markdownLineEnding(code)) {
  278. returnState = declaration;
  279. return lineEndingBefore(code);
  280. }
  281. effects.consume(code);
  282. return declaration;
  283. }
  284. /**
  285. * In instruction.
  286. *
  287. * ```markdown
  288. * > | a <?b?> c
  289. * ^
  290. * ```
  291. *
  292. * @type {State}
  293. */
  294. function instruction(code) {
  295. if (code === null) {
  296. return nok(code);
  297. }
  298. if (code === 63) {
  299. effects.consume(code);
  300. return instructionClose;
  301. }
  302. if (markdownLineEnding(code)) {
  303. returnState = instruction;
  304. return lineEndingBefore(code);
  305. }
  306. effects.consume(code);
  307. return instruction;
  308. }
  309. /**
  310. * In instruction, after `?`, at `>`.
  311. *
  312. * ```markdown
  313. * > | a <?b?> c
  314. * ^
  315. * ```
  316. *
  317. * @type {State}
  318. */
  319. function instructionClose(code) {
  320. return code === 62 ? end(code) : instruction(code);
  321. }
  322. /**
  323. * After `</`, in closing tag, at tag name.
  324. *
  325. * ```markdown
  326. * > | a </b> c
  327. * ^
  328. * ```
  329. *
  330. * @type {State}
  331. */
  332. function tagCloseStart(code) {
  333. // ASCII alphabetical.
  334. if (asciiAlpha(code)) {
  335. effects.consume(code);
  336. return tagClose;
  337. }
  338. return nok(code);
  339. }
  340. /**
  341. * After `</x`, in a tag name.
  342. *
  343. * ```markdown
  344. * > | a </b> c
  345. * ^
  346. * ```
  347. *
  348. * @type {State}
  349. */
  350. function tagClose(code) {
  351. // ASCII alphanumerical and `-`.
  352. if (code === 45 || asciiAlphanumeric(code)) {
  353. effects.consume(code);
  354. return tagClose;
  355. }
  356. return tagCloseBetween(code);
  357. }
  358. /**
  359. * In closing tag, after tag name.
  360. *
  361. * ```markdown
  362. * > | a </b> c
  363. * ^
  364. * ```
  365. *
  366. * @type {State}
  367. */
  368. function tagCloseBetween(code) {
  369. if (markdownLineEnding(code)) {
  370. returnState = tagCloseBetween;
  371. return lineEndingBefore(code);
  372. }
  373. if (markdownSpace(code)) {
  374. effects.consume(code);
  375. return tagCloseBetween;
  376. }
  377. return end(code);
  378. }
  379. /**
  380. * After `<x`, in opening tag name.
  381. *
  382. * ```markdown
  383. * > | a <b> c
  384. * ^
  385. * ```
  386. *
  387. * @type {State}
  388. */
  389. function tagOpen(code) {
  390. // ASCII alphanumerical and `-`.
  391. if (code === 45 || asciiAlphanumeric(code)) {
  392. effects.consume(code);
  393. return tagOpen;
  394. }
  395. if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
  396. return tagOpenBetween(code);
  397. }
  398. return nok(code);
  399. }
  400. /**
  401. * In opening tag, after tag name.
  402. *
  403. * ```markdown
  404. * > | a <b> c
  405. * ^
  406. * ```
  407. *
  408. * @type {State}
  409. */
  410. function tagOpenBetween(code) {
  411. if (code === 47) {
  412. effects.consume(code);
  413. return end;
  414. }
  415. // ASCII alphabetical and `:` and `_`.
  416. if (code === 58 || code === 95 || asciiAlpha(code)) {
  417. effects.consume(code);
  418. return tagOpenAttributeName;
  419. }
  420. if (markdownLineEnding(code)) {
  421. returnState = tagOpenBetween;
  422. return lineEndingBefore(code);
  423. }
  424. if (markdownSpace(code)) {
  425. effects.consume(code);
  426. return tagOpenBetween;
  427. }
  428. return end(code);
  429. }
  430. /**
  431. * In attribute name.
  432. *
  433. * ```markdown
  434. * > | a <b c> d
  435. * ^
  436. * ```
  437. *
  438. * @type {State}
  439. */
  440. function tagOpenAttributeName(code) {
  441. // ASCII alphabetical and `-`, `.`, `:`, and `_`.
  442. if (code === 45 || code === 46 || code === 58 || code === 95 || asciiAlphanumeric(code)) {
  443. effects.consume(code);
  444. return tagOpenAttributeName;
  445. }
  446. return tagOpenAttributeNameAfter(code);
  447. }
  448. /**
  449. * After attribute name, before initializer, the end of the tag, or
  450. * whitespace.
  451. *
  452. * ```markdown
  453. * > | a <b c> d
  454. * ^
  455. * ```
  456. *
  457. * @type {State}
  458. */
  459. function tagOpenAttributeNameAfter(code) {
  460. if (code === 61) {
  461. effects.consume(code);
  462. return tagOpenAttributeValueBefore;
  463. }
  464. if (markdownLineEnding(code)) {
  465. returnState = tagOpenAttributeNameAfter;
  466. return lineEndingBefore(code);
  467. }
  468. if (markdownSpace(code)) {
  469. effects.consume(code);
  470. return tagOpenAttributeNameAfter;
  471. }
  472. return tagOpenBetween(code);
  473. }
  474. /**
  475. * Before unquoted, double quoted, or single quoted attribute value, allowing
  476. * whitespace.
  477. *
  478. * ```markdown
  479. * > | a <b c=d> e
  480. * ^
  481. * ```
  482. *
  483. * @type {State}
  484. */
  485. function tagOpenAttributeValueBefore(code) {
  486. if (code === null || code === 60 || code === 61 || code === 62 || code === 96) {
  487. return nok(code);
  488. }
  489. if (code === 34 || code === 39) {
  490. effects.consume(code);
  491. marker = code;
  492. return tagOpenAttributeValueQuoted;
  493. }
  494. if (markdownLineEnding(code)) {
  495. returnState = tagOpenAttributeValueBefore;
  496. return lineEndingBefore(code);
  497. }
  498. if (markdownSpace(code)) {
  499. effects.consume(code);
  500. return tagOpenAttributeValueBefore;
  501. }
  502. effects.consume(code);
  503. return tagOpenAttributeValueUnquoted;
  504. }
  505. /**
  506. * In double or single quoted attribute value.
  507. *
  508. * ```markdown
  509. * > | a <b c="d"> e
  510. * ^
  511. * ```
  512. *
  513. * @type {State}
  514. */
  515. function tagOpenAttributeValueQuoted(code) {
  516. if (code === marker) {
  517. effects.consume(code);
  518. marker = undefined;
  519. return tagOpenAttributeValueQuotedAfter;
  520. }
  521. if (code === null) {
  522. return nok(code);
  523. }
  524. if (markdownLineEnding(code)) {
  525. returnState = tagOpenAttributeValueQuoted;
  526. return lineEndingBefore(code);
  527. }
  528. effects.consume(code);
  529. return tagOpenAttributeValueQuoted;
  530. }
  531. /**
  532. * In unquoted attribute value.
  533. *
  534. * ```markdown
  535. * > | a <b c=d> e
  536. * ^
  537. * ```
  538. *
  539. * @type {State}
  540. */
  541. function tagOpenAttributeValueUnquoted(code) {
  542. if (code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 96) {
  543. return nok(code);
  544. }
  545. if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
  546. return tagOpenBetween(code);
  547. }
  548. effects.consume(code);
  549. return tagOpenAttributeValueUnquoted;
  550. }
  551. /**
  552. * After double or single quoted attribute value, before whitespace or the end
  553. * of the tag.
  554. *
  555. * ```markdown
  556. * > | a <b c="d"> e
  557. * ^
  558. * ```
  559. *
  560. * @type {State}
  561. */
  562. function tagOpenAttributeValueQuotedAfter(code) {
  563. if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
  564. return tagOpenBetween(code);
  565. }
  566. return nok(code);
  567. }
  568. /**
  569. * In certain circumstances of a tag where only an `>` is allowed.
  570. *
  571. * ```markdown
  572. * > | a <b c="d"> e
  573. * ^
  574. * ```
  575. *
  576. * @type {State}
  577. */
  578. function end(code) {
  579. if (code === 62) {
  580. effects.consume(code);
  581. effects.exit("htmlTextData");
  582. effects.exit("htmlText");
  583. return ok;
  584. }
  585. return nok(code);
  586. }
  587. /**
  588. * At eol.
  589. *
  590. * > 👉 **Note**: we can’t have blank lines in text, so no need to worry about
  591. * > empty tokens.
  592. *
  593. * ```markdown
  594. * > | a <!--a
  595. * ^
  596. * | b-->
  597. * ```
  598. *
  599. * @type {State}
  600. */
  601. function lineEndingBefore(code) {
  602. effects.exit("htmlTextData");
  603. effects.enter("lineEnding");
  604. effects.consume(code);
  605. effects.exit("lineEnding");
  606. return lineEndingAfter;
  607. }
  608. /**
  609. * After eol, at optional whitespace.
  610. *
  611. * > 👉 **Note**: we can’t have blank lines in text, so no need to worry about
  612. * > empty tokens.
  613. *
  614. * ```markdown
  615. * | a <!--a
  616. * > | b-->
  617. * ^
  618. * ```
  619. *
  620. * @type {State}
  621. */
  622. function lineEndingAfter(code) {
  623. // Always populated by defaults.
  624. return markdownSpace(code) ? factorySpace(effects, lineEndingAfterPrefix, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code) : lineEndingAfterPrefix(code);
  625. }
  626. /**
  627. * After eol, after optional whitespace.
  628. *
  629. * > 👉 **Note**: we can’t have blank lines in text, so no need to worry about
  630. * > empty tokens.
  631. *
  632. * ```markdown
  633. * | a <!--a
  634. * > | b-->
  635. * ^
  636. * ```
  637. *
  638. * @type {State}
  639. */
  640. function lineEndingAfterPrefix(code) {
  641. effects.enter("htmlTextData");
  642. return returnState(code);
  643. }
  644. }