attribute.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. "use strict";
  2. var __extends = (this && this.__extends) || (function () {
  3. var extendStatics = function (d, b) {
  4. extendStatics = Object.setPrototypeOf ||
  5. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  6. function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
  7. return extendStatics(d, b);
  8. };
  9. return function (d, b) {
  10. if (typeof b !== "function" && b !== null)
  11. throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
  12. extendStatics(d, b);
  13. function __() { this.constructor = d; }
  14. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  15. };
  16. })();
  17. var __importDefault = (this && this.__importDefault) || function (mod) {
  18. return (mod && mod.__esModule) ? mod : { "default": mod };
  19. };
  20. var _a;
  21. Object.defineProperty(exports, "__esModule", { value: true });
  22. exports.unescapeValue = unescapeValue;
  23. var cssesc_1 = __importDefault(require("cssesc"));
  24. var unesc_1 = __importDefault(require("../util/unesc"));
  25. var namespace_1 = __importDefault(require("./namespace"));
  26. var types_1 = require("./types");
  27. var deprecate = require("util-deprecate");
  28. var WRAPPED_IN_QUOTES = /^('|")([^]*)\1$/;
  29. var warnOfDeprecatedValueAssignment = deprecate(function () { }, "Assigning an attribute a value containing characters that might need to be escaped is deprecated. " +
  30. "Call attribute.setValue() instead.");
  31. var warnOfDeprecatedQuotedAssignment = deprecate(function () { }, "Assigning attr.quoted is deprecated and has no effect. Assign to attr.quoteMark instead.");
  32. var warnOfDeprecatedConstructor = deprecate(function () { }, "Constructing an Attribute selector with a value without specifying quoteMark is deprecated. Note: The value should be unescaped now.");
  33. function unescapeValue(value) {
  34. var deprecatedUsage = false;
  35. var quoteMark = null;
  36. var unescaped = value;
  37. var m = unescaped.match(WRAPPED_IN_QUOTES);
  38. if (m) {
  39. quoteMark = m[1];
  40. unescaped = m[2];
  41. }
  42. unescaped = (0, unesc_1.default)(unescaped);
  43. if (unescaped !== value) {
  44. deprecatedUsage = true;
  45. }
  46. return {
  47. deprecatedUsage: deprecatedUsage,
  48. unescaped: unescaped,
  49. quoteMark: quoteMark,
  50. };
  51. }
  52. function handleDeprecatedContructorOpts(opts) {
  53. if (opts.quoteMark !== undefined) {
  54. return opts;
  55. }
  56. if (opts.value === undefined) {
  57. return opts;
  58. }
  59. warnOfDeprecatedConstructor();
  60. var _a = unescapeValue(opts.value), quoteMark = _a.quoteMark, unescaped = _a.unescaped;
  61. if (!opts.raws) {
  62. opts.raws = {};
  63. }
  64. if (opts.raws.value === undefined) {
  65. opts.raws.value = opts.value;
  66. }
  67. opts.value = unescaped;
  68. opts.quoteMark = quoteMark;
  69. return opts;
  70. }
  71. var Attribute = /** @class */ (function (_super) {
  72. __extends(Attribute, _super);
  73. function Attribute(opts) {
  74. if (opts === void 0) { opts = {}; }
  75. var _this = _super.call(this, handleDeprecatedContructorOpts(opts)) || this;
  76. _this.type = types_1.ATTRIBUTE;
  77. _this.raws = _this.raws || {};
  78. Object.defineProperty(_this.raws, "unquoted", {
  79. get: deprecate(function () { return _this.value; }, "attr.raws.unquoted is deprecated. Call attr.value instead."),
  80. set: deprecate(function () { return _this.value; }, "Setting attr.raws.unquoted is deprecated and has no effect. attr.value is unescaped by default now."),
  81. });
  82. _this._constructed = true;
  83. return _this;
  84. }
  85. /**
  86. * Returns the Attribute's value quoted such that it would be legal to use
  87. * in the value of a css file. The original value's quotation setting
  88. * used for stringification is left unchanged. See `setValue(value, options)`
  89. * if you want to control the quote settings of a new value for the attribute.
  90. *
  91. * You can also change the quotation used for the current value by setting quoteMark.
  92. *
  93. * Options:
  94. * * quoteMark {'"' | "'" | null} - Use this value to quote the value. If this
  95. * option is not set, the original value for quoteMark will be used. If
  96. * indeterminate, a double quote is used. The legal values are:
  97. * * `null` - the value will be unquoted and characters will be escaped as necessary.
  98. * * `'` - the value will be quoted with a single quote and single quotes are escaped.
  99. * * `"` - the value will be quoted with a double quote and double quotes are escaped.
  100. * * preferCurrentQuoteMark {boolean} - if true, prefer the source quote mark
  101. * over the quoteMark option value.
  102. * * smart {boolean} - if true, will select a quote mark based on the value
  103. * and the other options specified here. See the `smartQuoteMark()`
  104. * method.
  105. **/
  106. Attribute.prototype.getQuotedValue = function (options) {
  107. if (options === void 0) { options = {}; }
  108. var quoteMark = this._determineQuoteMark(options);
  109. var cssescopts = CSSESC_QUOTE_OPTIONS[quoteMark];
  110. var escaped = (0, cssesc_1.default)(this._value, cssescopts);
  111. return escaped;
  112. };
  113. Attribute.prototype._determineQuoteMark = function (options) {
  114. return options.smart ? this.smartQuoteMark(options) : this.preferredQuoteMark(options);
  115. };
  116. /**
  117. * Set the unescaped value with the specified quotation options. The value
  118. * provided must not include any wrapping quote marks -- those quotes will
  119. * be interpreted as part of the value and escaped accordingly.
  120. */
  121. Attribute.prototype.setValue = function (value, options) {
  122. if (options === void 0) { options = {}; }
  123. this._value = value;
  124. this._quoteMark = this._determineQuoteMark(options);
  125. this._syncRawValue();
  126. };
  127. /**
  128. * Intelligently select a quoteMark value based on the value's contents. If
  129. * the value is a legal CSS ident, it will not be quoted. Otherwise a quote
  130. * mark will be picked that minimizes the number of escapes.
  131. *
  132. * If there's no clear winner, the quote mark from these options is used,
  133. * then the source quote mark (this is inverted if `preferCurrentQuoteMark` is
  134. * true). If the quoteMark is unspecified, a double quote is used.
  135. *
  136. * @param options This takes the quoteMark and preferCurrentQuoteMark options
  137. * from the quoteValue method.
  138. */
  139. Attribute.prototype.smartQuoteMark = function (options) {
  140. var v = this.value;
  141. var numSingleQuotes = v.replace(/[^']/g, "").length;
  142. var numDoubleQuotes = v.replace(/[^"]/g, "").length;
  143. if (numSingleQuotes + numDoubleQuotes === 0) {
  144. var escaped = (0, cssesc_1.default)(v, { isIdentifier: true });
  145. if (escaped === v) {
  146. return Attribute.NO_QUOTE;
  147. }
  148. else {
  149. var pref = this.preferredQuoteMark(options);
  150. if (pref === Attribute.NO_QUOTE) {
  151. // pick a quote mark that isn't none and see if it's smaller
  152. var quote = this.quoteMark || options.quoteMark || Attribute.DOUBLE_QUOTE;
  153. var opts = CSSESC_QUOTE_OPTIONS[quote];
  154. var quoteValue = (0, cssesc_1.default)(v, opts);
  155. if (quoteValue.length < escaped.length) {
  156. return quote;
  157. }
  158. }
  159. return pref;
  160. }
  161. }
  162. else if (numDoubleQuotes === numSingleQuotes) {
  163. return this.preferredQuoteMark(options);
  164. }
  165. else if (numDoubleQuotes < numSingleQuotes) {
  166. return Attribute.DOUBLE_QUOTE;
  167. }
  168. else {
  169. return Attribute.SINGLE_QUOTE;
  170. }
  171. };
  172. /**
  173. * Selects the preferred quote mark based on the options and the current quote mark value.
  174. * If you want the quote mark to depend on the attribute value, call `smartQuoteMark(opts)`
  175. * instead.
  176. */
  177. Attribute.prototype.preferredQuoteMark = function (options) {
  178. var quoteMark = options.preferCurrentQuoteMark ? this.quoteMark : options.quoteMark;
  179. if (quoteMark === undefined) {
  180. quoteMark = options.preferCurrentQuoteMark ? options.quoteMark : this.quoteMark;
  181. }
  182. if (quoteMark === undefined) {
  183. quoteMark = Attribute.DOUBLE_QUOTE;
  184. }
  185. return quoteMark;
  186. };
  187. Object.defineProperty(Attribute.prototype, "quoted", {
  188. get: function () {
  189. var qm = this.quoteMark;
  190. return qm === "'" || qm === '"';
  191. },
  192. set: function (value) {
  193. warnOfDeprecatedQuotedAssignment();
  194. },
  195. enumerable: false,
  196. configurable: true
  197. });
  198. Object.defineProperty(Attribute.prototype, "quoteMark", {
  199. /**
  200. * returns a single (`'`) or double (`"`) quote character if the value is quoted.
  201. * returns `null` if the value is not quoted.
  202. * returns `undefined` if the quotation state is unknown (this can happen when
  203. * the attribute is constructed without specifying a quote mark.)
  204. */
  205. get: function () {
  206. return this._quoteMark;
  207. },
  208. /**
  209. * Set the quote mark to be used by this attribute's value.
  210. * If the quote mark changes, the raw (escaped) value at `attr.raws.value` of the attribute
  211. * value is updated accordingly.
  212. *
  213. * @param {"'" | '"' | null} quoteMark The quote mark or `null` if the value should be unquoted.
  214. */
  215. set: function (quoteMark) {
  216. if (!this._constructed) {
  217. this._quoteMark = quoteMark;
  218. return;
  219. }
  220. if (this._quoteMark !== quoteMark) {
  221. this._quoteMark = quoteMark;
  222. this._syncRawValue();
  223. }
  224. },
  225. enumerable: false,
  226. configurable: true
  227. });
  228. Attribute.prototype._syncRawValue = function () {
  229. var rawValue = (0, cssesc_1.default)(this._value, CSSESC_QUOTE_OPTIONS[this.quoteMark]);
  230. if (rawValue === this._value) {
  231. if (this.raws) {
  232. delete this.raws.value;
  233. }
  234. }
  235. else {
  236. this.raws.value = rawValue;
  237. }
  238. };
  239. Object.defineProperty(Attribute.prototype, "qualifiedAttribute", {
  240. get: function () {
  241. return this.qualifiedName(this.raws.attribute || this.attribute);
  242. },
  243. enumerable: false,
  244. configurable: true
  245. });
  246. Object.defineProperty(Attribute.prototype, "insensitiveFlag", {
  247. get: function () {
  248. return this.insensitive ? "i" : "";
  249. },
  250. enumerable: false,
  251. configurable: true
  252. });
  253. Object.defineProperty(Attribute.prototype, "value", {
  254. get: function () {
  255. return this._value;
  256. },
  257. /**
  258. * Before 3.0, the value had to be set to an escaped value including any wrapped
  259. * quote marks. In 3.0, the semantics of `Attribute.value` changed so that the value
  260. * is unescaped during parsing and any quote marks are removed.
  261. *
  262. * Because the ambiguity of this semantic change, if you set `attr.value = newValue`,
  263. * a deprecation warning is raised when the new value contains any characters that would
  264. * require escaping (including if it contains wrapped quotes).
  265. *
  266. * Instead, you should call `attr.setValue(newValue, opts)` and pass options that describe
  267. * how the new value is quoted.
  268. */
  269. set: function (v) {
  270. if (this._constructed) {
  271. var _a = unescapeValue(v), deprecatedUsage = _a.deprecatedUsage, unescaped = _a.unescaped, quoteMark = _a.quoteMark;
  272. if (deprecatedUsage) {
  273. warnOfDeprecatedValueAssignment();
  274. }
  275. if (unescaped === this._value && quoteMark === this._quoteMark) {
  276. return;
  277. }
  278. this._value = unescaped;
  279. this._quoteMark = quoteMark;
  280. this._syncRawValue();
  281. }
  282. else {
  283. this._value = v;
  284. }
  285. },
  286. enumerable: false,
  287. configurable: true
  288. });
  289. Object.defineProperty(Attribute.prototype, "insensitive", {
  290. get: function () {
  291. return this._insensitive;
  292. },
  293. /**
  294. * Set the case insensitive flag.
  295. * If the case insensitive flag changes, the raw (escaped) value at `attr.raws.insensitiveFlag`
  296. * of the attribute is updated accordingly.
  297. *
  298. * @param {true | false} insensitive true if the attribute should match case-insensitively.
  299. */
  300. set: function (insensitive) {
  301. if (!insensitive) {
  302. this._insensitive = false;
  303. // "i" and "I" can be used in "this.raws.insensitiveFlag" to store the original notation.
  304. // When setting `attr.insensitive = false` both should be erased to ensure correct serialization.
  305. if (this.raws && (this.raws.insensitiveFlag === "I" || this.raws.insensitiveFlag === "i")) {
  306. this.raws.insensitiveFlag = undefined;
  307. }
  308. }
  309. this._insensitive = insensitive;
  310. },
  311. enumerable: false,
  312. configurable: true
  313. });
  314. Object.defineProperty(Attribute.prototype, "attribute", {
  315. get: function () {
  316. return this._attribute;
  317. },
  318. set: function (name) {
  319. this._handleEscapes("attribute", name);
  320. this._attribute = name;
  321. },
  322. enumerable: false,
  323. configurable: true
  324. });
  325. Attribute.prototype._handleEscapes = function (prop, value) {
  326. if (this._constructed) {
  327. var escaped = (0, cssesc_1.default)(value, { isIdentifier: true });
  328. if (escaped !== value) {
  329. this.raws[prop] = escaped;
  330. }
  331. else {
  332. delete this.raws[prop];
  333. }
  334. }
  335. };
  336. Attribute.prototype._spacesFor = function (name) {
  337. var attrSpaces = { before: "", after: "" };
  338. var spaces = this.spaces[name] || {};
  339. var rawSpaces = (this.raws.spaces && this.raws.spaces[name]) || {};
  340. return Object.assign(attrSpaces, spaces, rawSpaces);
  341. };
  342. Attribute.prototype._stringFor = function (name, spaceName, concat) {
  343. if (spaceName === void 0) { spaceName = name; }
  344. if (concat === void 0) { concat = defaultAttrConcat; }
  345. var attrSpaces = this._spacesFor(spaceName);
  346. return concat(this.stringifyProperty(name), attrSpaces);
  347. };
  348. /**
  349. * returns the offset of the attribute part specified relative to the
  350. * start of the node of the output string.
  351. *
  352. * * "ns" - alias for "namespace"
  353. * * "namespace" - the namespace if it exists.
  354. * * "attribute" - the attribute name
  355. * * "attributeNS" - the start of the attribute or its namespace
  356. * * "operator" - the match operator of the attribute
  357. * * "value" - The value (string or identifier)
  358. * * "insensitive" - the case insensitivity flag;
  359. * @param part One of the possible values inside an attribute.
  360. * @returns -1 if the name is invalid or the value doesn't exist in this attribute.
  361. */
  362. Attribute.prototype.offsetOf = function (name) {
  363. var count = 1;
  364. var attributeSpaces = this._spacesFor("attribute");
  365. count += attributeSpaces.before.length;
  366. if (name === "namespace" || name === "ns") {
  367. return this.namespace ? count : -1;
  368. }
  369. if (name === "attributeNS") {
  370. return count;
  371. }
  372. count += this.namespaceString.length;
  373. if (this.namespace) {
  374. count += 1;
  375. }
  376. if (name === "attribute") {
  377. return count;
  378. }
  379. count += this.stringifyProperty("attribute").length;
  380. count += attributeSpaces.after.length;
  381. var operatorSpaces = this._spacesFor("operator");
  382. count += operatorSpaces.before.length;
  383. var operator = this.stringifyProperty("operator");
  384. if (name === "operator") {
  385. return operator ? count : -1;
  386. }
  387. count += operator.length;
  388. count += operatorSpaces.after.length;
  389. var valueSpaces = this._spacesFor("value");
  390. count += valueSpaces.before.length;
  391. var value = this.stringifyProperty("value");
  392. if (name === "value") {
  393. return value ? count : -1;
  394. }
  395. count += value.length;
  396. count += valueSpaces.after.length;
  397. var insensitiveSpaces = this._spacesFor("insensitive");
  398. count += insensitiveSpaces.before.length;
  399. if (name === "insensitive") {
  400. return this.insensitive ? count : -1;
  401. }
  402. return -1;
  403. };
  404. Attribute.prototype.toString = function () {
  405. var _this = this;
  406. var selector = [this.rawSpaceBefore, "["];
  407. selector.push(this._stringFor("qualifiedAttribute", "attribute"));
  408. if (this.operator && (this.value || this.value === "")) {
  409. selector.push(this._stringFor("operator"));
  410. selector.push(this._stringFor("value"));
  411. selector.push(this._stringFor("insensitiveFlag", "insensitive", function (attrValue, attrSpaces) {
  412. if (attrValue.length > 0 &&
  413. !_this.quoted &&
  414. attrSpaces.before.length === 0 &&
  415. !(_this.spaces.value && _this.spaces.value.after)) {
  416. attrSpaces.before = " ";
  417. }
  418. return defaultAttrConcat(attrValue, attrSpaces);
  419. }));
  420. }
  421. selector.push("]");
  422. selector.push(this.rawSpaceAfter);
  423. return selector.join("");
  424. };
  425. Attribute.NO_QUOTE = null;
  426. Attribute.SINGLE_QUOTE = "'";
  427. Attribute.DOUBLE_QUOTE = '"';
  428. return Attribute;
  429. }(namespace_1.default));
  430. exports.default = Attribute;
  431. var CSSESC_QUOTE_OPTIONS = (_a = {
  432. "'": { quotes: "single", wrap: true },
  433. '"': { quotes: "double", wrap: true }
  434. },
  435. _a[null] = { isIdentifier: true },
  436. _a);
  437. function defaultAttrConcat(attrValue, attrSpaces) {
  438. return "".concat(attrSpaces.before).concat(attrValue).concat(attrSpaces.after);
  439. }
  440. //# sourceMappingURL=attribute.js.map