attributes.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import boolbase from "boolbase";
  2. /**
  3. * All reserved characters in a regex, used for escaping.
  4. *
  5. * Taken from XRegExp, (c) 2007-2020 Steven Levithan under the MIT license
  6. * https://github.com/slevithan/xregexp/blob/95eeebeb8fac8754d54eafe2b4743661ac1cf028/src/xregexp.js#L794
  7. */
  8. const reChars = /[-[\]{}()*+?.,\\^$|#\s]/g;
  9. function escapeRegex(value) {
  10. return value.replace(reChars, "\\$&");
  11. }
  12. /**
  13. * Attributes that are case-insensitive in HTML.
  14. *
  15. * @private
  16. * @see https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
  17. */
  18. const caseInsensitiveAttributes = new Set([
  19. "accept",
  20. "accept-charset",
  21. "align",
  22. "alink",
  23. "axis",
  24. "bgcolor",
  25. "charset",
  26. "checked",
  27. "clear",
  28. "codetype",
  29. "color",
  30. "compact",
  31. "declare",
  32. "defer",
  33. "dir",
  34. "direction",
  35. "disabled",
  36. "enctype",
  37. "face",
  38. "frame",
  39. "hreflang",
  40. "http-equiv",
  41. "lang",
  42. "language",
  43. "link",
  44. "media",
  45. "method",
  46. "multiple",
  47. "nohref",
  48. "noresize",
  49. "noshade",
  50. "nowrap",
  51. "readonly",
  52. "rel",
  53. "rev",
  54. "rules",
  55. "scope",
  56. "scrolling",
  57. "selected",
  58. "shape",
  59. "target",
  60. "text",
  61. "type",
  62. "valign",
  63. "valuetype",
  64. "vlink",
  65. ]);
  66. function shouldIgnoreCase(selector, options) {
  67. return typeof selector.ignoreCase === "boolean"
  68. ? selector.ignoreCase
  69. : selector.ignoreCase === "quirks"
  70. ? !!options.quirksMode
  71. : !options.xmlMode && caseInsensitiveAttributes.has(selector.name);
  72. }
  73. /**
  74. * Attribute selectors
  75. */
  76. export const attributeRules = {
  77. equals(next, data, options) {
  78. const { adapter } = options;
  79. const { name } = data;
  80. let { value } = data;
  81. if (shouldIgnoreCase(data, options)) {
  82. value = value.toLowerCase();
  83. return (elem) => {
  84. const attr = adapter.getAttributeValue(elem, name);
  85. return (attr != null &&
  86. attr.length === value.length &&
  87. attr.toLowerCase() === value &&
  88. next(elem));
  89. };
  90. }
  91. return (elem) => adapter.getAttributeValue(elem, name) === value && next(elem);
  92. },
  93. hyphen(next, data, options) {
  94. const { adapter } = options;
  95. const { name } = data;
  96. let { value } = data;
  97. const len = value.length;
  98. if (shouldIgnoreCase(data, options)) {
  99. value = value.toLowerCase();
  100. return function hyphenIC(elem) {
  101. const attr = adapter.getAttributeValue(elem, name);
  102. return (attr != null &&
  103. (attr.length === len || attr.charAt(len) === "-") &&
  104. attr.substr(0, len).toLowerCase() === value &&
  105. next(elem));
  106. };
  107. }
  108. return function hyphen(elem) {
  109. const attr = adapter.getAttributeValue(elem, name);
  110. return (attr != null &&
  111. (attr.length === len || attr.charAt(len) === "-") &&
  112. attr.substr(0, len) === value &&
  113. next(elem));
  114. };
  115. },
  116. element(next, data, options) {
  117. const { adapter } = options;
  118. const { name, value } = data;
  119. if (/\s/.test(value)) {
  120. return boolbase.falseFunc;
  121. }
  122. const regex = new RegExp(`(?:^|\\s)${escapeRegex(value)}(?:$|\\s)`, shouldIgnoreCase(data, options) ? "i" : "");
  123. return function element(elem) {
  124. const attr = adapter.getAttributeValue(elem, name);
  125. return (attr != null &&
  126. attr.length >= value.length &&
  127. regex.test(attr) &&
  128. next(elem));
  129. };
  130. },
  131. exists(next, { name }, { adapter }) {
  132. return (elem) => adapter.hasAttrib(elem, name) && next(elem);
  133. },
  134. start(next, data, options) {
  135. const { adapter } = options;
  136. const { name } = data;
  137. let { value } = data;
  138. const len = value.length;
  139. if (len === 0) {
  140. return boolbase.falseFunc;
  141. }
  142. if (shouldIgnoreCase(data, options)) {
  143. value = value.toLowerCase();
  144. return (elem) => {
  145. const attr = adapter.getAttributeValue(elem, name);
  146. return (attr != null &&
  147. attr.length >= len &&
  148. attr.substr(0, len).toLowerCase() === value &&
  149. next(elem));
  150. };
  151. }
  152. return (elem) => {
  153. var _a;
  154. return !!((_a = adapter.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.startsWith(value)) &&
  155. next(elem);
  156. };
  157. },
  158. end(next, data, options) {
  159. const { adapter } = options;
  160. const { name } = data;
  161. let { value } = data;
  162. const len = -value.length;
  163. if (len === 0) {
  164. return boolbase.falseFunc;
  165. }
  166. if (shouldIgnoreCase(data, options)) {
  167. value = value.toLowerCase();
  168. return (elem) => {
  169. var _a;
  170. return ((_a = adapter
  171. .getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.substr(len).toLowerCase()) === value && next(elem);
  172. };
  173. }
  174. return (elem) => {
  175. var _a;
  176. return !!((_a = adapter.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.endsWith(value)) &&
  177. next(elem);
  178. };
  179. },
  180. any(next, data, options) {
  181. const { adapter } = options;
  182. const { name, value } = data;
  183. if (value === "") {
  184. return boolbase.falseFunc;
  185. }
  186. if (shouldIgnoreCase(data, options)) {
  187. const regex = new RegExp(escapeRegex(value), "i");
  188. return function anyIC(elem) {
  189. const attr = adapter.getAttributeValue(elem, name);
  190. return (attr != null &&
  191. attr.length >= value.length &&
  192. regex.test(attr) &&
  193. next(elem));
  194. };
  195. }
  196. return (elem) => {
  197. var _a;
  198. return !!((_a = adapter.getAttributeValue(elem, name)) === null || _a === void 0 ? void 0 : _a.includes(value)) &&
  199. next(elem);
  200. };
  201. },
  202. not(next, data, options) {
  203. const { adapter } = options;
  204. const { name } = data;
  205. let { value } = data;
  206. if (value === "") {
  207. return (elem) => !!adapter.getAttributeValue(elem, name) && next(elem);
  208. }
  209. else if (shouldIgnoreCase(data, options)) {
  210. value = value.toLowerCase();
  211. return (elem) => {
  212. const attr = adapter.getAttributeValue(elem, name);
  213. return ((attr == null ||
  214. attr.length !== value.length ||
  215. attr.toLowerCase() !== value) &&
  216. next(elem));
  217. };
  218. }
  219. return (elem) => adapter.getAttributeValue(elem, name) !== value && next(elem);
  220. },
  221. };
  222. //# sourceMappingURL=attributes.js.map