@svgedit/svgcanvas 7.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/blur-event.js +156 -0
- package/clear.js +43 -0
- package/coords.js +298 -0
- package/copy-elem.js +45 -0
- package/dataStorage.js +28 -0
- package/dist/svgcanvas.js +515 -0
- package/dist/svgcanvas.js.map +1 -0
- package/draw.js +1064 -0
- package/elem-get-set.js +1077 -0
- package/event.js +1388 -0
- package/history.js +619 -0
- package/historyrecording.js +161 -0
- package/json.js +110 -0
- package/layer.js +228 -0
- package/math.js +221 -0
- package/namespaces.js +40 -0
- package/package.json +54 -0
- package/paint.js +88 -0
- package/paste-elem.js +127 -0
- package/path-actions.js +1237 -0
- package/path-method.js +1012 -0
- package/path.js +781 -0
- package/recalculate.js +794 -0
- package/rollup.config.js +40 -0
- package/sanitize.js +252 -0
- package/select.js +543 -0
- package/selected-elem.js +1297 -0
- package/selection.js +482 -0
- package/svg-exec.js +1289 -0
- package/svgcanvas.js +1347 -0
- package/svgroot.js +36 -0
- package/text-actions.js +530 -0
- package/touch.js +51 -0
- package/undo.js +279 -0
- package/utilities.js +1214 -0
package/rollup.config.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/* eslint-env node */
|
|
2
|
+
// This rollup script is run by the command:
|
|
3
|
+
// 'npm run build'
|
|
4
|
+
|
|
5
|
+
import rimraf from 'rimraf'
|
|
6
|
+
import babel from '@rollup/plugin-babel'
|
|
7
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
|
8
|
+
import commonjs from '@rollup/plugin-commonjs'
|
|
9
|
+
import { terser } from 'rollup-plugin-terser'
|
|
10
|
+
// import progress from 'rollup-plugin-progress';
|
|
11
|
+
import filesize from 'rollup-plugin-filesize'
|
|
12
|
+
|
|
13
|
+
const dest = ['dist/svgcanvas']
|
|
14
|
+
|
|
15
|
+
// remove existing distribution
|
|
16
|
+
rimraf('./dist', () => console.info('recreating dist'))
|
|
17
|
+
|
|
18
|
+
// config for svgedit core module
|
|
19
|
+
const config = [{
|
|
20
|
+
input: ['./svgcanvas.js'],
|
|
21
|
+
output: [
|
|
22
|
+
{
|
|
23
|
+
format: 'es',
|
|
24
|
+
inlineDynamicImports: true,
|
|
25
|
+
sourcemap: true,
|
|
26
|
+
file: 'dist/svgcanvas.js'
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
plugins: [
|
|
30
|
+
nodeResolve({
|
|
31
|
+
browser: true,
|
|
32
|
+
preferBuiltins: false
|
|
33
|
+
}),
|
|
34
|
+
commonjs(),
|
|
35
|
+
babel({ babelHelpers: 'bundled', exclude: [/\/core-js\//] }), // exclude core-js to avoid circular dependencies.
|
|
36
|
+
terser({ keep_fnames: true }), // keep_fnames is needed to avoid an error when calling extensions.
|
|
37
|
+
filesize()
|
|
38
|
+
]
|
|
39
|
+
}]
|
|
40
|
+
export default config
|
package/sanitize.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools for SVG sanitization.
|
|
3
|
+
* @module sanitize
|
|
4
|
+
* @license MIT
|
|
5
|
+
*
|
|
6
|
+
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { getReverseNS, NS } from './namespaces.js'
|
|
10
|
+
import { getHref, setHref, getUrlFromAttr } from './utilities.js'
|
|
11
|
+
|
|
12
|
+
const REVERSE_NS = getReverseNS()
|
|
13
|
+
|
|
14
|
+
// Todo: Split out into core attributes, presentation attributes, etc. so consistent
|
|
15
|
+
/**
|
|
16
|
+
* This defines which elements and attributes that we support (or at least
|
|
17
|
+
* don't remove).
|
|
18
|
+
* @type {PlainObject}
|
|
19
|
+
*/
|
|
20
|
+
/* eslint-disable max-len */
|
|
21
|
+
const svgGenericWhiteList = ['class', 'id', 'display', 'transform', 'style']
|
|
22
|
+
const svgWhiteList_ = {
|
|
23
|
+
// SVG Elements
|
|
24
|
+
a: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'xlink:href', 'xlink:title'],
|
|
25
|
+
circle: ['clip-path', 'clip-rule', 'cx', 'cy', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'r', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage'],
|
|
26
|
+
clipPath: ['clipPathUnits'],
|
|
27
|
+
defs: [],
|
|
28
|
+
desc: [],
|
|
29
|
+
ellipse: ['clip-path', 'clip-rule', 'cx', 'cy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage'],
|
|
30
|
+
feBlend: ['in', 'in2'],
|
|
31
|
+
feColorMatrix: ['in', 'type', 'value', 'result', 'values'],
|
|
32
|
+
feComposite: ['in', 'operator', 'result', 'in2'],
|
|
33
|
+
feFlood: ['flood-color', 'in', 'result', 'flood-opacity'],
|
|
34
|
+
feGaussianBlur: ['color-interpolation-filters', 'in', 'requiredFeatures', 'stdDeviation', 'result'],
|
|
35
|
+
feMerge: [],
|
|
36
|
+
feMergeNode: ['in'],
|
|
37
|
+
feMorphology: ['in', 'operator', 'radius'],
|
|
38
|
+
feOffset: ['dx', 'in', 'dy', 'result'],
|
|
39
|
+
filter: ['color-interpolation-filters', 'filterRes', 'filterUnits', 'height', 'primitiveUnits', 'requiredFeatures', 'width', 'x', 'xlink:href', 'y'],
|
|
40
|
+
foreignObject: ['font-size', 'height', 'opacity', 'requiredFeatures', 'width', 'x', 'y'],
|
|
41
|
+
g: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'font-family', 'font-size', 'font-style', 'font-weight', 'text-anchor'],
|
|
42
|
+
image: ['clip-path', 'clip-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'systemLanguage', 'width', 'x', 'xlink:href', 'xlink:title', 'y'],
|
|
43
|
+
line: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'x1', 'x2', 'y1', 'y2'],
|
|
44
|
+
linearGradient: ['gradientTransform', 'gradientUnits', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'x1', 'x2', 'xlink:href', 'y1', 'y2'],
|
|
45
|
+
marker: ['markerHeight', 'markerUnits', 'markerWidth', 'orient', 'preserveAspectRatio', 'refX', 'refY', 'se_type', 'systemLanguage', 'viewBox'],
|
|
46
|
+
mask: ['height', 'maskContentUnits', 'maskUnits', 'width', 'x', 'y'],
|
|
47
|
+
metadata: [],
|
|
48
|
+
path: ['clip-path', 'clip-rule', 'd', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage'],
|
|
49
|
+
pattern: ['height', 'patternContentUnits', 'patternTransform', 'patternUnits', 'requiredFeatures', 'systemLanguage', 'viewBox', 'width', 'x', 'xlink:href', 'y'],
|
|
50
|
+
polygon: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'sides', 'shape', 'edge', 'point', 'starRadiusMultiplier', 'r', 'radialshift', 'r2', 'orient', 'cx', 'cy'],
|
|
51
|
+
polyline: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'points', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'se:connector'],
|
|
52
|
+
radialGradient: ['cx', 'cy', 'fx', 'fy', 'gradientTransform', 'gradientUnits', 'r', 'requiredFeatures', 'spreadMethod', 'systemLanguage', 'xlink:href'],
|
|
53
|
+
rect: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'mask', 'opacity', 'requiredFeatures', 'rx', 'ry', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'width', 'x', 'y'],
|
|
54
|
+
stop: ['offset', 'requiredFeatures', 'stop-opacity', 'systemLanguage', 'stop-color', 'gradientUnits', 'gradientTransform'],
|
|
55
|
+
style: ['type'],
|
|
56
|
+
svg: ['clip-path', 'clip-rule', 'enable-background', 'filter', 'height', 'mask', 'preserveAspectRatio', 'requiredFeatures', 'systemLanguage', 'version', 'viewBox', 'width', 'x', 'xmlns', 'xmlns:se', 'xmlns:xlink', 'xmlns:oi', 'oi:animations', 'y', 'stroke-linejoin', 'fill-rule', 'aria-label', 'stroke-width', 'fill-rule', 'xml:space'],
|
|
57
|
+
switch: ['requiredFeatures', 'systemLanguage'],
|
|
58
|
+
symbol: ['fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'opacity', 'overflow', 'preserveAspectRatio', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'viewBox', 'width', 'height'],
|
|
59
|
+
text: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'mask', 'opacity', 'requiredFeatures', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'text-anchor', 'letter-spacing', 'word-spacing', 'text-decoration', 'textLength', 'lengthAdjust', 'x', 'xml:space', 'y'],
|
|
60
|
+
textPath: ['method', 'requiredFeatures', 'spacing', 'startOffset', 'systemLanguage', 'xlink:href'],
|
|
61
|
+
title: [],
|
|
62
|
+
tspan: ['clip-path', 'clip-rule', 'dx', 'dy', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'font-family', 'font-size', 'font-style', 'font-weight', 'mask', 'opacity', 'requiredFeatures', 'rotate', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'systemLanguage', 'text-anchor', 'textLength', 'x', 'xml:space', 'y'],
|
|
63
|
+
use: ['clip-path', 'clip-rule', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'height', 'mask', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'width', 'x', 'xlink:href', 'y', 'overflow'],
|
|
64
|
+
|
|
65
|
+
// MathML Elements
|
|
66
|
+
annotation: ['encoding'],
|
|
67
|
+
'annotation-xml': ['encoding'],
|
|
68
|
+
maction: ['actiontype', 'other', 'selection'],
|
|
69
|
+
math: ['xmlns'],
|
|
70
|
+
menclose: ['notation'],
|
|
71
|
+
merror: [],
|
|
72
|
+
mfrac: ['linethickness'],
|
|
73
|
+
mi: ['mathvariant'],
|
|
74
|
+
mmultiscripts: [],
|
|
75
|
+
mn: [],
|
|
76
|
+
mo: ['fence', 'lspace', 'maxsize', 'minsize', 'rspace', 'stretchy'],
|
|
77
|
+
mover: [],
|
|
78
|
+
mpadded: ['lspace', 'width', 'height', 'depth', 'voffset'],
|
|
79
|
+
mphantom: [],
|
|
80
|
+
mprescripts: [],
|
|
81
|
+
mroot: [],
|
|
82
|
+
mrow: ['xlink:href', 'xlink:type', 'xmlns:xlink'],
|
|
83
|
+
mspace: ['depth', 'height', 'width'],
|
|
84
|
+
msqrt: [],
|
|
85
|
+
mstyle: ['displaystyle', 'mathbackground', 'mathcolor', 'mathvariant', 'scriptlevel'],
|
|
86
|
+
msub: [],
|
|
87
|
+
msubsup: [],
|
|
88
|
+
msup: [],
|
|
89
|
+
mtable: ['align', 'columnalign', 'columnlines', 'columnspacing', 'displaystyle', 'equalcolumns', 'equalrows', 'frame', 'rowalign', 'rowlines', 'rowspacing', 'width'],
|
|
90
|
+
mtd: ['columnalign', 'columnspan', 'rowalign', 'rowspan'],
|
|
91
|
+
mtext: [],
|
|
92
|
+
mtr: ['columnalign', 'rowalign'],
|
|
93
|
+
munder: [],
|
|
94
|
+
munderover: [],
|
|
95
|
+
none: [],
|
|
96
|
+
semantics: []
|
|
97
|
+
}
|
|
98
|
+
/* eslint-enable max-len */
|
|
99
|
+
|
|
100
|
+
// add generic attributes to all elements of the whitelist
|
|
101
|
+
Object.keys(svgWhiteList_).forEach((element) => { svgWhiteList_[element] = [...svgWhiteList_[element], ...svgGenericWhiteList] })
|
|
102
|
+
|
|
103
|
+
// Produce a Namespace-aware version of svgWhitelist
|
|
104
|
+
const svgWhiteListNS_ = {}
|
|
105
|
+
Object.entries(svgWhiteList_).forEach(([elt, atts]) => {
|
|
106
|
+
const attNS = {}
|
|
107
|
+
Object.entries(atts).forEach(([_i, att]) => {
|
|
108
|
+
if (att.includes(':')) {
|
|
109
|
+
const v = att.split(':')
|
|
110
|
+
attNS[v[1]] = NS[(v[0]).toUpperCase()]
|
|
111
|
+
} else {
|
|
112
|
+
attNS[att] = att === 'xmlns' ? NS.XMLNS : null
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
svgWhiteListNS_[elt] = attNS
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sanitizes the input node and its children.
|
|
120
|
+
* It only keeps what is allowed from our whitelist defined above.
|
|
121
|
+
* @function module:sanitize.sanitizeSvg
|
|
122
|
+
* @param {Text|Element} node - The DOM element to be checked (we'll also check its children) or text node to be cleaned up
|
|
123
|
+
* @returns {void}
|
|
124
|
+
*/
|
|
125
|
+
export const sanitizeSvg = (node) => {
|
|
126
|
+
// Cleanup text nodes
|
|
127
|
+
if (node.nodeType === 3) { // 3 === TEXT_NODE
|
|
128
|
+
// Trim whitespace
|
|
129
|
+
node.nodeValue = node.nodeValue.trim()
|
|
130
|
+
// Remove if empty
|
|
131
|
+
if (!node.nodeValue.length) {
|
|
132
|
+
node.remove()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// We only care about element nodes.
|
|
137
|
+
// Automatically return for all non-element nodes, such as comments, etc.
|
|
138
|
+
if (node.nodeType !== 1) { // 1 == ELEMENT_NODE
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const doc = node.ownerDocument
|
|
143
|
+
const parent = node.parentNode
|
|
144
|
+
// can parent ever be null here? I think the root node's parent is the document...
|
|
145
|
+
if (!doc || !parent) {
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const allowedAttrs = svgWhiteList_[node.nodeName]
|
|
150
|
+
const allowedAttrsNS = svgWhiteListNS_[node.nodeName]
|
|
151
|
+
// if this element is supported, sanitize it
|
|
152
|
+
if (typeof allowedAttrs !== 'undefined') {
|
|
153
|
+
const seAttrs = []
|
|
154
|
+
let i = node.attributes.length
|
|
155
|
+
while (i--) {
|
|
156
|
+
// if the attribute is not in our whitelist, then remove it
|
|
157
|
+
const attr = node.attributes.item(i)
|
|
158
|
+
const attrName = attr.nodeName
|
|
159
|
+
const attrLocalName = attr.localName
|
|
160
|
+
const attrNsURI = attr.namespaceURI
|
|
161
|
+
// Check that an attribute with the correct localName in the correct namespace is on
|
|
162
|
+
// our whitelist or is a namespace declaration for one of our allowed namespaces
|
|
163
|
+
if (attrNsURI !== allowedAttrsNS[attrLocalName] && attrNsURI !== NS.XMLNS &&
|
|
164
|
+
!(attrNsURI === NS.XMLNS && REVERSE_NS[attr.value])) {
|
|
165
|
+
// Bypassing the whitelist to allow se: and oi: prefixes
|
|
166
|
+
// We can add specific namepaces on demand for now.
|
|
167
|
+
// Is there a more appropriate way to do this?
|
|
168
|
+
if (attrName.startsWith('se:') || attrName.startsWith('oi:') || attrName.startsWith('data-')) {
|
|
169
|
+
// We should bypass the namespace aswell
|
|
170
|
+
const seAttrNS = (attrName.startsWith('se:')) ? NS.SE : ((attrName.startsWith('oi:')) ? NS.OI : null)
|
|
171
|
+
seAttrs.push([attrName, attr.value, seAttrNS])
|
|
172
|
+
} else {
|
|
173
|
+
console.warn(`sanitizeSvg: attribute ${attrName} in element ${node.nodeName} not in whitelist is removed`)
|
|
174
|
+
node.removeAttributeNS(attrNsURI, attrLocalName)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// For the style attribute, rewrite it in terms of XML presentational attributes
|
|
179
|
+
if (attrName === 'style') {
|
|
180
|
+
const props = attr.value.split(';')
|
|
181
|
+
let p = props.length
|
|
182
|
+
while (p--) {
|
|
183
|
+
const [name, val] = props[p].split(':')
|
|
184
|
+
const styleAttrName = (name || '').trim()
|
|
185
|
+
const styleAttrVal = (val || '').trim()
|
|
186
|
+
// Now check that this attribute is supported
|
|
187
|
+
if (allowedAttrs.includes(styleAttrName)) {
|
|
188
|
+
node.setAttribute(styleAttrName, styleAttrVal)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
node.removeAttribute('style')
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
Object.values(seAttrs).forEach(([att, val, ns]) => {
|
|
196
|
+
node.setAttributeNS(ns, att, val)
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
// for some elements that have a xlink:href, ensure the URI refers to a local element
|
|
200
|
+
// (but not for links)
|
|
201
|
+
const href = getHref(node)
|
|
202
|
+
if (href &&
|
|
203
|
+
['filter', 'linearGradient', 'pattern',
|
|
204
|
+
'radialGradient', 'textPath', 'use'].includes(node.nodeName) && href[0] !== '#') {
|
|
205
|
+
// remove the attribute (but keep the element)
|
|
206
|
+
setHref(node, '')
|
|
207
|
+
console.warn(`sanitizeSvg: attribute href in element ${node.nodeName} pointing to a non-local reference (${href}) is removed`)
|
|
208
|
+
node.removeAttributeNS(NS.XLINK, 'href')
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Safari crashes on a <use> without a xlink:href, so we just remove the node here
|
|
212
|
+
if (node.nodeName === 'use' && !getHref(node)) {
|
|
213
|
+
console.warn(`sanitizeSvg: element ${node.nodeName} without a xlink:href is removed`)
|
|
214
|
+
node.remove()
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
// if the element has attributes pointing to a non-local reference,
|
|
218
|
+
// need to remove the attribute
|
|
219
|
+
Object.values(['clip-path', 'fill', 'filter', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'stroke'], (attr) => {
|
|
220
|
+
let val = node.getAttribute(attr)
|
|
221
|
+
if (val) {
|
|
222
|
+
val = getUrlFromAttr(val)
|
|
223
|
+
// simply check for first character being a '#'
|
|
224
|
+
if (val && val[0] !== '#') {
|
|
225
|
+
node.setAttribute(attr, '')
|
|
226
|
+
console.warn(`sanitizeSvg: attribute ${attr} in element ${node.nodeName} pointing to a non-local reference (${val}) is removed`)
|
|
227
|
+
node.removeAttribute(attr)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
// recurse to children
|
|
233
|
+
i = node.childNodes.length
|
|
234
|
+
while (i--) { sanitizeSvg(node.childNodes.item(i)) }
|
|
235
|
+
// else (element not supported), remove it
|
|
236
|
+
} else {
|
|
237
|
+
// remove all children from this node and insert them before this node
|
|
238
|
+
// TODO: in the case of animation elements this will hardly ever be correct
|
|
239
|
+
console.warn(`sanitizeSvg: element ${node.nodeName} not supported is removed`)
|
|
240
|
+
const children = []
|
|
241
|
+
while (node.hasChildNodes()) {
|
|
242
|
+
children.push(parent.insertBefore(node.firstChild, node))
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// remove this node from the document altogether
|
|
246
|
+
node.remove()
|
|
247
|
+
|
|
248
|
+
// call sanitizeSvg on each of those children
|
|
249
|
+
let i = children.length
|
|
250
|
+
while (i--) { sanitizeSvg(children[i]) }
|
|
251
|
+
}
|
|
252
|
+
}
|