UNPKG

jspurefix

Version:
218 lines (196 loc) 7.43 kB
import { FixDefinitions } from '../../dictionary/definition' import { ILooseObject } from '../../collections/collection' import { ContainedFieldSet, ContainedField, ContainedGroupField, ContainedComponentField, ContainedSimpleField } from '../../dictionary/contained' import { Ascii } from '../ascii' import { TagType } from '../tags' import { MsgEncoder } from '../msg-encoder' import { ElasticBuffer } from '../elastic-buffer' import moment = require('moment') import { dispatchFields, IFieldDispatcher } from '../../dictionary' interface IPopulatedAttributes { fields: ContainedSimpleField[], values: any[] } export class FixmlEncoder extends MsgEncoder { public attributePerLine: boolean = false public readonly eol: string = require('os').EOL private readonly beginDoc: string = `<FIXML>${this.eol}` private readonly endDoc: string = '</FIXML>' private readonly beginBatch: string = `<Batch>${this.eol}` private readonly endBatch: string = '</Batch>' public constructor (public readonly buffer: ElasticBuffer, public readonly definitions: FixDefinitions) { super(definitions) } private static asString (sf: ContainedSimpleField, v: any): string { switch (sf.definition.tagType) { case TagType.String: { return v as string } case TagType.Int: case TagType.Float: case TagType.Length: { return v.toString() } case TagType.Boolean: { return v ? 'Y' : 'N' } case TagType.UtcTimestamp: { const d: Date = v as Date return moment(d).format('YYYY-MM-DDTHH:mm:ss.SSS') } case TagType.UtcTimeOnly: { const d: Date = v as Date return moment.utc(d).format('HH:mm:ss.SSS') } case TagType.LocalDate: { const d: Date = v as Date return moment(d).format('YYYYMMDD') } case TagType.UtcDateOnly: { const d: Date = v as Date return moment.utc(d).format('YYYYMMDD') } } } public encodeSet (o: ILooseObject, set: ContainedFieldSet): void { const batch: ILooseObject[] = o.Batch const toWrite: ILooseObject[] = batch || [o] let depth = batch ? 1 : 0 const buffer = this.buffer const begin = this.beginDoc const indent: string = '\t' const endBatch = batch ? this.endBatch : '' const eol = this.eol buffer.reset() buffer.writeString(begin) if (batch) { this.batchStart(o, set, depth) } toWrite.forEach((next: ILooseObject) => { this.toXml(next, set.abbreviation, set, depth + 1) buffer.writeString(eol) }) if (batch) { buffer.writeString(`${indent}${endBatch}`) } buffer.writeString(this.endDoc) } private batchStart (o: ILooseObject, set: ContainedFieldSet, depth: number) { const buffer = this.buffer const indent: string = '\t' const beginBatch = this.beginBatch const hdr = o.StandardHeader const eol = this.eol buffer.writeString(`${indent}${beginBatch}`) if (hdr) { const h = set.fields[0] as ContainedComponentField this.toXml(hdr, h.name, h.definition, depth + 1) buffer.writeString(eol) } } private toXml (o: ILooseObject, name: string, set: ContainedFieldSet, depth: number): void { const buffer = this.buffer const selfClose: string = '/>' const close: string = '>' const open: string = '<' const indent: string = '\t'.repeat(depth) const newLine = this.eol const fields: ContainedField[] = this.getPopulatedFields(set, o) const eol: string = fields.length === 0 ? selfClose : close buffer.writeString(`${indent}${open}`) buffer.writeString(`${name} `) this.attributes(o, set, depth, this.attributePerLine) buffer.writeString(`${eol}`) dispatchFields(fields, { group: (g: ContainedGroupField) => this.complexGroup(o, g, depth), component: (c: ContainedComponentField) => this.complexComponent(o, c, depth) } as IFieldDispatcher) if (fields.length) { const end: string = `${newLine}${indent}</${name}>` buffer.writeString(`${end}`) } } private getPopulatedFields (set: ContainedFieldSet, o: ILooseObject): ContainedField[] { const keys: string[] = Object.keys(o) const fields: ContainedField[] = keys.reduce((a: ContainedField[], current: string) => { const field: ContainedField = set.localNameToField.get(current) if (field && !set.nameToLocalAttribute.containsKey(current)) { a.push(field) } return a }, []) fields.sort((a: ContainedField, b: ContainedField) => a.position - b.position) return fields } private encodeAttribute (name: string, val: string): void { if (val == null) { return } const buffer = this.buffer buffer.writeString(name) buffer.writeChar(Ascii.Equal) buffer.writeChar(Ascii.Dq) buffer.writeString(val) buffer.writeChar(Ascii.Dq) } private attributes (o: ILooseObject, set: ContainedFieldSet, depth: number, attributePerLine: boolean): void { const newLine = this.eol const indent: string = '\t'.repeat(depth + 1) const attributes = set.localAttribute const buffer = this.buffer if (attributes.length && attributePerLine) { buffer.writeString(newLine) buffer.writeString(indent) } const populatedAttributes: IPopulatedAttributes = this.getPopulatedAttributes(o, attributes) for (let a = 0; a < populatedAttributes.values.length; ++a) { const last = a === populatedAttributes.values.length - 1 const f = populatedAttributes.fields[a] if (a || this.attributePerLine) buffer.writeChar(Ascii.Space) this.encodeAttribute(f.name, FixmlEncoder.asString(f, populatedAttributes.values[a])) if (!last && attributePerLine) { buffer.writeString(newLine) buffer.writeString(indent) } } } private getPopulatedAttributes (o: ILooseObject, attributes: ContainedSimpleField[]): IPopulatedAttributes { return attributes.reduce((a: IPopulatedAttributes, f: ContainedSimpleField) => { let v: any = o[f.definition.name] if (v == null) { v = o[f.name] } if (v != null) { a.values.push(v) a.fields.push(f) } return a }, { values: [], fields: [] } as IPopulatedAttributes) } private complexGroup (o: ILooseObject, field: ContainedField, depth: number) { const gf: ContainedGroupField = field as ContainedGroupField const elements: ILooseObject[] = o[gf.definition.name] if (elements) { if (Array.isArray(elements)) { for (const e of elements) { this.buffer.writeString(this.eol) this.toXml(e, gf.name, gf.definition,depth + 1) } } else { throw new Error(`expected array for member ${gf.definition.name}`) } } } private complexComponent (o: ILooseObject, field: ContainedField, depth: number) { const cf: ContainedComponentField = field as ContainedComponentField const def = cf.definition const instance: ILooseObject = o[def.name] if (instance) { this.buffer.writeString(this.eol) this.toXml(instance, cf.name, def, depth + 1) } } }