| 'use strict'; |
| |
| var SAX = require('sax'), |
| JSAPI = require('./jsAPI.js'), |
| CSSClassList = require('./css-class-list'), |
| CSSStyleDeclaration = require('./css-style-declaration'), |
| entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^\']+)'|"([^\"]+)")\s*>/g; |
| |
| var config = { |
| strict: true, |
| trim: false, |
| normalize: true, |
| lowercase: true, |
| xmlns: true, |
| position: true |
| }; |
| |
| /** |
| * Convert SVG (XML) string to SVG-as-JS object. |
| * |
| * @param {String} data input data |
| * @param {Function} callback |
| */ |
| module.exports = function(data, callback) { |
| |
| 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; |
| |
| } |
| |
| sax.ondoctype = function(doctype) { |
| |
| pushToContent({ |
| doctype: doctype |
| }); |
| |
| var subsetStart = doctype.indexOf('['), |
| entityMatch; |
| |
| if (subsetStart >= 0) { |
| entityDeclaration.lastIndex = subsetStart; |
| |
| while ((entityMatch = entityDeclaration.exec(data)) != null) { |
| sax.ENTITIES[entityMatch[1]] = entityMatch[2] || entityMatch[3]; |
| } |
| } |
| }; |
| |
| 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(); |
| } |
| |
| elem.attrs[name] = { |
| name: name, |
| value: data.attributes[name].value, |
| prefix: data.attributes[name].prefix, |
| local: data.attributes[name].local |
| }; |
| } |
| } |
| |
| elem = pushToContent(elem); |
| current = elem; |
| |
| // Save info about <text> 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 <text> 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; |
| |
| var start = elem.content[0], |
| end = elem.content[elem.content.length - 1]; |
| |
| while (start && start.content && !start.text) start = start.content[0]; |
| if (start && start.text) start.text = start.text.replace(/^\s+/, ''); |
| |
| while (end && end.content && !end.text) end = end.content[end.content.length - 1]; |
| if (end && end.text) end.text = end.text.replace(/\s+$/, ''); |
| |
| return elem; |
| |
| } |
| |
| }; |