diff --git a/lib/style.js b/lib/style.js
index ef4dad0c..2d86ef47 100644
--- a/lib/style.js
+++ b/lib/style.js
@@ -3,8 +3,7 @@
const stable = require('stable');
const csstree = require('css-tree');
const specificity = require('csso/lib/restructure/prepare/specificity');
-const { selectAll, is } = require('css-select');
-const svgoCssSelectAdapter = require('./svgo/css-select-adapter.js');
+const { visit, matches } = require('./xast.js');
const { compareSpecificity } = require('./css-tools.js');
const {
attrsGroups,
@@ -12,11 +11,6 @@ const {
presentationNonInheritableGroupAttrs,
} = require('../plugins/_collections.js');
-const cssSelectOptions = {
- xmlMode: true,
- adapter: svgoCssSelectAdapter,
-};
-
const parseRule = (ruleNode, dynamic) => {
let selectors;
let selectorsSpecificity;
@@ -76,7 +70,7 @@ const parseStylesheet = (css, dynamic) => {
return rules;
};
-const computeOwnStyle = (node, stylesheet) => {
+const computeOwnStyle = (stylesheet, node) => {
const computedStyle = {};
const importantStyles = new Map();
@@ -90,7 +84,7 @@ const computeOwnStyle = (node, stylesheet) => {
// collect matching rules
for (const { selectors, declarations, dynamic } of stylesheet) {
- if (is(node, selectors, cssSelectOptions)) {
+ if (matches(node, selectors)) {
for (const { name, value, important } of declarations) {
const computed = computedStyle[name];
if (computed && computed.type === 'dynamic') {
@@ -132,43 +126,45 @@ const computeOwnStyle = (node, stylesheet) => {
return computedStyle;
};
-const computeStyle = (node) => {
- // find root
- let root = node;
- while (root.parentNode) {
- root = root.parentNode;
- }
- // find all styles
- const styleNodes = selectAll('style', root, cssSelectOptions);
- // parse all styles
+const collectStylesheet = (root) => {
const stylesheet = [];
- for (const styleNode of styleNodes) {
- const dynamic =
- styleNode.attributes.media != null &&
- styleNode.attributes.media !== 'all';
- if (
- styleNode.attributes.type == null ||
- styleNode.attributes.type === '' ||
- styleNode.attributes.type === 'text/css'
- ) {
- const children = styleNode.children;
- for (const child of children) {
- if (child.type === 'text' || child.type === 'cdata') {
- stylesheet.push(...parseStylesheet(child.value, dynamic));
+ // find and parse all styles
+ visit(root, {
+ element: {
+ enter: (node) => {
+ if (node.name === 'style') {
+ const dynamic =
+ node.attributes.media != null && node.attributes.media !== 'all';
+ if (
+ node.attributes.type == null ||
+ node.attributes.type === '' ||
+ node.attributes.type === 'text/css'
+ ) {
+ const children = node.children;
+ for (const child of children) {
+ if (child.type === 'text' || child.type === 'cdata') {
+ stylesheet.push(...parseStylesheet(child.value, dynamic));
+ }
+ }
+ }
}
- }
- }
- }
+ },
+ },
+ });
// sort by selectors specificity
stable.inplace(stylesheet, (a, b) =>
compareSpecificity(a.specificity, b.specificity)
);
+ return stylesheet;
+};
+exports.collectStylesheet = collectStylesheet;
+const computeStyle = (stylesheet, node) => {
// collect inherited styles
- const computedStyles = computeOwnStyle(node, stylesheet);
+ const computedStyles = computeOwnStyle(stylesheet, node);
let parent = node;
while (parent.parentNode && parent.parentNode.type !== 'root') {
- const inheritedStyles = computeOwnStyle(parent.parentNode, stylesheet);
+ const inheritedStyles = computeOwnStyle(stylesheet, parent.parentNode);
for (const [name, computed] of Object.entries(inheritedStyles)) {
if (
computedStyles[name] == null &&
diff --git a/lib/style.test.js b/lib/style.test.js
index 9171ffe1..42c7da35 100644
--- a/lib/style.test.js
+++ b/lib/style.test.js
@@ -1,7 +1,7 @@
'use strict';
const { expect } = require('chai');
-const { computeStyle } = require('./style.js');
+const { collectStylesheet, computeStyle } = require('./style.js');
const { querySelector } = require('./xast.js');
const svg2js = require('./svgo/svg2js.js');
@@ -31,24 +31,35 @@ describe('computeStyle', () => {
`);
- expect(computeStyle(querySelector(root, '#class'))).to.deep.equal({
+ const stylesheet = collectStylesheet(root);
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#class'))
+ ).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'red' },
});
- expect(computeStyle(querySelector(root, '#two-classes'))).to.deep.equal({
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#two-classes'))
+ ).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'green' },
stroke: { type: 'static', inherited: false, value: 'black' },
});
- expect(computeStyle(querySelector(root, '#attribute'))).to.deep.equal({
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#attribute'))
+ ).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'purple' },
});
- expect(computeStyle(querySelector(root, '#inline-style'))).to.deep.equal({
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#inline-style'))
+ ).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'grey' },
});
- expect(computeStyle(querySelector(root, '#inheritance'))).to.deep.equal({
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#inheritance'))
+ ).to.deep.equal({
fill: { type: 'static', inherited: true, value: 'yellow' },
});
expect(
- computeStyle(querySelector(root, '#nested-inheritance'))
+ computeStyle(stylesheet, querySelector(root, '#nested-inheritance'))
).to.deep.equal({
fill: { type: 'static', inherited: true, value: 'blue' },
});
@@ -70,23 +81,33 @@ describe('computeStyle', () => {
`);
+ const stylesheet = collectStylesheet(root);
expect(
- computeStyle(querySelector(root, '#complex-selector'))
+ computeStyle(stylesheet, querySelector(root, '#complex-selector'))
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'red' },
});
expect(
- computeStyle(querySelector(root, '#attribute-over-inheritance'))
+ computeStyle(
+ stylesheet,
+ querySelector(root, '#attribute-over-inheritance')
+ )
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'orange' },
});
expect(
- computeStyle(querySelector(root, '#style-rule-over-attribute'))
+ computeStyle(
+ stylesheet,
+ querySelector(root, '#style-rule-over-attribute')
+ )
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'blue' },
});
expect(
- computeStyle(querySelector(root, '#inline-style-over-style-rule'))
+ computeStyle(
+ stylesheet,
+ querySelector(root, '#inline-style-over-style-rule')
+ )
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'purple' },
});
@@ -104,18 +125,25 @@ describe('computeStyle', () => {
`);
+ const stylesheet = collectStylesheet(root);
expect(
- computeStyle(querySelector(root, '#complex-selector'))
+ computeStyle(stylesheet, querySelector(root, '#complex-selector'))
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'green' },
});
expect(
- computeStyle(querySelector(root, '#style-rule-over-inline-style'))
+ computeStyle(
+ stylesheet,
+ querySelector(root, '#style-rule-over-inline-style')
+ )
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'green' },
});
expect(
- computeStyle(querySelector(root, '#inline-style-over-style-rule'))
+ computeStyle(
+ stylesheet,
+ querySelector(root, '#inline-style-over-style-rule')
+ )
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'purple' },
});
@@ -141,21 +169,30 @@ describe('computeStyle', () => {
`);
- expect(computeStyle(querySelector(root, '#media-query'))).to.deep.equal({
+ const stylesheet = collectStylesheet(root);
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#media-query'))
+ ).to.deep.equal({
fill: { type: 'dynamic', inherited: false },
});
- expect(computeStyle(querySelector(root, '#hover'))).to.deep.equal({
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#hover'))
+ ).to.deep.equal({
fill: { type: 'dynamic', inherited: false },
});
- expect(computeStyle(querySelector(root, '#inherited'))).to.deep.equal({
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#inherited'))
+ ).to.deep.equal({
fill: { type: 'dynamic', inherited: true },
});
expect(
- computeStyle(querySelector(root, '#inherited-overriden'))
+ computeStyle(stylesheet, querySelector(root, '#inherited-overriden'))
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'blue' },
});
- expect(computeStyle(querySelector(root, '#static'))).to.deep.equal({
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#static'))
+ ).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'black' },
});
});
@@ -177,13 +214,20 @@ describe('computeStyle', () => {
`);
- expect(computeStyle(querySelector(root, '#media-query'))).to.deep.equal({
+ const stylesheet = collectStylesheet(root);
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#media-query'))
+ ).to.deep.equal({
fill: { type: 'dynamic', inherited: false },
});
- expect(computeStyle(querySelector(root, '#kinda-static'))).to.deep.equal({
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#kinda-static'))
+ ).to.deep.equal({
fill: { type: 'dynamic', inherited: false },
});
- expect(computeStyle(querySelector(root, '#static'))).to.deep.equal({
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#static'))
+ ).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'blue' },
});
});
@@ -205,15 +249,20 @@ describe('computeStyle', () => {
`);
- expect(computeStyle(querySelector(root, '#valid-type'))).to.deep.equal({
+ const stylesheet = collectStylesheet(root);
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#valid-type'))
+ ).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'red' },
});
- expect(computeStyle(querySelector(root, '#empty-type'))).to.deep.equal({
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#empty-type'))
+ ).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'green' },
});
- expect(computeStyle(querySelector(root, '#invalid-type'))).to.deep.equal(
- {}
- );
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#invalid-type'))
+ ).to.deep.equal({});
});
it('ignores keyframes atrule', () => {
@@ -238,7 +287,10 @@ describe('computeStyle', () => {
`);
- expect(computeStyle(querySelector(root, '#element'))).to.deep.equal({
+ const stylesheet = collectStylesheet(root);
+ expect(
+ computeStyle(stylesheet, querySelector(root, '#element'))
+ ).to.deep.equal({
animation: {
type: 'static',
inherited: false,
diff --git a/plugins/convertPathData.js b/plugins/convertPathData.js
index e09c2890..cb4cd2b4 100644
--- a/plugins/convertPathData.js
+++ b/plugins/convertPathData.js
@@ -1,6 +1,6 @@
'use strict';
-const { computeStyle } = require('../lib/style.js');
+const { collectStylesheet, computeStyle } = require('../lib/style.js');
const { pathElems } = require('./_collections.js');
const { path2js, js2path } = require('./_path.js');
const { applyTransforms } = require('./_applyTransforms.js');
@@ -55,11 +55,12 @@ let arcTolerance;
* @author Kir Belevich
*/
exports.fn = (root, params) => {
+ const stylesheet = collectStylesheet(root);
return {
element: {
enter: (node) => {
if (pathElems.includes(node.name) && node.attributes.d != null) {
- const computedStyle = computeStyle(node);
+ const computedStyle = computeStyle(stylesheet, node);
precision = params.floatPrecision;
error =
precision !== false
diff --git a/plugins/mergePaths.js b/plugins/mergePaths.js
index 8bff3de2..cd5863e1 100644
--- a/plugins/mergePaths.js
+++ b/plugins/mergePaths.js
@@ -1,7 +1,7 @@
'use strict';
const { detachNodeFromParent } = require('../lib/xast.js');
-const { computeStyle } = require('../lib/style.js');
+const { collectStylesheet, computeStyle } = require('../lib/style.js');
const { path2js, js2path, intersects } = require('./_path.js');
exports.type = 'visitor';
@@ -22,6 +22,7 @@ exports.fn = (root, params) => {
floatPrecision,
noSpaceAfterFlags = false, // a20 60 45 0 1 30 20 → a20 60 45 0130 20
} = params;
+ const stylesheet = collectStylesheet(root);
return {
element: {
@@ -53,7 +54,7 @@ exports.fn = (root, params) => {
}
// preserve paths with markers
- const computedStyle = computeStyle(child);
+ const computedStyle = computeStyle(stylesheet, child);
if (
computedStyle['marker-start'] ||
computedStyle['marker-mid'] ||
diff --git a/plugins/removeHiddenElems.js b/plugins/removeHiddenElems.js
index 2f98e3fb..8d7be78c 100644
--- a/plugins/removeHiddenElems.js
+++ b/plugins/removeHiddenElems.js
@@ -5,7 +5,7 @@ const {
closestByName,
detachNodeFromParent,
} = require('../lib/xast.js');
-const { computeStyle } = require('../lib/style.js');
+const { collectStylesheet, computeStyle } = require('../lib/style.js');
const { parsePathData } = require('../lib/path.js');
exports.type = 'visitor';
@@ -49,12 +49,14 @@ exports.fn = (root, params) => {
polylineEmptyPoints = true,
polygonEmptyPoints = true,
} = params;
+ const stylesheet = collectStylesheet(root);
+
return {
element: {
enter: (node) => {
// Removes hidden elements
// https://www.w3schools.com/cssref/pr_class_visibility.asp
- const computedStyle = computeStyle(node);
+ const computedStyle = computeStyle(stylesheet, node);
if (
isHidden &&
computedStyle.visibility &&