import { XMLBuilder } from 'xmlbuilder2/lib/interfaces.js'; import { create } from 'xmlbuilder2'; import Dataset from '#models/dataset'; import Field from './Field.js'; import BaseModel from '#models/base_model'; import { DateTime } from 'luxon'; export default class Strategy { private version: number; private config; private xml: XMLBuilder; constructor(config: any) { this.version = 1.0; this.config = config; } public async createDomDocument(): Promise { if (this.config.model === null) { throw new Error('No Model given for serialization.'); } // domDocument = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, ''); this.xml = create({ version: '1.0', encoding: 'UTF-8' }, ''); this.xml.root().att('version', this.version.toString()); // this.xml.root().att('version', this.getVersion()); this.xml.root().att('xmlns:xlink', 'http://www.w3.org/1999/xlink'); await this._mapModel(this.config.model); return this.xml; //.end({ prettyPrint: true }); } private async _mapModel(model: Dataset) { const fields: Array = await model.describe(); const excludeFields = this.getConfig().excludeFields; let fieldsDiff; if (excludeFields.length > 0) { fieldsDiff = fields.filter((fieldname) => !excludeFields.includes(fieldname)); } else { fieldsDiff = fields; } const modelNode: XMLBuilder = this.createModelNode(model); // rootNode.appendChild(childNode); for (const fieldname of fieldsDiff) { const field = model.getField(fieldname); this.mapField(field as Field, modelNode); } } private mapField(field: Field, modelNode: XMLBuilder) { const modelClass = field.getValueModelClass(); let fieldValues = field.getValue(); if (this.config.excludeEmpty) { if ( fieldValues === null || (typeof fieldValues === 'string' && fieldValues.trim() === '') || (Array.isArray(fieldValues) && fieldValues.length === 0) ) { return; } } if (modelClass === null) { this.mapSimpleField(modelNode, field); } else { // map related models with values: // let modelInstance = new modelClass(); const fieldName = field.getName(); if (!Array.isArray(fieldValues)) { fieldValues = [fieldValues]; } for (const value of fieldValues) { const childNode = modelNode.ele(fieldName); // rootNode.appendChild(childNode); // if a field has no value then there is nothing more to do // TODO maybe there must be another solution if (value === null) { continue; } if (modelClass.prototype instanceof BaseModel) { this.mapModelAttributes(value, childNode); } else if (modelClass instanceof DateTime) { // console.log('Value is a luxon date'); this.mapDateAttributes(value, childNode); } else if (Array.isArray(value)) { console.log('Value is an array'); // this.mapArrayAttributes(value, childNode); } } } } private mapDateAttributes(model: DateTime, childNode: XMLBuilder) { childNode.att('Year', model.year.toString()); childNode.att('Month', model.month.toString()); childNode.att('Day', model.day.toString()); childNode.att('Hour', model.hour.toString()); childNode.att('Minute', model.minute.toString()); childNode.att('Second', model.second.toString()); childNode.att('UnixTimestamp', model.toUnixInteger().toString()); let zoneName = model.zoneName ? model.zoneName : ''; childNode.att('Timezone', zoneName); } private mapModelAttributes(myObject: any, childNode: XMLBuilder) { Object.keys(myObject).forEach((prop) => { let value = myObject[prop]; // console.log(`${prop}: ${value}`); if (value != null) { if (value instanceof DateTime) { value = value.toFormat('yyyy-MM-dd HH:mm:ss').trim(); } else { value = value.toString().trim(); } // Replace invalid XML-1.0-Characters by UTF-8 replacement character. let fieldValues = value.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '\xEF\xBF\xBD '); // Create an attribute with the field name and field values // const attr = { [fieldName]: fieldValues }; // Add the attribute to the root element childNode.att(prop, fieldValues); } }); } private mapSimpleField(modelNode: XMLBuilder, field: Field) { const fieldName = field.getName(); let fieldValues = this.getFieldValues(field); if (fieldValues != null) { // Replace invalid XML-1.0-Characters by UTF-8 replacement character. fieldValues = fieldValues.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '\xEF\xBF\xBD '); // Create an attribute with the field name and field values // const attr = { [fieldName]: fieldValues }; // Add the attribute to the root element modelNode.att(fieldName, fieldValues); } } private getFieldValues(field: any): string { let fieldValues: number | string | Array = field.getValue(); //275 // workaround for simple fields with multiple values if (field.hasMultipleValues() === true && Array.isArray(fieldValues)) { fieldValues = fieldValues.join(','); } // Uncomment if needed // if (fieldValues instanceof DateTimeZone) { // fieldValues = fieldValues.getName(); // } return fieldValues?.toString().trim(); } private createModelNode(model: Dataset) { const className = 'Rdr_' + model.constructor.name.split('\\').pop(); //Rdr_Dataset // return dom.createElement(className); return this.xml.root().ele(className); } // private getVersion() { // return Math.floor(this.version); // } private getConfig() { return this.config; } }