[6a3a178] | 1 | 'use strict';
|
---|
| 2 |
|
---|
| 3 | const { visitSkip, detachNodeFromParent } = require('../lib/xast.js');
|
---|
| 4 | const { collectStylesheet, computeStyle } = require('../lib/style.js');
|
---|
| 5 | const {
|
---|
| 6 | elems,
|
---|
| 7 | attrsGroups,
|
---|
| 8 | elemsGroups,
|
---|
| 9 | attrsGroupsDefaults,
|
---|
| 10 | presentationNonInheritableGroupAttrs,
|
---|
| 11 | } = require('./_collections');
|
---|
| 12 |
|
---|
| 13 | exports.type = 'visitor';
|
---|
| 14 | exports.name = 'removeUnknownsAndDefaults';
|
---|
| 15 | exports.active = true;
|
---|
| 16 | exports.description =
|
---|
| 17 | 'removes unknown elements content and attributes, removes attrs with default values';
|
---|
| 18 |
|
---|
| 19 | // resolve all groups references
|
---|
| 20 |
|
---|
| 21 | /**
|
---|
| 22 | * @type {Map<string, Set<string>>}
|
---|
| 23 | */
|
---|
| 24 | const allowedChildrenPerElement = new Map();
|
---|
| 25 | /**
|
---|
| 26 | * @type {Map<string, Set<string>>}
|
---|
| 27 | */
|
---|
| 28 | const allowedAttributesPerElement = new Map();
|
---|
| 29 | /**
|
---|
| 30 | * @type {Map<string, Map<string, string>>}
|
---|
| 31 | */
|
---|
| 32 | const attributesDefaultsPerElement = new Map();
|
---|
| 33 |
|
---|
| 34 | for (const [name, config] of Object.entries(elems)) {
|
---|
| 35 | /**
|
---|
| 36 | * @type {Set<string>}
|
---|
| 37 | */
|
---|
| 38 | const allowedChildren = new Set();
|
---|
| 39 | if (config.content) {
|
---|
| 40 | for (const elementName of config.content) {
|
---|
| 41 | allowedChildren.add(elementName);
|
---|
| 42 | }
|
---|
| 43 | }
|
---|
| 44 | if (config.contentGroups) {
|
---|
| 45 | for (const contentGroupName of config.contentGroups) {
|
---|
| 46 | const elemsGroup = elemsGroups[contentGroupName];
|
---|
| 47 | if (elemsGroup) {
|
---|
| 48 | for (const elementName of elemsGroup) {
|
---|
| 49 | allowedChildren.add(elementName);
|
---|
| 50 | }
|
---|
| 51 | }
|
---|
| 52 | }
|
---|
| 53 | }
|
---|
| 54 | /**
|
---|
| 55 | * @type {Set<string>}
|
---|
| 56 | */
|
---|
| 57 | const allowedAttributes = new Set();
|
---|
| 58 | if (config.attrs) {
|
---|
| 59 | for (const attrName of config.attrs) {
|
---|
| 60 | allowedAttributes.add(attrName);
|
---|
| 61 | }
|
---|
| 62 | }
|
---|
| 63 | /**
|
---|
| 64 | * @type {Map<string, string>}
|
---|
| 65 | */
|
---|
| 66 | const attributesDefaults = new Map();
|
---|
| 67 | if (config.defaults) {
|
---|
| 68 | for (const [attrName, defaultValue] of Object.entries(config.defaults)) {
|
---|
| 69 | attributesDefaults.set(attrName, defaultValue);
|
---|
| 70 | }
|
---|
| 71 | }
|
---|
| 72 | for (const attrsGroupName of config.attrsGroups) {
|
---|
| 73 | const attrsGroup = attrsGroups[attrsGroupName];
|
---|
| 74 | if (attrsGroup) {
|
---|
| 75 | for (const attrName of attrsGroup) {
|
---|
| 76 | allowedAttributes.add(attrName);
|
---|
| 77 | }
|
---|
| 78 | }
|
---|
| 79 | const groupDefaults = attrsGroupsDefaults[attrsGroupName];
|
---|
| 80 | if (groupDefaults) {
|
---|
| 81 | for (const [attrName, defaultValue] of Object.entries(groupDefaults)) {
|
---|
| 82 | attributesDefaults.set(attrName, defaultValue);
|
---|
| 83 | }
|
---|
| 84 | }
|
---|
| 85 | }
|
---|
| 86 | allowedChildrenPerElement.set(name, allowedChildren);
|
---|
| 87 | allowedAttributesPerElement.set(name, allowedAttributes);
|
---|
| 88 | attributesDefaultsPerElement.set(name, attributesDefaults);
|
---|
| 89 | }
|
---|
| 90 |
|
---|
| 91 | /**
|
---|
| 92 | * Remove unknown elements content and attributes,
|
---|
| 93 | * remove attributes with default values.
|
---|
| 94 | *
|
---|
| 95 | * @author Kir Belevich
|
---|
| 96 | *
|
---|
| 97 | * @type {import('../lib/types').Plugin<{
|
---|
| 98 | * unknownContent?: boolean,
|
---|
| 99 | * unknownAttrs?: boolean,
|
---|
| 100 | * defaultAttrs?: boolean,
|
---|
| 101 | * uselessOverrides?: boolean,
|
---|
| 102 | * keepDataAttrs?: boolean,
|
---|
| 103 | * keepAriaAttrs?: boolean,
|
---|
| 104 | * keepRoleAttr?: boolean,
|
---|
| 105 | * }>}
|
---|
| 106 | */
|
---|
| 107 | exports.fn = (root, params) => {
|
---|
| 108 | const {
|
---|
| 109 | unknownContent = true,
|
---|
| 110 | unknownAttrs = true,
|
---|
| 111 | defaultAttrs = true,
|
---|
| 112 | uselessOverrides = true,
|
---|
| 113 | keepDataAttrs = true,
|
---|
| 114 | keepAriaAttrs = true,
|
---|
| 115 | keepRoleAttr = false,
|
---|
| 116 | } = params;
|
---|
| 117 | const stylesheet = collectStylesheet(root);
|
---|
| 118 |
|
---|
| 119 | return {
|
---|
| 120 | element: {
|
---|
| 121 | enter: (node, parentNode) => {
|
---|
| 122 | // skip namespaced elements
|
---|
| 123 | if (node.name.includes(':')) {
|
---|
| 124 | return;
|
---|
| 125 | }
|
---|
| 126 | // skip visiting foreignObject subtree
|
---|
| 127 | if (node.name === 'foreignObject') {
|
---|
| 128 | return visitSkip;
|
---|
| 129 | }
|
---|
| 130 |
|
---|
| 131 | // remove unknown element's content
|
---|
| 132 | if (unknownContent && parentNode.type === 'element') {
|
---|
| 133 | const allowedChildren = allowedChildrenPerElement.get(
|
---|
| 134 | parentNode.name
|
---|
| 135 | );
|
---|
| 136 | if (allowedChildren == null || allowedChildren.size === 0) {
|
---|
| 137 | // remove unknown elements
|
---|
| 138 | if (allowedChildrenPerElement.get(node.name) == null) {
|
---|
| 139 | detachNodeFromParent(node, parentNode);
|
---|
| 140 | return;
|
---|
| 141 | }
|
---|
| 142 | } else {
|
---|
| 143 | // remove not allowed children
|
---|
| 144 | if (allowedChildren.has(node.name) === false) {
|
---|
| 145 | detachNodeFromParent(node, parentNode);
|
---|
| 146 | return;
|
---|
| 147 | }
|
---|
| 148 | }
|
---|
| 149 | }
|
---|
| 150 |
|
---|
| 151 | const allowedAttributes = allowedAttributesPerElement.get(node.name);
|
---|
| 152 | const attributesDefaults = attributesDefaultsPerElement.get(node.name);
|
---|
| 153 | const computedParentStyle =
|
---|
| 154 | parentNode.type === 'element'
|
---|
| 155 | ? computeStyle(stylesheet, parentNode)
|
---|
| 156 | : null;
|
---|
| 157 |
|
---|
| 158 | // remove element's unknown attrs and attrs with default values
|
---|
| 159 | for (const [name, value] of Object.entries(node.attributes)) {
|
---|
| 160 | if (keepDataAttrs && name.startsWith('data-')) {
|
---|
| 161 | continue;
|
---|
| 162 | }
|
---|
| 163 | if (keepAriaAttrs && name.startsWith('aria-')) {
|
---|
| 164 | continue;
|
---|
| 165 | }
|
---|
| 166 | if (keepRoleAttr && name === 'role') {
|
---|
| 167 | continue;
|
---|
| 168 | }
|
---|
| 169 | // skip xmlns attribute
|
---|
| 170 | if (name === 'xmlns') {
|
---|
| 171 | continue;
|
---|
| 172 | }
|
---|
| 173 | // skip namespaced attributes except xml:* and xlink:*
|
---|
| 174 | if (name.includes(':')) {
|
---|
| 175 | const [prefix] = name.split(':');
|
---|
| 176 | if (prefix !== 'xml' && prefix !== 'xlink') {
|
---|
| 177 | continue;
|
---|
| 178 | }
|
---|
| 179 | }
|
---|
| 180 |
|
---|
| 181 | if (
|
---|
| 182 | unknownAttrs &&
|
---|
| 183 | allowedAttributes &&
|
---|
| 184 | allowedAttributes.has(name) === false
|
---|
| 185 | ) {
|
---|
| 186 | delete node.attributes[name];
|
---|
| 187 | }
|
---|
| 188 | if (
|
---|
| 189 | defaultAttrs &&
|
---|
| 190 | node.attributes.id == null &&
|
---|
| 191 | attributesDefaults &&
|
---|
| 192 | attributesDefaults.get(name) === value
|
---|
| 193 | ) {
|
---|
| 194 | // keep defaults if parent has own or inherited style
|
---|
| 195 | if (
|
---|
| 196 | computedParentStyle == null ||
|
---|
| 197 | computedParentStyle[name] == null
|
---|
| 198 | ) {
|
---|
| 199 | delete node.attributes[name];
|
---|
| 200 | }
|
---|
| 201 | }
|
---|
| 202 | if (uselessOverrides && node.attributes.id == null) {
|
---|
| 203 | const style =
|
---|
| 204 | computedParentStyle == null ? null : computedParentStyle[name];
|
---|
| 205 | if (
|
---|
| 206 | presentationNonInheritableGroupAttrs.includes(name) === false &&
|
---|
| 207 | style != null &&
|
---|
| 208 | style.type === 'static' &&
|
---|
| 209 | style.value === value
|
---|
| 210 | ) {
|
---|
| 211 | delete node.attributes[name];
|
---|
| 212 | }
|
---|
| 213 | }
|
---|
| 214 | }
|
---|
| 215 | },
|
---|
| 216 | },
|
---|
| 217 | };
|
---|
| 218 | };
|
---|