- add classes inside app/library for creting Tethys xml: Field.ts, Strategy.ts, XmlModel.ts
Some checks failed
CI Pipeline / japa-tests (push) Failing after 51s
Some checks failed
CI Pipeline / japa-tests (push) Failing after 51s
- added model DocumentXmlCache.ts - npm updates - changed all models inside app/Models to use corrected BaseModel.ts - added extra extension class DatasetExtension.ts for app/dataset.ts for caching internal and external fields
This commit is contained in:
parent
4ad281bcd4
commit
ebb24cc75c
24 changed files with 1170 additions and 324 deletions
69
app/Library/Field.ts
Normal file
69
app/Library/Field.ts
Normal file
|
@ -0,0 +1,69 @@
|
|||
export default class Field {
|
||||
// private _multiplicity: number | string = 1;
|
||||
private _hasMultipleValues: boolean = false;
|
||||
private _valueModelClass: string | null = null;
|
||||
private _linkModelClass: string | null = null;
|
||||
// private _owningModelClass: string | null = null;
|
||||
private _value: any;
|
||||
private _name: string;
|
||||
|
||||
constructor(name: string) {
|
||||
this._name = name;
|
||||
this._value = null;
|
||||
}
|
||||
|
||||
getValueModelClass(): any {
|
||||
return this._valueModelClass;
|
||||
}
|
||||
|
||||
setValueModelClass(classname: any): Field {
|
||||
this._valueModelClass = classname;
|
||||
return this;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
// setOwningModelClass(classname: string): Field {
|
||||
// this._owningModelClass = classname;
|
||||
// return this;
|
||||
// }
|
||||
|
||||
public setValue(value: string | string[] | number | boolean): Field {
|
||||
if (value === null || value === this._value) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (Array.isArray(value) && value.length === 0) {
|
||||
value = [];
|
||||
} else if (typeof value === 'boolean') {
|
||||
value = value ? 1 : 0;
|
||||
} else {
|
||||
this._value = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public getValue() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
hasMultipleValues(): boolean {
|
||||
return this._hasMultipleValues;
|
||||
}
|
||||
|
||||
setMultiplicity(max: number | '*'): Field {
|
||||
if (max !== '*') {
|
||||
if (typeof max !== 'number' || max < 1) {
|
||||
throw new Error('Only integer values > 1 or "*" allowed.');
|
||||
}
|
||||
}
|
||||
// this._multiplicity = max;
|
||||
this._hasMultipleValues = (typeof max == 'number' && max > 1) || max === '*';
|
||||
return this;
|
||||
}
|
||||
|
||||
getLinkModelClass(): string | null {
|
||||
return this._linkModelClass;
|
||||
}
|
||||
}
|
177
app/Library/Strategy.ts
Normal file
177
app/Library/Strategy.ts
Normal file
|
@ -0,0 +1,177 @@
|
|||
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
|
||||
import { create } from 'xmlbuilder2';
|
||||
import Dataset from 'App/Models/Dataset';
|
||||
import Field from './Field';
|
||||
import BaseModel from 'App/Models/BaseModel';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export default class Strategy {
|
||||
private version: number;
|
||||
private config;
|
||||
private xml: XMLBuilder;
|
||||
|
||||
constructor(config) {
|
||||
this.version = 1.0;
|
||||
this.config = config;
|
||||
}
|
||||
public async createDomDocument(): Promise<XMLBuilder> {
|
||||
if (this.config.model === null) {
|
||||
throw new Error('No Model given for serialization.');
|
||||
}
|
||||
// domDocument = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
|
||||
|
||||
this.xml = create({ version: '1.0', encoding: 'UTF-8' }, '<Opus></Opus>');
|
||||
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<string> = 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, modelNode);
|
||||
}
|
||||
}
|
||||
|
||||
private mapField(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, 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<string> = 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) {
|
||||
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;
|
||||
}
|
||||
}
|
117
app/Library/XmlModel.ts
Normal file
117
app/Library/XmlModel.ts
Normal file
|
@ -0,0 +1,117 @@
|
|||
import DocumentXmlCache from 'App/Models/DocumentXmlCache';
|
||||
import { XMLBuilder } from 'xmlbuilder2/lib/interfaces';
|
||||
import Dataset from 'App/Models/Dataset';
|
||||
import Strategy from './Strategy';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
/**
|
||||
* This is the description of the interface
|
||||
*
|
||||
* @interface Conf
|
||||
* @member {Model} model holds the current dataset model
|
||||
* @member {XMLBuilder} dom holds the current DOM representation
|
||||
* @member {Array<string>} excludeFields List of fields to skip on serialization.
|
||||
* @member {boolean} excludeEmpty True, if empty fields get excluded from serialization.
|
||||
* @member {string} baseUri Base URI for xlink:ref elements
|
||||
*/
|
||||
export interface Conf {
|
||||
model: Dataset;
|
||||
dom?: XMLBuilder;
|
||||
excludeFields: Array<string>;
|
||||
excludeEmpty: boolean;
|
||||
baseUri: string;
|
||||
}
|
||||
|
||||
export default class XmlModel {
|
||||
private config: Conf;
|
||||
// private strategy = null;
|
||||
private cache: DocumentXmlCache | null = null;
|
||||
private _caching = false;
|
||||
private strategy: Strategy;
|
||||
|
||||
constructor(dataset: Dataset) {
|
||||
// $this->strategy = new Strategy();// Opus_Model_Xml_Version1;
|
||||
// $this->config = new Conf();
|
||||
// $this->strategy->setup($this->config);
|
||||
|
||||
this.config = {
|
||||
excludeEmpty: false,
|
||||
baseUri: '',
|
||||
excludeFields: [],
|
||||
model: dataset,
|
||||
};
|
||||
|
||||
this.strategy = new Strategy({
|
||||
excludeEmpty: true,
|
||||
baseUri: '',
|
||||
excludeFields: [],
|
||||
model: dataset,
|
||||
});
|
||||
}
|
||||
|
||||
set model(model: Dataset) {
|
||||
this.config.model = model;
|
||||
}
|
||||
|
||||
public excludeEmptyFields(): void {
|
||||
this.config.excludeEmpty = true;
|
||||
}
|
||||
|
||||
get xmlCache(): DocumentXmlCache | null {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
set xmlCache(cache: DocumentXmlCache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
get caching(): boolean {
|
||||
return this._caching;
|
||||
}
|
||||
set caching(caching: boolean) {
|
||||
this._caching = caching;
|
||||
}
|
||||
|
||||
public async getDomDocument(): Promise<XMLBuilder | null> {
|
||||
const dataset = this.config.model;
|
||||
|
||||
let domDocument: XMLBuilder | null = await this.getDomDocumentFromXmlCache();
|
||||
if (domDocument == null) {
|
||||
domDocument = await this.strategy.createDomDocument();
|
||||
// domDocument = create({ version: '1.0', encoding: 'UTF-8', standalone: true }, '<root></root>');
|
||||
if (this._caching) {
|
||||
// caching is desired:
|
||||
this.cache = this.cache || new DocumentXmlCache();
|
||||
this.cache.document_id = dataset.id;
|
||||
this.cache.xml_version = 1; // (int)$this->strategy->getVersion();
|
||||
// this.cache.server_date_modified = dataset.server_date_modified.toFormat("yyyy-MM-dd HH:mm:ss");
|
||||
this.cache.xml_data = domDocument.end();
|
||||
await this.cache.save();
|
||||
}
|
||||
}
|
||||
return domDocument;
|
||||
}
|
||||
|
||||
private async getDomDocumentFromXmlCache(): Promise<XMLBuilder | null> {
|
||||
const dataset: Dataset = this.config.model;
|
||||
if (!this.cache) {
|
||||
return null;
|
||||
}
|
||||
//.toFormat('YYYY-MM-DD HH:mm:ss');
|
||||
let date: DateTime = dataset.server_date_modified;
|
||||
const actuallyCached: boolean = await DocumentXmlCache.hasValidEntry(dataset.id, date);
|
||||
if (!actuallyCached) {
|
||||
return null;
|
||||
}
|
||||
//cache is actual return it for oai:
|
||||
try {
|
||||
if (this.cache) {
|
||||
return this.cache.getDomDocument();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue