123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130 |
- 'use strict';
- const { visit } = require('../lib/xast.js');
- const { inheritableAttrs, pathElems } = require('./_collections.js');
- exports.type = 'visitor';
- exports.name = 'moveElemsAttrsToGroup';
- exports.active = true;
- exports.description = 'Move common attributes of group children to the group';
- /**
- * Move common attributes of group children to the group
- *
- * @example
- * <g attr1="val1">
- * <g attr2="val2">
- * text
- * </g>
- * <circle attr2="val2" attr3="val3"/>
- * </g>
- * ⬇
- * <g attr1="val1" attr2="val2">
- * <g>
- * text
- * </g>
- * <circle attr3="val3"/>
- * </g>
- *
- * @author Kir Belevich
- *
- * @type {import('../lib/types').Plugin<void>}
- */
- exports.fn = (root) => {
- // find if any style element is present
- let deoptimizedWithStyles = false;
- visit(root, {
- element: {
- enter: (node) => {
- if (node.name === 'style') {
- deoptimizedWithStyles = true;
- }
- },
- },
- });
- return {
- element: {
- exit: (node) => {
- // process only groups with more than 1 children
- if (node.name !== 'g' || node.children.length <= 1) {
- return;
- }
- // deoptimize the plugin when style elements are present
- // selectors may rely on id, classes or tag names
- if (deoptimizedWithStyles) {
- return;
- }
- /**
- * find common attributes in group children
- * @type {Map<string, string>}
- */
- const commonAttributes = new Map();
- let initial = true;
- let everyChildIsPath = true;
- for (const child of node.children) {
- if (child.type === 'element') {
- if (pathElems.includes(child.name) === false) {
- everyChildIsPath = false;
- }
- if (initial) {
- initial = false;
- // collect all inheritable attributes from first child element
- for (const [name, value] of Object.entries(child.attributes)) {
- // consider only inheritable attributes
- if (inheritableAttrs.includes(name)) {
- commonAttributes.set(name, value);
- }
- }
- } else {
- // exclude uncommon attributes from initial list
- for (const [name, value] of commonAttributes) {
- if (child.attributes[name] !== value) {
- commonAttributes.delete(name);
- }
- }
- }
- }
- }
- // preserve transform on children when group has clip-path or mask
- if (
- node.attributes['clip-path'] != null ||
- node.attributes.mask != null
- ) {
- commonAttributes.delete('transform');
- }
- // preserve transform when all children are paths
- // so the transform could be applied to path data by other plugins
- if (everyChildIsPath) {
- commonAttributes.delete('transform');
- }
- // add common children attributes to group
- for (const [name, value] of commonAttributes) {
- if (name === 'transform') {
- if (node.attributes.transform != null) {
- node.attributes.transform = `${node.attributes.transform} ${value}`;
- } else {
- node.attributes.transform = value;
- }
- } else {
- node.attributes[name] = value;
- }
- }
- // delete common attributes from children
- for (const child of node.children) {
- if (child.type === 'element') {
- for (const [name] of commonAttributes) {
- delete child.attributes[name];
- }
- }
- }
- },
- },
- };
- };
|