markdown-table.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import csvToMarkdown from 'csv-to-markdown-table';
  2. import { markdownTable } from 'markdown-table';
  3. import stringWidth from 'string-width';
  4. // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
  5. // https://regex101.com/r/7BN2fR/7
  6. const tableAlignmentLineRE = /^[-:|][-:|\s]*$/;
  7. const tableAlignmentLineNegRE = /^[^-:]*$/; // it is need to check to ignore empty row which is matched above RE
  8. const linePartOfTableRE = /^\|[^\r\n]*|[^\r\n]*\|$|([^|\r\n]+\|[^|\r\n]*)+/; // own idea
  9. const defaultOptions = { stringLength: stringWidth };
  10. /**
  11. * markdown table class for markdown-table module
  12. * ref. https://github.com/wooorm/markdown-table
  13. */
  14. export class MarkdownTable {
  15. constructor(table, options) {
  16. this.table = table || [];
  17. this.options = Object.assign(options || {}, defaultOptions);
  18. this.toString = this.toString.bind(this);
  19. }
  20. toString() {
  21. return markdownTable(this.table, this.options);
  22. }
  23. /**
  24. * returns cloned Markdowntable instance
  25. * (This method clones only the table field.)
  26. */
  27. clone() {
  28. const newTable = [];
  29. for (let i = 0; i < this.table.length; i++) {
  30. newTable.push([].concat(this.table[i]));
  31. }
  32. return new MarkdownTable(newTable, this.options);
  33. }
  34. /**
  35. * normalize all cell data(trim & convert the newline character to space or pad '' if cell data is null)
  36. */
  37. normalizeCells() {
  38. for (let i = 0; i < this.table.length; i++) {
  39. for (let j = 0; j < this.table[i].length; j++) {
  40. if (this.table[i][j] != null) {
  41. this.table[i][j] = this.table[i][j].trim().replace(/\r?\n/g, ' ');
  42. } else {
  43. this.table[i][j] = '';
  44. }
  45. }
  46. }
  47. return this;
  48. }
  49. /**
  50. * return a MarkdownTable instance made from a string of HTML table tag
  51. *
  52. * If a parser error occurs, an error object with an error message is thrown.
  53. * The error message is a innerHTML, so must not assign it into element.innerHTML because it can lead to Mutation-based XSS
  54. */
  55. static fromHTMLTableTag(str) {
  56. // set up DOMParser
  57. const domParser = new window.DOMParser();
  58. // use DOMParser to prevent DOM based XSS (https://developer.mozilla.org/en-US/docs/Web/API/DOMParser)
  59. const dom = domParser.parseFromString(str, 'application/xml');
  60. if (dom.querySelector('parsererror')) {
  61. throw new Error(dom.documentElement.innerHTML);
  62. }
  63. const tableElement = dom.querySelector('table');
  64. const trElements = tableElement.querySelectorAll('tr');
  65. const table = [];
  66. let maxRowSize = 0;
  67. for (let i = 0; i < trElements.length; i++) {
  68. const row = [];
  69. const cellElements = trElements[i].querySelectorAll('th,td');
  70. for (let j = 0; j < cellElements.length; j++) {
  71. row.push(cellElements[j].innerHTML);
  72. }
  73. table.push(row);
  74. if (maxRowSize < row.length) maxRowSize = row.length;
  75. }
  76. const align = [];
  77. for (let i = 0; i < maxRowSize; i++) {
  78. align.push('');
  79. }
  80. return new MarkdownTable(table, { align });
  81. }
  82. /**
  83. * return a MarkdownTable instance made from a string of delimiter-separated values
  84. */
  85. static fromDSV(str, delimiter) {
  86. return MarkdownTable.fromMarkdownString(
  87. csvToMarkdown(str, delimiter, true),
  88. );
  89. }
  90. /**
  91. * return a MarkdownTable instance
  92. * ref. https://github.com/wooorm/markdown-table
  93. * @param {string} str markdown string
  94. */
  95. static fromMarkdownString(str) {
  96. const arrMDTableLines = str.split(/(\r\n|\r|\n)/);
  97. const contents = [];
  98. let aligns = [];
  99. for (let n = 0; n < arrMDTableLines.length; n++) {
  100. const line = arrMDTableLines[n];
  101. if (
  102. tableAlignmentLineRE.test(line) &&
  103. !tableAlignmentLineNegRE.test(line)
  104. ) {
  105. // parse line which described alignment
  106. const alignRuleRE = [
  107. { align: 'c', regex: /^:-+:$/ },
  108. { align: 'l', regex: /^:-+$/ },
  109. { align: 'r', regex: /^-+:$/ },
  110. ];
  111. let lineText = '';
  112. lineText = line.replace(/^\||\|$/g, ''); // strip off pipe charactor which is placed head of line and last of line.
  113. lineText = lineText.replace(/\s*/g, '');
  114. aligns = lineText.split(/\|/).map((col) => {
  115. const rule = alignRuleRE.find((rule) => {
  116. return col.match(rule.regex);
  117. });
  118. return rule != null ? rule.align : '';
  119. });
  120. } else if (linePartOfTableRE.test(line)) {
  121. // parse line whether header or body
  122. let lineText = '';
  123. lineText = line.replace(/\s*\|\s*/g, '|');
  124. lineText = lineText.replace(/^\||\|$/g, ''); // strip off pipe charactor which is placed head of line and last of line.
  125. const row = lineText.split(/\|/);
  126. contents.push(row);
  127. }
  128. }
  129. return new MarkdownTable(contents, { align: aligns });
  130. }
  131. }