html.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /**
  2. * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
  3. * @typedef {import('./syntax.js').Align} Align
  4. */
  5. const alignment = {
  6. none: '',
  7. left: ' align="left"',
  8. right: ' align="right"',
  9. center: ' align="center"'
  10. }
  11. /**
  12. * HTML extension for micromark (passed in `htmlExtensions`).
  13. *
  14. * @type {HtmlExtension}
  15. */
  16. export const gfmTableHtml = {
  17. enter: {
  18. table(token) {
  19. /** @type {Array<Align>} */
  20. // @ts-expect-error Custom.
  21. const tableAlign = token._align
  22. this.lineEndingIfNeeded()
  23. this.tag('<table>')
  24. this.setData('tableAlign', tableAlign)
  25. },
  26. tableBody() {
  27. // Clear slurping line ending from the delimiter row.
  28. this.setData('slurpOneLineEnding')
  29. this.tag('<tbody>')
  30. },
  31. tableData() {
  32. const tableAlign = /** @type {Array<Align>} */ (
  33. this.getData('tableAlign')
  34. )
  35. const tableColumn = /** @type {number} */ (this.getData('tableColumn'))
  36. const align = alignment[tableAlign[tableColumn]]
  37. if (align === undefined) {
  38. // Capture results to ignore them.
  39. this.buffer()
  40. } else {
  41. this.lineEndingIfNeeded()
  42. this.tag('<td' + align + '>')
  43. }
  44. },
  45. tableHead() {
  46. this.lineEndingIfNeeded()
  47. this.tag('<thead>')
  48. },
  49. tableHeader() {
  50. const tableAlign = /** @type {Array<Align>} */ (
  51. this.getData('tableAlign')
  52. )
  53. const tableColumn = /** @type {number} */ (this.getData('tableColumn'))
  54. const align = alignment[tableAlign[tableColumn]]
  55. this.lineEndingIfNeeded()
  56. this.tag('<th' + align + '>')
  57. },
  58. tableRow() {
  59. this.setData('tableColumn', 0)
  60. this.lineEndingIfNeeded()
  61. this.tag('<tr>')
  62. }
  63. },
  64. exit: {
  65. // Overwrite the default code text data handler to unescape escaped pipes when
  66. // they are in tables.
  67. codeTextData(token) {
  68. let value = this.sliceSerialize(token)
  69. if (this.getData('tableAlign')) {
  70. value = value.replace(/\\([\\|])/g, replace)
  71. }
  72. this.raw(this.encode(value))
  73. },
  74. table() {
  75. this.setData('tableAlign')
  76. // If there was no table body, make sure the slurping from the delimiter row
  77. // is cleared.
  78. this.setData('slurpAllLineEndings')
  79. this.lineEndingIfNeeded()
  80. this.tag('</table>')
  81. },
  82. tableBody() {
  83. this.lineEndingIfNeeded()
  84. this.tag('</tbody>')
  85. },
  86. tableData() {
  87. const tableAlign = /** @type {Array<Align>} */ (
  88. this.getData('tableAlign')
  89. )
  90. const tableColumn = /** @type {number} */ (this.getData('tableColumn'))
  91. if (tableColumn in tableAlign) {
  92. this.tag('</td>')
  93. this.setData('tableColumn', tableColumn + 1)
  94. } else {
  95. // Stop capturing.
  96. this.resume()
  97. }
  98. },
  99. tableHead() {
  100. this.lineEndingIfNeeded()
  101. this.tag('</thead>')
  102. this.setData('slurpOneLineEnding', true)
  103. // Slurp the line ending from the delimiter row.
  104. },
  105. tableHeader() {
  106. const tableColumn = /** @type {number} */ (this.getData('tableColumn'))
  107. this.tag('</th>')
  108. this.setData('tableColumn', tableColumn + 1)
  109. },
  110. tableRow() {
  111. const tableAlign = /** @type {Array<Align>} */ (
  112. this.getData('tableAlign')
  113. )
  114. let tableColumn = /** @type {number} */ (this.getData('tableColumn'))
  115. while (tableColumn < tableAlign.length) {
  116. this.lineEndingIfNeeded()
  117. this.tag('<td' + alignment[tableAlign[tableColumn]] + '></td>')
  118. tableColumn++
  119. }
  120. this.setData('tableColumn', tableColumn)
  121. this.lineEndingIfNeeded()
  122. this.tag('</tr>')
  123. }
  124. }
  125. }
  126. /**
  127. * @param {string} $0
  128. * @param {string} $1
  129. * @returns {string}
  130. */
  131. function replace($0, $1) {
  132. // Pipes work, backslashes don’t (but can’t escape pipes).
  133. return $1 === '|' ? $1 : $0
  134. }