Skip to content

Added @XMLText decorator supporting optional 'required' option #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
# ide
.idea

# vscode
.vscode

# libraries
node_modules

Expand Down
26 changes: 14 additions & 12 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
export {DEFAULT_ATTRIBUTE_PROPERTY, ns} from './lib/utils';
export { DEFAULT_ATTRIBUTE_PROPERTY, DEFAULT_TEXT_PROPERTY, ns } from './lib/utils';

export {XMLAttribute} from './lib/annotations/XMLAttribute';
export {XMLChild} from './lib/annotations/XMLChild';
export {XMLElement} from './lib/annotations/XMLElement';
export { XMLText } from './lib/annotations/XMLText';
export { XMLAttribute } from './lib/annotations/XMLAttribute';
export { XMLChild } from './lib/annotations/XMLChild';
export { XMLElement } from './lib/annotations/XMLElement';

export {XMLElement as xml} from './lib/models/XMLElement';
export {XMLAttribute as xmlAttribute} from './lib/models/XMLAttribute';
export {XMLChild as xmlChild} from './lib/models/XMLChild';

export {ISchemaOptions} from './lib/interfaces/ISchemaOptions';
export {IXMLAttributeOptions} from './lib/interfaces/IXMLAttributeOptions';
export {IXMLChildOptions} from './lib/interfaces/IXMLChildOptions';
export {IXMLElementOptions} from './lib/interfaces/IXMLElementOptions';
export { XMLElement as xml } from './lib/models/XMLElement';
export { XMLText as xmlText } from './lib/models/XMLText';
export { XMLAttribute as xmlAttribute } from './lib/models/XMLAttribute';
export { XMLChild as xmlChild } from './lib/models/XMLChild';

export { ISchemaOptions } from './lib/interfaces/ISchemaOptions';
export { IXMLTextOptions } from './lib/interfaces/IXMLTextOptions';
export { IXMLAttributeOptions } from './lib/interfaces/IXMLAttributeOptions';
export { IXMLChildOptions } from './lib/interfaces/IXMLChildOptions';
export { IXMLElementOptions } from './lib/interfaces/IXMLElementOptions';
14 changes: 14 additions & 0 deletions lib/annotations/XMLText.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import "reflect-metadata";
import { XMLText as XMLTextModel } from "../models/XMLText";
import { IXMLTextOptions } from "../interfaces/IXMLTextOptions";

export function XMLText(target: any, key: string, descriptor?: TypedPropertyDescriptor<any>): void;
export function XMLText(options: IXMLTextOptions): Function;
export function XMLText(...args: any[]): void | Function {
if (args.length === 1) {
return (target: any, key: string, descriptor?: TypedPropertyDescriptor<any>) => {
return XMLTextModel.annotate(target, key, args[0], descriptor);
};
}
return XMLTextModel.annotate(args[0], args[1], void 0, args[2]);
}
8 changes: 8 additions & 0 deletions lib/interfaces/ICustomXMLTextOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IXMLTextOptions } from './IXMLTextOptions';

export interface ICustomXMLTextOptions extends IXMLTextOptions {
name: string;
getter?: (entity?: any) => any;
value?: any;
restrictTo?: any[];
}
6 changes: 6 additions & 0 deletions lib/interfaces/IFullXMLTextOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IXMLTextOptions } from './IXMLTextOptions';

export interface IFullXMLTextOptions extends IXMLTextOptions {
name: string;
getter: (entity: any) => any;
}
4 changes: 4 additions & 0 deletions lib/interfaces/IXMLTextOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IXMLTextOptions {
name?: string;
required?: boolean;
}
41 changes: 19 additions & 22 deletions lib/models/XMLAttribute.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
import {XMLElement} from "./XMLElement";
import {IXMLAttributeOptions} from "../interfaces/IXMLAttributeOptions";
import {IFullXMLAttributeOptions} from "../interfaces/IFullXMLAttributeOptions";
import {ICustomXMLAttributeOptions} from "../interfaces/ICustomXMLAttributeOptions";
import {createCustomGetter} from "../utils";
import { XMLElement } from './XMLElement';
import { IXMLAttributeOptions } from '../interfaces/IXMLAttributeOptions';
import { IFullXMLAttributeOptions } from '../interfaces/IFullXMLAttributeOptions';
import { ICustomXMLAttributeOptions } from '../interfaces/ICustomXMLAttributeOptions';
import { createCustomGetter } from '../utils';

export class XMLAttribute {

private name: string;

static annotate(target: any,
key: string,
options: IXMLAttributeOptions = {},
descriptor?: TypedPropertyDescriptor<any>): void {

static annotate(
target: any,
key: string,
options: IXMLAttributeOptions = {},
descriptor?: TypedPropertyDescriptor<any>
): void {
const element = XMLElement.getOrCreateIfNotExists(target);
const fullOptions = Object.assign({
const fullOptions = {
getter(entity: any): any {
if (descriptor && descriptor.get) {
return descriptor.get.call(entity);
}
return entity[key];
}
}, options);
},
...options
};

fullOptions.name = options.name || key;

Expand All @@ -33,35 +34,31 @@ export class XMLAttribute {
const hasValue = options.value !== void 0;

if ((hasGetter && hasValue) || (!hasGetter && !hasValue)) {

throw new Error(`Either a getter or a value has to be defined for attribute "${options.name}".`);
}

const fullOptions = Object.assign({
const fullOptions = {
getter: createCustomGetter(options),
}, options);

...options
};

return new XMLAttribute(fullOptions);
}

setSchema(target: any, entity: any): void {

const value = this.options.getter.call(null, entity);

if (value !== void 0) {
target[this.name] = value;
} else if (this.options.required) {

throw new Error(`Attribute ${this.name} is required, but empty.`);
}
}

private constructor(private options: IFullXMLAttributeOptions) {

this.name = options.name;

if (options.namespace) {

this.name = options.namespace + ':' + this.name;
}
}
Expand Down
90 changes: 41 additions & 49 deletions lib/models/XMLChild.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
import 'es6-shim';
import {XMLElement} from "./XMLElement";
import * as _ from "lodash";
import {ns} from "../utils";
import {IXMLChildOptions} from "../interfaces/IXMLChildOptions";
import {IFullXMLChildOptions} from "../interfaces/IFullXMLChildOptions";
import {ICustomXMLChildOptions} from "../interfaces/ICustomXMLChildOptions";
import {createCustomGetter} from "../utils";
import {ISchemaOptions} from "../interfaces/ISchemaOptions";

type Tree = {name: string; attributes: {[name: string]: string}};
import { XMLElement } from './XMLElement';
import * as _ from 'lodash';
import { IXMLChildOptions } from '../interfaces/IXMLChildOptions';
import { IFullXMLChildOptions } from '../interfaces/IFullXMLChildOptions';
import { ICustomXMLChildOptions } from '../interfaces/ICustomXMLChildOptions';
import { createCustomGetter, ns } from '../utils';
import { ISchemaOptions } from '../interfaces/ISchemaOptions';

interface ITree {
name: string;
attributes: { [name: string]: string };
}

export class XMLChild {

private name: string;

static annotate(target: any,
key: string,
options: IXMLChildOptions = {},
descriptor?: TypedPropertyDescriptor<any>): void {

static annotate(
target: any,
key: string,
options: IXMLChildOptions = {},
descriptor?: TypedPropertyDescriptor<any>
): void {
const element = XMLElement.getOrCreateIfNotExists(target);
const fullOptions = Object.assign({
const fullOptions = {
getter(entity: any): any {
if (descriptor && descriptor.get) {
return descriptor.get.call(entity);
}

return entity[key];
}
}, options);
},
...options
};

fullOptions.name = options.name || key;

Expand All @@ -40,37 +43,32 @@ export class XMLChild {
const hasValue = options.value !== void 0;

if ((hasGetter && hasValue) || (!hasGetter && !hasValue)) {

throw new Error(`Either a getter or a value has to be defined for attribute "${options.name}".`);
}

const fullOptions = Object.assign({
const fullOptions = {
getter: createCustomGetter(options),
}, options);
...options
};

return new XMLChild(fullOptions);
}

setSchema(target: any, parentEntity: any, isAsync: boolean = false, schemaOptions: ISchemaOptions): any {

const entity = this.options.getter.call(null, parentEntity);
const process = (schema: any) => {

if (schema !== void 0 && schema !== null) {

const structure: string|undefined = this.options.implicitStructure;
const structure: string | undefined = this.options.implicitStructure;
if (structure) {

// a schema can be an array or an object,
// so we ensure that this is always an
// array and don't have to distinguish
[].concat(schema).forEach(_schema => this.resolveImplicitStructure(structure, target, _schema));
} else {

if (entity === schema && this.options.nestedNamespace) {
let nsSchema = {};
const nsSchema = {};

for (let key in schema) {
for (const key in schema) {
if (schema.hasOwnProperty(key)) {
nsSchema[ns(this.options.nestedNamespace, key)] = schema[key];
}
Expand All @@ -85,17 +83,13 @@ export class XMLChild {
};

if (isAsync) {

XMLElement.getSchemaAsync(entity, schemaOptions)
.then(schema => process(schema));
XMLElement.getSchemaAsync(entity, schemaOptions).then(schema => process(schema));
} else {

process(XMLElement.getSchema(entity, schemaOptions));
}
}

private constructor(private options: IFullXMLChildOptions) {

this.name = options.name;

if (options.stripPluralS) {
Expand All @@ -110,22 +104,24 @@ export class XMLChild {
private resolveImplicitStructure(structure: string, target: any, schema: any): void {
const PLACEHOLDER = '$';

if (!new RegExp(`.\\.\\${PLACEHOLDER}`).test(structure) &&
if (
!new RegExp(`.\\.\\${PLACEHOLDER}`).test(structure) &&
!new RegExp(`.\\.\\${PLACEHOLDER}\\..`).test(structure) &&
!new RegExp(`\\${PLACEHOLDER}\\..`).test(structure)) {
!new RegExp(`\\${PLACEHOLDER}\\..`).test(structure)
) {
throw new Error(`Structure '${structure}' is invalid`);
}

let tree = this.getImplicitNodeTree(structure);
const tree = this.getImplicitNodeTree(structure);
const indexOfPlaceholder = tree.findIndex(node => node.name === PLACEHOLDER);
tree[indexOfPlaceholder].name = this.name;

for (let i = 0; i < tree.length; i++) {
let node = tree[i];
const node = tree[i];
if (!Array.isArray(target)) {
if (!target[node.name]) {
if (i !== indexOfPlaceholder) {
target[node.name] = {'@': node.attributes};
target[node.name] = { '@': node.attributes };
} else {
target[node.name] = [];
}
Expand All @@ -140,19 +136,18 @@ export class XMLChild {
if (Array.isArray(target)) {
target.push(schema);
} else {
target[node.name] = _.merge(schema, {'@': node.attributes});
target[node.name] = _.merge(schema, { '@': node.attributes });
}
}
}
}

private getImplicitNodeTree(treeString: string): Tree[] {
private getImplicitNodeTree(treeString: string): ITree[] {
const REGEX = new RegExp('([a-z\\w0-9-\\$\\:]+?)\\[(.*?)\\]|([a-z\\w0-9-\\$\\:]+)', 'gi');
let match = REGEX.exec(treeString);
const tree: Tree[] = [];
const tree: ITree[] = [];

while (match !== null) {

const tagName = match[1] || match[3];
const attributeString = match[2];
tree.push({
Expand All @@ -164,14 +159,11 @@ export class XMLChild {
return tree;
}

private getAttributes(attributeString: string): {[attrName: string]: string} {

let attributes = {};
private getAttributes(attributeString: string): { [attrName: string]: string } {
const attributes = {};

if (attributeString) {

attributeString.split(',').forEach(val => {

const attributesArr = val.split('=');
attributes[attributesArr[0]] = attributesArr[1];
});
Expand Down
Loading