jspurefix
Version:
pure node js fix engine
468 lines (425 loc) • 15.6 kB
text/typescript
import { ISaxNode } from '../../sax-node'
import { XsdParser } from './xsd-parser'
import {
ContainedGroupField,
IContainedSet,
ContainedSetBuilder,
ContainedComponentField,
ContainedSimpleField,
ContainedField
} from '../../contained'
import {
MessageDefinition,
FixDefinitions,
GroupFieldDefinition,
ComponentFieldDefinition
} from '../../definition'
export class ComponentsParser extends XsdParser {
private readonly attributeGroups: Map<string, IAttributeGroup> = new Map<string, IAttributeGroup>()
private readonly groups: Map<string, IGroup> = new Map<string, IGroup>()
private readonly unboundElements: IElement[] = []
private readonly complexTypes: Map<string, IComplexType> = new Map<string, IComplexType>()
private newComplexTypes: IComplexType[]
private currentGroup: (IGroup | null)
private readonly currentAttributeGroupStack: IAttributeGroup[] = []
private currentComplexType: (IComplexType | null)
private previousComplexType: IComplexType
public constructor (public readonly definitions: FixDefinitions) {
super(definitions)
}
private static getName (group: (IGroup | undefined), attributeGroup: (IAttributeGroup | undefined), type: IComplexType): string {
let name: string
if (type?.appInfo) {
name = type.appInfo.name
} else if (group) {
name = group.name
} else if (attributeGroup) {
name = attributeGroup.name
} else {
name = type.name
}
return name
}
public value (line: number, n: string, v: string): void {
// do nothing
switch (n) {
case 'xs:documentation': {
if (this.currentComplexType?.annotation) {
this.currentComplexType.annotation.documentation = v
}
break
}
}
this.pending = null
}
public close (line: number, node: string): void {
switch (node) {
case 'xs:complexType': {
if (this.currentComplexType != null) {
const complex: IComplexType = this.currentComplexType
this.previousComplexType = complex
this.complexTypes.set(complex.name, complex)
this.newComplexTypes.push(complex)
this.currentComplexType = null
}
break
}
case 'xs:group': {
if (this.currentGroup) {
this.groups.set(this.currentGroup.name, this.currentGroup)
this.currentGroup = null
}
break
}
case 'xs:attributeGroup': {
const attributeStack: IAttributeGroup[] = this.currentAttributeGroupStack
if (attributeStack.length > 0) {
const group: IAttributeGroup | undefined = attributeStack.pop()
if (group?.name) {
this.attributeGroups.set(group?.name, group)
}
}
break
}
case 'xs:schema': {
this.insertFields()
break
}
}
}
public open (line: number, node: ISaxNode): void {
switch (node.name) {
case 'xs:schema': {
this.newComplexTypes = []
break
}
case 'xs:group': {
this.xsGroup(node)
break
}
case 'xs:attributeGroup': {
this.xsAttributeGroup(node)
break
}
case 'xs:element': {
this.xsElement(node)
break
}
case 'xs:appinfo': {
if (this.currentComplexType) {
this.currentComplexType.appInfo = {} as IAppInfo
}
break
}
case 'xs:attribute': {
this.xsAttribute(node)
break
}
case 'xs:complexType': {
const unbound: IElement[] = this.unboundElements
this.currentComplexType = {
name: node.attributes.name || unbound[unbound.length - 1].name
} as IComplexType
break
}
case 'xs:extension': {
const current: IComplexType | null = this.currentComplexType
if (current && node.attributes.base) {
current.extensionBase = node.attributes.base
}
break
}
case 'fm:Xref': {
this.assign(node, this.currentComplexType?.appInfo)
break
}
case 'xs:annotation': {
if (this.currentComplexType) {
this.currentComplexType.annotation = {} as IAnnotation
}
break
}
case 'xs:documentation': {
if (this.currentComplexType) {
this.pending = node.name
}
break
}
}
}
private xsAttribute (node: ISaxNode): void {
const attribute: IAttribute = {} as IAttribute
this.assign(node, attribute)
const stack: IAttributeGroup[] = this.currentAttributeGroupStack
if (stack.length > 0) {
const peek: IAttributeGroup = stack[stack.length - 1]
const groupAttributes: IAttribute[] = peek.attributes
groupAttributes[groupAttributes.length] = attribute
}
}
private xsGroup (node: ISaxNode): void {
if (node.attributes.name) {
this.currentGroup = {
name: node.attributes.name,
elements: [] as IElement[]
} as IGroup
} else if (node.attributes.ref) {
if (this.currentComplexType) {
this.currentComplexType.group = node.attributes.ref
} else if (this.currentGroup) {
// this is part of a sequence for a group
const elements = this.currentGroup.elements
elements[elements.length] = {
type: node.name,
name: node.attributes.ref
} as IElement
}
}
}
private xsAttributeGroup (node: ISaxNode): void {
const attributeStack: IAttributeGroup[] = this.currentAttributeGroupStack
if (node.attributes.name) {
attributeStack.push({
name: node.attributes.name,
attributes: [] as IAttribute[]
} as IAttributeGroup)
} else if (node.attributes.ref) {
if (this.currentComplexType) {
this.currentComplexType.attributeGroup = node.attributes.ref
} else if (attributeStack.length > 0) {
const peek: IAttributeGroup = attributeStack[attributeStack.length - 1]
peek.attributes[peek.attributes.length] = {
type: node.name,
name: node.attributes.ref
} as IAttribute
}
}
}
private xsElement (node: ISaxNode): void {
const element: IElement = {} as IElement
this.assign(node, element)
const currentComplex: IComplexType | null = this.currentComplexType
const currentGroup: IGroup | null = this.currentGroup
if (!currentGroup && currentComplex) {
if (!currentComplex.element) {
currentComplex.element = []
}
const elements: IElement[] = currentComplex.element
elements[elements.length] = element
} else if (currentGroup) {
const elements: IElement[] = currentGroup.elements
elements[elements.length] = element
} else {
if (element.substitutionGroup && this.previousComplexType) {
this.previousComplexType.messageName = element.name
} else {
this.unboundElements.push(element)
}
}
}
private addElement (set: IContainedSet, element: IElement): void {
const minOccurs: number = parseInt(element.minOccurs, 10)
const isGroup: boolean = element.maxOccurs === 'unbounded'
const isComponent: boolean = element.maxOccurs === '1' || !isGroup
const key = element.type || element.ref || element.name
const builder = new ContainedSetBuilder(set)
const containedType: IComplexType | undefined = this.complexTypes.get(key)
if (containedType) {
if (isComponent) {
const containedDefinition: ComponentFieldDefinition = this.getComponent(containedType)
const containedField: ContainedComponentField =
new ContainedComponentField(containedDefinition,
set.fields.length,
minOccurs > 0,
element.name)
builder.add(containedField)
} else if (isGroup) {
const containedDefinition: GroupFieldDefinition = this.getGroup(containedType)
const containedField: ContainedGroupField =
new ContainedGroupField(containedDefinition,
set.fields.length,
minOccurs > 0,
element.name)
builder.add(containedField)
}
} else {
if (key !== 'Message') {
throw new Error(`cannot resolve component ${key} contained in ${set.name}`)
}
}
}
private addElements (set: IContainedSet, elements: IElement[]): void {
if (elements) {
elements.forEach((element: IElement) => {
switch (element.type) {
case 'xs:group': {
const groupElements: IGroup | undefined = this.groups.get(element.name)
if (groupElements) {
this.addElements(set, groupElements.elements)
} else {
throw new Error(`unable to get xs:group ${element.name}`)
}
}
break
default: {
this.addElement(set, element)
}
}
})
}
}
private addSimpleAttribute (set: IContainedSet, attribute: IAttribute): void {
let sf = this.definitions.getSimple(attribute.type)
if (!sf) {
sf = this.definitions.getSimple(attribute.name, set.category)
}
if (sf) {
const contained: ContainedSimpleField = new ContainedSimpleField(sf,
set.fields.length,
attribute.use !== 'optional',
true,
attribute.name)
const builder = new ContainedSetBuilder(set)
builder.add(contained)
} else if (set.name !== 'FixmlAttributes') {
throw new Error(`unable to resolve simple attribute ${attribute.name}`)
}
}
private addAttributes (set: IContainedSet, attributes: IAttribute[]): void {
attributes.forEach((attribute: IAttribute) => {
switch (attribute.type) {
case 'xs:attributeGroup': {
const attributeGroup: IAttributeGroup | undefined = this.attributeGroups.get(attribute.name)
if (attributeGroup) {
this.addAttributes(set, attributeGroup.attributes)
} else {
throw new Error(`unable to get xs:attributeGroup ${attribute.name}`)
}
break
}
default: {
this.addSimpleAttribute(set, attribute)
}
}
})
}
private getGroup (type: IComplexType): GroupFieldDefinition {
const group: IGroup | undefined = this.groups.get(type.group)
const attributeGroup: IAttributeGroup | undefined = this.attributeGroups.get(type.attributeGroup)
const name: string = ComponentsParser.getName(group, attributeGroup, type)
const category = type.appInfo != null ? type.appInfo.Category : null
const groupDefinition: GroupFieldDefinition = new GroupFieldDefinition(name, name, category, null, null)
this.populateSet(type, groupDefinition)
return groupDefinition
}
private getComponent (type: IComplexType): ComponentFieldDefinition {
const definitions: FixDefinitions = this.definitions
const group: IGroup | undefined = this.groups.get(type.group)
const attributeGroup: IAttributeGroup | undefined = this.attributeGroups.get(type.attributeGroup)
let name: string = ComponentsParser.getName(group, attributeGroup, type)
const cached: ComponentFieldDefinition | undefined = definitions.component.get(name)
if (cached) {
return cached
}
const category = type.appInfo != null ? type.appInfo.Category : null
if (type.extensionBase) {
const base: IComplexType | undefined = this.complexTypes.get(type.extensionBase)
if (base) {
name = base.appInfo.name
}
}
const component: ComponentFieldDefinition = new ComponentFieldDefinition(name, name, category, null)
this.populateSet(type, component)
definitions.component.set(component.name, component)
definitions.component.set(type.name, component)
return component
}
private getMessage (type: IComplexType): MessageDefinition {
const definitions: FixDefinitions = this.definitions
const messages = definitions.message
const name: string = type.appInfo.name
const message: MessageDefinition = new MessageDefinition(name,
type.messageName,
type.appInfo.MsgID,
type.appInfo.Category,
type.annotation.documentation)
const builder = new ContainedSetBuilder(message)
const abstractMessage: ComponentFieldDefinition | undefined = definitions.component.get('Message')
abstractMessage?.fields.forEach((f: ContainedField) => {
builder.add(f)
})
this.populateSet(type, message)
messages.set(message.name, message)
if (type.messageName && type.messageName !== name) {
messages.set(type.messageName, message)
}
return message
}
private getBaseAttributes (type: IComplexType): (IAttributeGroup | null) {
const attributeGroups: Map<string, IAttributeGroup> = this.attributeGroups
let baseGroup
if (type.extensionBase) {
const base: IComplexType | undefined = this.complexTypes.get(type.extensionBase)
if (base) {
baseGroup = attributeGroups.get(base.attributeGroup)
}
return baseGroup ?? null
}
return null
}
private populateSet (type: IComplexType, set: IContainedSet): void {
const group: IGroup | undefined = this.groups.get(type.group)
const elements: IElement[] = group ? group.elements : type.element
const attributeGroups = this.attributeGroups
const attributeGroup: IAttributeGroup | undefined = attributeGroups.get(type.attributeGroup)
const baseGroup: IAttributeGroup | null = this.getBaseAttributes(type)
// if a base is specified add the attributes from there
if (baseGroup) {
this.addAttributes(set, baseGroup.attributes)
}
if (attributeGroup) {
this.addAttributes(set, attributeGroup.attributes)
}
this.addElements(set, elements)
}
private constructType (type: IComplexType): void {
const componentType: string = type.appInfo != null ? type.appInfo.ComponentType : 'Block'
switch (componentType) {
case 'Message': {
const message: MessageDefinition = this.getMessage(type)
if (!message) {
throw new Error(`cannot resolve ${type.name}`)
}
break
}
// these may be included from top level messages and added into definitions
case 'Block': {
const component: ComponentFieldDefinition = this.getComponent(type)
if (!component) {
throw new Error(`cannot resolve ${type.name}`)
}
break
}
case 'ImplicitBlock':
case 'XMLDataBlock':
case 'BlockRepeating':
case 'ImplicitBlockRepeating': {
break
}
default:
throw new Error(`unknown type ${componentType}`)
}
}
private insertFields (): void {
this.newComplexTypes.forEach((type: IComplexType) => {
this.constructType(type)
})
this.unboundElements.forEach((e: IElement) => {
const definitions = this.definitions
const component: ComponentFieldDefinition | undefined = definitions.component.get(e.type)
if (component) {
definitions.component.set(e.name, component)
}
})
}
}