123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- 'use strict';
- exports.type = 'visitor';
- exports.name = 'sortAttrs';
- exports.active = false;
- exports.description = 'Sort element attributes for better compression';
- /**
- * Sort element attributes for better compression
- *
- * @author Nikolay Frantsev
- *
- * @type {import('../lib/types').Plugin<{
- * order?: Array<string>
- * xmlnsOrder?: 'front' | 'alphabetical'
- * }>}
- */
- exports.fn = (_root, params) => {
- const {
- order = [
- 'id',
- 'width',
- 'height',
- 'x',
- 'x1',
- 'x2',
- 'y',
- 'y1',
- 'y2',
- 'cx',
- 'cy',
- 'r',
- 'fill',
- 'stroke',
- 'marker',
- 'd',
- 'points',
- ],
- xmlnsOrder = 'front',
- } = params;
- /**
- * @type {(name: string) => number}
- */
- const getNsPriority = (name) => {
- if (xmlnsOrder === 'front') {
- // put xmlns first
- if (name === 'xmlns') {
- return 3;
- }
- // xmlns:* attributes second
- if (name.startsWith('xmlns:')) {
- return 2;
- }
- }
- // other namespaces after and sort them alphabetically
- if (name.includes(':')) {
- return 1;
- }
- // other attributes
- return 0;
- };
- /**
- * @type {(a: [string, string], b: [string, string]) => number}
- */
- const compareAttrs = ([aName], [bName]) => {
- // sort namespaces
- const aPriority = getNsPriority(aName);
- const bPriority = getNsPriority(bName);
- const priorityNs = bPriority - aPriority;
- if (priorityNs !== 0) {
- return priorityNs;
- }
- // extract the first part from attributes
- // for example "fill" from "fill" and "fill-opacity"
- const [aPart] = aName.split('-');
- const [bPart] = bName.split('-');
- // rely on alphabetical sort when the first part is the same
- if (aPart !== bPart) {
- const aInOrderFlag = order.includes(aPart) ? 1 : 0;
- const bInOrderFlag = order.includes(bPart) ? 1 : 0;
- // sort by position in order param
- if (aInOrderFlag === 1 && bInOrderFlag === 1) {
- return order.indexOf(aPart) - order.indexOf(bPart);
- }
- // put attributes from order param before others
- const priorityOrder = bInOrderFlag - aInOrderFlag;
- if (priorityOrder !== 0) {
- return priorityOrder;
- }
- }
- // sort alphabetically
- return aName < bName ? -1 : 1;
- };
- return {
- element: {
- enter: (node) => {
- const attrs = Object.entries(node.attributes);
- attrs.sort(compareAttrs);
- /**
- * @type {Record<string, string>}
- */
- const sortedAttributes = {};
- for (const [name, value] of attrs) {
- sortedAttributes[name] = value;
- }
- node.attributes = sortedAttributes;
- },
- },
- };
- };
|