splitAtDelimiters.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /* eslint no-constant-condition:0 */
  2. export interface DelimiterSpec {
  3. left: string;
  4. right: string;
  5. display: boolean;
  6. }
  7. export interface SplitAtDelimiterData {
  8. type: "text" | "math";
  9. data: string;
  10. rawData?: string;
  11. display?: boolean;
  12. }
  13. const findEndOfMath = function(delimiter: string, text: string, startIndex: number): number {
  14. // Adapted from
  15. // https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx
  16. let index = startIndex;
  17. let braceLevel = 0;
  18. const delimLength = delimiter.length;
  19. while (index < text.length) {
  20. const character = text[index];
  21. if (braceLevel <= 0 &&
  22. text.slice(index, index + delimLength) === delimiter) {
  23. return index;
  24. } else if (character === "\\") {
  25. index++;
  26. } else if (character === "{") {
  27. braceLevel++;
  28. } else if (character === "}") {
  29. braceLevel--;
  30. }
  31. index++;
  32. }
  33. return -1;
  34. };
  35. const escapeRegex = function(string: string): string {
  36. return string.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
  37. };
  38. const amsRegex = /^\\begin{/;
  39. const splitAtDelimiters = function(text: string, delimiters: DelimiterSpec[]): SplitAtDelimiterData[] {
  40. let index;
  41. const data: SplitAtDelimiterData[] = [];
  42. const regexLeft = new RegExp(
  43. "(" + delimiters.map((x) => escapeRegex(x.left)).join("|") + ")"
  44. );
  45. while (true) {
  46. index = text.search(regexLeft);
  47. if (index === -1) {
  48. break;
  49. }
  50. if (index > 0) {
  51. data.push({
  52. type: "text",
  53. data: text.slice(0, index),
  54. });
  55. text = text.slice(index); // now text starts with delimiter
  56. }
  57. // ... so this always succeeds:
  58. const i = delimiters.findIndex((delim) => text.startsWith(delim.left));
  59. index = findEndOfMath(delimiters[i].right, text, delimiters[i].left.length);
  60. if (index === -1) {
  61. break;
  62. }
  63. const rawData = text.slice(0, index + delimiters[i].right.length);
  64. const math = amsRegex.test(rawData)
  65. ? rawData
  66. : text.slice(delimiters[i].left.length, index);
  67. data.push({
  68. type: "math",
  69. data: math,
  70. rawData,
  71. display: delimiters[i].display,
  72. });
  73. text = text.slice(index + delimiters[i].right.length);
  74. }
  75. if (text !== "") {
  76. data.push({
  77. type: "text",
  78. data: text,
  79. });
  80. }
  81. return data;
  82. };
  83. export default splitAtDelimiters;