character-reference.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /**
  2. * @import {
  3. * Code,
  4. * Construct,
  5. * State,
  6. * TokenizeContext,
  7. * Tokenizer
  8. * } from 'micromark-util-types'
  9. */
  10. import { decodeNamedCharacterReference } from 'decode-named-character-reference';
  11. import { asciiAlphanumeric, asciiDigit, asciiHexDigit } from 'micromark-util-character';
  12. /** @type {Construct} */
  13. export const characterReference = {
  14. name: 'characterReference',
  15. tokenize: tokenizeCharacterReference
  16. };
  17. /**
  18. * @this {TokenizeContext}
  19. * Context.
  20. * @type {Tokenizer}
  21. */
  22. function tokenizeCharacterReference(effects, ok, nok) {
  23. const self = this;
  24. let size = 0;
  25. /** @type {number} */
  26. let max;
  27. /** @type {(code: Code) => boolean} */
  28. let test;
  29. return start;
  30. /**
  31. * Start of character reference.
  32. *
  33. * ```markdown
  34. * > | a&b
  35. * ^
  36. * > | a{b
  37. * ^
  38. * > | a	b
  39. * ^
  40. * ```
  41. *
  42. * @type {State}
  43. */
  44. function start(code) {
  45. effects.enter("characterReference");
  46. effects.enter("characterReferenceMarker");
  47. effects.consume(code);
  48. effects.exit("characterReferenceMarker");
  49. return open;
  50. }
  51. /**
  52. * After `&`, at `#` for numeric references or alphanumeric for named
  53. * references.
  54. *
  55. * ```markdown
  56. * > | a&b
  57. * ^
  58. * > | a{b
  59. * ^
  60. * > | a	b
  61. * ^
  62. * ```
  63. *
  64. * @type {State}
  65. */
  66. function open(code) {
  67. if (code === 35) {
  68. effects.enter("characterReferenceMarkerNumeric");
  69. effects.consume(code);
  70. effects.exit("characterReferenceMarkerNumeric");
  71. return numeric;
  72. }
  73. effects.enter("characterReferenceValue");
  74. max = 31;
  75. test = asciiAlphanumeric;
  76. return value(code);
  77. }
  78. /**
  79. * After `#`, at `x` for hexadecimals or digit for decimals.
  80. *
  81. * ```markdown
  82. * > | a{b
  83. * ^
  84. * > | a	b
  85. * ^
  86. * ```
  87. *
  88. * @type {State}
  89. */
  90. function numeric(code) {
  91. if (code === 88 || code === 120) {
  92. effects.enter("characterReferenceMarkerHexadecimal");
  93. effects.consume(code);
  94. effects.exit("characterReferenceMarkerHexadecimal");
  95. effects.enter("characterReferenceValue");
  96. max = 6;
  97. test = asciiHexDigit;
  98. return value;
  99. }
  100. effects.enter("characterReferenceValue");
  101. max = 7;
  102. test = asciiDigit;
  103. return value(code);
  104. }
  105. /**
  106. * After markers (`&#x`, `&#`, or `&`), in value, before `;`.
  107. *
  108. * The character reference kind defines what and how many characters are
  109. * allowed.
  110. *
  111. * ```markdown
  112. * > | a&b
  113. * ^^^
  114. * > | a{b
  115. * ^^^
  116. * > | a	b
  117. * ^
  118. * ```
  119. *
  120. * @type {State}
  121. */
  122. function value(code) {
  123. if (code === 59 && size) {
  124. const token = effects.exit("characterReferenceValue");
  125. if (test === asciiAlphanumeric && !decodeNamedCharacterReference(self.sliceSerialize(token))) {
  126. return nok(code);
  127. }
  128. // To do: `markdown-rs` uses a different name:
  129. // `CharacterReferenceMarkerSemi`.
  130. effects.enter("characterReferenceMarker");
  131. effects.consume(code);
  132. effects.exit("characterReferenceMarker");
  133. effects.exit("characterReference");
  134. return ok;
  135. }
  136. if (test(code) && size++ < max) {
  137. effects.consume(code);
  138. return value;
  139. }
  140. return nok(code);
  141. }
  142. }