Skip to content

Commit

Permalink
feat(Vectorizer): add option to support camel case attributes (#2339)
Browse files Browse the repository at this point in the history
  • Loading branch information
kumilingus authored Oct 9, 2023
1 parent b65f3aa commit 2c4804e
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 14 deletions.
125 changes: 111 additions & 14 deletions src/V/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -663,15 +663,17 @@ const V = (function() {
*/
VPrototype.removeAttr = function(name) {

var qualifiedName = V.qualifyAttr(name);
var el = this.node;
const trueName = attributeNames[name];

if (qualifiedName.ns) {
if (el.hasAttributeNS(qualifiedName.ns, qualifiedName.local)) {
el.removeAttributeNS(qualifiedName.ns, qualifiedName.local);
const { ns, local } = V.qualifyAttr(trueName);
const el = this.node;

if (ns) {
if (el.hasAttributeNS(ns, local)) {
el.removeAttributeNS(ns, local);
}
} else if (el.hasAttribute(name)) {
el.removeAttribute(name);
} else if (el.hasAttribute(trueName)) {
el.removeAttribute(trueName);
}
return this;
};
Expand All @@ -692,7 +694,7 @@ const V = (function() {
}

if (V.isString(name) && V.isUndefined(value)) {
return this.node.getAttribute(name);
return this.node.getAttribute(attributeNames[name]);
}

if (typeof name === 'object') {
Expand Down Expand Up @@ -1257,23 +1259,24 @@ const V = (function() {
*/
VPrototype.setAttribute = function(name, value) {

var el = this.node;
const el = this.node;

if (value === null) {
this.removeAttr(name);
return this;
}

var qualifiedName = V.qualifyAttr(name);
const trueName = attributeNames[name];

if (qualifiedName.ns) {
const { ns } = V.qualifyAttr(trueName);
if (ns) {
// Attribute names can be namespaced. E.g. `image` elements
// have a `xlink:href` attribute to set the source of the image.
el.setAttributeNS(qualifiedName.ns, name, value);
} else if (name === 'id') {
el.setAttributeNS(ns, trueName, value);
} else if (trueName === 'id') {
el.id = value;
} else {
el.setAttribute(name, value);
el.setAttribute(trueName, value);
}

return this;
Expand Down Expand Up @@ -1378,6 +1381,100 @@ const V = (function() {
return xml;
};

// Create an empty object which does not inherit any properties from `Object.prototype`.
// This is useful when we want to use an object as a dictionary without having to
// worry about inherited properties such as `toString`, `valueOf` etc.
const _attributeNames = Object.create(null);

// List of attributes for which not to split camel case words.
// It contains known SVG attribute names and may be extended with user-defined attribute names.
[
'baseFrequency',
'baseProfile',
'clipPathUnits',
'contentScriptType',
'contentStyleType',
'diffuseConstant',
'edgeMode',
'externalResourcesRequired',
'filterRes', // deprecated
'filterUnits',
'gradientTransform',
'gradientUnits',
'kernelMatrix',
'kernelUnitLength',
'keyPoints',
'lengthAdjust',
'limitingConeAngle',
'markerHeight',
'markerUnits',
'markerWidth',
'maskContentUnits',
'maskUnits',
'numOctaves',
'pathLength',
'patternContentUnits',
'patternTransform',
'patternUnits',
'pointsAtX',
'pointsAtY',
'pointsAtZ',
'preserveAlpha',
'preserveAspectRatio',
'primitiveUnits',
'refX',
'refY',
'requiredExtensions',
'requiredFeatures',
'specularConstant',
'specularExponent',
'spreadMethod',
'startOffset',
'stdDeviation',
'stitchTiles',
'surfaceScale',
'systemLanguage',
'tableValues',
'targetX',
'targetY',
'textLength',
'viewBox',
'viewTarget', // deprecated
'xChannelSelector',
'yChannelSelector',
'zoomAndPan' // deprecated
].forEach((name) => _attributeNames[name] = name);

const attributeNames = new Proxy(_attributeNames, {
get(cache, name) {
// The cache is a dictionary of attribute names. See `_attributeNames` above.
// If the attribute name is not in the cache, it means that it is not
// a camel-case attribute name. In that case, we need to convert
// the attribute name to dash-separated words.
if (!V.supportCamelCaseAttributes) return name;
if (name in cache) {
return cache[name];
}
// Convert camel case to dash-separated words.
return (cache[name] = name.replace(/[A-Z]/g, '-$&').toLowerCase());
}
});

// Note: The `attributeNames` and `supportCamelCaseAttributes` properties are not enumerable
// in this version to avoid breaking changes. They will be made enumerable in the next major version.

// Dictionary of attribute names
Object.defineProperty(V, 'attributeNames', {
value: attributeNames,
writable: false,
});

// Should camel case attributes be supported?
Object.defineProperty(V, 'supportCamelCaseAttributes', {
value: false,
writable: true,
});

/**
* @param {string} name
* @returns {{ns: string|null, local: string}} namespace and attribute name
Expand Down
40 changes: 40 additions & 0 deletions test/vectorizer/vectorizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -922,6 +922,46 @@ QUnit.module('vectorizer', function(hooks) {
});
});

QUnit.module('camel case support', function(hooks) {

hooks.before(function() {
V.supportCamelCaseAttributes = true;
});

hooks.after(function() {
V.supportCamelCaseAttributes = false;
});

QUnit.test('constructor', function(assert) {
const vel = V('rect', { strokeWidth: 5 });
assert.equal(vel.node.getAttribute('stroke-width'), 5);
});

QUnit.test('attr()', function(assert) {
const vel = V('rect');
vel.attr('strokeWidth', 5);
assert.equal(vel.attr('strokeWidth'), 5);
assert.equal(vel.attr('stroke-width'), 5);
assert.equal(vel.node.getAttribute('stroke-width'), 5);
vel.attr('stroke-width', 10);
assert.equal(vel.attr('strokeWidth'), 10);
assert.equal(vel.attr('stroke-width'), 10);
assert.equal(vel.node.getAttribute('stroke-width'), 10);
vel.attr('strokeWidth', null);
assert.equal(vel.attr('strokeWidth'), null);
assert.equal(vel.attr('stroke-width'), null);
assert.equal(vel.node.getAttribute('stroke-width'), null);
});

QUnit.test('removeAttr()', function(assert) {
const vel = V('rect');
vel.attr('strokeWidth', 5);
assert.equal(vel.node.getAttribute('stroke-width'), 5);
vel.removeAttr('strokeWidth');
assert.equal(vel.node.getAttribute('stroke-width'), null);
});
});

QUnit.test('remove simple', function(assert) {

var a = V('a').attr('href', 'www.seznam.cz');
Expand Down

0 comments on commit 2c4804e

Please sign in to comment.