blob: 6bba13e58866b9117d9b0ed7ef7adad41bd733c7 [file] [log] [blame]
'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;
}
};