1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-29 20:21:14 +03:00

Prepare root and element nodes for xast

Ref https://github.com/syntax-tree/xast

- added type: root | element
- renamed elem to name
- replaced "elem" property checks with check for correct type
This commit is contained in:
Bogdan Chadkin
2021-03-10 18:45:50 +03:00
parent 54daf72d50
commit e82a672bbf
29 changed files with 95 additions and 80 deletions

View File

@ -168,7 +168,7 @@ const computeStyle = (node) => {
// collect inherited styles // collect inherited styles
const computedStyles = computeOwnStyle(node, stylesheet); const computedStyles = computeOwnStyle(node, stylesheet);
let parent = node; let parent = node;
while (parent.parentNode && parent.parentNode.elem !== '#document') { while (parent.parentNode && parent.parentNode.type !== 'root') {
const inheritedStyles = computeOwnStyle(parent.parentNode, stylesheet); const inheritedStyles = computeOwnStyle(parent.parentNode, stylesheet);
for (const [name, computed] of Object.entries(inheritedStyles)) { for (const [name, computed] of Object.entries(inheritedStyles)) {
if ( if (

View File

@ -5,7 +5,7 @@
* @return {node is any} * @return {node is any}
*/ */
const isTag = (node) => { const isTag = (node) => {
return node.isElem(); return node.type === 'element';
}; };
const existsOne = (test, elems) => { const existsOne = (test, elems) => {
@ -27,7 +27,7 @@ const getChildren = (node) => {
}; };
const getName = (elemAst) => { const getName = (elemAst) => {
return elemAst.elem; return elemAst.name;
}; };
const getParent = (node) => { const getParent = (node) => {

View File

@ -97,17 +97,22 @@ JS2SVG.prototype.convert = function (data) {
this.indentLevel++; this.indentLevel++;
data.content.forEach(function (item) { data.content.forEach(function (item) {
if (item.elem) { if (item.type === 'element') {
svg += this.createElem(item); svg += this.createElem(item);
} else if (item.type === 'text') { }
if (item.type === 'text') {
svg += this.createText(item); svg += this.createText(item);
} else if (item.type === 'doctype') { }
if (item.type === 'doctype') {
svg += this.createDoctype(item); svg += this.createDoctype(item);
} else if (item.type === 'instruction') { }
if (item.type === 'instruction') {
svg += this.createProcInst(item); svg += this.createProcInst(item);
} else if (item.type === 'comment') { }
if (item.type === 'comment') {
svg += this.createComment(item); svg += this.createComment(item);
} else if (item.type === 'cdata') { }
if (item.type === 'cdata') {
svg += this.createCDATA(item); svg += this.createCDATA(item);
} }
}, this); }, this);
@ -211,7 +216,7 @@ JS2SVG.prototype.createElem = function (data) {
return ( return (
this.createIndent() + this.createIndent() +
this.config.tagShortStart + this.config.tagShortStart +
data.elem + data.name +
this.createAttrs(data) + this.createAttrs(data) +
this.config.tagShortEnd this.config.tagShortEnd
); );
@ -219,11 +224,11 @@ JS2SVG.prototype.createElem = function (data) {
return ( return (
this.createIndent() + this.createIndent() +
this.config.tagShortStart + this.config.tagShortStart +
data.elem + data.name +
this.createAttrs(data) + this.createAttrs(data) +
this.config.tagOpenEnd + this.config.tagOpenEnd +
this.config.tagCloseStart + this.config.tagCloseStart +
data.elem + data.name +
this.config.tagCloseEnd this.config.tagCloseEnd
); );
} }
@ -260,14 +265,14 @@ JS2SVG.prototype.createElem = function (data) {
return ( return (
openIndent + openIndent +
tagOpenStart + tagOpenStart +
data.elem + data.name +
this.createAttrs(data) + this.createAttrs(data) +
tagOpenEnd + tagOpenEnd +
processedData + processedData +
dataEnd + dataEnd +
closeIndent + closeIndent +
tagCloseStart + tagCloseStart +
data.elem + data.name +
tagCloseEnd tagCloseEnd
); );
} }

View File

@ -68,11 +68,16 @@ JSAPI.prototype.clone = function () {
* @return {Boolean} * @return {Boolean}
*/ */
JSAPI.prototype.isElem = function (param) { JSAPI.prototype.isElem = function (param) {
if (!param) return !!this.elem; if (this.type !== 'element') {
return false;
if (Array.isArray(param)) return !!this.elem && param.indexOf(this.elem) > -1; }
if (param == null) {
return !!this.elem && this.elem === param; return true;
}
if (Array.isArray(param)) {
return param.includes(this.name);
}
return this.name === param;
}; };
/** /**
@ -82,7 +87,7 @@ JSAPI.prototype.isElem = function (param) {
* @return {Object} element * @return {Object} element
*/ */
JSAPI.prototype.renameElem = function (name) { JSAPI.prototype.renameElem = function (name) {
if (name && typeof name === 'string') this.elem = name; if (name && typeof name === 'string') this.name = name;
return this; return this;
}; };

View File

@ -23,16 +23,14 @@ var config = {
*/ */
module.exports = function (data) { module.exports = function (data) {
var sax = SAX.parser(config.strict, config), var sax = SAX.parser(config.strict, config),
root = new JSAPI({ elem: '#document', content: [] }), root = new JSAPI({ type: 'root', content: [] }),
current = root, current = root,
stack = [root]; stack = [root];
function pushToContent(content) { function pushToContent(content) {
content = new JSAPI(content, current); const wrapped = new JSAPI(content, current);
current.content.push(wrapped);
(current.content = current.content || []).push(content); return wrapped;
return content;
} }
sax.ondoctype = function (doctype) { sax.ondoctype = function (doctype) {
@ -80,42 +78,44 @@ module.exports = function (data) {
}; };
sax.onopentag = function (data) { sax.onopentag = function (data) {
var elem = { var element = {
elem: data.name, type: 'element',
name: data.name,
attrs: {}, attrs: {},
content: [],
}; };
elem.class = new CSSClassList(elem); element.class = new CSSClassList(element);
elem.style = new CSSStyleDeclaration(elem); element.style = new CSSStyleDeclaration(element);
if (Object.keys(data.attributes).length) { if (Object.keys(data.attributes).length) {
for (const [name, attr] of Object.entries(data.attributes)) { for (const [name, attr] of Object.entries(data.attributes)) {
if (name === 'class') { if (name === 'class') {
// has class attribute // has class attribute
elem.class.hasClass(); element.class.hasClass();
} }
if (name === 'style') { if (name === 'style') {
// has style attribute // has style attribute
elem.style.hasStyle(); element.style.hasStyle();
} }
elem.attrs[name] = { element.attrs[name] = {
name: name, name: name,
value: attr.value, value: attr.value,
}; };
} }
} }
elem = pushToContent(elem); element = pushToContent(element);
current = elem; current = element;
stack.push(elem); stack.push(element);
}; };
sax.ontext = function (text) { sax.ontext = function (text) {
// prevent trimming of meaningful whitespace inside textual tags // prevent trimming of meaningful whitespace inside textual tags
if (textElems.includes(current.elem) && !data.prefix) { if (textElems.includes(current.name) && !data.prefix) {
pushToContent({ pushToContent({
type: 'text', type: 'text',
value: text, value: text,

View File

@ -27,7 +27,7 @@ var regNewlinesNeedSpace = /(\S)\r?\n(\S)/g,
* @author Kir Belevich * @author Kir Belevich
*/ */
exports.fn = function (item, params) { exports.fn = function (item, params) {
if (item.isElem()) { if (item.type === 'element') {
item.eachAttr(function (attr) { item.eachAttr(function (attr) {
if (params.newlines) { if (params.newlines) {
// new line which requires a space instead of themselve // new line which requires a space instead of themselve

View File

@ -122,14 +122,14 @@ exports.fn = function (data, params) {
// quit if <style> or <script> present ('force' param prevents quitting) // quit if <style> or <script> present ('force' param prevents quitting)
if (!params.force) { if (!params.force) {
var isNotEmpty = Boolean(item.content); var isNotEmpty = item.isEmpty() === false;
if (item.isElem(styleOrScript) && isNotEmpty) { if (item.isElem(styleOrScript) && isNotEmpty) {
hasStyleOrScript = true; hasStyleOrScript = true;
continue; continue;
} }
// Don't remove IDs if the whole SVG consists only of defs. // Don't remove IDs if the whole SVG consists only of defs.
if (item.isElem('svg') && item.content) { if (item.isElem('svg')) {
var hasDefsOnly = true; var hasDefsOnly = true;
for (var j = 0; j < item.content.length; j++) { for (var j = 0; j < item.content.length; j++) {
@ -144,7 +144,7 @@ exports.fn = function (data, params) {
} }
} }
// …and don't remove any ID if yes // …and don't remove any ID if yes
if (item.isElem()) { if (item.type === 'element') {
item.eachAttr(function (attr) { item.eachAttr(function (attr) {
var key, match; var key, match;

View File

@ -36,7 +36,7 @@ var regNumericValues = /^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|
* @author Kir Belevich * @author Kir Belevich
*/ */
exports.fn = function (item, params) { exports.fn = function (item, params) {
if (item.isElem()) { if (item.type === 'element') {
var floatPrecision = params.floatPrecision; var floatPrecision = params.floatPrecision;
if (item.hasAttr('viewBox')) { if (item.hasAttr('viewBox')) {

View File

@ -42,7 +42,7 @@ function hasAnimatedAttr(item) {
*/ */
exports.fn = function (item) { exports.fn = function (item) {
// non-empty elements // non-empty elements
if (item.isElem() && !item.isElem('switch') && !item.isEmpty()) { if (item.type === 'element' && !item.isElem('switch') && !item.isEmpty()) {
item.content.forEach(function (g, i) { item.content.forEach(function (g, i) {
// non-empty groups // non-empty groups
if (g.isElem('g') && !g.isEmpty()) { if (g.isElem('g') && !g.isEmpty()) {
@ -51,7 +51,7 @@ exports.fn = function (item) {
var inner = g.content[0]; var inner = g.content[0];
if ( if (
inner.isElem() && inner.type === 'element' &&
!inner.hasAttr('id') && !inner.hasAttr('id') &&
!g.hasAttr('filter') && !g.hasAttr('filter') &&
!(g.hasAttr('class') && inner.hasAttr('class')) && !(g.hasAttr('class') && inner.hasAttr('class')) &&

View File

@ -50,7 +50,7 @@ var collections = require('./_collections'),
* @author Kir Belevich * @author Kir Belevich
*/ */
exports.fn = function (item, params) { exports.fn = function (item, params) {
if (item.elem) { if (item.type === 'element') {
item.eachAttr(function (attr) { item.eachAttr(function (attr) {
if (collections.colorsProps.indexOf(attr.name) > -1) { if (collections.colorsProps.indexOf(attr.name) > -1) {
var val = attr.value, var val = attr.value,

View File

@ -67,7 +67,7 @@ var stylingProps = require('./_collections').attrsGroups.presentation,
* @author Kir Belevich * @author Kir Belevich
*/ */
exports.fn = function (item, params) { exports.fn = function (item, params) {
if (item.elem && item.hasAttr('style')) { if (item.type === 'element' && item.hasAttr('style')) {
// ['opacity: 1', 'color: #000'] // ['opacity: 1', 'color: #000']
var styleValue = item.attr('style').value, var styleValue = item.attr('style').value,
styles = [], styles = [],

View File

@ -44,7 +44,7 @@ var cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
* @author Kir Belevich * @author Kir Belevich
*/ */
exports.fn = function (item, params) { exports.fn = function (item, params) {
if (item.elem) { if (item.type === 'element') {
// transform // transform
if (item.hasAttr('transform')) { if (item.hasAttr('transform')) {
convertTransform(item, 'transform', params); convertTransform(item, 'transform', params);

View File

@ -248,7 +248,7 @@ exports.fn = function (document, opts) {
1 1
); );
if (styleParentEl.elem === 'defs' && styleParentEl.content.length === 0) { if (styleParentEl.name === 'defs' && styleParentEl.content.length === 0) {
// also clean up now empty <def/>s // also clean up now empty <def/>s
var defsParentEl = styleParentEl.parentNode; var defsParentEl = styleParentEl.parentNode;
defsParentEl.spliceContent( defsParentEl.spliceContent(

View File

@ -26,7 +26,7 @@ exports.params = {
* @author Kir Belevich, Lev Solntsev * @author Kir Belevich, Lev Solntsev
*/ */
exports.fn = function (item, params) { exports.fn = function (item, params) {
if (!item.isElem() || item.isEmpty()) return; if (item.type !== 'element' || item.isEmpty()) return;
var prevContentItem = null, var prevContentItem = null,
prevContentItemKeys = null; prevContentItemKeys = null;

View File

@ -81,7 +81,7 @@ function findStyleElems(ast) {
if (item.isElem('style') && !item.isEmpty()) { if (item.isElem('style') && !item.isEmpty()) {
styles.push(item); styles.push(item);
} else if (item.isElem() && item.hasAttr('style')) { } else if (item.type === 'element' && item.hasAttr('style')) {
styles.push(item); styles.push(item);
} }
} }
@ -118,8 +118,8 @@ function collectUsageData(ast, options) {
safe = false; safe = false;
} }
if (item.isElem()) { if (item.type === 'element') {
usageData.tags[item.elem] = true; usageData.tags[item.name] = true;
if (item.hasAttr('id')) { if (item.hasAttr('id')) {
usageData.ids[item.attr('id').value] = true; usageData.ids[item.attr('id').value] = true;

View File

@ -39,7 +39,7 @@ exports.fn = function (item) {
hasTransform = false, hasTransform = false,
hasClip = item.hasAttr('clip-path') || item.hasAttr('mask'), hasClip = item.hasAttr('clip-path') || item.hasAttr('mask'),
intersected = item.content.every(function (inner) { intersected = item.content.every(function (inner) {
if (inner.isElem() && inner.hasAttr()) { if (inner.type === 'element' && inner.hasAttr()) {
// don't mess with possible styles (hack until CSS parsing is implemented) // don't mess with possible styles (hack until CSS parsing is implemented)
if (inner.hasAttr('class')) return false; if (inner.hasAttr('class')) return false;
if (!Object.keys(intersection).length) { if (!Object.keys(intersection).length) {

View File

@ -197,7 +197,7 @@ exports.fn = function (node, opts, extra) {
// <style/> property values // <style/> property values
if (node.elem === 'style') { if (node.name === 'style') {
if (node.isEmpty()) { if (node.isEmpty()) {
// skip empty <style/>s // skip empty <style/>s
return node; return node;

View File

@ -87,7 +87,7 @@ exports.fn = function (item, params) {
params.attrs = [params.attrs]; params.attrs = [params.attrs];
} }
if (item.isElem()) { if (item.type === 'element') {
var elemSeparator = var elemSeparator =
typeof params.elemSeparator == 'string' typeof params.elemSeparator == 'string'
? params.elemSeparator ? params.elemSeparator
@ -122,7 +122,7 @@ exports.fn = function (item, params) {
// loop patterns // loop patterns
patterns.forEach(function (pattern) { patterns.forEach(function (pattern) {
// matches element // matches element
if (pattern[0].test(item.elem)) { if (pattern[0].test(item.name)) {
// loop attributes // loop attributes
item.eachAttr(function (attr) { item.eachAttr(function (attr) {
var name = attr.name; var name = attr.name;

View File

@ -34,7 +34,7 @@ exports.fn = function (item, params) {
editorNamespaces = editorNamespaces.concat(params.additionalNamespaces); editorNamespaces = editorNamespaces.concat(params.additionalNamespaces);
} }
if (item.elem) { if (item.type === 'element') {
if (item.isElem('svg')) { if (item.isElem('svg')) {
item.eachAttr(function (attr) { item.eachAttr(function (attr) {
const { prefix, local } = parseName(attr.name); const { prefix, local } = parseName(attr.name);
@ -56,7 +56,7 @@ exports.fn = function (item, params) {
}); });
// <sodipodi:*> // <sodipodi:*>
const { prefix } = parseName(item.elem); const { prefix } = parseName(item.name);
if (prefixes.includes(prefix)) { if (prefixes.includes(prefix)) {
return false; return false;
} }

View File

@ -60,7 +60,7 @@ exports.fn = function (item, params) {
}); });
// abort if current item is no an element // abort if current item is no an element
if (!item.isElem()) { if (item.type !== 'element') {
return; return;
} }

View File

@ -17,7 +17,7 @@ exports.description = 'removes empty attributes';
* @author Kir Belevich * @author Kir Belevich
*/ */
exports.fn = function (item) { exports.fn = function (item) {
if (item.elem) { if (item.type === 'element') {
item.eachAttr(function (attr) { item.eachAttr(function (attr) {
if ( if (
attr.value === '' && attr.value === '' &&

View File

@ -48,7 +48,7 @@ exports.params = {
* @author Kir Belevich * @author Kir Belevich
*/ */
exports.fn = function (item, params) { exports.fn = function (item, params) {
if (item.elem) { if (item.type === 'element') {
// Removes hidden elements // Removes hidden elements
// https://www.w3schools.com/cssref/pr_class_visibility.asp // https://www.w3schools.com/cssref/pr_class_visibility.asp
const computedStyle = computeStyle(item); const computedStyle = computeStyle(item);

View File

@ -102,7 +102,9 @@ function parseViewBox(svg) {
}; };
var path = new JSAPI({ var path = new JSAPI({
elem: 'path', type: 'element',
name: 'path',
content: []
}); });
path.addAttr({ path.addAttr({
name: 'd', name: 'd',

View File

@ -68,8 +68,8 @@ for (const elem of Object.values(elems)) {
*/ */
exports.fn = function (item, params) { exports.fn = function (item, params) {
// elems w/o namespace prefix // elems w/o namespace prefix
if (item.isElem() && !parseName(item.elem).prefix) { if (item.type === 'element' && !parseName(item.name).prefix) {
var elem = item.elem; var elem = item.name;
// remove unknown element's content // remove unknown element's content
if ( if (
@ -80,12 +80,12 @@ exports.fn = function (item, params) {
) { ) {
item.content.forEach(function (content, i) { item.content.forEach(function (content, i) {
if ( if (
content.isElem() && content.type === 'element' &&
!parseName(content.elem).prefix && !parseName(content.name).prefix &&
((elems[elem].content && // Do we have a record of its permitted content? ((elems[elem].content && // Do we have a record of its permitted content?
elems[elem].content.indexOf(content.elem) === -1) || elems[elem].content.indexOf(content.name) === -1) ||
(!elems[elem].content && // we dont know about its permitted content (!elems[elem].content && // we dont know about its permitted content
!elems[content.elem])) // check that we know about the element at all !elems[content.name])) // check that we know about the element at all
) { ) {
item.content.splice(i, 1); item.content.splice(i, 1);
} }

View File

@ -65,7 +65,7 @@ exports.fn = function (data) {
} }
if (xmlnsCollection.length) { if (xmlnsCollection.length) {
const { prefix } = parseName(item.elem); const { prefix } = parseName(item.name);
// check item for the ns-attrs // check item for the ns-attrs
if (prefix) { if (prefix) {
removeNSfromCollection(prefix); removeNSfromCollection(prefix);

View File

@ -49,7 +49,8 @@ exports.fn = function (data) {
if (defs.length > 0) { if (defs.length > 0) {
const defsTag = new JSAPI( const defsTag = new JSAPI(
{ {
elem: 'defs', type: 'element',
name: 'defs',
content: [], content: [],
attrs: [], attrs: [],
}, },

View File

@ -44,7 +44,7 @@ exports.fn = function (item, params) {
orderlen = params.order.length + 1, orderlen = params.order.length + 1,
xmlnsOrder = params.xmlnsOrder || 'front'; xmlnsOrder = params.xmlnsOrder || 'front';
if (item.elem) { if (item.type === 'element') {
item.eachAttr(function (attr) { item.eachAttr(function (attr) {
attrs.push(attr); attrs.push(attr);
}); });

View File

@ -19,23 +19,23 @@ exports.fn = function (item) {
if (item.isElem('defs')) { if (item.isElem('defs')) {
if (item.content) { if (item.content) {
var frequency = item.content.reduce(function (frequency, child) { var frequency = item.content.reduce(function (frequency, child) {
if (child.elem in frequency) { if (child.name in frequency) {
frequency[child.elem]++; frequency[child.name]++;
} else { } else {
frequency[child.elem] = 1; frequency[child.name] = 1;
} }
return frequency; return frequency;
}, {}); }, {});
item.content.sort(function (a, b) { item.content.sort(function (a, b) {
var frequencyComparison = frequency[b.elem] - frequency[a.elem]; var frequencyComparison = frequency[b.name] - frequency[a.name];
if (frequencyComparison !== 0) { if (frequencyComparison !== 0) {
return frequencyComparison; return frequencyComparison;
} }
var lengthComparison = b.elem.length - a.elem.length; var lengthComparison = b.name.length - a.name.length;
if (lengthComparison !== 0) { if (lengthComparison !== 0) {
return lengthComparison; return lengthComparison;
} }
return a.elem != b.elem ? (a.elem > b.elem ? -1 : 1) : 0; return a.name != b.name ? (a.name > b.name ? -1 : 1) : 0;
}); });
} }

View File

@ -76,9 +76,11 @@ describe('svg2js', function () {
}); });
}); });
describe('elem', function () { describe('name', function () {
it('should have property elem: "svg"', function () { it('should have property name: "svg"', function () {
expect(root.content[3]).to.have.property('elem', 'svg'); expect(root.content[3]).to.include({
name: 'svg',
});
}); });
}); });