| 'use strict'; |
| |
| var csstree = require('css-tree'), |
| csstools = require('../css-tools'); |
| |
| |
| var CSSStyleDeclaration = function(node) { |
| this.parentNode = node; |
| |
| this.properties = new Map(); |
| this.hasSynced = false; |
| |
| this.styleAttr = null; |
| this.styleValue = null; |
| |
| this.parseError = false; |
| }; |
| |
| /** |
| * Performs a deep clone of this object. |
| * |
| * @param parentNode the parentNode to assign to the cloned result |
| */ |
| CSSStyleDeclaration.prototype.clone = function(parentNode) { |
| var node = this; |
| var nodeData = {}; |
| |
| Object.keys(node).forEach(function(key) { |
| if (key !== 'parentNode') { |
| nodeData[key] = node[key]; |
| } |
| }); |
| |
| // Deep-clone node data. |
| nodeData = JSON.parse(JSON.stringify(nodeData)); |
| |
| var clone = new CSSStyleDeclaration(parentNode); |
| Object.assign(clone, nodeData); |
| return clone; |
| }; |
| |
| CSSStyleDeclaration.prototype.hasStyle = function() { |
| this.addStyleHandler(); |
| }; |
| |
| |
| |
| |
| // attr.style |
| |
| CSSStyleDeclaration.prototype.addStyleHandler = function() { |
| |
| this.styleAttr = { // empty style attr |
| 'name': 'style', |
| 'value': null |
| }; |
| |
| Object.defineProperty(this.parentNode.attrs, 'style', { |
| get: this.getStyleAttr.bind(this), |
| set: this.setStyleAttr.bind(this), |
| enumerable: true, |
| configurable: true |
| }); |
| |
| this.addStyleValueHandler(); |
| }; |
| |
| // attr.style.value |
| |
| CSSStyleDeclaration.prototype.addStyleValueHandler = function() { |
| |
| Object.defineProperty(this.styleAttr, 'value', { |
| get: this.getStyleValue.bind(this), |
| set: this.setStyleValue.bind(this), |
| enumerable: true, |
| configurable: true |
| }); |
| }; |
| |
| CSSStyleDeclaration.prototype.getStyleAttr = function() { |
| return this.styleAttr; |
| }; |
| |
| CSSStyleDeclaration.prototype.setStyleAttr = function(newStyleAttr) { |
| this.setStyleValue(newStyleAttr.value); // must before applying value handler! |
| |
| this.styleAttr = newStyleAttr; |
| this.addStyleValueHandler(); |
| this.hasSynced = false; // raw css changed |
| }; |
| |
| CSSStyleDeclaration.prototype.getStyleValue = function() { |
| return this.getCssText(); |
| }; |
| |
| CSSStyleDeclaration.prototype.setStyleValue = function(newValue) { |
| this.properties.clear(); // reset all existing properties |
| this.styleValue = newValue; |
| this.hasSynced = false; // raw css changed |
| }; |
| |
| |
| |
| |
| CSSStyleDeclaration.prototype._loadCssText = function() { |
| if (this.hasSynced) { |
| return; |
| } |
| this.hasSynced = true; // must be set here to prevent loop in setProperty(...) |
| |
| if (!this.styleValue || this.styleValue.length === 0) { |
| return; |
| } |
| var inlineCssStr = this.styleValue; |
| |
| var declarations = {}; |
| try { |
| declarations = csstree.parse(inlineCssStr, { |
| context: 'declarationList', |
| parseValue: false |
| }); |
| } catch (parseError) { |
| this.parseError = parseError; |
| return; |
| } |
| this.parseError = false; |
| |
| var self = this; |
| declarations.children.each(function(declaration) { |
| try { |
| var styleDeclaration = csstools.csstreeToStyleDeclaration(declaration); |
| self.setProperty(styleDeclaration.name, styleDeclaration.value, styleDeclaration.priority); |
| } catch(styleError) { |
| if(styleError.message !== 'Unknown node type: undefined') { |
| self.parseError = styleError; |
| } |
| } |
| }); |
| }; |
| |
| |
| // only reads from properties |
| |
| /** |
| * Get the textual representation of the declaration block (equivalent to .cssText attribute). |
| * |
| * @return {String} Textual representation of the declaration block (empty string for no properties) |
| */ |
| CSSStyleDeclaration.prototype.getCssText = function() { |
| var properties = this.getProperties(); |
| |
| if (this.parseError) { |
| // in case of a parse error, pass through original styles |
| return this.styleValue; |
| } |
| |
| var cssText = []; |
| properties.forEach(function(property, propertyName) { |
| var strImportant = property.priority === 'important' ? '!important' : ''; |
| cssText.push(propertyName.trim() + ':' + property.value.trim() + strImportant); |
| }); |
| return cssText.join(';'); |
| }; |
| |
| CSSStyleDeclaration.prototype._handleParseError = function() { |
| if (this.parseError) { |
| console.warn('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: ' + this.parseError); |
| } |
| }; |
| |
| |
| CSSStyleDeclaration.prototype._getProperty = function(propertyName) { |
| if(typeof propertyName === 'undefined') { |
| throw Error('1 argument required, but only 0 present.'); |
| } |
| |
| var properties = this.getProperties(); |
| this._handleParseError(); |
| |
| var property = properties.get(propertyName.trim()); |
| return property; |
| }; |
| |
| /** |
| * Return the optional priority, "important". |
| * |
| * @param {String} propertyName representing the property name to be checked. |
| * @return {String} priority that represents the priority (e.g. "important") if one exists. If none exists, returns the empty string. |
| */ |
| CSSStyleDeclaration.prototype.getPropertyPriority = function(propertyName) { |
| var property = this._getProperty(propertyName); |
| return property ? property.priority : ''; |
| }; |
| |
| /** |
| * Return the property value given a property name. |
| * |
| * @param {String} propertyName representing the property name to be checked. |
| * @return {String} value containing the value of the property. If not set, returns the empty string. |
| */ |
| CSSStyleDeclaration.prototype.getPropertyValue = function(propertyName) { |
| var property = this._getProperty(propertyName); |
| return property ? property.value : null; |
| }; |
| |
| /** |
| * Return a property name. |
| * |
| * @param {Number} index of the node to be fetched. The index is zero-based. |
| * @return {String} propertyName that is the name of the CSS property at the specified index. |
| */ |
| CSSStyleDeclaration.prototype.item = function(index) { |
| if(typeof index === 'undefined') { |
| throw Error('1 argument required, but only 0 present.'); |
| } |
| |
| var properties = this.getProperties(); |
| this._handleParseError(); |
| |
| return Array.from(properties.keys())[index]; |
| }; |
| |
| /** |
| * Return all properties of the node. |
| * |
| * @return {Map} properties that is a Map with propertyName as key and property (propertyValue + propertyPriority) as value. |
| */ |
| CSSStyleDeclaration.prototype.getProperties = function() { |
| this._loadCssText(); |
| return this.properties; |
| }; |
| |
| |
| // writes to properties |
| |
| /** |
| * Remove a property from the CSS declaration block. |
| * |
| * @param {String} propertyName representing the property name to be removed. |
| * @return {String} oldValue equal to the value of the CSS property before it was removed. |
| */ |
| CSSStyleDeclaration.prototype.removeProperty = function(propertyName) { |
| if(typeof propertyName === 'undefined') { |
| throw Error('1 argument required, but only 0 present.'); |
| } |
| |
| this.hasStyle(); |
| |
| var properties = this.getProperties(); |
| this._handleParseError(); |
| |
| var oldValue = this.getPropertyValue(propertyName); |
| properties.delete(propertyName.trim()); |
| return oldValue; |
| }; |
| |
| /** |
| * Modify an existing CSS property or creates a new CSS property in the declaration block. |
| * |
| * @param {String} propertyName representing the CSS property name to be modified. |
| * @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. |
| * @param {String} [priority] allowing the "important" CSS priority to be set. If not specified, treated as the empty string. |
| * @return {undefined} |
| */ |
| CSSStyleDeclaration.prototype.setProperty = function(propertyName, value, priority) { |
| if(typeof propertyName === 'undefined') { |
| throw Error('propertyName argument required, but only not present.'); |
| } |
| |
| this.hasStyle(); |
| |
| var properties = this.getProperties(); |
| this._handleParseError(); |
| |
| var property = { |
| value: value.trim(), |
| priority: priority.trim() |
| }; |
| properties.set(propertyName.trim(), property); |
| |
| return property; |
| }; |
| |
| |
| module.exports = CSSStyleDeclaration; |