UNPKG

vsvg-parser

Version:

A minimal implementation of a svg parser used in vsvg

229 lines (185 loc) 6.87 kB
'use strict'; var startTag = /^<([-A-Za-z0-9_:]+)(.*?)(\/?)>/g, // match opening tag endTag = /<\/([-A-Za-z0-9_:]+)[^>]*>/, // this just matches the first one docTag = /^<(\?)(.*?)(\/?)>/g, // this is like a doc tag of a proper svg, commentTag = /^<(\!--)(.*?)(\/?)>/g, attr = /([-A-Za-z0-9_:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // match tag attributes exports.parse = parse; function makeArray(arr) { return Array.prototype.slice.call(arr, 0); } /* getAttributes - turns an array of attributes into a key value object params attributes { Array } - array of strings eg. ['x="5"'] returns attributes { Object } - object of key values eg. { x: '5' } */ var getAttributes = exports.getAttributes = function getAttributes(attributes) { var _attributes = {}; function addToAttributes(keyvalue) { var arr = keyvalue.split(/=/), key = arr[0], value = arr[1] ? arr[1].slice(1).slice(0, -1) : ''; _attributes[key] = value; } attributes.forEach(addToAttributes); return _attributes; }; /* getTagIndex - given a tagName it will return the index of the last tag that matches the tagName params tagName { String } - the tagName eg, svg, text, line tags { Array } - array of tags, the tag object has a tagName variable that is matched against the tagName returns index { Number } - returns index of tag, or -1 if not in array */ var getTagIndex = exports.getTagIndex = function getTagIndex(tagName, tags) { for (var i = tags.length - 1; i >= 0; i -= 1) { if (tags[i].tagName === tagName) { return i; } } return -1; }; /* getLastOpenTag - gets the index of the last opened tag params tags { Array } - array of tags, the tag object has a closed variable that is test which indicates if the tag is closed. Array is ran through in reverse returns index { Number } - returns index of tag, or -1 if not in array */ var getLastOpenTag = exports.getLastOpenTag = function getLastOpenTag(tags) { for (var i = tags.length - 1; i >= 0; i -= 1) { if (!tags[i].closed) { return i; } } return -1; }; /* createTree - turns an array of elements and turns them into tree based off position array params tags { Array } - array of tags, the tag object consist of three main things, tagName, position, attributes returns attributes { Object } - object which is a nest object representation of the original svg */ var createTree = exports.createTree = function createTree(tags) { var _tags = []; function getArray(position, arr) { var _position = makeArray(position); if (_position.length === 1) { return arr; } var next = arr[_position[0]].children; _position.shift(); return getArray(_position, next); } function addTagToTree(tag) { var arr = getArray(tag.position, _tags); arr.push(tag); } tags.forEach(addTagToTree); return _tags; }; /* parse - will parse a xml string and turn it into an array of tags params xml { String } - a xml string eg. '<svg><line /></svg>' returns index { Array } - array of tags in a tree form same as the structure as the xml string eg. [{ tagName: 'svg', attributes: {}, position: [0] children: [{ tagName: 'line', attributes: {}, children: [], postion: [0, 0] }] }] */ function parse(xml) { xml = xml.replace(/(\r\n|\n|\r)/gm, ''); // remove all line breaks var tags = [], position = [-1], // initial position openTag, attributes, end, text, index, prevTag, prevLength, closed, tagName, tag, specialTag; while (xml) { // we carve away at the xml variable // this checks to see if the previous string length is same as // the current string length if (xml.length === prevLength) { throw new Error('Failed to parse SVG at chars: ' + xml.substring(0, 5)); } // set prevLength prevLength = xml.length; xml = xml.trim(); // there is some issues with open tag if this is not done openTag = xml.match(startTag); specialTag = xml.match(docTag) || xml.match(commentTag); if (openTag) { // if there is an open tag grab the attribute, and remove tag from xml string openTag = openTag[0]; attributes = openTag.match(attr); xml = xml.substring(openTag.length); // reseting some vars text = null; prevTag = null; closed = null; if (/\/>$/.test(openTag)) { // testing for self closing tags closed = true; } } else if (specialTag) { xml = xml.substring(specialTag[0].length); attributes = []; } else { end = xml.match(endTag); // see if there is an end tag attributes = []; if (end) { // if there is a end tag find the last tag with same name, set text, and remove data from xml string index = getTagIndex(end[1], tags); prevTag = tags[index]; text = xml.slice(0, end.index); xml = xml.substring(end.index + end[0].length); } } tagName = attributes.shift(); // tagName with be the first in array if (tagName || text) { // tagName or text will be set if it is somewhat of a good output tag = { tagName: tagName, attributes: getAttributes(attributes), // convert to object children: [], text: text, inside: getLastOpenTag(tags), // this is needed to get an accurate position closed: closed || !!text }; if (tag.inside > -1) { position.push(-1); // push this value it is sometime just cut off position[tags[tag.inside].position.length] += 1; position = position.slice(0, tags[tag.inside].position.length + 1); // eg. [0, 0, 1] this is a map of where this tag should be at } else { position[0] += 1; } tag.position = makeArray(position); tags.push(tag); // push the tag } if (prevTag) { prevTag.closed = true; // close the prevTag } } return createTree(tags); // convert flat array to tree }