jspurefix
Version:
pure node js fix engine
147 lines (135 loc) • 4.17 kB
text/typescript
import { ISaxNode, SAXStream } from '../../dict-primitive'
import { Dictionary } from '../../../collections'
import { INumericKeyed } from '../../../collections/collection'
import * as path from 'path'
import * as fs from 'fs'
export interface IInclude {
parent: string
children: string[]
}
export interface IGraphNode {
file: string
node: number
edges: number[]
}
/*
construct a dependency graph from all the xsd files to compute an order to build the definition
*/
export class IncludeGraph {
private nodes: Dictionary<number>
private includes: IInclude[]
private graph: INumericKeyed<IGraphNode>
constructor (public readonly root: string, public readonly main: string) {
}
public async build (): Promise<any> {
this.includes = await this.getIncludes(this.main)
this.assignNodes()
this.getGraph()
}
public resolve (file: string): string[] {
const label: number = this.nodes.get(file)
if (label == null) {
return null
}
const depends: number[] = []
this.resolve_nodes(label, depends)
return depends.map((i: number) => {
return this.graph[i].file
})
}
private resolve_nodes (label: number, depends: number[]): void {
const node: IGraphNode = this.graph[label]
for (let e of node.edges) {
if (depends.indexOf(e) < 0) {
this.resolve_nodes(e, depends)
}
}
depends.push(label)
}
private getGraph () {
const nodes = this.nodes
this.graph = this.includes.reduce((a: INumericKeyed<IGraphNode>, current: IInclude) => {
const parent: number = nodes.get(current.parent)
let parentNode = a[parent]
if (!parentNode) {
a[parent] = parentNode = {
file: current.parent,
node: parent,
edges: []
} as IGraphNode
}
current.children.forEach((s: string) => {
const child: number = nodes.get(s)
if (parentNode.edges.indexOf(child) < 0) {
parentNode.edges.push(child)
}
})
return a
}, {} as INumericKeyed<IGraphNode>)
}
private assignNodes () {
let next: number = 0
this.nodes = this.includes.reduce((a: Dictionary<number>, current: IInclude) => {
if (!a.containsKey(current.parent)) {
a.add(current.parent, next++)
}
current.children.forEach((c: string) => {
if (!a.containsKey(c)) {
a.add(c, next++)
}
})
return a
}, new Dictionary<number>())
}
private scanIncludes (file: string): Promise<string[]> {
return new Promise<string[]>((accept, reject) => {
const includes: string[] = []
const pass: fs.ReadStream = fs.createReadStream(path.join(this.root, file))
const saxStream: SAXStream = require('sax').createStream(true, {})
saxStream.on('opentag', (node) => {
const saxNode: ISaxNode = node
switch (saxNode.name) {
case 'xs:include': {
includes.push(saxNode.attributes.schemaLocation)
break
}
}
})
saxStream.on('ready', () => {
accept(includes)
})
saxStream.on('error', (r) => {
reject(r)
})
pass.pipe(saxStream)
})
}
private getIncludes (main: string): Promise<IInclude[]> {
const q: any[] = []
q.push([main])
const ordered: IInclude[] = []
const seen: Dictionary<boolean> = new Dictionary<boolean>()
return new Promise<IInclude[]>(async (accept, reject) => {
try {
while (q.length > 0) {
const batch: string[] = q.pop()
for (let next of batch) {
if (seen.containsKey(next)) {
continue
}
seen.add(next, true)
const includes: string[] = await this.scanIncludes(next)
ordered.push({
parent: next,
children: includes
} as IInclude)
q.push(includes)
}
}
accept(ordered)
} catch (e) {
reject(e)
}
})
}
}