@mieweb/wikigdrive
Version:
Google Drive to MarkDown synchronization
183 lines (182 loc) • 5.77 kB
JavaScript
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as htmlparser2 from 'htmlparser2';
function createObj(classType, attributes) {
const obj = new classType();
if (classType._attrs) {
for (const xmlAttrName in attributes) {
const attr = classType._attrs[xmlAttrName];
if (attr) {
const value = attributes[xmlAttrName];
obj[attr.fieldName] = value;
}
}
}
return obj;
}
function addXmlHandlers(classType) {
classType.onOpenTag = (context, xmlElementTag, attributes) => {
if (!classType._elems)
return;
const elemDef = classType._elems[xmlElementTag];
if (elemDef) {
if (!elemDef.classTypeName || typeof elemDef.classTypeName !== 'string') {
throw new Error('noClassType for tag: ' + xmlElementTag + ', fieldName: ' + elemDef.fieldName);
}
const subObj = createObj(context.getClass(elemDef.classTypeName), attributes);
const obj = context.top.val;
if (elemDef.isArray) {
if (!Array.isArray(obj[elemDef.fieldName])) {
obj[elemDef.fieldName] = [];
}
obj[elemDef.fieldName].push(subObj);
}
else {
obj[elemDef.fieldName] = subObj;
}
context.push({
val: subObj,
classType: context.getClass(elemDef.classTypeName),
closeTag: xmlElementTag
});
}
};
classType.onCloseTag = (context, xmlElementTag) => {
if (context.top.closeTag === xmlElementTag) {
context.pop();
}
};
classType.onText = (context, text) => {
const fieldName = context.top.classType._xmlTextField;
if (fieldName) {
const obj = context.top.val;
const props = classType._xmlTextProps;
if (props.isArray) {
if (!obj[fieldName]) {
obj[fieldName] = [];
}
obj[fieldName].push(text);
}
else {
if (!obj[fieldName]) {
obj[fieldName] = '';
}
obj[fieldName] += text;
}
}
};
}
export function XmlRootElement(xmlElementTag) {
return (classType) => {
classType._xmlElementTag = xmlElementTag;
addXmlHandlers(classType);
};
}
export function XmlElement() {
return (classType) => {
addXmlHandlers(classType);
};
}
export function XmlAttribute(xmlAttrName, fieldName) {
return (classType) => {
if (!classType._attrs)
classType._attrs = {};
classType._attrs[xmlAttrName] = {
fieldName
};
};
}
export function XmlElementChild(xmlElementTag, fieldName, subClassTypeName, props = { isArray: false }) {
if (!subClassTypeName) {
throw new Error('No subClassType');
}
return (classType) => {
if (!classType._elems)
classType._elems = {};
const xmlElemDef = {
fieldName,
classTypeName: subClassTypeName,
...props
};
classType._elems[xmlElementTag] = xmlElemDef;
};
}
export function XmlText(fieldName, props = { isArray: false }) {
return (classType) => {
classType._xmlTextField = fieldName;
classType._xmlTextProps = props;
};
}
function createRootStackPos(rootClassTypeName, rootClassType) {
const val = { retVal: null };
const classType = {
_elems: {
[rootClassType._xmlElementTag]: {
fieldName: 'retVal',
classTypeName: rootClassTypeName,
}
}
};
addXmlHandlers(classType);
return {
val,
classType,
closeTag: ''
};
}
export class UnMarshaller {
constructor(classes, rootClassTypeName) {
Object.defineProperty(this, "classes", {
enumerable: true,
configurable: true,
writable: true,
value: classes
});
Object.defineProperty(this, "rootClassTypeName", {
enumerable: true,
configurable: true,
writable: true,
value: rootClassTypeName
});
Object.defineProperty(this, "parser", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "stack", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
this.stack.push(createRootStackPos(this.rootClassTypeName, this.getClass(this.rootClassTypeName)));
this.parser = new htmlparser2.Parser({
onopentag: (tagname, attributes) => this.top.classType.onOpenTag(this, tagname, attributes),
ontext: (text) => this.top.classType.onText(this, text),
onclosetag: (tagname) => this.top.classType.onCloseTag(this, tagname),
}, { xmlMode: true });
}
get top() {
return this.stack[this.stack.length - 1];
}
push(val) {
this.stack.push(val);
}
pop() {
return this.stack.pop();
}
unmarshal(content) {
if ('string' !== typeof content) {
content = new TextDecoder().decode(content);
}
this.parser.write(content);
this.parser.end();
return this.top.val['retVal'];
}
getClass(classTypeName) {
if (!this.classes[classTypeName]) {
throw new Error('No class registered: ' + classTypeName);
}
return this.classes[classTypeName];
}
}