diff --git a/lib/svgo/svg2js.js b/lib/svgo/svg2js.js index 6bba13e58..8245a8aa0 100644 --- a/lib/svgo/svg2js.js +++ b/lib/svgo/svg2js.js @@ -1,19 +1,13 @@ 'use strict'; -var SAX = require('sax'), +const { property } = require('css-tree'); + +var txml = require('txml'), JSAPI = require('./jsAPI.js'), CSSClassList = require('./css-class-list'), CSSStyleDeclaration = require('./css-style-declaration'), entityDeclaration = //g; -var config = { - strict: true, - trim: false, - normalize: true, - lowercase: true, - xmlns: true, - position: true -}; /** * Convert SVG (XML) string to SVG-as-JS object. @@ -22,179 +16,112 @@ var config = { * @param {Function} callback */ module.exports = function(data, callback) { + try { + var DOM = txml(data, {noChildNodes:['?xml','!DOCTYPE', '!ENTITY'], keepComments: true }); - var sax = SAX.parser(config.strict, config), - root = new JSAPI({ elem: '#document', content: [] }), - current = root, - stack = [root], - textContext = null, - parsingError = false; - - function pushToContent(content) { - - content = new JSAPI(content, current); - - (current.content = current.content || []).push(content); - - return content; - + const root = dom2js(DOM); + + callback(root); + } catch(err) { + callback({error: 'Error in parsing SVG: ' + err.message }); } - sax.ondoctype = function(doctype) { - - pushToContent({ - doctype: doctype - }); - - var subsetStart = doctype.indexOf('['), - entityMatch; +}; - if (subsetStart >= 0) { - entityDeclaration.lastIndex = subsetStart; +/** + * + * @param {(txml.INode | string)[]} dom + */ +function dom2js(dom, root, ENTITIES){ + if(!root) root = new JSAPI({ elem: '#document', content: [] }); + if(!ENTITIES) ENTITIES={}; + if(!Array.isArray(dom)) return dom + dom.forEach(n => { + if (typeof n === 'object') { + let child; + if(n.tagName === '?xml'){ + child = pushToContent({ processinginstruction: { + name: 'xml', + body: Object.keys(n.attributes).map(name=>name+'='+JSON.stringify(n.attributes[name])).join(' '), + } }, root) + } else { + var elem = { + name: n.tagName, + elem: n.tagName, + prefix: '', + local: n.tagName, + attrs: n.attributes, + content: [] + }; - while ((entityMatch = entityDeclaration.exec(data)) != null) { - sax.ENTITIES[entityMatch[1]] = entityMatch[2] || entityMatch[3]; + Object.keys(elem.attrs).forEach(name=>{ + if(name.startsWith('xmlns:')){ + + elem.attrs[name] = ENTITIES[elem.attrs[name].substr(1,elem.attrs[name].length-2)] || elem.attrs[name]; + } + //xmlns:x="&ns_extend;" + elem.attrs[name] = { + name, + value: elem.attrs[name], + prefix: '', + local: name + }; + }); + + elem.class = new CSSClassList(elem); + elem.style = new CSSStyleDeclaration(elem); + + child = pushToContent(elem, root); } - } - }; - - sax.onprocessinginstruction = function(data) { - - pushToContent({ - processinginstruction: data - }); - - }; - - sax.oncomment = function(comment) { - - pushToContent({ - comment: comment.trim() - }); - - }; - - sax.oncdata = function(cdata) { - - pushToContent({ - cdata: cdata - }); - - }; - - sax.onopentag = function(data) { - - var elem = { - elem: data.name, - prefix: data.prefix, - local: data.local, - attrs: {} - }; - - elem.class = new CSSClassList(elem); - elem.style = new CSSStyleDeclaration(elem); - - if (Object.keys(data.attributes).length) { - for (var name in data.attributes) { - - if (name === 'class') { // has class attribute - elem.class.hasClass(); - } - - if (name === 'style') { // has style attribute - elem.style.hasStyle(); + + if (n.children) { + dom2js(n.children, child, ENTITIES) + } + } else if (typeof n === 'string') { + if(n.startsWith('')){ + pushToContent({ + comment: n.substring(4,n.length-4).trim() + }, root); + } else if(n.startsWith('!DOCTYPE')) { + const data = n.substr(8); + pushToContent({ + doctype: data + }, root); + + var subsetStart = data.indexOf('['); + + if (subsetStart >= 0) { + entityDeclaration.lastIndex = subsetStart; + //console.log(data) + var entryLines = data.split('el.split('>')[0]) + .map(el=>el.split(' ')); + + entryLines.forEach(([name, rest]) => { + ENTITIES[name] = JSON.parse(rest) + }); + //console.log({ENTITIES}); } - - elem.attrs[name] = { - name: name, - value: data.attributes[name].value, - prefix: data.attributes[name].prefix, - local: data.attributes[name].local - }; + } else { + pushToContent(n, root); } + }else{ + pushToContent(n, root); } + }); - elem = pushToContent(elem); - current = elem; - - // Save info about tag to prevent trimming of meaningful whitespace - if (data.name == 'text' && !data.prefix) { - textContext = current; - } - - stack.push(elem); - - }; - - sax.ontext = function(text) { - - if (/\S/.test(text) || textContext) { - - if (!textContext) - text = text.trim(); - - pushToContent({ - text: text - }); - - } - - }; - - sax.onclosetag = function() { - - var last = stack.pop(); - - // Trim text inside tag. - if (last == textContext) { - trim(textContext); - textContext = null; - } - current = stack[stack.length - 1]; - - }; - - sax.onerror = function(e) { - - e.message = 'Error in parsing SVG: ' + e.message; - if (e.message.indexOf('Unexpected end') < 0) { - throw e; - } - - }; - - sax.onend = function() { - - if (!this.error) { - callback(root); - } else { - callback({ error: this.error.message }); - } - - }; - - try { - sax.write(data); - } catch (e) { - callback({ error: e.message }); - parsingError = true; - } - if (!parsingError) sax.close(); - - function trim(elem) { - if (!elem.content) return elem; + return root; +} - var start = elem.content[0], - end = elem.content[elem.content.length - 1]; +function pushToContent(content, parent) { - while (start && start.content && !start.text) start = start.content[0]; - if (start && start.text) start.text = start.text.replace(/^\s+/, ''); + const jsapi = new JSAPI(content, parent); - while (end && end.content && !end.text) end = end.content[end.content.length - 1]; - if (end && end.text) end.text = end.text.replace(/\s+$/, ''); + (parent.content = parent.content || []).push(jsapi); - return elem; + return jsapi; - } +} -}; diff --git a/package-lock.json b/package-lock.json index b83f54d95..a725c989b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1403,8 +1403,7 @@ "safe-buffer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", - "dev": true + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" }, "safer-buffer": { "version": "2.1.2", @@ -1561,6 +1560,40 @@ "has-flag": "^1.0.0" } }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, "tough-cookie": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", @@ -1594,6 +1627,14 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, + "txml": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/txml/-/txml-3.2.5.tgz", + "integrity": "sha512-AtN8AgJLiDanttIXJaQlxH8/R0NOCNwto8kcO7BaxdLgsN9b7itM9lnTD7c2O3TadP+hHB9j7ra5XGFRPNnk/g==", + "requires": { + "through2": "^3.0.1" + } + }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", @@ -1650,6 +1691,11 @@ "punycode": "^2.1.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, "util.promisify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", diff --git a/package.json b/package.json index 4f4f18267..83edab4b6 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "js-yaml": "^3.13.1", "mkdirp": "~0.5.1", "object.values": "^1.1.0", - "sax": "~1.2.4", "stable": "^0.1.8", + "txml": "^3.2.5", "unquote": "~1.1.1", "util.promisify": "~1.0.0" },