282 lines
6.3 KiB
JavaScript
282 lines
6.3 KiB
JavaScript
|
function getLocator(source, options) {
|
|||
|
if (options === void 0) { options = {}; }
|
|||
|
var offsetLine = options.offsetLine || 0;
|
|||
|
var offsetColumn = options.offsetColumn || 0;
|
|||
|
var originalLines = source.split('\n');
|
|||
|
var start = 0;
|
|||
|
var lineRanges = originalLines.map(function (line, i) {
|
|||
|
var end = start + line.length + 1;
|
|||
|
var range = { start: start, end: end, line: i };
|
|||
|
start = end;
|
|||
|
return range;
|
|||
|
});
|
|||
|
var i = 0;
|
|||
|
function rangeContains(range, index) {
|
|||
|
return range.start <= index && index < range.end;
|
|||
|
}
|
|||
|
function getLocation(range, index) {
|
|||
|
return { line: offsetLine + range.line, column: offsetColumn + index - range.start, character: index };
|
|||
|
}
|
|||
|
function locate(search, startIndex) {
|
|||
|
if (typeof search === 'string') {
|
|||
|
search = source.indexOf(search, startIndex || 0);
|
|||
|
}
|
|||
|
var range = lineRanges[i];
|
|||
|
var d = search >= range.end ? 1 : -1;
|
|||
|
while (range) {
|
|||
|
if (rangeContains(range, search))
|
|||
|
return getLocation(range, search);
|
|||
|
i += d;
|
|||
|
range = lineRanges[i];
|
|||
|
}
|
|||
|
}
|
|||
|
return locate;
|
|||
|
}
|
|||
|
function locate(source, search, options) {
|
|||
|
if (typeof options === 'number') {
|
|||
|
throw new Error('locate takes a { startIndex, offsetLine, offsetColumn } object as the third argument');
|
|||
|
}
|
|||
|
return getLocator(source, options)(search, options && options.startIndex);
|
|||
|
}
|
|||
|
|
|||
|
var validNameCharacters = /[a-zA-Z0-9:_-]/;
|
|||
|
var whitespace = /[\s\t\r\n]/;
|
|||
|
var quotemark = /['"]/;
|
|||
|
|
|||
|
function repeat(str, i) {
|
|||
|
var result = '';
|
|||
|
while (i--) { result += str; }
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
function parse(source) {
|
|||
|
var header = '';
|
|||
|
var stack = [];
|
|||
|
|
|||
|
var state = metadata;
|
|||
|
var currentElement = null;
|
|||
|
var root = null;
|
|||
|
|
|||
|
function error(message) {
|
|||
|
var ref = locate(source, i);
|
|||
|
var line = ref.line;
|
|||
|
var column = ref.column;
|
|||
|
var before = source.slice(0, i);
|
|||
|
var beforeLine = /(^|\n).*$/.exec(before)[0].replace(/\t/g, ' ');
|
|||
|
var after = source.slice(i);
|
|||
|
var afterLine = /.*(\n|$)/.exec(after)[0];
|
|||
|
|
|||
|
var snippet = "" + beforeLine + afterLine + "\n" + (repeat(' ', beforeLine.length)) + "^";
|
|||
|
|
|||
|
throw new Error(
|
|||
|
(message + " (" + line + ":" + column + "). If this is valid SVG, it's probably a bug in svg-parser. Please raise an issue at https://github.com/Rich-Harris/svg-parser/issues – thanks!\n\n" + snippet)
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
function metadata() {
|
|||
|
while ((i < source.length && source[i] !== '<') || !validNameCharacters.test(source[i + 1])) {
|
|||
|
header += source[i++];
|
|||
|
}
|
|||
|
|
|||
|
return neutral();
|
|||
|
}
|
|||
|
|
|||
|
function neutral() {
|
|||
|
var text = '';
|
|||
|
while (i < source.length && source[i] !== '<') { text += source[i++]; }
|
|||
|
|
|||
|
if (/\S/.test(text)) {
|
|||
|
currentElement.children.push({ type: 'text', value: text });
|
|||
|
}
|
|||
|
|
|||
|
if (source[i] === '<') {
|
|||
|
return tag;
|
|||
|
}
|
|||
|
|
|||
|
return neutral;
|
|||
|
}
|
|||
|
|
|||
|
function tag() {
|
|||
|
var char = source[i];
|
|||
|
|
|||
|
if (char === '?') { return neutral; } // <?xml...
|
|||
|
|
|||
|
if (char === '!') {
|
|||
|
if (source.slice(i + 1, i + 3) === '--') { return comment; }
|
|||
|
if (source.slice(i + 1, i + 8) === '[CDATA[') { return cdata; }
|
|||
|
if (/doctype/i.test(source.slice(i + 1, i + 8))) { return neutral; }
|
|||
|
}
|
|||
|
|
|||
|
if (char === '/') { return closingTag; }
|
|||
|
|
|||
|
var tagName = getName();
|
|||
|
|
|||
|
var element = {
|
|||
|
type: 'element',
|
|||
|
tagName: tagName,
|
|||
|
properties: {},
|
|||
|
children: []
|
|||
|
};
|
|||
|
|
|||
|
if (currentElement) {
|
|||
|
currentElement.children.push(element);
|
|||
|
} else {
|
|||
|
root = element;
|
|||
|
}
|
|||
|
|
|||
|
var attribute;
|
|||
|
while (i < source.length && (attribute = getAttribute())) {
|
|||
|
element.properties[attribute.name] = attribute.value;
|
|||
|
}
|
|||
|
|
|||
|
var selfClosing = false;
|
|||
|
|
|||
|
if (source[i] === '/') {
|
|||
|
i += 1;
|
|||
|
selfClosing = true;
|
|||
|
}
|
|||
|
|
|||
|
if (source[i] !== '>') {
|
|||
|
error('Expected >');
|
|||
|
}
|
|||
|
|
|||
|
if (!selfClosing) {
|
|||
|
currentElement = element;
|
|||
|
stack.push(element);
|
|||
|
}
|
|||
|
|
|||
|
return neutral;
|
|||
|
}
|
|||
|
|
|||
|
function comment() {
|
|||
|
var index = source.indexOf('-->', i);
|
|||
|
if (!~index) { error('expected -->'); }
|
|||
|
|
|||
|
i = index + 2;
|
|||
|
return neutral;
|
|||
|
}
|
|||
|
|
|||
|
function cdata() {
|
|||
|
var index = source.indexOf(']]>', i);
|
|||
|
if (!~index) { error('expected ]]>'); }
|
|||
|
|
|||
|
currentElement.children.push(source.slice(i + 7, index));
|
|||
|
|
|||
|
i = index + 2;
|
|||
|
return neutral;
|
|||
|
}
|
|||
|
|
|||
|
function closingTag() {
|
|||
|
var tagName = getName();
|
|||
|
|
|||
|
if (!tagName) { error('Expected tag name'); }
|
|||
|
|
|||
|
if (tagName !== currentElement.tagName) {
|
|||
|
error(("Expected closing tag </" + tagName + "> to match opening tag <" + (currentElement.tagName) + ">"));
|
|||
|
}
|
|||
|
|
|||
|
allowSpaces();
|
|||
|
|
|||
|
if (source[i] !== '>') {
|
|||
|
error('Expected >');
|
|||
|
}
|
|||
|
|
|||
|
stack.pop();
|
|||
|
currentElement = stack[stack.length - 1];
|
|||
|
|
|||
|
return neutral;
|
|||
|
}
|
|||
|
|
|||
|
function getName() {
|
|||
|
var name = '';
|
|||
|
while (i < source.length && validNameCharacters.test(source[i])) { name += source[i++]; }
|
|||
|
|
|||
|
return name;
|
|||
|
}
|
|||
|
|
|||
|
function getAttribute() {
|
|||
|
if (!whitespace.test(source[i])) { return null; }
|
|||
|
allowSpaces();
|
|||
|
|
|||
|
var name = getName();
|
|||
|
if (!name) { return null; }
|
|||
|
|
|||
|
var value = true;
|
|||
|
|
|||
|
allowSpaces();
|
|||
|
if (source[i] === '=') {
|
|||
|
i += 1;
|
|||
|
allowSpaces();
|
|||
|
|
|||
|
value = getAttributeValue();
|
|||
|
if (!isNaN(value) && value.trim() !== '') { value = +value; } // TODO whitelist numeric attributes?
|
|||
|
}
|
|||
|
|
|||
|
return { name: name, value: value };
|
|||
|
}
|
|||
|
|
|||
|
function getAttributeValue() {
|
|||
|
return quotemark.test(source[i]) ? getQuotedAttributeValue() : getUnquotedAttributeValue();
|
|||
|
}
|
|||
|
|
|||
|
function getUnquotedAttributeValue() {
|
|||
|
var value = '';
|
|||
|
do {
|
|||
|
var char = source[i];
|
|||
|
if (char === ' ' || char === '>' || char === '/') {
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
value += char;
|
|||
|
i += 1;
|
|||
|
} while (i < source.length);
|
|||
|
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
function getQuotedAttributeValue() {
|
|||
|
var quotemark = source[i++];
|
|||
|
|
|||
|
var value = '';
|
|||
|
var escaped = false;
|
|||
|
|
|||
|
while (i < source.length) {
|
|||
|
var char = source[i++];
|
|||
|
if (char === quotemark && !escaped) {
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
if (char === '\\' && !escaped) {
|
|||
|
escaped = true;
|
|||
|
}
|
|||
|
|
|||
|
value += escaped ? ("\\" + char) : char;
|
|||
|
escaped = false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function allowSpaces() {
|
|||
|
while (i < source.length && whitespace.test(source[i])) { i += 1; }
|
|||
|
}
|
|||
|
|
|||
|
var i = metadata.length;
|
|||
|
while (i < source.length) {
|
|||
|
if (!state) { error('Unexpected character'); }
|
|||
|
state = state();
|
|||
|
i += 1;
|
|||
|
}
|
|||
|
|
|||
|
if (state !== neutral) {
|
|||
|
error('Unexpected end of input');
|
|||
|
}
|
|||
|
|
|||
|
if (root.tagName === 'svg') { root.metadata = header; }
|
|||
|
return {
|
|||
|
type: 'root',
|
|||
|
children: [root]
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
export { parse };
|
|||
|
//# sourceMappingURL=svg-parser.esm.js.map
|