17.01.2022 в 19:27
Anri
ShortXML aka xml с переменными
Я создал javascript библиотеку - сериализатор/десериализатор, где одинаковые части объекта выводятся только один раз.
Например
input
{
obj: {
fizz: {
lol: 'kek'
},
buzz: {
lol: 'kek'
},
},
obj2: {
fizz: {
lol: 'kek'
},
buzz: {
lol: 'kek'
},
fizzbuzz: {
lol: 'kek'
}
},
}
Превратится
<root>
<obj>
<fizz shortxml_var="14">
<lol shortxml_var="15">
kek
</lol>
</fizz>
<buzz shortxml_var="16">
<lol shortxml_var="15"/>
</buzz>
</obj>
<obj2>
<fizz shortxml_var="14"/>
<buzz shortxml_var="16"/>
<fizzbuzz>
<lol shortxml_var="15"/>
</fizzbuzz>
</obj2>
</root>
Итак, начнем с сериализации:
превращаем наш объект в dom, находим одинаковые узлы и собираем строку.
let i = 0;
//uuid
let uuidv4 = () => {
return i++;
}
//step 1 - create tree
let start = new node();
start.key = "root";
let stack = [start];
//создаем дерево
let createTree = (obj, prevKey, prevIsArray = false) => {
for (let key in obj) {
let el = new node();
el.id = uuidv4();
el.key = (prevIsArray) ? prevKey : key;
//в эксеемеле массивы идут без родительской ноды
el.isArray = Array.isArray(obj[key]);
stack[stack.length - 1].childrens.push(el);
if ((/boolean|number|string/).test(typeof obj[key])) {
el.value = obj[key];
} else {
stack.push(el);
createTree(obj[key], key, el.isArray);
stack.pop();
}
}
}
createTree(obj);
//step 2 find clone
//ищем одинаковые ноды
let shorting = (node) => {
let cNode = (node);
let variable = uuidv4();
let setVariable = (node) => {
if (cNode.id !== node.id) {
function equal(a, b) {
if (a?.childrens.length && b?.childrens.length) {
for (let p in a.childrens) {
if (!equal(a.childrens[p], b.childrens[p])) {
return false;
}
}
}
if (a?.value === b?.value) {
if (a?.key === b?.key) {
return true;
}
}
return false;
}
//если нашли, изменим им свойство вариеблес
if (equal(node, cNode)) {
node.variable = variable;
cNode.variable = variable;
} else {
node.childrens.forEach((item) => {
setVariable(item);
});
}
}
};
setVariable(stack[0]);
node.childrens.forEach((item) => {
//если нода уже найдена, пропускаем ее
if (!item.variable) {
shorting(item);
}
})
};
shorting(stack[0]);
//step 3 build xml
//собираем строку
let variable = [];
let resXML = '';
let buildXml = (node) => {
//если переменная уже распечатана, не печатаем ее
if (variable[node.variable]) {
resXML += `<${node.key} shortxml_var="${node.variable}"/>`;
return;
}
//печатаем переменную
if (node.variable) {
variable[node.variable] = true;
if (node.isArray) {
//выведем связующий узел для массива
resXML += `<${node.key} shortxml_isarray="true" shortxml_var="${node.variable}">`;
} else {
resXML += `<${node.key} shortxml_var="${node.variable}">`;
}
} else {
if (!node.isArray) {
resXML += "<" + node.key + ">";
}
}
if (node.value && !node.isArray) {
resXML += node.value;
}
node.childrens.forEach((item) => {
buildXml(item, isVariable);
});
if (!node.isArray || node.variable) {
resXML += "</" + node.key + ">";
}
}
buildXml(stack[0])
return resXML;
Теперь десериализация:
сперва соберем dom, затем создаем объект — сначала найдем все ноды с переменной, соберем их и сложим в variables, затем обойдем дерево еще раз и соберем наш объект.
//step 1 build dom
//собираем дерево
let startNode = new node();
let stack = [];
stack.push(startNode);
superxmlparser74.parse(str,
(item) => {
let el = new node();
el.tag = item.tag.trim();
let variable = item.attr.find((item) => item.key === "shortxml_var")?.value[0];
el.isarray = item.attr.find((item) => item.key === "shortxml_isarray")?.value[0];
el.attr = item.attr.filter((item) => {
return !item.key.includes("shortxml")
});
el.var = variable;
el.varPrint = (varPrint === 'true') ? true : false;
stack[stack.length - 1].childrens.push(el);
stack.push(el);
},
(item) => {
stack[stack.length - 1].value = item.value.trim();
},
(item) => {
stack.pop();
},
() => {
}, (item) => {
//selfclosing тут нераспечатанные переменные
let el = new node();
el.tag = item.tag.trim();
let variable = item.attr.find((item) => item.key === "shortxml_var")?.value[0];
el.attr = item.attr.filter((item) => {
return !item.key.includes("shortxml")
});
el.var = variable;
el.varPrint = (varPrint === 'true') ? true : false;
stack[stack.length - 1].childrens.push(el);
});
startNode = startNode.childrens[0];
//step 2 build obj
let obj = {};
let variables = [];
//функция для сборки обьектов
let deep = (node, tNode) => {
let t = {};
//вытаскиваем готовый обьект
if (node.var && variables[node.var]) {
t = JSON.parse(JSON.stringify(variables[node.var]))
}
if (node.childrens.length) {
if (Array.isArray(tNode[node.tag])) {
tNode[node.tag].push(t);
} else if (tNode[node.tag]) {
tNode[node.tag] = [tNode[node.tag]];
tNode[node.tag].push(t);
} else {
tNode[node.tag] = t;
}
if (node.var && variables[node.var]) {
return;
}
node.attr.forEach((item) => {
t[item.key] = item.value[0];
});
node.childrens.forEach((item) => {
deep(item, t, buildVar);
});
} else {
if (node.var && variables[node.var]) {
node.value = t;
}
if (Array.isArray(tNode[node.tag])) {
tNode[node.tag].push(node.value);
} else if (tNode[node.tag]) {
tNode[node.tag] = [tNode[node.tag]];
tNode[node.tag].push(node.value);
} else {
tNode[node.tag] = node.value;
}
}
}
//generate var obj;
//функция для сборки всех переменных
let getVar = (node) => {
if (node.childrens.length) {
if (node.var && !variables[node.var]) {
let cObjVar = {}
if (node.isarray) {
//массивы
cObjVar = node.childrens.map((elArrNode) => {
let c = {};
deep(elArrNode, c, true);
return c[elArrNode.tag];
});
variables[node.var] = cObjVar;
} else {
//обычные ноды
deep(node, cObjVar, true);
variables[node.var] = cObjVar[node.tag];
}
}
node.childrens.forEach((item) => {
getVar(item);
});
} else {
if (node.var && !variables[node.var]) {
variables[node.var] = node.value;
}
}
}
//обойдем дерево и сложим все переменные в variables
getVar(startNode);
//соберем наш объект
deep(startNode, obj);
return obj['root'];
Весь код доступен в гитхабе — https://github.com/ru51a4/shortXML
Решение в npmjs — https://www.npmjs.com/package/shortxml