Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 82 additions & 27 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,77 @@ Helpers.prototype.tag = function tag(element) {

return '<' + element.name + this.attributes(element) + '>';
};

/** Is the attribute allowed on the HTML element? If so, allow special
* treatment. If not, then just return the full attribute and its value.
* @param {String} name name of the element
* @param {String} key name of the attribute
* @return {Boolean}
*/
Helpers.prototype.allowAttributeInNode = function (name, key) {
var attrDefinition = Object.hasOwnProperty.call(list.attributes, key) && list.attributes[key];
if (attrDefinition) {
return attrDefinition === '*' || ~attrDefinition.indexOf(name);
}
return ~key.indexOf('data-');
}
/** allow remove of attribute
* @param {String} name name of the element
* @param {String} key name of the attribute
* @param {String} value value of the attribute
* @return {Boolean}
*/
Helpers.prototype.allowRemoveAttributeValue = function (key, value) {
if (this.config.spare) {
return false;
}
if (!value) {
return true;
}
if (key === 'draggable') {
return value === 'auto';
}
return (value === 'true' || value === key) && ~list.redundant.indexOf(key);
};
/** allow remove of attribute value
* @param {String} name name of the element
* @param {String} key name of the attribute
* @param {String} value value of the attribute
* @return {Boolean}
*/
Helpers.prototype.allowRemoveAttribute = function (name, key, value) {
if (this.config.empty){
return false;
}
if (/data|itemscope/.test(key)) {
return false;
}
if (key === 'draggable') {
if (value === 'true') {
return ~['img','a'].indexOf(name)
} else if (value === 'false') {
return !~['img','a'].indexOf(name)
}
}
if (value) {
return false;
}
return !~list.redundant.indexOf(key);
};
/** create attribute string
* @param {String} key name of the attribute
* @param {String} value value of the attribute
* @return {String}
*/
Helpers.prototype.toAttributeString = function (key, value) {
var result = '';
if (key) {
result += ' ' + key;
if (value !== null) {
result += '=' + this.quote(!this.config.whitespace ? compact(value).trim() : value);
}
}
return result;
};
/**
* Loop set of attributes belonging to an element. Surrounds attributes with
* quotes if required, omits if not.
Expand All @@ -121,41 +191,26 @@ Helpers.prototype.attributes = function attributes(element) {
var attr = element.attribs
, self = this
, name = element.name
, value, bool, allowed;
, value;

if (!attr || typeof attr !== 'object') return '';
return Object.keys(attr).reduce(function (result, key) {
value = attr[key];
bool = ~list.redundant.indexOf(key);

//
// Is the attribute allowed on the HTML element? If so, allow special
// treatment. If not, then just return the full attribute and its value.
//
allowed = Object.hasOwnProperty.call(list.attributes, key) && list.attributes[key];
allowed = allowed
? allowed === '*' || ~allowed.indexOf(name)
: ~key.indexOf('data-');

//
// Remove attributes that are empty, not boolean and no semantic value.
//
if (!self.config.empty && !/data|itemscope/.test(key) && !bool && !value && allowed) return result;

//
// Boolean attributes should be added sparse, also unset attributes
// should remain unset if retained with `empty` option.
//
result = result + ' ' + key;
if (!self.config.spare) {
if (!value.length) return result;
if (allowed && bool && (value === key || 'true' === value)) return result;
if (self.allowAttributeInNode(name, key)) {
if (self.allowRemoveAttribute(name, key, value)) {
key = null;
value = null;
}else if (self.allowRemoveAttributeValue(key, value)) {
value = null;
}
} else if (!value) {
value = null;
}

//
// Return full attribute with value.
//
return result + '=' + self.quote(!self.config.whitespace ? compact(value).trim() : value);
return result + self.toAttributeString(key, value);
}, '');
};

Expand Down
3 changes: 1 addition & 2 deletions lib/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ exports.redundant = [
'async', 'defer', 'formnovalidate', 'checked', 'scoped', 'reversed',
'selected', 'autoplay', 'controls', 'loop', 'muted', 'seamless',
'default', 'ismap', 'novalidate', 'open', 'typemustmatch', 'truespeed',
'itemscope', 'autocomplete', 'download', 'draggable', 'novalidate',
'itemscope', 'autocomplete', 'download', 'novalidate',
'sortable', 'spellcheck'
];

Expand Down Expand Up @@ -137,7 +137,6 @@ exports.attributes = {
'dirname': ['input', 'textarea'],
'disabled': ['button', 'command', 'fieldset', 'input', 'keygen', 'optgroup', 'option', 'select', 'textarea'],
'download': ['a', 'area'],
'draggable': '*',
'dropzone': '*',
'enctype': 'form',
'for': ['label', 'output'],
Expand Down
25 changes: 24 additions & 1 deletion test/minimize-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,30 @@ describe('Minimize', function () {
done();
});
});

it('should remove value from draggable attribute', function (done) {
minimize.parse('<div draggable="auto">', function (error, result) {
expect(result).to.equal('<div draggable></div>');
done();
});
});
it('should not remove value from draggable attribute', function (done) {
minimize.parse('<div draggable="true"></div>', function (error, result) {
expect(result).to.equal('<div draggable=true></div>');
done();
});
});
it('should remove draggable attribute from element', function (done) {
minimize.parse('<img draggable="true"/>', function (error, result) {
expect(result).to.equal('<img>');
done();
});
});
it('should remove draggable attribute from element', function (done) {
minimize.parse('<div draggable="false"></div>', function (error, result) {
expect(result).to.equal('<div></div>');
done();
});
});
it('should remove CDATA from scripts', function (done) {
minimize.parse(html.cdata, function (error, result) {
expect(result).to.equal("<script type=text/javascript>\n\n...code...\n\n</script>");
Expand Down