UNPKG

jspurefix

Version:
210 lines (186 loc) 6.29 kB
import * as fs from 'fs' import { SAXParser } from 'sax' import { IDictDoneCb, ISaxNode, SAXStream } from '../../dict-primitive' import { FixDefinitions } from '../../definition' import { FieldDefinitionParser } from './field-definition-parser' import { FieldSetParser } from './field-set-parser' import { MessageParser } from './message-parser' import { NodeParser } from './node-parser' import { FixParser } from '../../fix-parser' import { FixDefinitionSource, VersionUtil } from '../../fix-versions' import { GetJsFixLogger } from '../../../config' import { promisify } from 'util' enum ParseState { Begin = 1, FieldDefinitions = 2, ComponentsFirstPass = 3, ComponentsSecondPass = 4, ComponentsThirdPass = 5, Messages = 6 } export class QuickFixXmlFileParser extends FixParser { public parseState: ParseState = ParseState.Begin public numberPasses: number = 0 public definitions: FixDefinitions private readonly singlePass = promisify(QuickFixXmlFileParser.subscribe) constructor (public readonly xmlPath: string, public getLogger: GetJsFixLogger) { super() } private static subscribe (instance: QuickFixXmlFileParser, saxStream: SAXStream, done: IDictDoneCb): void { let parser: NodeParser instance.numberPasses++ switch (instance.parseState) { case ParseState.Begin: { instance.parseState = ParseState.FieldDefinitions break } case ParseState.FieldDefinitions: { instance.parseState = ParseState.ComponentsFirstPass break } case ParseState.ComponentsFirstPass: { instance.parseState = ParseState.ComponentsSecondPass break } case ParseState.ComponentsSecondPass: { instance.parseState = ParseState.ComponentsThirdPass break } case ParseState.ComponentsThirdPass: { instance.parseState = ParseState.Messages break } } const saxParser: SAXParser = saxStream._parser saxStream.on('error', (e: Error) => { done(e, null) }) saxStream.on('closetag', (name) => { if (parser != null) { parser.close(saxParser.line, name) } switch (name) { case 'repository': case 'messages': case 'components': case 'header': case 'trailer': { parser = null break } } }) saxStream.on('opentag', (node) => { const saxNode: ISaxNode = node as ISaxNode switch (saxNode.name) { case 'fix': { switch (instance.parseState) { case ParseState.FieldDefinitions: { const major = saxNode.attributes.major const minor = saxNode.attributes.major const description: string = `FIX.${major}.${minor}` instance.definitions = new FixDefinitions(FixDefinitionSource.QuickFix, VersionUtil.resolve(description)) break } } break } case 'fields': { switch (instance.parseState) { case ParseState.FieldDefinitions: { parser = new FieldDefinitionParser(instance) break } default: { parser = null } } break } case 'messages': { switch (instance.parseState) { case ParseState.Messages: { parser = new MessageParser(instance) break } default: break } break } case 'components': { // can have a group containing components which contain themselves components of groups // each step will build forward references to a deeper level to ensure final messages // have all dependencies correctly defined. switch (instance.parseState) { case ParseState.ComponentsFirstPass: case ParseState.ComponentsSecondPass: case ParseState.ComponentsThirdPass: parser = new FieldSetParser(instance) break } break } case 'field': case 'value': case 'component': case 'group': { if (parser != null) { parser.open(saxParser.line, saxNode) } break } case 'message': { switch (instance.parseState) { case ParseState.Messages: { if (parser != null) { parser.open(saxParser.line, saxNode) } break } default: break } break } case 'header': case 'trailer': { switch (instance.parseState) { case ParseState.Messages: { parser = new FieldSetParser(instance) parser.open(saxParser.line, node) break } } break } } }) saxStream.on('ready', () => { if (done) { parser = null done(null, instance.definitions) } }) } public parse (): Promise<FixDefinitions> { return new Promise<FixDefinitions>(async (accept, reject) => { try { await this.onePass() // first fetch all field definitions await this.onePass() // first pass of components, will not know about forward components. await this.onePass() // second pass of components top level with forward references replace await this.onePass() // third pass of components all fully resolved i.e. pick up versions from pass above await this.onePass() // lastly messages with all dependencies accept(this.definitions) } catch (e) { reject(e) } }) } private async onePass (): Promise<any> { const pass: fs.ReadStream = fs.createReadStream(this.xmlPath) const saxStream: SAXStream = require('sax').createStream(true, {}) pass.pipe(saxStream) await this.singlePass(this, saxStream) } }