mini-dom-parser
Version:
Un DOMParser minimalista sin dependencias, compatible con xmldom para casos simples.
120 lines (102 loc) • 3.25 kB
JavaScript
// ---------------- DOM CORE ----------------
class Node {
constructor(nodeType, nodeName, nodeValue = null) {
this.nodeType = nodeType; // 1 = element, 3 = text, 9 = document
this.nodeName = nodeName;
this.nodeValue = nodeValue;
this.childNodes = [];
this.attributes = {};
this.parentNode = null;
}
appendChild(node) {
node.parentNode = this;
this.childNodes.push(node);
}
getElementsByTagName(tagName) {
let results = [];
function traverse(node) {
if (node.nodeType === 1 && node.nodeName === tagName) results.push(node);
for (const child of node.childNodes) traverse(child);
}
traverse(this);
return results;
}
}
class Document extends Node {
constructor() {
super(9, "#document");
}
}
class DOMParser {
parseFromString(str, mimeType) {
if (!mimeType || !mimeType.includes("xml")) {
throw new Error("Unsupported mimeType: " + mimeType);
}
const doc = new Document();
str = str.replace(/<\?xml.*?\?>/, "");
const tagRegex = /<([^!?\/\s>]+)([^>]*)>|<\/([^>]+)>|([^<]+)/g;
let stack = [doc];
let match;
while ((match = tagRegex.exec(str))) {
if (match[1]) {
const nodeName = match[1].includes(":") ? match[1].split(":")[1] : match[1];
const node = new Node(1, nodeName);
const attrs = match[2]?.trim();
if (attrs) {
const attrRegex = /(\S+)=["']([^"']+)["']/g;
let attr;
while ((attr = attrRegex.exec(attrs))) {
node.attributes[attr[1]] = attr[2];
}
}
stack[stack.length - 1].appendChild(node);
stack.push(node);
} else if (match[3]) {
if (stack.length > 1) stack.pop();
} else if (match[4] && match[4].trim()) {
const textNode = new Node(3, "#text", match[4].trim());
stack[stack.length - 1].appendChild(textNode);
}
}
return doc;
}
}
// ---------------- XML → JSON HELPER ----------------
function nodeToJson(node) {
if (node.nodeType === 3) return node.nodeValue;
if (node.nodeType === 1) {
let obj = {};
for (const child of node.childNodes) {
const name = child.nodeType === 1
? child.nodeName
: "#text";
const value = nodeToJson(child);
if (name === "#text") {
if (!obj) return value;
if (!obj.value) obj.value = value;
else obj.value += " " + value;
} else {
if (!obj[name]) obj[name] = value;
else {
if (!Array.isArray(obj[name])) obj[name] = [obj[name]];
obj[name].push(value);
}
}
}
// simplificar nodo de solo texto
if (Object.keys(obj).length === 1 && obj.value !== undefined) return obj.value;
return obj;
}
if (node.nodeType === 9) {
let result = {};
for (const child of node.childNodes) result[child.nodeName] = nodeToJson(child);
return result;
}
return null;
}
function xmlToJson(xmlString) {
const parser = new DOMParser();
const doc = parser.parseFromString(xmlString, "text/xml");
return nodeToJson(doc);
}
export { DOMParser, xmlToJson };