UNPKG

jspurefix

Version:
219 lines (193 loc) 6.91 kB
import { ILooseObject } from '../../collections/collection' import { ContainedGroupField, ContainedSimpleField, ContainedFieldSet, ContainedField, ContainedFieldType, ContainedComponentField } from '../../dictionary/contained' import { SimpleFieldDefinition, FixDefinitions } from '../../dictionary/definition' import { MsgEncoder } from '../msg-encoder' import { ElasticBuffer } from '../elastic-buffer' import { TimeFormatter } from './time-formatter' import { ITimeFormatter } from '../time-formatter' import { Ascii } from '../ascii' import { Tags, TagType } from '../tags' import { dispatchFields } from '../../dictionary' export class AsciiEncoder extends MsgEncoder { public bodyLengthPos: number constructor (public readonly buffer: ElasticBuffer, public readonly definitions: FixDefinitions, public readonly timeFormatter: ITimeFormatter = new TimeFormatter(buffer), public readonly delimiter: number = Ascii.Soh) { super(definitions) } private static checkGroupInstanceHasDelimiter (gf: ContainedGroupField, instance: ILooseObject): boolean { const delimiterField: ContainedSimpleField = gf.definition.firstSimple if (!delimiterField) { throw new Error(`group definition has delimiter field ${delimiterField.definition.name}`) } // may have a group represented by a component where first simple field is further down. while (instance != null) { if (instance[delimiterField.definition.name] != null) { return true } const first = gf.definition.fields[0] switch (first.type) { case ContainedFieldType.Component: { const cf: ContainedComponentField = first as ContainedComponentField instance = instance[cf.definition.name] break } default: instance = null } } } public encodeSet (objectToEncode: ILooseObject, set: ContainedFieldSet): void { const fields: ContainedField[] = this.getFields(set, objectToEncode) dispatchFields(fields, { simple: (sf: ContainedSimpleField) => { const val: any = objectToEncode[sf.name] if (val != null) { this.encodeSimple(objectToEncode, set, sf, val) } }, component: (cf: ContainedComponentField) => { const instance: ILooseObject = objectToEncode[cf.definition.name] if (instance) { this.encodeSet(instance, cf.definition) } }, group: (gf: ContainedGroupField) => { this.encodeInstances(objectToEncode, gf) } }) } private getFields (set: ContainedFieldSet, o: ILooseObject): ContainedField[] { const keys: string[] = Object.keys(o) let j: number = 0 const fields: ContainedField[] = keys.reduce((a: ContainedField[], current: string) => { const field: ContainedField = set.localNameToField.get(current) if (field) { a[j++] = field } return a }, new Array(keys.length)) fields.sort((a: ContainedField, b: ContainedField) => a.position - b.position) return fields } private encodeInstances (o: ILooseObject, gf: ContainedGroupField): void { const noOfField: SimpleFieldDefinition = gf.definition.noOfField const instances: ILooseObject[] = o[gf.name] || o[noOfField.name] const buffer = this.buffer if (!Array.isArray(instances)) { throw new Error(`expected array instance for group ${noOfField.name}`) } if (instances) { // a repeated group has number of instances at the start of group this.WriteTagEquals(noOfField.tag) buffer.writeWholeNumber(instances.length) buffer.writeChar(this.delimiter) instances.forEach((i: ILooseObject) => { if (AsciiEncoder.checkGroupInstanceHasDelimiter(gf, i)) { this.encodeSet(i, gf.definition) } else { const delimiter: ContainedSimpleField = gf.definition.firstSimple throw new Error(`group instance with no delimiter field ${delimiter.definition.name}`) } }) } } private WriteTagEquals (tag: number): void { const buffer = this.buffer buffer.writeWholeNumber(tag) buffer.writeChar(Ascii.Equal) } private encodeSimple (o: ILooseObject, set: ContainedFieldSet, sf: ContainedSimpleField, val: any): void { const definition = sf.definition const tag: number = definition.tag const buffer = this.buffer const delimiter = this.delimiter const tf = this.timeFormatter const pos = buffer.getPos() let tagType: TagType if (typeof val === 'string') { switch (definition.tagType) { case TagType.Boolean: { tagType = definition.tagType const vs: string = val const first: string = vs.length > 0 ? vs.charAt(0) : 'N' val = first === 'Y' || first === 'T' break } default: { tagType = TagType.String } } } else { tagType = definition.tagType } switch (tagType) { case TagType.RawData: { // may need to first write raw message length (see below) break } default: { this.WriteTagEquals(tag) break } } switch (tagType) { case TagType.String: { buffer.writeString(val as string) break } case TagType.Float: { buffer.writeNumber(val as number) break } case TagType.Int: case TagType.Length: { buffer.writeWholeNumber(val as number) break } case TagType.Boolean: { buffer.writeBoolean(val as boolean) break } case TagType.UtcTimestamp: { tf.writeUtcTimestamp(val as Date) break } case TagType.UtcTimeOnly: { tf.writeUtcTime(val as Date) break } case TagType.UtcDateOnly: { tf.writeUtcDate(val as Date) break } case TagType.LocalDate: { tf.writeLocalDate(val as Date) break } case TagType.RawData: { const b = val as Buffer const lenField: ContainedSimpleField = set.fields[sf.position - 1] as ContainedSimpleField if (o[lenField.name] == null) { this.WriteTagEquals(lenField.definition.tag) buffer.writeWholeNumber(b.length) buffer.writeChar(delimiter) } this.WriteTagEquals(tag) buffer.writeBuffer(b) break } default: { buffer.writeString(val as string) break } } buffer.writeChar(delimiter) switch (tag) { case Tags.BodyLengthTag: this.bodyLengthPos = pos + 2 break } } }