Skip to content

Commit

Permalink
exchange sax to txml
Browse files Browse the repository at this point in the history
  • Loading branch information
TobiasNickel committed Sep 13, 2020
1 parent 07ca976 commit 5cefca6
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 172 deletions.
265 changes: 96 additions & 169 deletions lib/svgo/svg2js.js
Original file line number Diff line number Diff line change
@@ -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 = /<!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.
Expand All @@ -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('<!--') && n.endsWith('-->')){
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('<!ENTITY ');
entryLines.shift();
entryLines = entryLines
.map(el=>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 <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;
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;

}
}

};
50 changes: 48 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down

0 comments on commit 5cefca6

Please sign in to comment.