css-style-declaration.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. 'use strict';
  2. var csstree = require('css-tree'),
  3. csstools = require('../css-tools');
  4. var CSSStyleDeclaration = function (node) {
  5. this.parentNode = node;
  6. this.properties = new Map();
  7. this.hasSynced = false;
  8. this.styleValue = null;
  9. this.parseError = false;
  10. const value = node.attributes.style;
  11. if (value != null) {
  12. this.addStyleValueHandler();
  13. this.setStyleValue(value);
  14. }
  15. };
  16. // attr.style.value
  17. CSSStyleDeclaration.prototype.addStyleValueHandler = function () {
  18. Object.defineProperty(this.parentNode.attributes, 'style', {
  19. get: this.getStyleValue.bind(this),
  20. set: this.setStyleValue.bind(this),
  21. enumerable: true,
  22. configurable: true,
  23. });
  24. };
  25. CSSStyleDeclaration.prototype.getStyleValue = function () {
  26. return this.getCssText();
  27. };
  28. CSSStyleDeclaration.prototype.setStyleValue = function (newValue) {
  29. this.properties.clear(); // reset all existing properties
  30. this.styleValue = newValue;
  31. this.hasSynced = false; // raw css changed
  32. };
  33. CSSStyleDeclaration.prototype._loadCssText = function () {
  34. if (this.hasSynced) {
  35. return;
  36. }
  37. this.hasSynced = true; // must be set here to prevent loop in setProperty(...)
  38. if (!this.styleValue || this.styleValue.length === 0) {
  39. return;
  40. }
  41. var inlineCssStr = this.styleValue;
  42. var declarations = {};
  43. try {
  44. declarations = csstree.parse(inlineCssStr, {
  45. context: 'declarationList',
  46. parseValue: false,
  47. });
  48. } catch (parseError) {
  49. this.parseError = parseError;
  50. return;
  51. }
  52. this.parseError = false;
  53. var self = this;
  54. declarations.children.each(function (declaration) {
  55. try {
  56. var styleDeclaration = csstools.csstreeToStyleDeclaration(declaration);
  57. self.setProperty(
  58. styleDeclaration.name,
  59. styleDeclaration.value,
  60. styleDeclaration.priority
  61. );
  62. } catch (styleError) {
  63. if (styleError.message !== 'Unknown node type: undefined') {
  64. self.parseError = styleError;
  65. }
  66. }
  67. });
  68. };
  69. // only reads from properties
  70. /**
  71. * Get the textual representation of the declaration block (equivalent to .cssText attribute).
  72. *
  73. * @return {string} Textual representation of the declaration block (empty string for no properties)
  74. */
  75. CSSStyleDeclaration.prototype.getCssText = function () {
  76. var properties = this.getProperties();
  77. if (this.parseError) {
  78. // in case of a parse error, pass through original styles
  79. return this.styleValue;
  80. }
  81. var cssText = [];
  82. properties.forEach(function (property, propertyName) {
  83. var strImportant = property.priority === 'important' ? '!important' : '';
  84. cssText.push(
  85. propertyName.trim() + ':' + property.value.trim() + strImportant
  86. );
  87. });
  88. return cssText.join(';');
  89. };
  90. CSSStyleDeclaration.prototype._handleParseError = function () {
  91. if (this.parseError) {
  92. console.warn(
  93. "Warning: Parse error when parsing inline styles, style properties of this element cannot be used. The raw styles can still be get/set using .attr('style').value. Error details: " +
  94. this.parseError
  95. );
  96. }
  97. };
  98. CSSStyleDeclaration.prototype._getProperty = function (propertyName) {
  99. if (typeof propertyName === 'undefined') {
  100. throw Error('1 argument required, but only 0 present.');
  101. }
  102. var properties = this.getProperties();
  103. this._handleParseError();
  104. var property = properties.get(propertyName.trim());
  105. return property;
  106. };
  107. /**
  108. * Return the optional priority, "important".
  109. *
  110. * @param {string} propertyName representing the property name to be checked.
  111. * @return {string} priority that represents the priority (e.g. "important") if one exists. If none exists, returns the empty string.
  112. */
  113. CSSStyleDeclaration.prototype.getPropertyPriority = function (propertyName) {
  114. var property = this._getProperty(propertyName);
  115. return property ? property.priority : '';
  116. };
  117. /**
  118. * Return the property value given a property name.
  119. *
  120. * @param {string} propertyName representing the property name to be checked.
  121. * @return {string} value containing the value of the property. If not set, returns the empty string.
  122. */
  123. CSSStyleDeclaration.prototype.getPropertyValue = function (propertyName) {
  124. var property = this._getProperty(propertyName);
  125. return property ? property.value : null;
  126. };
  127. /**
  128. * Return a property name.
  129. *
  130. * @param {number} index of the node to be fetched. The index is zero-based.
  131. * @return {string} propertyName that is the name of the CSS property at the specified index.
  132. */
  133. CSSStyleDeclaration.prototype.item = function (index) {
  134. if (typeof index === 'undefined') {
  135. throw Error('1 argument required, but only 0 present.');
  136. }
  137. var properties = this.getProperties();
  138. this._handleParseError();
  139. return Array.from(properties.keys())[index];
  140. };
  141. /**
  142. * Return all properties of the node.
  143. *
  144. * @return {Map} properties that is a Map with propertyName as key and property (propertyValue + propertyPriority) as value.
  145. */
  146. CSSStyleDeclaration.prototype.getProperties = function () {
  147. this._loadCssText();
  148. return this.properties;
  149. };
  150. // writes to properties
  151. /**
  152. * Remove a property from the CSS declaration block.
  153. *
  154. * @param {string} propertyName representing the property name to be removed.
  155. * @return {string} oldValue equal to the value of the CSS property before it was removed.
  156. */
  157. CSSStyleDeclaration.prototype.removeProperty = function (propertyName) {
  158. if (typeof propertyName === 'undefined') {
  159. throw Error('1 argument required, but only 0 present.');
  160. }
  161. this.addStyleValueHandler();
  162. var properties = this.getProperties();
  163. this._handleParseError();
  164. var oldValue = this.getPropertyValue(propertyName);
  165. properties.delete(propertyName.trim());
  166. return oldValue;
  167. };
  168. /**
  169. * Modify an existing CSS property or creates a new CSS property in the declaration block.
  170. *
  171. * @param {string} propertyName representing the CSS property name to be modified.
  172. * @param {string} value containing the new property value. If not specified, treated as the empty string. value must not contain "!important" -- that should be set using the priority parameter.
  173. * @param {string} priority allowing the "important" CSS priority to be set. If not specified, treated as the empty string.
  174. * @return {{value: string, priority: string}}
  175. */
  176. CSSStyleDeclaration.prototype.setProperty = function (
  177. propertyName,
  178. value,
  179. priority
  180. ) {
  181. if (typeof propertyName === 'undefined') {
  182. throw Error('propertyName argument required, but only not present.');
  183. }
  184. this.addStyleValueHandler();
  185. var properties = this.getProperties();
  186. this._handleParseError();
  187. var property = {
  188. value: value.trim(),
  189. priority: priority.trim(),
  190. };
  191. properties.set(propertyName.trim(), property);
  192. return property;
  193. };
  194. module.exports = CSSStyleDeclaration;