rapid-xml-to-json
Version:
Fast, dependency-free XML-parser for parsing XML structures into JSON. Optimised for speed and low memory usage.
159 lines (157 loc) • 6.58 kB
JavaScript
function xmlToJson(xml) {
var o = {};
var cur = o;
var curName = '';
var curContentList = [];
var hasContent = false;
for (var i = 0; i < xml.length; i++) {
var curChar = xml[i];
if (curChar === '<') { // Inside an opening or closing tag
if (xml[i + 1] === '/') { // Inside a closing tag
cur = cur.__parent;
if (hasContent) { // Insert content instead of current object
if (cur[curName] instanceof Array) {
var lastIdx = cur[curName].length - 1;
cur[curName][lastIdx]['#text'] = '';
for (var i_1 = 0; i_1 < curContentList.length; i_1++) {
var curContent = curContentList[i_1];
cur[curName][lastIdx]['#text'] += xml.slice(curContent[0], curContent[1]);
}
}
else {
cur[curName]['#text'] = '';
for (var i_2 = 0; i_2 < curContentList.length; i_2++) {
var curContent = curContentList[i_2];
cur[curName]['#text'] += xml.slice(curContent[0], curContent[1]);
}
}
}
while (xml[i] !== '>') {
i++;
}
curName = '';
}
else if (xml[i + 1] === '?' || xml[i + 1] === '!') { // Ignore processing instructions, stylesheets, DTD validation,
if (xml[i + 1] === '!') {
// Handle declarations
// TODO: Create skipUntil utility function
if (xml.startsWith('<!DOCTYPE', i)) { // Doctype
i += 9;
while (xml[i] !== '>') {
i++;
}
}
else if (xml.startsWith('<!--', i)) { // Comment
i += 4;
while (!xml.startsWith('-->', i)) {
i++;
}
i += 2;
}
else if (xml.startsWith('<![CDATA[', i)) { // Handle CDATA
i += 9;
var contentFrom = i;
while (!xml.startsWith(']]>', i)) {
i++;
}
var contentTo = i;
curContentList.push([contentFrom, contentTo]);
i += 2;
hasContent = true;
continue; // Skip hasContent = false
}
}
else {
// Ignore processing instructions and stylesheets
while (xml[i] !== '>') {
i++;
}
}
}
else { // Opening
// Handle next
i++; // Move to first char
var name_1 = ''; // Create a name
while (xml[i] !== ' ' && xml[i] !== '>' && xml[i] !== '/') { // Go until attributes or closing. TODO: Handle XML attributes
name_1 += xml[i];
i++;
}
var isSelfClosing = false;
var attrs = [];
while (xml[i] !== '>') { // Go until closing tag
i++;
if (xml[i] !== ' ' && xml[i] !== '\n' && xml[i] !== '>' && xml[i] !== '/') {
var attrNameStart = i;
var attrNameEnd = -1;
while (xml[i] !== '=') {
i++;
}
attrNameEnd = i;
i += 2; // Skip ="
var attrValueStart = i;
while (xml[i] !== '"' || xml[i - 1] === '\\') {
i++;
}
attrs.push([attrNameStart, attrNameEnd, attrValueStart, i]);
}
}
if (xml[i - 1] === '/') {
isSelfClosing = true;
}
if (cur[name_1]) { // Already defined, create an array or add to existing array
if (!(cur[name_1] instanceof Array)) {
cur[name_1] = [cur[name_1]];
}
var next = {};
cur[name_1].push(next);
next.__parent = cur;
if (!isSelfClosing) {
cur = next;
curName = name_1;
}
}
else { // Not defined yet. Create new property on json object
cur[name_1] = {};
cur[name_1].__parent = cur;
if (!isSelfClosing) {
cur = cur[name_1];
curName = name_1;
}
}
if (attrs.length) {
if (isSelfClosing) { // If it's self-closing with attributes, hop down into it
if (cur[name_1] instanceof Array) {
cur = cur[name_1][cur[name_1].length - 1];
}
else {
cur = cur[name_1];
}
}
cur['$attrs'] = {};
for (var i_3 = 0; i_3 < attrs.length; i_3++) {
cur['$attrs'][xml.slice(attrs[i_3][0], attrs[i_3][1])] = xml.slice(attrs[i_3][2], attrs[i_3][3]);
}
if (isSelfClosing) { // And hop back up
cur = cur.__parent;
}
}
}
hasContent = false;
curContentList = [];
}
else if (curName) { // Inside curName
var contentFrom = i; // TODO: Handle mixed content
while (xml[i] !== '<') {
if (!hasContent && (xml[i] !== ' ' && xml[i] !== '\n')) {
hasContent = true;
}
i++;
}
var contentTo = i;
i--;
curContentList.push([contentFrom, contentTo]); // TODO: Trim spaces
}
}
return o;
}
export { xmlToJson };