table.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. // GFM table, non-standard
  2. 'use strict';
  3. var isSpace = require('../common/utils').isSpace;
  4. function getLine(state, line) {
  5. var pos = state.bMarks[line] + state.blkIndent,
  6. max = state.eMarks[line];
  7. return state.src.substr(pos, max - pos);
  8. }
  9. function escapedSplit(str) {
  10. var result = [],
  11. pos = 0,
  12. max = str.length,
  13. ch,
  14. escapes = 0,
  15. lastPos = 0,
  16. backTicked = false,
  17. lastBackTick = 0;
  18. ch = str.charCodeAt(pos);
  19. while (pos < max) {
  20. if (ch === 0x60/* ` */) {
  21. if (backTicked) {
  22. // make \` close code sequence, but not open it;
  23. // the reason is: `\` is correct code block
  24. backTicked = false;
  25. lastBackTick = pos;
  26. } else if (escapes % 2 === 0) {
  27. backTicked = true;
  28. lastBackTick = pos;
  29. }
  30. } else if (ch === 0x7c/* | */ && (escapes % 2 === 0) && !backTicked) {
  31. result.push(str.substring(lastPos, pos));
  32. lastPos = pos + 1;
  33. }
  34. if (ch === 0x5c/* \ */) {
  35. escapes++;
  36. } else {
  37. escapes = 0;
  38. }
  39. pos++;
  40. // If there was an un-closed backtick, go back to just after
  41. // the last backtick, but as if it was a normal character
  42. if (pos === max && backTicked) {
  43. backTicked = false;
  44. pos = lastBackTick + 1;
  45. }
  46. ch = str.charCodeAt(pos);
  47. }
  48. result.push(str.substring(lastPos));
  49. return result;
  50. }
  51. module.exports = function table(state, startLine, endLine, silent) {
  52. var ch, lineText, pos, i, nextLine, columns, columnCount, token,
  53. aligns, t, tableLines, tbodyLines;
  54. // should have at least two lines
  55. if (startLine + 2 > endLine) { return false; }
  56. nextLine = startLine + 1;
  57. if (state.sCount[nextLine] < state.blkIndent) { return false; }
  58. // if it's indented more than 3 spaces, it should be a code block
  59. if (state.sCount[nextLine] - state.blkIndent >= 4) { return false; }
  60. // first character of the second line should be '|', '-', ':',
  61. // and no other characters are allowed but spaces;
  62. // basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp
  63. pos = state.bMarks[nextLine] + state.tShift[nextLine];
  64. if (pos >= state.eMarks[nextLine]) { return false; }
  65. ch = state.src.charCodeAt(pos++);
  66. if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */) { return false; }
  67. while (pos < state.eMarks[nextLine]) {
  68. ch = state.src.charCodeAt(pos);
  69. if (ch !== 0x7C/* | */ && ch !== 0x2D/* - */ && ch !== 0x3A/* : */ && !isSpace(ch)) { return false; }
  70. pos++;
  71. }
  72. lineText = getLine(state, startLine + 1);
  73. columns = lineText.split('|');
  74. aligns = [];
  75. for (i = 0; i < columns.length; i++) {
  76. t = columns[i].trim();
  77. if (!t) {
  78. // allow empty columns before and after table, but not in between columns;
  79. // e.g. allow ` |---| `, disallow ` ---||--- `
  80. if (i === 0 || i === columns.length - 1) {
  81. continue;
  82. } else {
  83. return false;
  84. }
  85. }
  86. if (!/^:?-+:?$/.test(t)) { return false; }
  87. if (t.charCodeAt(t.length - 1) === 0x3A/* : */) {
  88. aligns.push(t.charCodeAt(0) === 0x3A/* : */ ? 'center' : 'right');
  89. } else if (t.charCodeAt(0) === 0x3A/* : */) {
  90. aligns.push('left');
  91. } else {
  92. aligns.push('');
  93. }
  94. }
  95. lineText = getLine(state, startLine).trim();
  96. if (lineText.indexOf('|') === -1) { return false; }
  97. if (state.sCount[startLine] - state.blkIndent >= 4) { return false; }
  98. columns = escapedSplit(lineText.replace(/^\||\|$/g, ''));
  99. // header row will define an amount of columns in the entire table,
  100. // and align row shouldn't be smaller than that (the rest of the rows can)
  101. columnCount = columns.length;
  102. if (columnCount > aligns.length) { return false; }
  103. if (silent) { return true; }
  104. token = state.push('table_open', 'table', 1);
  105. token.map = tableLines = [ startLine, 0 ];
  106. token = state.push('thead_open', 'thead', 1);
  107. token.map = [ startLine, startLine + 1 ];
  108. token = state.push('tr_open', 'tr', 1);
  109. token.map = [ startLine, startLine + 1 ];
  110. for (i = 0; i < columns.length; i++) {
  111. token = state.push('th_open', 'th', 1);
  112. token.map = [ startLine, startLine + 1 ];
  113. if (aligns[i]) {
  114. token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ];
  115. }
  116. token = state.push('inline', '', 0);
  117. token.content = columns[i].trim();
  118. token.map = [ startLine, startLine + 1 ];
  119. token.children = [];
  120. token = state.push('th_close', 'th', -1);
  121. }
  122. token = state.push('tr_close', 'tr', -1);
  123. token = state.push('thead_close', 'thead', -1);
  124. token = state.push('tbody_open', 'tbody', 1);
  125. token.map = tbodyLines = [ startLine + 2, 0 ];
  126. for (nextLine = startLine + 2; nextLine < endLine; nextLine++) {
  127. if (state.sCount[nextLine] < state.blkIndent) { break; }
  128. lineText = getLine(state, nextLine).trim();
  129. if (lineText.indexOf('|') === -1) { break; }
  130. if (state.sCount[nextLine] - state.blkIndent >= 4) { break; }
  131. columns = escapedSplit(lineText.replace(/^\||\|$/g, ''));
  132. token = state.push('tr_open', 'tr', 1);
  133. for (i = 0; i < columnCount; i++) {
  134. token = state.push('td_open', 'td', 1);
  135. if (aligns[i]) {
  136. token.attrs = [ [ 'style', 'text-align:' + aligns[i] ] ];
  137. }
  138. token = state.push('inline', '', 0);
  139. token.content = columns[i] ? columns[i].trim() : '';
  140. token.children = [];
  141. token = state.push('td_close', 'td', -1);
  142. }
  143. token = state.push('tr_close', 'tr', -1);
  144. }
  145. token = state.push('tbody_close', 'tbody', -1);
  146. token = state.push('table_close', 'table', -1);
  147. tableLines[1] = tbodyLines[1] = nextLine;
  148. state.line = nextLine;
  149. return true;
  150. };