UNPKG

jspurefix

Version:
347 lines (323 loc) 12.2 kB
import { MsgParser } from '../msg-parser' import { Tags } from '../tags' import { FixDefinitions, MessageDefinition } from '../../dictionary/definition' import { ISaxNode, SAXStream } from '../../dictionary' import { ContainedField, ContainedFieldType, ContainedComponentField, ContainedGroupField, ContainedSimpleField, ContainedFieldSet } from '../../dictionary/contained' import { SegmentDescription, SegmentType } from '../segment-description' import { IJsFixConfig, IJsFixLogger } from '../../config' import { MsgView } from '../msg-view' import { Structure } from '../structure' import { FixmlView } from './fixml-view' import { Readable } from 'stream' export class FiXmlParser extends MsgParser { private readonly locations: Tags private values: string[] = [] private readonly saxStream: SAXStream private readonly definitions: FixDefinitions private readonly segments: SegmentDescription[] = [] private readonly segmentStack: SegmentDescription[] = [] private readonly logger: IJsFixLogger private last: SegmentDescription private raw: string constructor (public readonly config: IJsFixConfig, public readonly readStream: Readable, public readonly maxMessageLocations: number = 10 * 1024) { super() this.definitions = this.config.definitions const description = config.description const me = description.application.name this.logger = config.logFactory.logger(`${me}:FiXmlParser`) this.saxStream = require('sax').createStream(true, {}) this.locations = new Tags(this.definitions, maxMessageLocations) this.logger.info('subscribe to stream') this.subscribe() } private reset (): void { const segments = this.segments while (segments.length > 0) { segments.pop() } const stack = this.segmentStack this.last = null this.raw = null this.values = [] this.locations.reset() while (stack.length) { stack.pop() } } private subscribe (): void { const writeStream = this.saxStream const readStream = this.readStream let instance = this readStream.pipe(writeStream).on('ready', () => { this.logger.info('stream close event') this.emit('close') }) readStream.on('error', (e) => { this.logger.error(e) this.emit('error', e) }) readStream.on('end', () => { this.logger.info('stream end event') this.emit('end') }) writeStream.on('data', (i: Buffer) => { if (instance.last) { instance.emit('decoded', instance.last.name, i.toString()) } else { this.raw = i.toString() } }) writeStream.on('opentag', (node) => { const stack = this.segmentStack const saxNode: ISaxNode = node as ISaxNode switch (saxNode.name) { case 'FIXML': this.reset() break case 'Batch': { const locations = this.locations const ptr = locations.nextTagPos - 1 const segment: SegmentDescription = new SegmentDescription(saxNode.name, -1, null, ptr, stack.length, SegmentType.Batch) segment.startPosition = 0 stack.push(segment) break } case 'Hdr': { this.hdr(saxNode) break } default: { const stack = this.segmentStack const isBatch: boolean = stack.length === 1 && stack[0].type === SegmentType.Batch const isMsg: boolean = stack.length === 0 if (isMsg) { // one single message this.msg(saxNode) } else if (isBatch) { // one message within a batch or it this.msgInBatch(saxNode) } else { // or it is a structure within a message this.element(saxNode) } } } }) writeStream.on('closetag', (name: string) => { this.pop(name) }) writeStream.on('error', (e: Error) => { this.emit('error', e) }) } private hdr (saxNode: ISaxNode): void { const stack = this.segmentStack if (stack.length === 0) { throw new Error(`Hdr not expected before batch or message ${saxNode.name}`) } let peek: SegmentDescription = stack[stack.length - 1] switch (peek.type) { case SegmentType.Batch: { // manually handle this component const hdr = this.definitions.component.get('StandardHeader') const segment: SegmentDescription = this.parseAttributes(saxNode.name, hdr, saxNode, SegmentType.Component) this.segmentStack.push(segment) break } case SegmentType.Msg: { this.element(saxNode) break } default: throw new Error(`Hdr not expected before batch or message ${saxNode.name}`) } } private msgInBatch (saxNode: ISaxNode): void { this.logger.info(` ${saxNode.name}: start batch`) const locations = this.locations const batch = this.segmentStack[0] const ptr = locations.nextTagPos if (!batch.delimiterPositions) { batch.startGroup(-1) } batch.addDelimiterPosition(ptr) this.logger.debug(` ${saxNode.name}: begin parse msg ${batch.delimiterPositions.length} in batch`) this.msg(saxNode, true) } private getView (): MsgView { const structure: Structure = new Structure(this.locations, this.segments) const last = structure.segments[structure.segments.length - 1] return new FixmlView(last, this.values, structure) } private pop (name: string): SegmentDescription { const locations = this.locations const stack = this.segmentStack const segments = this.segments while (stack.length > 0) { const pop = stack.pop() const ptr = locations.nextTagPos - 1 pop.end(segments.length, ptr, locations.tagPos[ptr].tag) segments[segments.length] = pop switch (pop.type) { case SegmentType.Msg: { // raise msg event const last = segments[segments.length - 1] this.last = last this.emit('msg', last.name, this.getView()) if (this.raw) { this.emit('decoded', this.last.name, this.raw) this.raw = null } break } case SegmentType.Batch: { const last = segments[segments.length - 1] this.logger.debug(`emit batch with ${pop.delimiterPositions.length} elements`) this.emit('batch', last.set.abbreviation, this.getView()) break } } if (pop.name === name) { return pop } } return null } private startGroup (saxNode: ISaxNode, gf: ContainedGroupField) { const locations = this.locations const stack: SegmentDescription[] = this.segmentStack const ptr = locations.nextTagPos const def = gf.definition const segment: SegmentDescription = this.parseAttributes(saxNode.name, def, saxNode, SegmentType.Component) const group: SegmentDescription = new SegmentDescription(def.name, locations.tagPos[ptr].tag, def, ptr, stack.length, SegmentType.Group) group.startGroup(locations.tagPos[ptr].tag) group.addDelimiterPosition(ptr) stack.push(group) stack.push(segment) } private getNextField (saxNode: ISaxNode): ContainedField { const stack: SegmentDescription[] = this.segmentStack while (stack.length > 0) { let peek: SegmentDescription = stack[stack.length - 1] let field = peek.set.localNameToField.get(saxNode.name) if (field) { return field } // if this is a group of the same type as already on stack // take the field from the next level up if (peek.type === SegmentType.Group && stack.length > 1) { const contained = stack[stack.length - 2].set.localNameToField.get(saxNode.name) if (contained instanceof ContainedGroupField) { if (contained.definition.name === peek.name) { // this is the same type for next instance in the same group. return contained } } } // then have ended previous group. Pop and return to see if this field lives on the parent const locations = this.locations const ptr = locations.nextTagPos - 1 const pop = stack.pop() const segments = this.segments pop.end(segments.length, ptr, locations.tagPos[ptr].tag) segments[segments.length] = pop } return null } private dispatch (saxNode: ISaxNode, field: ContainedField) { switch (field.type) { case ContainedFieldType.Component: { const cf: ContainedComponentField = field as ContainedComponentField const segment: SegmentDescription = this.parseAttributes(saxNode.name, cf.definition, saxNode, SegmentType.Component) this.segmentStack.push(segment) break } case ContainedFieldType.Group: { this.dispatchGroup(saxNode, field as ContainedGroupField) break } } } private dispatchGroup (saxNode: ISaxNode, gf: ContainedGroupField): void { const stack: SegmentDescription[] = this.segmentStack const peek: SegmentDescription = stack[stack.length - 1] switch (peek.type) { case SegmentType.Msg: case SegmentType.Component: { this.startGroup(saxNode, gf) break } case SegmentType.Group: { if (gf.name === saxNode.name) { const ptr = this.locations.nextTagPos peek.addDelimiterPosition(ptr) stack[stack.length] = this.parseAttributes(saxNode.name, gf.definition, saxNode, SegmentType.Component) } else { throw new Error(`expected another group instance of ${gf.name} but got ${saxNode.name}`) } break } default: throw new Error(`dispatchGroup has field ${gf.name} peek type ${peek.type}`) } } private element (saxNode: ISaxNode): void { // may terminate a group and move fields const field = this.getNextField(saxNode) if (!field) { const stack: SegmentDescription[] = this.segmentStack let peek: SegmentDescription = stack[stack.length - 1] throw new Error(`field ${saxNode.name} not known in set ${peek.set.name}`) } this.dispatch(saxNode, field) } private msg (saxNode: ISaxNode, inBatch: boolean = false): void { this.logger.debug(`${saxNode.name}: begin parse msg`) const type: string = saxNode.name const def: MessageDefinition = this.definitions.message.get(type) if (!def) { throw new Error(`unknown message type ${type}`) } if (inBatch) { const batch = this.segmentStack[0] batch.set = def } const segment: SegmentDescription = this.parseAttributes(type, def, saxNode, SegmentType.Msg) this.segmentStack.push(segment) } private parseAttributes (name: string, set: ContainedFieldSet, saxNode: ISaxNode, type: SegmentType): SegmentDescription { const locations = this.locations const attributes = saxNode.attributes const values = this.values let ptr: number if (attributes) { const keys: string[] = Object.keys(attributes) ptr = keys.length > 0 ? locations.nextTagPos : locations.nextTagPos - 1 for (let j: number = 0; j < keys.length; ++j) { const k: string = keys[j] const v: string = attributes[k] const field: ContainedSimpleField = set.localNameToField.get(k) as ContainedSimpleField if (!field) { this.logger.warning(`no field ${k} in set ${set.name}`) locations.store(j, 1, -1) } else { locations.store(j, 1, field.definition.tag) } values[values.length] = v } return new SegmentDescription(name, locations.tagPos[ptr].tag, set, ptr, this.segmentStack.length, type) } } }