| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- '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;
|