-
Notifications
You must be signed in to change notification settings - Fork 83
Create unique ID prefix within SVGs to prevent ID collisions #108
base: master
Are you sure you want to change the base?
Changes from all commits
fac069f
c6e37ef
a4490b9
43c574a
3bc85ed
72ab380
87b6ed3
1cf6149
e07b61d
2b74ac0
088bb2c
6e59f5e
61e55b6
a065720
d193241
b863896
8c1fc94
f7e91c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ var DEFAULTS = { | |
| 'for': 'htmlFor' | ||
| }, | ||
| classIdPrefix: false, | ||
| uniqueIdPrefix: true, | ||
| raw: false, | ||
| xmlnsTest: /^xmlns(Xlink)?$/ | ||
| }; | ||
|
|
@@ -38,6 +39,13 @@ module.exports = function (opts) { | |
| })); | ||
| } | ||
|
|
||
| if (options.uniqueIdPrefix) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This option sounds and feels like it should default to any reasons for not making it default to being on?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I would agree with that. Super busy today, but if I can carve out ten minutes I can update that. |
||
| filters. | ||
| push(require('./sanitize/filters/unique-svg-ids')({ | ||
| prefix: options.uniqueIdPrefix | ||
| })); | ||
| } | ||
|
|
||
| if (options.root) { | ||
| filters. | ||
| push(require('./sanitize/filters/custom-root')(options.root)); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| var R = require('ramda'); | ||
| var css = require('css'); | ||
|
|
||
| // This should be changed, because this isn't going to help | ||
| // anyone in the case of a failure to get filename for prefix. | ||
| var DEFAULTS = { | ||
| prefix: 'filename-prefix__', | ||
| }; | ||
|
|
||
| module.exports = function configureUniquePrefixId (opts) { | ||
| var options = R.merge(DEFAULTS, opts || {}); | ||
| var cache = options.cache = {}; | ||
| var selectorRegex = /(url\(#)((\w|-)*)(\))/gmi; | ||
|
|
||
| // Find the ID reference in items such as: "url(#a)" and return "a" | ||
| _getMatches = (field, val) => { | ||
| var str = val.toString(); | ||
| var matches = selectorRegex.exec(str); | ||
| selectorRegex.lastIndex = 0; | ||
| // clean up and get rid of the quotes | ||
| return opts.prefix + matches[2].replace('\"', ""); | ||
| } | ||
|
|
||
| return function createUniquePrefixId (value) { | ||
| // Find all the xlink:href props with local references and update | ||
| if (value.xlinkHref && value.xlinkHref.toString().startsWith("#")){ | ||
| var newValue = "#" + opts.prefix + value.xlinkHref.toString().replace("#", ""); | ||
| value.xlinkHref = newValue; | ||
| this.update(value); | ||
| } | ||
|
|
||
| // Find all href props and update | ||
| if (value.href && value.href.toString().startsWith("#")){ | ||
| var newValue = "#" + opts.prefix + value.href.toString().replace("#", ""); | ||
| value.href = newValue; | ||
| this.update(value); | ||
| } | ||
|
|
||
| // Find all IDs and update with filename prefix | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couple of thoughts:
By abstracting the update away into separate functions, and operating over a list of definitions, it becomes trivial to handle new cases (just add them to the list). Bonus Points: Allow for end users to configure their loader with additional tests (exporting the utility operator functions too).
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for looking this over and providing the feedback. I will endeavor to get back around to this and resolve as soon as I am able.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally agree about abstracting items into separate functions. As the old saying goes, "I would have written a shorter letter if I'd had more time". I was able to carve out a few minutes to add a couple of tests and continue the not-abstract, brute force methods for some expanded cases. Perhaps I will have more time in the future to revisit, refactor, and make sure that all cases of ID reference are handled There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi! loader convert only There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @SilverFox70 created pull request to resolve this issue SilverFox70#5 |
||
| if (value.id){ | ||
| var newValue = opts.prefix + value.id; | ||
| value.id = newValue; | ||
| this.update(value); | ||
| } | ||
|
|
||
| // Find all fill props and update with filename prefix | ||
| if (value.fill && value.fill.toString().startsWith("url")){ | ||
| var newValue = "url(#" + _getMatches('fill', value.fill) + ")"; | ||
| value.fill = newValue; | ||
| this.update(value); | ||
| } | ||
|
|
||
| // Find all mask props and update with filename prefix | ||
| if (value.mask && value.mask.toString().startsWith("url")){ | ||
| var newValue = _getMatches('mask', value.mask); | ||
| var newValue = "url(#" + _getMatches('fill', value.mask) + ")"; | ||
| value.mask = newValue; | ||
| this.update(value); | ||
| } | ||
|
|
||
| // Find all clipPath props and update with filename prefix | ||
| if (value.clipPath && value.clipPath.toString().startsWith("url")){ | ||
| var newValue = "url(#" + _getMatches('clipPath', value.clipPath) + ")"; | ||
| value.clipPath = newValue; | ||
| this.update(value); | ||
| } | ||
| }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| /*globals describe, it*/ | ||
| require('should'); | ||
|
|
||
| describe('svg-react-loader/lib/sanitize/filters/unique-svg-ids', () => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe you can also include a simple test in
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 |
||
| const traverse = require('traverse'); | ||
| const prefixFilename = | ||
| require('../../../../lib/sanitize/filters/unique-svg-ids')({prefix: 'svgFilename__'}); | ||
|
|
||
| it('should work on a simple tree', () => { | ||
| const tree = { | ||
| id: 'a', | ||
| fill: 'url(#a)', | ||
| mask: 'url(#a)', | ||
| xlinkHref: '#a' | ||
| }; | ||
|
|
||
| var result = traverse.map(tree, prefixFilename); | ||
|
|
||
| result. | ||
| should. | ||
| eql({ | ||
| id: 'svgFilename__a', | ||
| fill: 'url(#svgFilename__a)', | ||
| mask: 'url(#svgFilename__a)', | ||
| xlinkHref: '#svgFilename__a' | ||
| }); | ||
| }); | ||
|
|
||
| it('should work on a more complex tree', () => { | ||
| const tree = { | ||
| id: 'a', | ||
| fill: 'url(#a)', | ||
| mask: 'url(#a)', | ||
| xlinkHref: '#a', | ||
| props: { | ||
| id: 'b', | ||
| fill: 'url(#b)', | ||
| mask: 'url(#b)', | ||
| xlinkHref: '#b' | ||
| } | ||
| }; | ||
|
|
||
| const result = traverse.map(tree, prefixFilename); | ||
|
|
||
| result. | ||
| should. | ||
| eql({ | ||
| id: 'svgFilename__a', | ||
| fill: 'url(#svgFilename__a)', | ||
| mask: 'url(#svgFilename__a)', | ||
| xlinkHref: '#svgFilename__a', | ||
| props: { | ||
| id: 'svgFilename__b', | ||
| fill: 'url(#svgFilename__b)', | ||
| mask: 'url(#svgFilename__b)', | ||
| xlinkHref: '#svgFilename__b' | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| it('should not update values for fill, mask, or xlink:href if they are not references to IDs', () => { | ||
| const tree = { | ||
| id: 'c', | ||
| fill: '#fafafa', | ||
| mask: '#ae4d19', | ||
| xlinkHref: 'http://www.w3.org/1999/xlink' | ||
| }; | ||
|
|
||
| const result = traverse.map(tree, prefixFilename); | ||
|
|
||
| result. | ||
| should. | ||
| eql({ | ||
| id: 'svgFilename__c', | ||
| fill: '#fafafa', | ||
| mask: '#ae4d19', | ||
| xlinkHref: 'http://www.w3.org/1999/xlink' | ||
| }); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to use the value, after interpolation, of
classIdPrefixas a default?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(interpolation will be fixed as soon as #107 is merged, btw)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if that would work as intended.
Using the same prefix would across all instances and files would collide too, no?
It might be more relevant to allow tokenizing within a default string, a la:
[name]-[hash]-