strings.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // Forked from https://github.com/jsdom/jsdom/blob/main/lib/jsdom/living/helpers/strings.js
  2. "use strict";
  3. // https://infra.spec.whatwg.org/#ascii-whitespace
  4. const asciiWhitespaceRe = /^[\t\n\f\r ]$/;
  5. exports.asciiWhitespaceRe = asciiWhitespaceRe;
  6. // https://infra.spec.whatwg.org/#ascii-lowercase
  7. exports.asciiLowercase = (s) => {
  8. if (!/[^\x00-\x7f]/.test(s)) {
  9. return s.toLowerCase();
  10. }
  11. const len = s.length;
  12. const out = new Array(len);
  13. for (let i = 0; i < len; i++) {
  14. const code = s.charCodeAt(i);
  15. // If the character is between 'A' (65) and 'Z' (90), convert using bitwise OR with 32
  16. out[i] = code >= 65 && code <= 90 ? String.fromCharCode(code | 32) : s[i];
  17. }
  18. return out.join("");
  19. };
  20. // https://infra.spec.whatwg.org/#ascii-uppercase
  21. exports.asciiUppercase = (s) => {
  22. if (!/[^\x00-\x7f]/.test(s)) {
  23. return s.toUpperCase();
  24. }
  25. const len = s.length;
  26. const out = new Array(len);
  27. for (let i = 0; i < len; i++) {
  28. const code = s.charCodeAt(i);
  29. // If the character is between 'a' (97) and 'z' (122), convert using bitwise AND with ~32
  30. out[i] = code >= 97 && code <= 122 ? String.fromCharCode(code & ~32) : s[i];
  31. }
  32. return out.join("");
  33. };
  34. // https://infra.spec.whatwg.org/#strip-newlines
  35. exports.stripNewlines = (s) => {
  36. return s.replace(/[\n\r]+/g, "");
  37. };
  38. // https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace
  39. exports.stripLeadingAndTrailingASCIIWhitespace = (s) => {
  40. return s.replace(/^[ \t\n\f\r]+/, "").replace(/[ \t\n\f\r]+$/, "");
  41. };
  42. // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
  43. exports.stripAndCollapseASCIIWhitespace = (s) => {
  44. return s
  45. .replace(/[ \t\n\f\r]+/g, " ")
  46. .replace(/^[ \t\n\f\r]+/, "")
  47. .replace(/[ \t\n\f\r]+$/, "");
  48. };
  49. // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-simple-colour
  50. exports.isValidSimpleColor = (s) => {
  51. return /^#[a-fA-F\d]{6}$/.test(s);
  52. };
  53. // https://infra.spec.whatwg.org/#ascii-case-insensitive
  54. exports.asciiCaseInsensitiveMatch = (a, b) => {
  55. if (a.length !== b.length) {
  56. return false;
  57. }
  58. for (let i = 0; i < a.length; ++i) {
  59. if ((a.charCodeAt(i) | 32) !== (b.charCodeAt(i) | 32)) {
  60. return false;
  61. }
  62. }
  63. return true;
  64. };
  65. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-integers
  66. // Error is represented as null.
  67. const parseInteger = (exports.parseInteger = (input) => {
  68. // The implementation here is slightly different from the spec's. We want to use parseInt(), but parseInt() trims
  69. // Unicode whitespace in addition to just ASCII ones, so we make sure that the trimmed prefix contains only ASCII
  70. // whitespace ourselves.
  71. const numWhitespace = input.length - input.trimStart().length;
  72. if (/[^\t\n\f\r ]/.test(input.slice(0, numWhitespace))) {
  73. return null;
  74. }
  75. // We don't allow hexadecimal numbers here.
  76. // eslint-disable-next-line radix
  77. const value = parseInt(input, 10);
  78. if (Number.isNaN(value)) {
  79. return null;
  80. }
  81. // parseInt() returns -0 for "-0". Normalize that here.
  82. return value === 0 ? 0 : value;
  83. });
  84. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-non-negative-integers
  85. // Error is represented as null.
  86. exports.parseNonNegativeInteger = (input) => {
  87. const value = parseInteger(input);
  88. if (value === null) {
  89. return null;
  90. }
  91. if (value < 0) {
  92. return null;
  93. }
  94. return value;
  95. };
  96. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-floating-point-number
  97. const floatingPointNumRe = /^-?(?:\d+|\d*\.\d+)(?:[eE][-+]?\d+)?$/;
  98. exports.isValidFloatingPointNumber = (str) => floatingPointNumRe.test(str);
  99. // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-floating-point-number-values
  100. // Error is represented as null.
  101. exports.parseFloatingPointNumber = (str) => {
  102. // The implementation here is slightly different from the spec's. We need to use parseFloat() in order to retain
  103. // accuracy, but parseFloat() trims Unicode whitespace in addition to just ASCII ones, so we make sure that the
  104. // trimmed prefix contains only ASCII whitespace ourselves.
  105. const numWhitespace = str.length - str.trimStart().length;
  106. if (/[^\t\n\f\r ]/.test(str.slice(0, numWhitespace))) {
  107. return null;
  108. }
  109. const parsed = parseFloat(str);
  110. return isFinite(parsed) ? parsed : null;
  111. };
  112. // https://infra.spec.whatwg.org/#split-on-ascii-whitespace
  113. exports.splitOnASCIIWhitespace = (str) => {
  114. let position = 0;
  115. const tokens = [];
  116. while (position < str.length && asciiWhitespaceRe.test(str[position])) {
  117. position++;
  118. }
  119. if (position === str.length) {
  120. return tokens;
  121. }
  122. while (position < str.length) {
  123. const start = position;
  124. while (position < str.length && !asciiWhitespaceRe.test(str[position])) {
  125. position++;
  126. }
  127. tokens.push(str.slice(start, position));
  128. while (position < str.length && asciiWhitespaceRe.test(str[position])) {
  129. position++;
  130. }
  131. }
  132. return tokens;
  133. };
  134. // https://infra.spec.whatwg.org/#split-on-commas
  135. exports.splitOnCommas = (str) => {
  136. let position = 0;
  137. const tokens = [];
  138. while (position < str.length) {
  139. let start = position;
  140. while (position < str.length && str[position] !== ",") {
  141. position++;
  142. }
  143. let end = position;
  144. while (start < str.length && asciiWhitespaceRe.test(str[start])) {
  145. start++;
  146. }
  147. while (end > start && asciiWhitespaceRe.test(str[end - 1])) {
  148. end--;
  149. }
  150. tokens.push(str.slice(start, end));
  151. if (position < str.length) {
  152. position++;
  153. }
  154. }
  155. return tokens;
  156. };