UNPKG

gpml2pvjson

Version:
738 lines (737 loc) 61.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.toPvjson = exports.RECURSION_LIMIT = void 0; require("source-map-support/register"); // TODO should I get rid of the lib above for production browser build? var lodash_1 = require("lodash"); var fp_1 = require("lodash/fp"); var hl = require("highland"); var VError = require("verror"); var cxml_xpath_1 = require("../spinoffs/cxml-xpath"); var GPML2013a = require("../../xmlns/pathvisio.org/GPML/2013a"); var GPMLDefaults = require("../GPMLDefaults"); var Processor_1 = require("../Processor"); var edge_1 = require("../edge/edge"); var group_1 = require("../group"); var gpml_utilities_1 = require("../gpml-utilities"); var VOCABULARY_NAME_TO_IRI = require("../spinoffs/VOCABULARY_NAME_TO_IRI.json"); var GPML2013aKeyMappings = require("./KeyMappings.json"); var GPML2013aKeyValueMappings = require("./KeyValueConverters"); var GPML2013aValueMappings = require("./ValueMappings.json"); var GPML2013aValueConverters = require("./ValueConverters"); // TODO get text alignment correctly mapped to Box Model CSS terms var iassign = require("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 }); exports.RECURSION_LIMIT = 1000; function partitionStream(s, partitioner) { var yes = s.fork().filter(function (x) { return partitioner(x); }); var no = s.fork().filter(function (x) { return !partitioner(x); }); return [yes, no]; } function extendDeep(targetOrTargetArray, source) { var target = fp_1.isArray(targetOrTargetArray) ? targetOrTargetArray[0] : targetOrTargetArray; // TODO: We run into problems if we try to extend both // GraphicalLine and Interaction, because they share // EdgeGraphicsType. To avoid an infinite recursion of // extending, I'm using a short-term solution of just // marking whether a target has been extended, and // if so, skipping it. // Look into a better way of handling this. if (!target.hasOwnProperty("_extended")) { fp_1.toPairsIn(target) .filter(function (_a) { var targetKey = _a[0], targetValue = _a[1]; return source.hasOwnProperty(targetKey) && fp_1.isObject(source[targetKey]); }) .forEach(function (_a) { var targetKey = _a[0], targetValue = _a[1]; extendDeep(targetValue, source[targetKey]); }); lodash_1.assign(target.constructor.prototype, source); target._extended = true; } } var stringifyKeyValue = fp_1.curry(function (source, key) { return source.hasOwnProperty(key) ? fp_1.startCase(key) + ": " + source[key] : null; }); extendDeep(GPML2013a.document.Pathway.constructor.prototype, GPMLDefaults.Pathway); extendDeep(GPML2013a.DataNodeType.prototype, GPMLDefaults.DataNode); extendDeep(GPML2013a.GraphicalLineType.prototype, GPMLDefaults.GraphicalLine); extendDeep(GPML2013a.GroupType.prototype, GPMLDefaults.Group); extendDeep(GPML2013a.InteractionType.prototype, GPMLDefaults.Interaction); extendDeep(GPML2013a.LabelType.prototype, GPMLDefaults.Label); extendDeep(GPML2013a.ShapeType.prototype, GPMLDefaults.Shape); extendDeep(GPML2013a.StateType.prototype, GPMLDefaults.State); extendDeep(GPML2013a.EdgeGraphicsType.prototype.Anchor, GPMLDefaults.Anchor); // TODO specify types function toPvjson(inputStreamWithMessedUpRDFIDs, pathwayIri) { // NOTE: GPML2013a incorrectly uses "rdf:id" instead of "rdf:ID". // We need to fix this error so that CXML can process the GPML. var inputStream = hl(inputStreamWithMessedUpRDFIDs) .splitBy(' rdf:id="') .intersperse(' rdf:ID="'); var selectorToCXML = { // TODO why does TS require that we use the Pathway's "constructor.prototype" // instead of just the Pathway? // Why does Pathway.Graphics not need that? // Why do many of the other require using the prototype? // "/Pathway/@*": GPML2013a.document.Pathway.constructor.prototype, "/Pathway/Comment": GPML2013a.document.Pathway.Comment[0], "/Pathway/Graphics/@*": GPML2013a.document.Pathway.Graphics, "/Pathway/DataNode": GPML2013a.DataNodeType.prototype, "/Pathway/State": GPML2013a.StateType.prototype, "/Pathway/Interaction": GPML2013a.InteractionType.prototype, "/Pathway/GraphicalLine": GPML2013a.GraphicalLineType.prototype, "/Pathway/Label": GPML2013a.LabelType.prototype, "/Pathway/Shape": GPML2013a.ShapeType.prototype, "/Pathway/Group": GPML2013a.GroupType.prototype, "/Pathway/InfoBox": GPML2013a.InfoBoxType.prototype, "/Pathway/Legend": GPML2013a.LegendType.prototype, "/Pathway/Biopax/bp:PublicationXref": GPML2013a.document.Pathway.Biopax.PublicationXref[0], "/Pathway/Biopax/bp:openControlledVocabulary": GPML2013a.document.Pathway.Biopax.openControlledVocabulary[0] }; var cxmlXPath = new cxml_xpath_1.CXMLXPath(inputStream, GPML2013a, { bp: "http://www.biopax.org/release/biopax-level3.owl#" //rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#" }); var cxmlSources = cxmlXPath.parse(selectorToCXML); var processor = new Processor_1.Processor(GPML2013aKeyMappings, GPML2013aKeyValueMappings, GPML2013aValueMappings, GPML2013aValueConverters); var fillInGPMLPropertiesFromParent = processor.fillInGPMLPropertiesFromParent, getPvjsonEntityLatestByGraphId = processor.getPvjsonEntityLatestByGraphId, graphIdsByGraphRef = processor.graphIdsByGraphRef, graphIdToZIndex = processor.graphIdToZIndex, getGPMLElementByGraphId = processor.getGPMLElementByGraphId, preprocessGPMLElement = processor.preprocessGPMLElement, processGPMLAndPropertiesAndType = processor.processGPMLAndPropertiesAndType, processProperties = processor.processProperties, processPropertiesAndType = processor.processPropertiesAndType, setPvjsonEntity = processor.setPvjsonEntity; if (pathwayIri) { processor.output = iassign(processor.output, function (o) { return o.pathway; }, function (pathway) { pathway.id = pathwayIri; return pathway; }); } var sortByZIndex = gpml_utilities_1.sortByMap(graphIdToZIndex); var pathwayMetadataStream = hl([ cxmlSources["/Pathway/@*"].doto(function (pathway) { if (gpml_utilities_1.supportedNamespaces.indexOf(pathway._namespace) === -1) { // TODO should we do anything further? throw new Error("Unsupported namespace: " + pathway._namespace); } }), cxmlSources["/Pathway/Graphics/@*"] ]) .merge() .map(processProperties) .reduce({}, function (acc, metadataChunk) { return fp_1.assign(acc, metadataChunk); }) // there should only be one item through this last step .map(function (metadata) { processor.output = iassign(processor.output, function (o) { return o.pathway; }, function (pathway) { var mergedPathway = fp_1.assign(pathway, metadata); // NOTE: GPML schema specifies that name is required var name = mergedPathway.name; var splitName = name.split(" ("); if (!!splitName && splitName.length === 2 && !!name.match(/\(/g) && name.match(/\(/g).length === 1 && !!name.match(/\)/g) && name.match(/\)/g).length === 1) { mergedPathway.standardName = splitName[0]; mergedPathway.displayName = splitName[1].replace(")", ""); } else { mergedPathway.standardName = name; mergedPathway.displayName = name; } // mergedPathway.textContent = ""; // mergedPathway.textContent += mergedPathway.hasOwnProperty("name") ? mergedPathway["name"] : ""; // if (!!mergedPathway.id) { // mergedPathway.textContent += " (" + mergedPathway.id + ")"; // } var context = [ "https://cdn.rawgit.com/wikipathways/WpVocabularies/7a46a05/contexts/pvjs.jsonld" ]; if (!!mergedPathway.id) { context.push({ "@base": mergedPathway.id + "/" }); } else { // If there's no pathway IRI specified, we at least give the user a URL // to search WikiPathways. This way, the user at least has a chance of // to search WikiPathways to possibly find the source for this data. // NOTE: GPML schema specifies that organism is optional var organismIriComponent = mergedPathway.hasOwnProperty("organism") ? "&species=" + mergedPathway.organism : ""; mergedPathway.isSimilarTo = encodeURI("http://wikipathways.org/index.php/Special:SearchPathways?query=" + name + organismIriComponent + "&doSearch=1"); } return fp_1.assign({ "@context": context }, mergedPathway); }); return processor.output; }) .errors(function (err) { throw new VError(err, " when processing pathwayMetadataStream\n\t\t\t\t"); }); var pathwayCommentStream = hl(cxmlSources["/Pathway/Comment"]).map(function (Comment) { processor.output = iassign(processor.output, function (o) { return o.pathway; }, function (pathway) { var comments = pathway.comments || []; comments.push(processProperties(Comment)); pathway.comments = comments; return pathway; }); return processor.output; }); var dataNodeStream = cxmlSources["/Pathway/DataNode"] .map(processGPMLAndPropertiesAndType("DataNode")) .map(function (entity) { // TODO fix type def for unionLSV so I don't have to use "as" entity.type = gpml_utilities_1.unionLSV(entity.type, entity.wpType); return entity; }); var stateStream = cxmlSources["/Pathway/State"] .map(preprocessGPMLElement) .flatMap(function (gpmlState) { return hl(getGPMLElementByGraphId(gpmlState.GraphRef)).map(function (gpmlDataNode) { return fillInGPMLPropertiesFromParent(gpmlDataNode, gpmlState); }); }) .map(processPropertiesAndType("State")); var shapeStream = cxmlSources["/Pathway/Shape"] .map(processGPMLAndPropertiesAndType("Shape")) .map(function (pvjsonEntity) { var cellularComponent = pvjsonEntity.cellularComponent; // CellularComponent is not a BioPAX term, but "PhysicalEntity" is. if (!!cellularComponent) { pvjsonEntity.type = gpml_utilities_1.unionLSV(pvjsonEntity.type, "PhysicalEntity", "CellularComponent", cellularComponent); } return pvjsonEntity; }); var labelStream = cxmlSources["/Pathway/Label"].map(processGPMLAndPropertiesAndType("Label")); var gpmlInteractionStream = cxmlSources["/Pathway/Interaction"].map(preprocessGPMLElement); var gpmlGraphicalLineStream = cxmlSources["/Pathway/GraphicalLine"].map(preprocessGPMLElement); var edgeStream = hl([ gpmlInteractionStream .fork() .map(edge_1.preprocessGPML) .map(processPropertiesAndType("Interaction")), gpmlGraphicalLineStream .fork() .map(edge_1.preprocessGPML) .map(processPropertiesAndType("GraphicalLine")) ]).merge(); var anchorStream = hl([ gpmlInteractionStream.fork(), gpmlGraphicalLineStream.fork() ]) .merge() .filter(function (gpmlEdge) { return (gpml_utilities_1.isDefinedCXML(gpmlEdge.Graphics) && gpml_utilities_1.isDefinedCXML(gpmlEdge.Graphics.Anchor)); }) .flatMap(function (gpmlEdge) { var GraphId = gpmlEdge.GraphId, Graphics = gpmlEdge.Graphics; var fillInGPMLPropertiesFromEdge = fillInGPMLPropertiesFromParent(gpmlEdge); var gpmlAnchors = Graphics.Anchor; return hl(gpmlAnchors) .map(function (gpmlAnchor) { var anchorShape = gpmlAnchor.Shape; if (anchorShape === "None") { // NOTE: For Anchors with Shape="None", PathVisio-Java displays // the anchor as a 4x4 square when nothing is connected, // but does not display it when something is connected. // TODO: right now, PathVisio-Java writes out GPML such that the // Anchor only has a GraphId when an Edge connects to this Anchor, // but we may not be able to rely on this in the future. if (gpml_utilities_1.isDefinedCXML(gpmlAnchor.GraphId)) { lodash_1.assign(gpmlAnchor.Graphics, { Height: 0, Width: 0 }); } else { gpmlAnchor.Shape = "Rectangle"; lodash_1.assign(gpmlAnchor.Graphics, { Height: 4, Width: 4 }); } } else if (anchorShape === "Circle") { lodash_1.assign(gpmlAnchor.Graphics, { Height: 8, Width: 8 }); } else { throw new Error("Anchor Shape \"" + anchorShape + "\" is not supported."); } return gpmlAnchor; }) .map(preprocessGPMLElement) .map(function (gpmlAnchor) { var filledInAnchor = fillInGPMLPropertiesFromEdge(gpmlAnchor); filledInAnchor.GraphRef = GraphId; return filledInAnchor; }) .map(processPropertiesAndType("Anchor")); }); var groupStream = cxmlSources["/Pathway/Group"] .map(group_1.preprocessGPML(processor)) // PathVisio shouldn't do this, but it sometimes makes empty Groups. // We filter them out here. .filter(function (Group) { return !!Group.Contains; }) .map(processGPMLAndPropertiesAndType("Group")); var EDGES = ["Interaction", "GraphicalLine"]; var NODES = ["DataNode", "Shape", "Label", "State", "Group"]; function postprocessAll(s) { /* // We are sorting the elements by the order in which we must do their // post-processing, e.g., if one edge is attached to another edge via // an anchor, we must post-process the edge with the anchor before we // post-process the other edge. const isProcessableTests = [ curry(function(sortedIds: string[], pvjsonEntity: PvjsonEntity) { // In this test, we ensure the entity // 1) is not a group AND // 2) is not attached to a group and not to an edge // (ie., it is not attached to anything, or // it is attached to something, but that something is neither a group nor an edge) return ( pvjsonEntity.gpmlElementName !== "Group" && unionLSV( pvjsonEntity["isAttachedToOrVia"], pvjsonEntity["isAttachedTo"] ) .map( (isAttachedToOrViaId: string) => processor.output.entitiesById[isAttachedToOrViaId].gpmlElementName ) .filter( // Entity is attached to neither a group nor an edge. // (Testing that entity is not attached to an edge at all, // whether directly or indirectly via an anchor.) isAttachedToOrViaGpmlElementName => ["Group", "Interaction", "GraphicalLine", "Anchor"].indexOf( isAttachedToOrViaGpmlElementName ) > -1 ).length === 0 ); }), curry(function(sortedIds: string[], pvjsonEntity: PvjsonEntity) { const gpmlElementName = pvjsonEntity.gpmlElementName; if ( ["Interaction", "GraphicalLine", "State", "Anchor"].indexOf( gpmlElementName ) > -1 ) { // This entity is an edge, a state or an anchor. // All entities to which this entity is attached must be processable // before it is itself processable. // That means all entities to which this entity is attached to must // be processed before it can be processed. const isAttachedToIds = unionLSV( pvjsonEntity["isAttachedTo"], pvjsonEntity["isAttachedToOrVia"] ) as string[]; const isAttachedToInSortedIds = isAttachedToIds.filter( // entity with this id is sortedIds isAttachedToId => sortedIds.indexOf(isAttachedToId) > -1 ); return isAttachedToIds.length === isAttachedToInSortedIds.length; } else if (gpmlElementName === "Group") { // is processable when group does not contain an entity that is not processable return ( arrayify(pvjsonEntity["contains"]) .map(isAttachedToId => processor.output.entitiesById[isAttachedToId]) .filter( candidateEntity => sortedIds.indexOf(candidateEntity.id) > -1 ).length > 0 ); } }) ]; //*/ function sortUnsortedRecursive(_a, i) { var sortedIds = _a.sortedIds, unsorted = _a.unsorted; if (i === void 0) { i = 0; } // TODO is there something better we can do than use RECURSION_LIMIT? // WP2037 revision 90015 won't terminate without a limit, but converts // OK with the limit set. if (unsorted.length === 0 || i > exports.RECURSION_LIMIT) { return { sortedIds: sortedIds, unsorted: unsorted }; } i += 1; return sortUnsortedRecursive(sortUnsortedOnce({ sortedIds: sortedIds, unsorted: unsorted }), i); } function sortUnsortedOnce(_a) { var sortedIds = _a.sortedIds, unsorted = _a.unsorted; var _b = fp_1.partition(function (pvjsonEntity) { var dependencies = gpml_utilities_1.unionLSV(pvjsonEntity["contains"], pvjsonEntity["isAttachedToOrVia"], pvjsonEntity["isAttachedTo"]); return ( /* dependencies .map((id: string) => processor.output.entitiesById[id]) .indexOf(undefined) === -1 && //*/ fp_1.intersection(dependencies, sortedIds).length === dependencies.length); }, unsorted), sortedOnThisIteration = _b[0], stillUnsorted = _b[1]; sortedOnThisIteration /* .map(function(pvjsonEntity) { const testIndex = findIndex(function(isProcessableTest) { return isProcessableTest(sortedIds, pvjsonEntity); }, isProcessableTests); return { testIndex, pvjsonEntity }; }) .sort(function(a, b) { if (a.testIndex < b.testIndex) { return -1; } else if (a.testIndex > b.testIndex) { return 1; } else { return 0; } }) .map(x => x["pvjsonEntity"]["id"]) //*/ .forEach(function (pvjsonEntity) { sortedIds.push(pvjsonEntity.id); }); return { sortedIds: sortedIds, unsorted: stillUnsorted }; } return (s // TODO should we use scan and debounce here to pipe out the in-progress // pvjson as it's being converted? .reduce({ sortedIds: [], unsorted: [] }, function (_a, pvjsonEntity) { var sortedIds = _a.sortedIds, unsorted = _a.unsorted; unsorted.push(pvjsonEntity); return sortUnsortedOnce({ sortedIds: sortedIds, unsorted: unsorted }); }) .map(sortUnsortedRecursive) .map(function (acc) { var sortedIds = acc.sortedIds, unsorted = acc.unsorted; return sortedIds .map(function (id) { return processor.output.entitiesById[id]; }) .concat(unsorted); }) .sequence()); } var pvjsonEntityStream = hl([ hl([dataNodeStream, stateStream, shapeStream, labelStream]).merge(), hl([edgeStream, anchorStream]).merge(), groupStream ]) .sequence() // TODO should this be happening BEFORE the postprocessing step? .doto(setPvjsonEntity) .through(postprocessAll) .flatMap(function (pvjsonEntity) { var id = pvjsonEntity.id, zIndex = pvjsonEntity.zIndex; // TODO we might want to sort by other criteria, such as // to order a State above its DataNode, which would be // ordered above its Group, if any var insertEntityIdAndSortByZIndex = fp_1.flow([ gpml_utilities_1.insertIfNotExists(id), sortByZIndex ]); var finalSortedStream; if (gpml_utilities_1.isPvjsonEdgeOrBurr(pvjsonEntity)) { var isAttachedTo = pvjsonEntity.isAttachedTo; gpml_utilities_1.arrayify(isAttachedTo).forEach(function (graphRef) { var graphRefs = graphIdsByGraphRef[graphRef] || []; if (graphRefs.indexOf(id) === -1) { graphRefs.push(id); } graphIdsByGraphRef[graphRef] = graphRefs; }); if (gpml_utilities_1.isPvjsonBurr(pvjsonEntity)) { finalSortedStream = hl(getPvjsonEntityLatestByGraphId(isAttachedTo)).map(function (referencedEntity) { if (gpml_utilities_1.isPvjsonNode(referencedEntity)) { var attachmentDisplay = pvjsonEntity.attachmentDisplay; var _a = attachmentDisplay.relativeOffset, relativeOffsetScalarX = _a[0], relativeOffsetScalarY = _a[1]; attachmentDisplay.offset = [ relativeOffsetScalarX * referencedEntity.width, relativeOffsetScalarY * referencedEntity.height ]; pvjsonEntity.attachmentDisplay = fp_1.omit(["relativeOffset"], attachmentDisplay); } setPvjsonEntity(pvjsonEntity); // NOTE: burrs are not added to the property "contained". // Rather, they are added to the property "burrs". referencedEntity.burrs = referencedEntity.burrs || []; insertEntityIdAndSortByZIndex(referencedEntity.burrs); setPvjsonEntity(referencedEntity); return processor.output; }); } else if (gpml_utilities_1.isPvjsonEdge(pvjsonEntity)) { try { var pvjsonEdge = edge_1.postprocessPVJSON(processor.output.entitiesById, pvjsonEntity); processor.output = iassign(processor.output, function (o) { return o.pathway.contains; }, insertEntityIdAndSortByZIndex); setPvjsonEntity(pvjsonEdge); finalSortedStream = hl([processor.output]); } catch (err) { return hl.fromError(err); } } else { return hl.fromError(new VError("\n\t\tUnexpected entity type.\n\t\tOnly Edge or Burr should return true for\n\t\tisPvjsonEdgeOrBurr(\n\t\t\t" + JSON.stringify(pvjsonEntity, null, " ") + "\n\t\t)\n\t\t")); } } else if (gpml_utilities_1.isPvjsonGroup(pvjsonEntity)) { // We still have some GPML files with empty Groups and/or nested Groups // floating around, but we don't process them, because that's a // curation issue, not a gpml2pvjson issue. var containedCount = pvjsonEntity.contains.length; if (containedCount === 0 || pvjsonEntity.hasOwnProperty("groupRef")) { if (containedCount === 0) { return hl.fromError(new Error("\n\t\tEncountered empty Group:\n\t\t" + JSON.stringify(pvjsonEntity, null, " ") + "\n\t\t")); } if (pvjsonEntity.hasOwnProperty("groupRef")) { return hl.fromError(new Error("\n\t\tEncountered nested Group:\n\t\t" + JSON.stringify(pvjsonEntity, null, " ") + "\n\t\t")); } finalSortedStream = hl([processor.output]); } else { var graphIdOfGroup = pvjsonEntity.id; try { finalSortedStream = hl(pvjsonEntity.contains.map(function (containedId) { return processor.output.entitiesById[containedId]; })) .filter(function (groupedEntity) { return groupedEntity.kaavioType !== "Group"; }) .collect() .map(function (groupedEntities) { var pvjsonGroup = group_1.postprocessPVJSON(groupedEntities, pvjsonEntity); var graphIdToZIndex = processor.graphIdToZIndex; pvjsonGroup.contains = fp_1.sortBy([ function (thisEntityId) { return graphIdToZIndex[thisEntityId]; } ], groupedEntities.map(function (x) { return x.id; })); var id = pvjsonGroup.id, x = pvjsonGroup.x, y = pvjsonGroup.y; var groupedEntitiesFinal = groupedEntities.map(function (groupedEntity) { if (gpml_utilities_1.isPvjsonEdge(groupedEntity)) { groupedEntity.points = fp_1.map(function (point) { point.x -= x; point.y -= y; return point; }, groupedEntity.points); } else if (gpml_utilities_1.isPvjsonSingleFreeNode(groupedEntity)) { groupedEntity.height; groupedEntity.x -= x; groupedEntity.y -= y; } else { return hl.fromError(new Error("\n\t\t\tEncountered unexpected entity\n\t\t\t" + JSON.stringify(groupedEntity, null, " ") + "\n\t\t\tin Group\n\t\t\t" + JSON.stringify(pvjsonGroup, null, " ") + "\n\t\t\t")); } // NOTE: this is needed for GPML2013a, because GPML2013a uses both // GroupId/GroupRef and GraphId/GraphRef. GPML2017 uses a single // identifier per entity. That identifier can be referenced by // GroupRef and/or GraphRef. Pvjson follows GPML2017 in this, so // we convert from GPML2013a format: // GroupRef="GROUP_ID_VALUE" // to pvjson format: // {isPartOf: "GRAPH_ID_VALUE"} groupedEntity.isPartOf = id; return fp_1.omit(["groupRef"], groupedEntity); }); groupedEntitiesFinal.forEach(function (pvjsonEntity) { setPvjsonEntity(pvjsonEntity); }); setPvjsonEntity(pvjsonGroup); processor.output = iassign(processor.output, function (o) { return o.pathway.contains; }, function (contains) { return insertEntityIdAndSortByZIndex(fp_1.difference(contains, groupedEntitiesFinal.map(function (x) { return x["id"]; })), id); }); return pvjsonGroup; }) .map(function (pvjsonEntity) { return processor.output; }); } catch (err) { return hl.fromError(err); } } } else { setPvjsonEntity(pvjsonEntity); processor.output = iassign(processor.output, function (o) { return o.pathway.contains; }, insertEntityIdAndSortByZIndex); finalSortedStream = hl([processor.output]); } return finalSortedStream; }); pvjsonEntityStream .observe() .last() .doto(function () { processor.pvjsonEntityLatestStream.end(); }); var openControlledVocabularyStream = hl(cxmlSources["/Pathway/Biopax/bp:openControlledVocabulary"]) .map(processPropertiesAndType("openControlledVocabulary")) .map(function (openControlledVocabulary) { var vocabularyName = openControlledVocabulary.ontology; var vocabularyIRI = VOCABULARY_NAME_TO_IRI[vocabularyName]; if (!vocabularyIRI) { return hl.fromError(new Error("\n\t\tEncountered unexpected name \"" + vocabularyName + "\" for openControlledVocabulary,\n\t\twith xrefIdentifier \"" + openControlledVocabulary.xrefIdentifier + "\"\n\t\t")); /* TODO should we use this? vocabularyIRI = `http://www.ebi.ac.uk/miriam/main/search?query=${vocabularyName.replace( /\ /, "+" )}#`; //*/ } openControlledVocabulary.id = vocabularyIRI + openControlledVocabulary.xrefIdentifier; return openControlledVocabulary; }) .collect() .map(function (openControlledVocabularies) { // TODO should these go through the processor instead? processor.output = iassign(processor.output, function (o) { var pathway = o.pathway, entitiesById = o.entitiesById; openControlledVocabularies.forEach(function (openControlledVocabulary) { var id = openControlledVocabulary.id; entitiesById[id] = openControlledVocabulary; if (openControlledVocabulary.ontology === "Pathway Ontology") { pathway.type.push(id); } }); return o; }); return processor.output; }); var publicationXrefStream = hl(cxmlSources["/Pathway/Biopax/bp:PublicationXref"]) .map(processPropertiesAndType("PublicationXref")) .collect() .map(function (publicationXrefs) { publicationXrefs // TODO I believe we sort by date, but check that is true and // not by the order they appear in the GPML .sort(function (a, b) { var yearA = parseInt(a.year); var yearB = parseInt(b.year); if (yearA > yearB) { return 1; } else if (yearA < yearB) { return -1; } else { return 0; } }) .forEach(function (publicationXref, i) { publicationXref.textContent = String(i + 1); }); return publicationXrefs; }) .map(function (publicationXrefs) { // TODO should these go through the processor instead? processor.output = iassign(processor.output, function (o) { return o.entitiesById; }, function (entitiesById) { publicationXrefs.forEach(function (publicationXref) { entitiesById[publicationXref.id] = publicationXref; }); return entitiesById; }); return processor.output; }); /* TODO do we need to handle these? <xsd:element ref="gpml:InfoBox" minOccurs="1" maxOccurs="1" /> <xsd:element ref="gpml:Legend" minOccurs="0" maxOccurs="1"/> */ return hl([ pathwayMetadataStream, pathwayCommentStream, pvjsonEntityStream, openControlledVocabularyStream, publicationXrefStream ]) .merge() .errors(function (err) { throw new VError(err, " when converting pathway " + pathwayIri + "\n\t\t\t\t"); }); // // TODO Double-check old code to make sure nothing is missed. // // TODO does the stream ever end? // // TODO does backpressure work? // TODO compare the old pvjs/kaavio code below for grouping entities to // ensure we don't have any regression errors. /* getGroupedZIndexedEntities(zIndexedEntities) { const { entitiesById } = this.props; return zIndexedEntities .filter(entity => !entity.isPartOf) .reduce(function(acc, entity) { const kaavioType = entity.kaavioType; if (kaavioType === "Group") { // TODO: refactor this so that contains is actually a map of the contained elements. Not just an array of their IDs entity.contains = entity.contains .map(id => entitiesById[id]) .sort(function(a, b) { const zIndexA = a.zIndex; const zIndexB = b.zIndex; if (zIndexA < zIndexB) { return 1; } else if (zIndexA > zIndexB) { return -1; } else { return 0; } }) .map(entity => entity.id); } else if (entity.hasOwnProperty("burrs")) { entity.burrs = entity.burrs .map(id => entitiesById[id]) .sort(function(a, b) { const zIndexA = a.zIndex; const zIndexB = b.zIndex; if (zIndexA < zIndexB) { return 1; } else if (zIndexA > zIndexB) { return -1; } else { return 0; } }) .map(entity => entity.id); } if ( ["Burr"].indexOf(kaavioType) === -1 && !entity.hasOwnProperty("isPartOf") ) { acc.push(entity); } return acc; }, []); } //*/ } exports.toPvjson = toPvjson; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9Qdmpzb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvMjAxM2EvdG9Qdmpzb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsdUNBQXFDO0FBQ3JDLHVFQUF1RTtBQUV2RSxpQ0FBMkM7QUFDM0MsZ0NBdUJtQjtBQUNuQiw2QkFBK0I7QUFDL0IsK0JBQWlDO0FBRWpDLHFEQUFtRDtBQWtCbkQsZ0VBQWtFO0FBRWxFLDhDQUFnRDtBQUVoRCwwQ0FBeUM7QUFDekMscUNBR3NCO0FBQ3RCLGtDQUdrQjtBQUNsQixvREFjMkI7QUFDM0IsZ0ZBQWtGO0FBRWxGLHlEQUEyRDtBQUMzRCxnRUFBa0U7QUFDbEUsNkRBQStEO0FBQy9ELDREQUE4RDtBQUU5RCxrRUFBa0U7QUFFbEUsMENBQTRDO0FBQzVDLE9BQU8sQ0FBQyxTQUFTLENBQUM7SUFDaEIseUZBQXlGO0lBQ3pGLG9HQUFvRztJQUNwRyxlQUFlO0lBQ2YsZ0JBQWdCLEVBQUUsSUFBSTtDQUN2QixDQUFDLENBQUM7QUFVVSxRQUFBLGVBQWUsR0FBRyxJQUFJLENBQUM7QUFFcEMsU0FBUyxlQUFlLENBQ3RCLENBQXFCLEVBQ3JCLFdBQThCO0lBRTlCLElBQU0sR0FBRyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxNQUFNLENBQUMsVUFBQSxDQUFDLElBQUksT0FBQSxXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQWQsQ0FBYyxDQUFDLENBQUM7SUFDakQsSUFBTSxFQUFFLEdBQUcsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLE1BQU0sQ0FBQyxVQUFBLENBQUMsSUFBSSxPQUFBLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxFQUFmLENBQWUsQ0FBQyxDQUFDO0lBQ2pELE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDbkIsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLG1CQUFtQixFQUFFLE1BQU07SUFDN0MsSUFBTSxNQUFNLEdBQUcsWUFB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