forked from cory/tildefriends
254 lines
5.4 KiB
JavaScript
254 lines
5.4 KiB
JavaScript
|
"use strict";
|
||
|
|
||
|
//! { "category": "libraries" }
|
||
|
|
||
|
function xmlEncode(text) {
|
||
|
return text.replace(/([\&"'<>])/g, function(x, item) {
|
||
|
return {'&': '&', '"': '"', '<': '<', '>': '>', "'": '''}[item];
|
||
|
});
|
||
|
}
|
||
|
function xmlDecode(xml) {
|
||
|
return xml.replace(/("|<|>|&|')/g, function(x, item) {
|
||
|
return {'&': '&', '"': '"', '<': '<', '>': '>', ''': "'"}[item];
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function XmlStreamParser() {
|
||
|
this.buffer = "";
|
||
|
this._parsed = [];
|
||
|
this.reset();
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
XmlStreamParser.kText = "text";
|
||
|
XmlStreamParser.kElement = "element";
|
||
|
XmlStreamParser.kEndElement = "endElement";
|
||
|
XmlStreamParser.kAttributeName = "attributeName";
|
||
|
XmlStreamParser.kAttributeValue = "attributeValue";
|
||
|
|
||
|
XmlStreamParser.prototype.reset = function() {
|
||
|
this._state = XmlStreamParser.kText;
|
||
|
this._attributes = {};
|
||
|
this._attributeName = "";
|
||
|
this._attributeValue = "";
|
||
|
this._attributeEquals = false;
|
||
|
this._attributeQuote = "";
|
||
|
this._slash = false;
|
||
|
this._value = "";
|
||
|
this._decl = false;
|
||
|
}
|
||
|
|
||
|
XmlStreamParser.prototype.parse = function(data) {
|
||
|
this._parsed = [];
|
||
|
|
||
|
for (var i = 0; i < data.length; i++) {
|
||
|
var c = data.charAt(i);
|
||
|
this.parseCharacter(c);
|
||
|
}
|
||
|
|
||
|
return this._parsed;
|
||
|
}
|
||
|
|
||
|
XmlStreamParser.prototype.flush = function() {
|
||
|
var node = {type: this._state};
|
||
|
if (this._value) {
|
||
|
node.value = xmlDecode(this._value);
|
||
|
}
|
||
|
if (this._attributes.length || this._state == XmlStreamParser.kElement) {
|
||
|
node.attributes = this._attributes;
|
||
|
}
|
||
|
if (this._state != XmlStreamParser.kText || this._value) {
|
||
|
this.emit(node);
|
||
|
}
|
||
|
this.reset();
|
||
|
}
|
||
|
|
||
|
XmlStreamParser.prototype.parseCharacter = function(c) {
|
||
|
switch (this._state) {
|
||
|
case XmlStreamParser.kText:
|
||
|
if (c == '<') {
|
||
|
this.flush();
|
||
|
this._state = XmlStreamParser.kElement;
|
||
|
} else {
|
||
|
this._value += c;
|
||
|
}
|
||
|
break;
|
||
|
case XmlStreamParser.kElement:
|
||
|
case XmlStreamParser.kEndElement:
|
||
|
switch (c) {
|
||
|
case '>':
|
||
|
this.finishElement();
|
||
|
break;
|
||
|
case '/':
|
||
|
if (!this._value) {
|
||
|
this._state = XmlStreamParser.kEndElement;
|
||
|
} else if (!this._slash) {
|
||
|
this._slash = true;
|
||
|
} else {
|
||
|
this._value += c;
|
||
|
}
|
||
|
break;
|
||
|
case '?':
|
||
|
if (!this._value) {
|
||
|
this._decl = true;
|
||
|
} else {
|
||
|
this._value += '?';
|
||
|
}
|
||
|
break;
|
||
|
case ' ':
|
||
|
case '\t':
|
||
|
case '\r':
|
||
|
case '\n':
|
||
|
this._state = XmlStreamParser.kAttributeName;
|
||
|
break;
|
||
|
default:
|
||
|
if (this._slash) {
|
||
|
this._slash = false;
|
||
|
this._value += '/';
|
||
|
}
|
||
|
this._value += c;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case XmlStreamParser.kAttributeName:
|
||
|
switch (c) {
|
||
|
case ' ':
|
||
|
case '\t':
|
||
|
case '\r':
|
||
|
case '\n':
|
||
|
if (this._attributeName) {
|
||
|
this._state = XmlStreamParser.kAttributeValue;
|
||
|
}
|
||
|
break;
|
||
|
case '/':
|
||
|
if (!this._slash) {
|
||
|
this._slash = true;
|
||
|
} else {
|
||
|
this._value += '/';
|
||
|
}
|
||
|
break;
|
||
|
case '=':
|
||
|
this._state = XmlStreamParser.kAttributeValue;
|
||
|
break;
|
||
|
case '>':
|
||
|
if (this._attributeName) {
|
||
|
this._attributes[this._attributeName] = null;
|
||
|
}
|
||
|
this._state = XmlStreamParser.kElement;
|
||
|
this.finishElement();
|
||
|
break;
|
||
|
default:
|
||
|
this._attributeName += c;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
case XmlStreamParser.kAttributeValue:
|
||
|
switch (c) {
|
||
|
case ' ':
|
||
|
case '\t':
|
||
|
case '\r':
|
||
|
case '\n':
|
||
|
if (this._attributeValue) {
|
||
|
this._state = XmlStreamParser.kAttributeName;
|
||
|
}
|
||
|
break;
|
||
|
case '"':
|
||
|
case "'":
|
||
|
if (!this._attributeValue && !this._attributeQuote) {
|
||
|
this._attributeQuote = c;
|
||
|
} else if (this._attributeQuote == c) {
|
||
|
this._attributes[this._attributeName] = this._attributeValue;
|
||
|
this._attributeName = "";
|
||
|
this._attributeValue = "";
|
||
|
this._attributeQuote = "";
|
||
|
this._state = XmlStreamParser.kAttributeName;
|
||
|
} else {
|
||
|
this._attributeValue += c;
|
||
|
}
|
||
|
break;
|
||
|
case '>':
|
||
|
this.finishElement();
|
||
|
break;
|
||
|
default:
|
||
|
this._attributeValue += c;
|
||
|
break;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
XmlStreamParser.prototype.finishElement = function() {
|
||
|
if (this._decl) {
|
||
|
this.reset();
|
||
|
} else {
|
||
|
var value = this._value;
|
||
|
var slash = this._slash;
|
||
|
this.flush();
|
||
|
if (slash) {
|
||
|
this._state = XmlStreamParser.kEndElement;
|
||
|
this._value = value;
|
||
|
this.flush();
|
||
|
}
|
||
|
}
|
||
|
this._state = XmlStreamParser.kText;
|
||
|
}
|
||
|
|
||
|
XmlStreamParser.prototype.emit = function(node) {
|
||
|
this._parsed.push(node);
|
||
|
}
|
||
|
|
||
|
function XmlStanzaParser(depth) {
|
||
|
this._depth = depth || 0;
|
||
|
this._parsed = [];
|
||
|
this._stack = [];
|
||
|
this._stream = new XmlStreamParser();
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
XmlStanzaParser.prototype.reset = function() {
|
||
|
this._parsed = [];
|
||
|
this._stack = [];
|
||
|
this._stream.reset();
|
||
|
}
|
||
|
|
||
|
XmlStanzaParser.prototype.emit = function(stanza) {
|
||
|
this._parsed.push(stanza);
|
||
|
}
|
||
|
|
||
|
XmlStanzaParser.prototype.parse = function(data) {
|
||
|
this._parsed = [];
|
||
|
var nodes = this._stream.parse(data);
|
||
|
for (var i = 0; i < nodes.length; i++) {
|
||
|
this.parseNode(nodes[i]);
|
||
|
}
|
||
|
return this._parsed;
|
||
|
}
|
||
|
|
||
|
XmlStanzaParser.prototype.parseNode = function(node) {
|
||
|
switch (node.type) {
|
||
|
case XmlStreamParser.kElement:
|
||
|
this._stack.push({name: node.value, attributes: node.attributes, children: [], text: ""});
|
||
|
break;
|
||
|
case XmlStreamParser.kEndElement:
|
||
|
if (this._stack.length == 1 + this._depth) {
|
||
|
this.emit(this._stack.pop());
|
||
|
} else {
|
||
|
var last = this._stack.pop();
|
||
|
this._stack[this._stack.length - 1].children.push(last);
|
||
|
}
|
||
|
break;
|
||
|
case XmlStreamParser.kText:
|
||
|
if (this._stack && this._stack.length) {
|
||
|
this._stack[this._stack.length - 1].text += node.value;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
exports.StanzaParser = function(depth) {
|
||
|
let parser = new XmlStanzaParser(depth);
|
||
|
return {
|
||
|
parse: parser.parse.bind(parser),
|
||
|
reset: parser.reset.bind(parser),
|
||
|
};
|
||
|
}
|