Files
rspade_system/node_modules/svgo/plugins/removeXlink.js
root d523f0f600 Fix code quality violations and exclude Manifest from checks
Document application modes (development/debug/production)
Add global file drop handler, order column normalization, SPA hash fix
Serve CDN assets via /_vendor/ URLs instead of merging into bundles
Add production minification with license preservation
Improve JSON formatting for debugging and production optimization
Add CDN asset caching with CSS URL inlining for production builds
Add three-mode system (development, debug, production)
Update Manifest CLAUDE.md to reflect helper class architecture
Refactor Manifest.php into helper classes for better organization
Pre-manifest-refactor checkpoint: Add app_mode documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-14 10:38:22 +00:00

228 lines
6.3 KiB
JavaScript

import { elems } from './_collections.js';
/**
* @typedef RemoveXlinkParams
* @property {boolean=} includeLegacy
* By default this plugin ignores legacy elements that were deprecated or
* removed in SVG 2. Set to true to force performing operations on those too.
*/
export const name = 'removeXlink';
export const description =
'remove xlink namespace and replaces attributes with the SVG 2 equivalent where applicable';
/** URI indicating the Xlink namespace. */
const XLINK_NAMESPACE = 'http://www.w3.org/1999/xlink';
/**
* Map of `xlink:show` values to the SVG 2 `target` attribute values.
*
* @type {Record<string, string>}
* @see https://developer.mozilla.org/docs/Web/SVG/Attribute/xlink:show#usage_notes
*/
const SHOW_TO_TARGET = {
new: '_blank',
replace: '_self',
};
/**
* Elements that use xlink:href, but were deprecated in SVG 2 and therefore
* don't support the SVG 2 href attribute.
*
* @type {Set<string>}
* @see https://developer.mozilla.org/docs/Web/SVG/Attribute/xlink:href
* @see https://developer.mozilla.org/docs/Web/SVG/Attribute/href
*/
const LEGACY_ELEMENTS = new Set([
'cursor',
'filter',
'font-face-uri',
'glyphRef',
'tref',
]);
/**
* @param {import('../lib/types.js').XastElement} node
* @param {ReadonlyArray<string>} prefixes
* @param {string} attr
* @returns {string[]}
*/
const findPrefixedAttrs = (node, prefixes, attr) => {
return prefixes
.map((prefix) => `${prefix}:${attr}`)
.filter((attr) => node.attributes[attr] != null);
};
/**
* Removes XLink namespace prefixes and converts references to XLink attributes
* to the native SVG equivalent.
*
* XLink namespace is deprecated in SVG 2.
*
* @type {import('../lib/types.js').Plugin<RemoveXlinkParams>}
* @see https://developer.mozilla.org/docs/Web/SVG/Attribute/xlink:href
*/
export const fn = (_, params) => {
const { includeLegacy } = params;
/**
* XLink namespace prefixes that are currently in the stack.
*
* @type {string[]}
*/
const xlinkPrefixes = [];
/**
* Namespace prefixes that exist in {@link xlinkPrefixes} but were overridden
* in a child element to point to another namespace, and is not treated as an
* XLink attribute.
*
* @type {string[]}
*/
const overriddenPrefixes = [];
/**
* Namespace prefixes that were used in one of the {@link LEGACY_ELEMENTS}.
*
* @type {string[]}
*/
const usedInLegacyElement = [];
return {
element: {
enter: (node) => {
for (const [key, value] of Object.entries(node.attributes)) {
if (key.startsWith('xmlns:')) {
const prefix = key.split(':', 2)[1];
if (value === XLINK_NAMESPACE) {
xlinkPrefixes.push(prefix);
continue;
}
if (xlinkPrefixes.includes(prefix)) {
overriddenPrefixes.push(prefix);
}
}
}
if (
overriddenPrefixes.some((prefix) => xlinkPrefixes.includes(prefix))
) {
return;
}
const showAttrs = findPrefixedAttrs(node, xlinkPrefixes, 'show');
let showHandled = node.attributes.target != null;
for (let i = showAttrs.length - 1; i >= 0; i--) {
const attr = showAttrs[i];
const value = node.attributes[attr];
const mapping = SHOW_TO_TARGET[value];
if (showHandled || mapping == null) {
delete node.attributes[attr];
continue;
}
if (mapping !== elems[node.name]?.defaults?.target) {
node.attributes.target = mapping;
}
delete node.attributes[attr];
showHandled = true;
}
const titleAttrs = findPrefixedAttrs(node, xlinkPrefixes, 'title');
for (let i = titleAttrs.length - 1; i >= 0; i--) {
const attr = titleAttrs[i];
const value = node.attributes[attr];
const hasTitle = node.children.filter(
(child) => child.type === 'element' && child.name === 'title',
);
if (hasTitle.length > 0) {
delete node.attributes[attr];
continue;
}
/** @type {import('../lib/types.js').XastElement} */
const titleTag = {
type: 'element',
name: 'title',
attributes: {},
children: [
{
type: 'text',
value,
},
],
};
Object.defineProperty(titleTag, 'parentNode', {
writable: true,
value: node,
});
node.children.unshift(titleTag);
delete node.attributes[attr];
}
const hrefAttrs = findPrefixedAttrs(node, xlinkPrefixes, 'href');
if (
hrefAttrs.length > 0 &&
LEGACY_ELEMENTS.has(node.name) &&
!includeLegacy
) {
hrefAttrs
.map((attr) => attr.split(':', 1)[0])
.forEach((prefix) => usedInLegacyElement.push(prefix));
return;
}
for (let i = hrefAttrs.length - 1; i >= 0; i--) {
const attr = hrefAttrs[i];
const value = node.attributes[attr];
if (node.attributes.href != null) {
delete node.attributes[attr];
continue;
}
node.attributes.href = value;
delete node.attributes[attr];
}
},
exit: (node) => {
for (const [key, value] of Object.entries(node.attributes)) {
const [prefix, attr] = key.split(':', 2);
if (
xlinkPrefixes.includes(prefix) &&
!overriddenPrefixes.includes(prefix) &&
!usedInLegacyElement.includes(prefix) &&
!includeLegacy
) {
delete node.attributes[key];
continue;
}
if (key.startsWith('xmlns:') && !usedInLegacyElement.includes(attr)) {
if (value === XLINK_NAMESPACE) {
const index = xlinkPrefixes.indexOf(attr);
xlinkPrefixes.splice(index, 1);
delete node.attributes[key];
continue;
}
if (overriddenPrefixes.includes(prefix)) {
const index = overriddenPrefixes.indexOf(attr);
overriddenPrefixes.splice(index, 1);
}
}
}
},
},
};
};