source: trip-planner-front/node_modules/svgo/lib/style.js@ 8d391a1

Last change on this file since 8d391a1 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 7.8 KB
Line 
1'use strict';
2
3/**
4 * @typedef {import('css-tree').Rule} CsstreeRule
5 * @typedef {import('./types').Specificity} Specificity
6 * @typedef {import('./types').Stylesheet} Stylesheet
7 * @typedef {import('./types').StylesheetRule} StylesheetRule
8 * @typedef {import('./types').StylesheetDeclaration} StylesheetDeclaration
9 * @typedef {import('./types').ComputedStyles} ComputedStyles
10 * @typedef {import('./types').XastRoot} XastRoot
11 * @typedef {import('./types').XastElement} XastElement
12 * @typedef {import('./types').XastParent} XastParent
13 * @typedef {import('./types').XastChild} XastChild
14 */
15
16const stable = require('stable');
17const csstree = require('css-tree');
18// @ts-ignore not defined in @types/csso
19const specificity = require('csso/lib/restructure/prepare/specificity');
20const { visit, matches } = require('./xast.js');
21const {
22 attrsGroups,
23 inheritableAttrs,
24 presentationNonInheritableGroupAttrs,
25} = require('../plugins/_collections.js');
26
27// @ts-ignore not defined in @types/csstree
28const csstreeWalkSkip = csstree.walk.skip;
29
30/**
31 * @type {(ruleNode: CsstreeRule, dynamic: boolean) => StylesheetRule}
32 */
33const parseRule = (ruleNode, dynamic) => {
34 let selectors;
35 let selectorsSpecificity;
36 /**
37 * @type {Array<StylesheetDeclaration>}
38 */
39 const declarations = [];
40 csstree.walk(ruleNode, (cssNode) => {
41 if (cssNode.type === 'SelectorList') {
42 // compute specificity from original node to consider pseudo classes
43 selectorsSpecificity = specificity(cssNode);
44 const newSelectorsNode = csstree.clone(cssNode);
45 csstree.walk(newSelectorsNode, (pseudoClassNode, item, list) => {
46 if (pseudoClassNode.type === 'PseudoClassSelector') {
47 dynamic = true;
48 list.remove(item);
49 }
50 });
51 selectors = csstree.generate(newSelectorsNode);
52 return csstreeWalkSkip;
53 }
54 if (cssNode.type === 'Declaration') {
55 declarations.push({
56 name: cssNode.property,
57 value: csstree.generate(cssNode.value),
58 important: cssNode.important === true,
59 });
60 return csstreeWalkSkip;
61 }
62 });
63 if (selectors == null || selectorsSpecificity == null) {
64 throw Error('assert');
65 }
66 return {
67 dynamic,
68 selectors,
69 specificity: selectorsSpecificity,
70 declarations,
71 };
72};
73
74/**
75 * @type {(css: string, dynamic: boolean) => Array<StylesheetRule>}
76 */
77const parseStylesheet = (css, dynamic) => {
78 /**
79 * @type {Array<StylesheetRule>}
80 */
81 const rules = [];
82 const ast = csstree.parse(css, {
83 parseValue: false,
84 parseAtrulePrelude: false,
85 });
86 csstree.walk(ast, (cssNode) => {
87 if (cssNode.type === 'Rule') {
88 rules.push(parseRule(cssNode, dynamic || false));
89 return csstreeWalkSkip;
90 }
91 if (cssNode.type === 'Atrule') {
92 if (cssNode.name === 'keyframes') {
93 return csstreeWalkSkip;
94 }
95 csstree.walk(cssNode, (ruleNode) => {
96 if (ruleNode.type === 'Rule') {
97 rules.push(parseRule(ruleNode, dynamic || true));
98 return csstreeWalkSkip;
99 }
100 });
101 return csstreeWalkSkip;
102 }
103 });
104 return rules;
105};
106
107/**
108 * @type {(css: string) => Array<StylesheetDeclaration>}
109 */
110const parseStyleDeclarations = (css) => {
111 /**
112 * @type {Array<StylesheetDeclaration>}
113 */
114 const declarations = [];
115 const ast = csstree.parse(css, {
116 context: 'declarationList',
117 parseValue: false,
118 });
119 csstree.walk(ast, (cssNode) => {
120 if (cssNode.type === 'Declaration') {
121 declarations.push({
122 name: cssNode.property,
123 value: csstree.generate(cssNode.value),
124 important: cssNode.important === true,
125 });
126 }
127 });
128 return declarations;
129};
130
131/**
132 * @type {(stylesheet: Stylesheet, node: XastElement) => ComputedStyles}
133 */
134const computeOwnStyle = (stylesheet, node) => {
135 /**
136 * @type {ComputedStyles}
137 */
138 const computedStyle = {};
139 const importantStyles = new Map();
140
141 // collect attributes
142 for (const [name, value] of Object.entries(node.attributes)) {
143 if (attrsGroups.presentation.includes(name)) {
144 computedStyle[name] = { type: 'static', inherited: false, value };
145 importantStyles.set(name, false);
146 }
147 }
148
149 // collect matching rules
150 for (const { selectors, declarations, dynamic } of stylesheet.rules) {
151 if (matches(node, selectors)) {
152 for (const { name, value, important } of declarations) {
153 const computed = computedStyle[name];
154 if (computed && computed.type === 'dynamic') {
155 continue;
156 }
157 if (dynamic) {
158 computedStyle[name] = { type: 'dynamic', inherited: false };
159 continue;
160 }
161 if (
162 computed == null ||
163 important === true ||
164 importantStyles.get(name) === false
165 ) {
166 computedStyle[name] = { type: 'static', inherited: false, value };
167 importantStyles.set(name, important);
168 }
169 }
170 }
171 }
172
173 // collect inline styles
174 const styleDeclarations =
175 node.attributes.style == null
176 ? []
177 : parseStyleDeclarations(node.attributes.style);
178 for (const { name, value, important } of styleDeclarations) {
179 const computed = computedStyle[name];
180 if (computed && computed.type === 'dynamic') {
181 continue;
182 }
183 if (
184 computed == null ||
185 important === true ||
186 importantStyles.get(name) === false
187 ) {
188 computedStyle[name] = { type: 'static', inherited: false, value };
189 importantStyles.set(name, important);
190 }
191 }
192
193 return computedStyle;
194};
195
196/**
197 * Compares two selector specificities.
198 * extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
199 *
200 * @type {(a: Specificity, b: Specificity) => number}
201 */
202const compareSpecificity = (a, b) => {
203 for (var i = 0; i < 4; i += 1) {
204 if (a[i] < b[i]) {
205 return -1;
206 } else if (a[i] > b[i]) {
207 return 1;
208 }
209 }
210
211 return 0;
212};
213
214/**
215 * @type {(root: XastRoot) => Stylesheet}
216 */
217const collectStylesheet = (root) => {
218 /**
219 * @type {Array<StylesheetRule>}
220 */
221 const rules = [];
222 /**
223 * @type {Map<XastElement, XastParent>}
224 */
225 const parents = new Map();
226 visit(root, {
227 element: {
228 enter: (node, parentNode) => {
229 // store parents
230 parents.set(node, parentNode);
231 // find and parse all styles
232 if (node.name === 'style') {
233 const dynamic =
234 node.attributes.media != null && node.attributes.media !== 'all';
235 if (
236 node.attributes.type == null ||
237 node.attributes.type === '' ||
238 node.attributes.type === 'text/css'
239 ) {
240 const children = node.children;
241 for (const child of children) {
242 if (child.type === 'text' || child.type === 'cdata') {
243 rules.push(...parseStylesheet(child.value, dynamic));
244 }
245 }
246 }
247 }
248 },
249 },
250 });
251 // sort by selectors specificity
252 stable.inplace(rules, (a, b) =>
253 compareSpecificity(a.specificity, b.specificity)
254 );
255 return { rules, parents };
256};
257exports.collectStylesheet = collectStylesheet;
258
259/**
260 * @type {(stylesheet: Stylesheet, node: XastElement) => ComputedStyles}
261 */
262const computeStyle = (stylesheet, node) => {
263 const { parents } = stylesheet;
264 // collect inherited styles
265 const computedStyles = computeOwnStyle(stylesheet, node);
266 let parent = parents.get(node);
267 while (parent != null && parent.type !== 'root') {
268 const inheritedStyles = computeOwnStyle(stylesheet, parent);
269 for (const [name, computed] of Object.entries(inheritedStyles)) {
270 if (
271 computedStyles[name] == null &&
272 // ignore not inheritable styles
273 inheritableAttrs.includes(name) === true &&
274 presentationNonInheritableGroupAttrs.includes(name) === false
275 ) {
276 computedStyles[name] = { ...computed, inherited: true };
277 }
278 }
279 parent = parents.get(parent);
280 }
281 return computedStyles;
282};
283exports.computeStyle = computeStyle;
Note: See TracBrowser for help on using the repository browser.