jspurefix
Version:
pure node js fix engine
210 lines (186 loc) • 6.29 kB
text/typescript
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)
}
}