UNPKG

mdf-reader

Version:

Reads MDF YAML and returns helpful accessors

476 lines (450 loc) 13.4 kB
import yaml from 'js-yaml'; import { createMerger } from 'smob'; function myProps(...nm) { if (nm.length > 0) { let ret = Object.entries(this.props_) .filter( (kv) => nm.includes(kv[0]) ) .map((kv) => { return kv[1]; }); return (ret.length === 1 ? ret[0] : ret); } else { return Object.values(this.props_); } } function myTags(key) { if (!this.taglist_) { this.taglist_ = []; } if (key) { return (Object.fromEntries(this.taglist_))[key]; } return this.taglist_; } function myValueSet(rdr) { if (this.pvs) { return this.pvs.map( (h) => rdr.terms_[h].value ); } else { return []; } } function myTerms(rdr) { if (!this.termlist_) { this.termlist_ = []; } return this.termlist_ .map( (t) => rdr.terms_[t] ); } function compositeKeyHook() { this.nodes() .forEach( (node) => { node.composite_key_props = () => { if (this.mdf.Nodes[node.handle].CompKey) { let ck = this.mdf.Nodes[node.handle].CompKey; return ck .map( (name) => { let m = name.match("^(?:([^.]*)[.])?([^.]*)"); if (!m[1]) { m[1] = node.handle; } return this.nodes(m[1]).props(m[2]) }); } else { return []; } }; }); } export class MDFReader { static #parse_hooks = [ compositeKeyHook ]; static add_parse_hook(hook) { this.#parse_hooks.push(hook); return this; } constructor(...sources) { this.mdf = {}; this.handle = null; this.nodes_ = null; this.edges_ = null; this.props_ = null; this.terms_ = null; this.tags_ = {}; this.sources = []; this.#readSources( ...sources); let {Handle, Version} = this.mdf; this.handle = Handle; this.version = Version; this.#parse_terms(). #parse_props(). #parse_nodes(). #parse_edges(). #run_parse_hooks(this.constructor.#parse_hooks); } nodes(...hdl) { if (hdl.length > 0) { let ret = Object.entries(this.nodes_) .filter( ([h, node]) => hdl.includes(h) ) .map( ([h, node]) => node ); return (ret.length === 1 ? ret[0] : ret); } else { return Object.keys(this.nodes_) .sort() .map( (p) => this.nodes_[p] ); } } edges(...edge_types) { if (edge_types.length > 0) { let ret = Object.entries(this.edges_) .filter( ([h, edges]) => edge_types.includes(h) ) .flatMap(([h, edges]) => { return Object.values(edges.insts) .flatMap((dests) => Object.values(dests)); }); return ret; } else { return Object.keys(this.edges_) .sort() .flatMap( (edge_type) => { return Object.values(this.edges_[edge_type].insts) .flatMap((dests) => Object.values(dests)); }); } } props(...hdl) { if (hdl.length > 0) { let ret = Object.entries(this.props_) .filter( (kv) => hdl.includes(kv[0]) ) .map((kv) => { return kv[1]; }); return (ret.length === 1 ? ret[0] : ret); } else { return Object.keys(this.props_) .sort() .map( (p) => this.props_[p] ); } } terms(...hdl) { if (hdl.length > 0) { let ret = Object.entries(this.terms_) .filter( (kv) => hdl.includes(kv[0]) ) .map((kv) => { return kv[1]; }); return (ret.length === 1 ? ret[0] : ret); } else { return Object.keys(this.terms_) .sort() .map( (p) => this.terms_[p] ); } } tag_kvs(...key) { let ret = []; let [key_] = key; Object.keys(this.tags_) .filter( (k) => key_ ? k === key_ : true ) .sort() .map( (k) => { Object.keys(this.tags_[k]) .sort() .map( (v) => { ret.push([k, v]); }); }); return ret; } tagged_items(key, value) { if (this.tags_[key]) { if (this.tags_[key][value]) { return this.tags_[key][value]; } else { return []; } } else { return []; } } outgoing_edges(node_hdl) { let ret = []; Object.entries(this.edges_) .filter( ([type, edges]) => edges.insts[node_hdl] ) .forEach( ([type, edges]) => { Object.values(edges.insts[node_hdl]) .forEach( (edge) => { ret.push(edge); }); }); return ret; } incoming_edges(node_hdl) { let ret = []; Object.entries(this.edges_) .forEach( ([type, edges]) => { Object.entries(edges.insts) .forEach( ([src, by_dst]) => { by_dst[node_hdl] && ret.push(by_dst[node_hdl]); }); }); return ret; } #readSources(...sources) { let merger = createMerger({arrayDistinct:true}); for (const source of sources) { if (source.constructor.name === "String") { let mdf = yaml.load(source); this.sources.push( mdf ); } else if (typeof(source) === "object") { this.sources.push(source); } else { throw new Error("string or plain object required"); } } this.mdf = merger(...this.sources); } updateTerms(handle, spec) { let {Value:value, Origin:origin_name, Desc:desc, Code:origin_id, Version:origin_version, Definition:definition, Handle: exp_handle } = spec; // obj.mdf.Terms[tm]; if (!handle) { if (!exp_handle) { handle = value; } else { handle = exp_handle; } } this.terms_[handle] = { _kind: "Term", handle, value, origin_name, origin_id, origin_version, definition, }; return handle; }; updateTags(key, value, item){ if (!this.tags_[key]) { this.tags_[key] = {}; } if (!this.tags_[key][value]) { this.tags_[key][value] = []; } this.tags_[key][value].push(item); if (!item.taglist_) { item.taglist_ = []; item.tags = myTags.bind(item); } item.taglist_.push([key, value]); }; #parse_terms() { if (!this.terms_) { // Handle, Value, Origin, Code, Definition, Version this.terms_ = {}; for (const tm in this.mdf.Terms) { this.updateTerms(tm, this.mdf.Terms[tm]); } } return this; } #parse_props() { if (!this.props_) { this.props_ = {}; if (this.mdf.PropDefinitions) { for (const pr in this.mdf.PropDefinitions) { let handle = pr; let item_type = null; let { Type: type, Enum:pvs, Req: is_required, Desc: desc, Key: is_key, Nul: is_nullable, Deprecated: is_deprecated, Strict: is_strict, Tags: tags, Term: terms, } = this.mdf.PropDefinitions[pr]; if (typeof(type) == 'object' && type.value_type && type.value_type == 'list') { item_type = type['item_type'] || type['Enum']; if (Array.isArray(item_type)) { pvs = item_type; item_type = 'value_set'; } type = "list"; } let spec = { handle, desc, type, is_required, is_key, is_nullable, is_deprecated, is_strict, tags, _kind:"Property", }; this.props_[pr] = spec; if (item_type) { this.props_[pr]["item_type"] = item_type; } if (pvs) { if (!type) { spec.type = "value_set"; } this.props_[pr]["pvs"] = pvs; pvs.forEach( (v) => { if (!this.terms_[v]) { this.updateTerms(null, {Value:v, Origin:"<Local>"}); } }); this.props_[pr].valueSet = myValueSet.bind(this.props_[pr], this); } if (tags) { for (const key in tags) { this.updateTags(key, tags[key], this.props_[pr]); } } if (terms) { let termlist_ = []; terms.forEach( (t) => { termlist_.push( this.updateTerms(null, t) ); }); this.props_[pr].termlist_ = termlist_; this.props_[pr].terms = myTerms.bind(this.props_[pr], this); } this.props_[pr].tags = myTags.bind(this.props_[pr]); } } } return this; } #parse_nodes() { if (!this.nodes_) { this.nodes_ = {}; for (const nd in this.mdf.Nodes) { let spec = this.mdf.Nodes[nd]; let univ = []; if (this.mdf.UniversalNodeProperties) { univ = Object.values(this.mdf.UniversalNodeProperties).flatMap(x => x); } let pr_spec = spec.Props || []; pr_spec = pr_spec .concat( univ.flatMap(x => x) ); this.nodes_[nd] = {_kind:"Node", handle:nd, desc:spec.Desc}; this.nodes_[nd].props_ = {}; if (pr_spec) { for (const pr of pr_spec) { if (!this.props_[pr]) { console.log('No property definition present for "%s" of node "%s"', pr, nd); this.props_[pr] = { handle:pr, _kind: 'Property', tags:null, terms:null, type:'UNDEFINED'}; this.props_[pr].tags = myTags.bind(this.props_[pr]); } this.nodes_[nd].props_[pr] = this.props_[pr]; this.props_[pr].owner = this.nodes_[nd]; } } this.nodes_[nd].props = myProps.bind(this.nodes_[nd]); if (spec.Tags) { this.nodes_[nd]["tags"] = spec.Tags; for (const key in spec.Tags) { this.updateTags(key, spec.Tags[key], this.nodes_[nd]); } } if (spec.Term) { let termlist_ = []; spec.Term.forEach( (t) => { termlist_.push( this.updateTerms(null, t) ); }); this.nodes_[nd].termlist_ = termlist_; this.nodes_[nd].terms = myTerms.bind(this.nodes_[nd],this); } this.nodes_[nd].tags = myTags.bind(this.nodes_[nd]); } } return this; } #parse_edges() { if (!this.edges_) { this.edges_ = {}; for (const edge_nm in this.mdf.Relationships) { let spec = this.mdf.Relationships[edge_nm]; let mul_def = spec.Mul; this.edges_[edge_nm] = {_kind:"EdgeType", handle:edge_nm, desc:spec.desc }; let insts = {}; let univ = []; if (this.mdf.UniversalRelationshipProperties) { univ = Object.values(this.mdf.UniversalRelationshipProperties).flatMap(x => x); } for (const end_pair of spec.Ends) { let {Mul, Tags:tags} = end_pair; if (!insts[end_pair["Src"]]) { insts[end_pair["Src"]] = {}; } insts[end_pair["Src"]][end_pair["Dst"]] = {multiplicity: (Mul ? Mul : mul_def), _kind:"Edge", handle:`${edge_nm}:${end_pair.Src}:${end_pair.Dst}`, src: end_pair.Src, dst: end_pair.Dst, type: edge_nm, tags}; insts[end_pair["Src"]][end_pair["Dst"]].props_ = {}; let pr_spec = this.mdf.Relationships[edge_nm].Props || []; pr_spec = pr_spec .concat( univ.flatMap(x => x) ); if (pr_spec.length) { for (const pr in pr_spec) { if (!this.props_[pr]) { console.log('No property defintion present for "%s" of edge type "%s"', pr, edge_nm); this.props_[pr] = { handle:pr, _kind: 'Property'}; this.props_[pr].tags = myTags.bind(this.props_[pr]); } insts[end_pair["Src"]][end_pair["Dst"]].props_[pr] = this.props_[pr]; this.props_[pr].owner = insts[end_pair["Src"]][end_pair["Dst"]]; } } insts[end_pair["Src"]][end_pair["Dst"]].props = myProps.bind(insts[end_pair["Src"]][end_pair["Dst"]]); if (tags) { for (const key in tags) { this.updateTags(key, tags[key], insts[end_pair["Src"]][end_pair["Dst"]]); } } insts[end_pair["Src"]][end_pair["Dst"]].tags = myTags.bind(insts[end_pair["Src"]][end_pair["Dst"]]); } this.edges_[edge_nm].insts = insts; if (spec.Tags) { this.edges_[edge_nm]["tags"] = spec.Tags; for (const key in this.edges_[edge_nm]["tags"]) { this.updateTags(key, this.edges_[edge_nm]["tags"][key], this.edges_[edge_nm]); } } if (spec.Term) { let termlist_ = []; spec.Term.forEach( (t) => { termlist_.push( this.updateTerms(null, t) ); }); this.edges_[edge_nm].termlist_ = termlist_; this.edges_[edge_nm].terms = myTerms.bind(this.edges_[edge_nm], this); } this.edges_[edge_nm].tags = myTags.bind(this.edges_[edge_nm]); } } return this; } #run_parse_hooks(hooks) { if (hooks.length == 0) { return this; } hooks.forEach( (hook) => { hook.call(this); }); return this; } }