UNPKG

gpml2pvjson

Version:
376 lines 32.5 kB
import "source-map-support/register"; // TODO should I get rid of the lib above for production browser build? import { defaultsDeep as defaultsDeepM } from "lodash"; import { camelCase, concat, curry, fromPairs, isArray, isObject, isString, toPairsIn } from "lodash/fp"; import * as hl from "highland"; import * as VError from "verror"; import * as iassign from "immutable-assign"; iassign.setOption({ // Deep freeze both input and output. Used in development to make sure they don't change. // TODO watch issue and re-enable when addressed: https://github.com/engineforce/ImassignM/issues/11 //freeze: true, ignoreIfNoChange: true }); import { isDefinedCXML, unionLSV } from "./gpml-utilities"; import { GraphIdManager } from "./GraphIdManager"; const GPML_ELEMENT_NAME_TO_KAAVIO_TYPE = { Anchor: "Burr", BiopaxRef: "Citation", DataNode: "SingleFreeNode", GraphicalLine: "Edge", Group: "Group", Interaction: "Edge", Label: "SingleFreeNode", //openControlledVocabulary: "Skip", //PublicationXref: "Skip", Shape: "SingleFreeNode", State: "Burr" }; const VALUES_TO_SKIP = ["", null, undefined]; // TODO update lodash/fp TS defs to use "x is ..." function isStringTS(x) { return isString(x); } function isArrayTS(x) { return isArray(x); } // NOTE: isPlainObject does not return true for an instance of a class function isRecord(x) { return !isArray(x) && isObject(x); } export class Processor { constructor(KeyMappings, KeyValueConverters, ValueMappings, ValueConverters) { this.output = { pathway: { // NOTE: GPML does not contain a way to express fill (background color). // It's always just white. fill: "white", strokeWidth: 0, stroke: "black", contains: [], drawAs: "rect", gpmlElementName: "Pathway", height: 0, // it appears type = {id: string} & type = {id?: string} makes id required. // TODO can we override that just for PathwayStarter? id: undefined, kaavioType: "Group", name: "New Pathway", // TODO what should the padding be? padding: 5, type: ["Pathway"], width: 0, x: 0, y: 0, zIndex: 0, // NOTE: these properties only apply contents of current element. They do not affect children. fontSize: 12, fontWeight: "bold", textAlign: "left", verticalAlign: "top" }, entitiesById: {} }; this.graphIdManager = new GraphIdManager(); this.graphIdsByGraphRef = {}; this.containedGraphIdsByGroupGraphId = {}; this.containedGraphIdsByGroupGroupId = {}; this.promisedGraphIdByGroupId = {}; this.groupIdToGraphIdStream = hl(); this.promisedGPMLElementByGraphId = {}; this.gpmlElementStream = hl(); this.promisedPvjsonEntityLatestByGraphId = {}; this.pvjsonEntityLatestStream = hl(); this.graphIdToZIndex = {}; this.ensureGraphIdExists = (gpmlElement) => { const { containedGraphIdsByGroupGroupId, graphIdManager, groupIdToGraphIdStream } = this; const { GroupId, GroupRef } = gpmlElement; let { GraphId } = gpmlElement; // TODO does this work for all elements? Are there any that we give an id that don't have one in GPML? // Does the schema allow the element to have a GraphId? if (!!GraphId) { // Does it actually have one? if (!isDefinedCXML(GraphId)) { // NOTE: we are making sure that elements that CAN have a GraphId // always DO have a GraphId. GraphIds are optional in GPML for Groups, // so we will add one if it's not already specified. But Pathway // elements never have GraphIds, so we don't add one for them. GraphId = gpmlElement.GraphId = graphIdManager.generateAndRecord(); } else { graphIdManager.recordExisting(GraphId); } if (isDefinedCXML(GroupRef)) { containedGraphIdsByGroupGroupId[GroupRef] = containedGraphIdsByGroupGroupId[GroupRef] || []; containedGraphIdsByGroupGroupId[GroupRef].push(GraphId); } if (isDefinedCXML(GroupId)) { groupIdToGraphIdStream.write([GroupId, GraphId]); } } else { throw new Error("GraphId missing."); } return gpmlElement; }; this.fillInGPMLPropertiesFromParent = curry((gpmlParentElement, gpmlChildElement) => { const { Graphics } = gpmlParentElement; // NOTE: this makes some assumptions about the distribution of ZOrder values in GPML // TODO This is what we used to do. Do we still need to do this? Or can we just sort them // based on whether they are burrs of each other? //element.zIndex = element.hasOwnProperty('zIndex') ? element.zIndex : referencedElement.zIndex + 1 / elementCount; const propertiesToFillIn = { Graphics: { ZOrder: Graphics.ZOrder } }; /* TODO can we delete this? if (isDefinedCXML(gpmlParentElement.GroupRef)) { propertiesToFillIn.GroupRef = gpmlParentElement.GroupRef; } //*/ return defaultsDeepM(gpmlChildElement, propertiesToFillIn); }); this.getPvjsonEntityLatestByGraphId = graphId => { let promisedPvjsonEntity = this.promisedPvjsonEntityLatestByGraphId[graphId]; if (promisedPvjsonEntity) { return promisedPvjsonEntity; } else { const { pvjsonEntityLatestStream } = this; // NOTE: we don't need to set the cache here, because the cache is // set for every item that flows through pvjsonEntityLatestStream promisedPvjsonEntity = new Promise(function (resolve, reject) { pvjsonEntityLatestStream .observe() .find(pvjsonEntity => pvjsonEntity.id === graphId) .errors(reject) .each(resolve); }); return promisedPvjsonEntity; } }; this.getGPMLElementByGraphId = GraphId => { let promisedGPMLElement = this.promisedGPMLElementByGraphId[GraphId]; if (promisedGPMLElement) { return promisedGPMLElement; } else { const { gpmlElementStream } = this; // NOTE: we don't need to set the cache here, because the cache is // set for every item that flows through gpmlElementStream promisedGPMLElement = new Promise(function (resolve, reject) { gpmlElementStream .observe() .find(gpmlElement => gpmlElement.GraphId === GraphId) .errors(reject) .each(resolve); }); return promisedGPMLElement; } }; this.preprocessGPMLElement = (gpmlElement) => { const { ensureGraphIdExists, gpmlElementStream } = this; const processedGPMLElement = ensureGraphIdExists(gpmlElement); // NOTE: side effect gpmlElementStream.write(processedGPMLElement); return processedGPMLElement; }; this.processGPMLAndPropertiesAndType = curry((gpmlElementName, gpmlElement) => { const { preprocessGPMLElement, processPropertiesAndType, pvjsonEntityLatestStream } = this; return processPropertiesAndType(gpmlElementName, preprocessGPMLElement(gpmlElement)); }); this.processProperties = curry((gpmlElement) => { const { processKV } = this; const entity = fromPairs(toPairsIn(gpmlElement).reduce((acc, x) => concat(acc, processKV(gpmlElement, x)), [])); if (!!entity.rotation) { entity.textRotation = -1 * entity.rotation; } // TODO: why doesn't the following work? //return entity as PvjsonEntity & Comment; return entity; }); this.processPropertiesAndType = curry((gpmlElementName, gpmlElement) => { const pvjsonEntity = this.processType(gpmlElementName, this.processProperties(gpmlElement)); this.pvjsonEntityLatestStream.write(pvjsonEntity); return pvjsonEntity; }); this.processType = curry((gpmlElementName, processed) => { const kaavioType = GPML_ELEMENT_NAME_TO_KAAVIO_TYPE[gpmlElementName]; processed.type = unionLSV(processed.type, gpmlElementName, kaavioType); if (processed.xrefDataSource && processed.xrefIdentifier) { processed.type = unionLSV(processed.type, `${processed.xrefDataSource}:${processed.xrefIdentifier}`); } if (!!kaavioType) { processed.kaavioType = kaavioType; } processed.gpmlElementName = gpmlElementName; return processed; }); this.setPvjsonEntity = pvjsonEntity => { const { graphIdToZIndex, promisedPvjsonEntityLatestByGraphId } = this; const { id, zIndex } = pvjsonEntity; graphIdToZIndex[id] = zIndex; promisedPvjsonEntityLatestByGraphId[id] = Promise.resolve(pvjsonEntity); this.output = iassign(this.output, function (o) { return o.entitiesById; }, function (entitiesById) { entitiesById[pvjsonEntity.id] = pvjsonEntity; return entitiesById; }); }; this.getGPMLKeyAsJSFunctionName = (gpmlKey) => { // NOTE: gpmlKeyAsJSFunctionName is for attributes like "Data-Source", because // the following would be invalid JS: // export function Data-Source() {}; // TODO what about things like spaces, etc.? return gpmlKey.replace("-", ""); }; this.getPvjsonValue = (gpmlElement, gpmlKey, gpmlValue) => { const { getGPMLKeyAsJSFunctionName, getPvjsonValue, processKV, ValueConverters, ValueMappings } = this; const gpmlKeyAsJSFunctionName = getGPMLKeyAsJSFunctionName(gpmlKey); let pvjsonValue; try { if (ValueConverters.hasOwnProperty(gpmlKeyAsJSFunctionName)) { return ValueConverters[gpmlKeyAsJSFunctionName](gpmlElement); } else if (isStringTS(gpmlValue)) { if (ValueMappings.hasOwnProperty(gpmlValue)) { return ValueMappings[gpmlValue]; } else { return gpmlValue; } } else if (isArrayTS(gpmlValue)) { return gpmlValue.map(valueItem => { return getPvjsonValue(valueItem, gpmlKey, valueItem); }); } else if (isRecord(gpmlValue)) { return fromPairs(toPairsIn(gpmlValue).reduce((acc, [key, value]) => { processKV(gpmlValue, [key, value]).forEach(function (x) { acc.push(x); }); return acc; }, [])); } else { return gpmlValue; } } catch (err) { throw new VError(err, ` when calling getPvjsonValue( ${JSON.stringify(gpmlElement, null, "")}, ${JSON.stringify(gpmlKey, null, "")}, ${JSON.stringify(gpmlValue, null, "")} ) `); } }; this.processKV = curry((gpmlElement, [gpmlKey, gpmlValue]) => { try { const { getGPMLKeyAsJSFunctionName, getPvjsonValue, KeyMappings, KeyValueConverters, processKV, ValueMappings } = this; const gpmlKeyAsJSFunctionName = getGPMLKeyAsJSFunctionName(gpmlKey); if (VALUES_TO_SKIP.indexOf(gpmlValue) > -1) { return []; } if (KeyValueConverters.hasOwnProperty(gpmlKeyAsJSFunctionName)) { return KeyValueConverters[gpmlKeyAsJSFunctionName](gpmlElement, KeyMappings, ValueMappings); } const pvjsonKey = KeyMappings[gpmlKey]; // NOTE "pvjson:merge" is for elements like "Graphics", where they // are nested in GPML but are merged into the parent in pvjson. if (gpmlKey[0] === "_" || pvjsonKey === "pvjson:delete" || (isObject(gpmlValue) && !isDefinedCXML(gpmlValue))) { // NOTE: we don't want to include "private" keys, such as // "_exists" or "_namespace". return []; } else if (pvjsonKey === "pvjson:merge") { return toPairsIn(gpmlValue).reduce((acc, pair) => concat(acc, processKV(gpmlElement, pair)), []); } else if (pvjsonKey === "pvjson:each") { // NOTE: Example of what uses this is GPML Attribute. // (in GPML, 'Attribute' is an XML *ELEMENT* named "Attribute") return toPairsIn(gpmlValue // NOTE: some attributes have empty values and will cause problems // if we don't use this filter to skip them. .filter(({ Key, Value }) => VALUES_TO_SKIP.indexOf(Value) === -1) .map(({ Key, Value }) => { return processKV(gpmlElement, [Key, Value]); }) .reduce((acc, [[processedKey, processedValue]]) => { // NOTE: this looks more complicated than it needs to be, // but it's to handle the case where there are two or more // sibling Attribute elements that share the same Key. // I don't know of any cases of this in our actual GPML, // but the XSD does not require unique Keys for sibling // Attributes. if (acc.hasOwnProperty(processedKey)) { acc[processedKey] = unionLSV(acc[processedKey], processedValue); } else { acc[processedKey] = processedValue; } return acc; }, {})); } else { const pvjsonValue = getPvjsonValue(gpmlElement, gpmlKey, gpmlValue); // NOTE: we don't include key/value pairs when the value is missing if (VALUES_TO_SKIP.indexOf(pvjsonValue) === -1) { return [[pvjsonKey || camelCase(gpmlKey), pvjsonValue]]; } else { return []; } } } catch (err) { throw new VError(err, ` when calling processor.processKV( gpmlElement: ${JSON.stringify(gpmlElement, null, " ")}, [ gpmlKey: ${gpmlKey}, gpmlValue: ${JSON.stringify(gpmlValue, null, " ")} ] ) `); } }); const { graphIdToZIndex, graphIdsByGraphRef, promisedGPMLElementByGraphId, gpmlElementStream, promisedGraphIdByGroupId, groupIdToGraphIdStream, promisedPvjsonEntityLatestByGraphId, pvjsonEntityLatestStream } = this; this.KeyMappings = KeyMappings; this.KeyValueConverters = KeyValueConverters; this.ValueMappings = ValueMappings; this.ValueConverters = ValueConverters; groupIdToGraphIdStream.each(function ([groupId, graphId]) { promisedGraphIdByGroupId[groupId] = Promise.resolve(graphId); }); gpmlElementStream.each(function (gpmlElement) { promisedGPMLElementByGraphId[gpmlElement.GraphId] = Promise.resolve(gpmlElement); }); pvjsonEntityLatestStream .doto(function (pvjsonEntity) { const { id, zIndex } = pvjsonEntity; graphIdToZIndex[id] = zIndex; promisedPvjsonEntityLatestByGraphId[id] = Promise.resolve(pvjsonEntity); }) .errors(function (err, push) { push(new VError(err, ` observed in pvjsonEntityLatestStream `)); }) .each(function (pvjsonEntity) { }); /* TODO do we need this? endStream.each(function(x) { groupIdToGraphIdStream.end(); gpmlElementStream.end(); pvjsonEntityLatestStream.end(); }); //*/ } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUHJvY2Vzc29yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL1Byb2Nlc3Nvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLDZCQUE2QixDQUFDO0FBQ3JDLHVFQUF1RTtBQUV2RSxPQUFPLEVBQUUsWUFBWSxJQUFJLGFBQWEsRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUN2RCxPQUFPLEVBRUwsU0FBUyxFQUNULE1BQU0sRUFDTixLQUFLLEVBR0wsU0FBUyxFQUVULE9BQU8sRUFDUCxRQUFRLEVBQ1IsUUFBUSxFQUlSLFNBQVMsRUFDVixNQUFNLFdBQVcsQ0FBQztBQUNuQixPQUFPLEtBQUssRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUMvQixPQUFPLEtBQUssTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUNqQyxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sQ0FBQyxTQUFTLENBQUM7SUFDaEIseUZBQXlGO0lBQ3pGLG9HQUFvRztJQUNwRyxlQUFlO0lBQ2YsZ0JBQWdCLEVBQUUsSUFBSTtDQUN2QixDQUFDLENBQUM7QUFhSCxPQUFPLEVBQUUsYUFBYSxFQUFFLFFBQVEsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQzNELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUVsRCxNQUFNLGdDQUFnQyxHQUFHO0lBQ3ZDLE1BQU0sRUFBRSxNQUFNO0lBQ2QsU0FBUyxFQUFFLFVBQVU7SUFDckIsUUFBUSxFQUFFLGdCQUFnQjtJQUMxQixhQUFhLEVBQUUsTUFBTTtJQUNyQixLQUFLLEVBQUUsT0FBTztJQUNkLFdBQVcsRUFBRSxNQUFNO0lBQ25CLEtBQUssRUFBRSxnQkFBZ0I7SUFDdkIsbUNBQW1DO0lBQ25DLDBCQUEwQjtJQUMxQixLQUFLLEVBQUUsZ0JBQWdCO0lBQ3ZCLEtBQUssRUFBRSxNQUFNO0NBQ2QsQ0FBQztBQUVGLE1BQU0sY0FBYyxHQUFHLENBQUMsRUFBRSxFQUFFLElB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