UNPKG

jspurefix

Version:
147 lines (135 loc) 4.17 kB
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) } }) } }