UNPKG

gpml2pvjson

Version:
748 lines (747 loc) 60.7 kB
import "source-map-support/register"; // TODO should I get rid of the lib above for production browser build? import { assign as assignM } from "lodash"; import { assign, curry, difference, flow, intersection, isArray, isObject, map, omit, partition, sortBy, startCase, toPairsIn } from "lodash/fp"; import * as hl from "highland"; import * as VError from "verror"; import { CXMLXPath } from "../spinoffs/cxml-xpath"; import * as GPML2013a from "../../xmlns/pathvisio.org/GPML/2013a"; import * as GPMLDefaults from "../GPMLDefaults"; import { Processor } from "../Processor"; import { preprocessGPML as preprocessEdgeGPML, postprocessPVJSON as postprocessEdgePVJSON } from "../edge/edge"; import { preprocessGPML as preprocessGroupGPML, postprocessPVJSON as postprocessGroupPVJSON } from "../group"; import { arrayify, insertIfNotExists, isDefinedCXML, isPvjsonBurr, isPvjsonEdge, isPvjsonGroup, isPvjsonSingleFreeNode, isPvjsonNode, isPvjsonEdgeOrBurr, sortByMap, supportedNamespaces, unionLSV } from "../gpml-utilities"; import * as VOCABULARY_NAME_TO_IRI from "../spinoffs/VOCABULARY_NAME_TO_IRI.json"; import * as GPML2013aKeyMappings from "./KeyMappings.json"; import * as GPML2013aKeyValueMappings from "./KeyValueConverters"; import * as GPML2013aValueMappings from "./ValueMappings.json"; import * as GPML2013aValueConverters from "./ValueConverters"; // TODO get text alignment correctly mapped to Box Model CSS terms 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 }); export const RECURSION_LIMIT = 1000; function partitionStream(s, partitioner) { const yes = s.fork().filter(x => partitioner(x)); const no = s.fork().filter(x => !partitioner(x)); return [yes, no]; } function extendDeep(targetOrTargetArray, source) { const target = 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")) { toPairsIn(target) .filter(([targetKey, targetValue]) => source.hasOwnProperty(targetKey) && isObject(source[targetKey])) .forEach(function ([targetKey, targetValue]) { extendDeep(targetValue, source[targetKey]); }); assignM(target.constructor.prototype, source); target._extended = true; } } const stringifyKeyValue = curry(function (source, key) { return source.hasOwnProperty(key) ? 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 export 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. const inputStream = hl(inputStreamWithMessedUpRDFIDs) .splitBy(' rdf:id="') .intersperse(' rdf:ID="'); const 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] }; const cxmlXPath = new CXMLXPath(inputStream, GPML2013a, { bp: "http://www.biopax.org/release/biopax-level3.owl#" //rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#" }); const cxmlSources = cxmlXPath.parse(selectorToCXML); const processor = new Processor(GPML2013aKeyMappings, GPML2013aKeyValueMappings, GPML2013aValueMappings, GPML2013aValueConverters); const { fillInGPMLPropertiesFromParent, getPvjsonEntityLatestByGraphId, graphIdsByGraphRef, graphIdToZIndex, getGPMLElementByGraphId, preprocessGPMLElement, processGPMLAndPropertiesAndType, processProperties, processPropertiesAndType, setPvjsonEntity } = processor; if (pathwayIri) { processor.output = iassign(processor.output, function (o) { return o.pathway; }, function (pathway) { pathway.id = pathwayIri; return pathway; }); } const sortByZIndex = sortByMap(graphIdToZIndex); const pathwayMetadataStream = hl([ cxmlSources["/Pathway/@*"].doto(function (pathway) { if (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 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) { const mergedPathway = assign(pathway, metadata); // NOTE: GPML schema specifies that name is required const { name } = mergedPathway; const 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 + ")"; // } const 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 const organismIriComponent = mergedPathway.hasOwnProperty("organism") ? `&species=${mergedPathway.organism}` : ""; mergedPathway.isSimilarTo = encodeURI(`http://wikipathways.org/index.php/Special:SearchPathways?query=${name}${organismIriComponent}&doSearch=1`); } return assign({ "@context": context }, mergedPathway); }); return processor.output; }) .errors(function (err) { throw new VError(err, ` when processing pathwayMetadataStream `); }); const pathwayCommentStream = hl(cxmlSources["/Pathway/Comment"]).map(function (Comment) { processor.output = iassign(processor.output, function (o) { return o.pathway; }, function (pathway) { const comments = pathway.comments || []; comments.push(processProperties(Comment)); pathway.comments = comments; return pathway; }); return processor.output; }); const 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 = unionLSV(entity.type, entity.wpType); return entity; }); const stateStream = cxmlSources["/Pathway/State"] .map(preprocessGPMLElement) .flatMap(function (gpmlState) { return hl(getGPMLElementByGraphId(gpmlState.GraphRef)).map(function (gpmlDataNode) { return fillInGPMLPropertiesFromParent(gpmlDataNode, gpmlState); }); }) .map(processPropertiesAndType("State")); const shapeStream = cxmlSources["/Pathway/Shape"] .map(processGPMLAndPropertiesAndType("Shape")) .map(function (pvjsonEntity) { const { cellularComponent } = pvjsonEntity; // CellularComponent is not a BioPAX term, but "PhysicalEntity" is. if (!!cellularComponent) { pvjsonEntity.type = unionLSV(pvjsonEntity.type, "PhysicalEntity", "CellularComponent", cellularComponent); } return pvjsonEntity; }); const labelStream = cxmlSources["/Pathway/Label"].map(processGPMLAndPropertiesAndType("Label")); const gpmlInteractionStream = cxmlSources["/Pathway/Interaction"].map(preprocessGPMLElement); const gpmlGraphicalLineStream = cxmlSources["/Pathway/GraphicalLine"].map(preprocessGPMLElement); const edgeStream = hl([ gpmlInteractionStream .fork() .map(preprocessEdgeGPML) .map(processPropertiesAndType("Interaction")), gpmlGraphicalLineStream .fork() .map(preprocessEdgeGPML) .map(processPropertiesAndType("GraphicalLine")) ]).merge(); const anchorStream = hl([ gpmlInteractionStream.fork(), gpmlGraphicalLineStream.fork() ]) .merge() .filter(function (gpmlEdge) { return (isDefinedCXML(gpmlEdge.Graphics) && isDefinedCXML(gpmlEdge.Graphics.Anchor)); }) .flatMap(function (gpmlEdge) { const { GraphId, Graphics } = gpmlEdge; const fillInGPMLPropertiesFromEdge = fillInGPMLPropertiesFromParent(gpmlEdge); const gpmlAnchors = Graphics.Anchor; return hl(gpmlAnchors) .map(function (gpmlAnchor) { const 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 (isDefinedCXML(gpmlAnchor.GraphId)) { assignM(gpmlAnchor.Graphics, { Height: 0, Width: 0 }); } else { gpmlAnchor.Shape = "Rectangle"; assignM(gpmlAnchor.Graphics, { Height: 4, Width: 4 }); } } else if (anchorShape === "Circle") { assignM(gpmlAnchor.Graphics, { Height: 8, Width: 8 }); } else { throw new Error(`Anchor Shape "${anchorShape}" is not supported.`); } return gpmlAnchor; }) .map(preprocessGPMLElement) .map(function (gpmlAnchor) { const filledInAnchor = fillInGPMLPropertiesFromEdge(gpmlAnchor); filledInAnchor.GraphRef = GraphId; return filledInAnchor; }) .map(processPropertiesAndType("Anchor")); }); const groupStream = cxmlSources["/Pathway/Group"] .map(preprocessGroupGPML(processor)) // PathVisio shouldn't do this, but it sometimes makes empty Groups. // We filter them out here. .filter((Group) => !!Group.Contains) .map(processGPMLAndPropertiesAndType("Group")); const EDGES = ["Interaction", "GraphicalLine"]; const 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({ sortedIds, unsorted }, 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 > RECURSION_LIMIT) { return { sortedIds, unsorted }; } i += 1; return sortUnsortedRecursive(sortUnsortedOnce({ sortedIds, unsorted }), i); } function sortUnsortedOnce({ sortedIds, unsorted }) { let [sortedOnThisIteration, stillUnsorted] = partition(function (pvjsonEntity) { const dependencies = unionLSV(pvjsonEntity["contains"], pvjsonEntity["isAttachedToOrVia"], pvjsonEntity["isAttachedTo"]); return ( /* dependencies .map((id: string) => processor.output.entitiesById[id]) .indexOf(undefined) === -1 && //*/ intersection(dependencies, sortedIds).length === dependencies.length); }, unsorted); 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 ({ sortedIds, unsorted }, pvjsonEntity) { unsorted.push(pvjsonEntity); return sortUnsortedOnce({ sortedIds, unsorted }); }) .map(sortUnsortedRecursive) .map(function (acc) { const { sortedIds, unsorted } = acc; return sortedIds .map((id) => processor.output.entitiesById[id]) .concat(unsorted); }) .sequence()); } const 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) { const { id, zIndex } = pvjsonEntity; // 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 const insertEntityIdAndSortByZIndex = flow([ insertIfNotExists(id), sortByZIndex ]); let finalSortedStream; if (isPvjsonEdgeOrBurr(pvjsonEntity)) { const isAttachedTo = pvjsonEntity.isAttachedTo; arrayify(isAttachedTo).forEach(function (graphRef) { const graphRefs = graphIdsByGraphRef[graphRef] || []; if (graphRefs.indexOf(id) === -1) { graphRefs.push(id); } graphIdsByGraphRef[graphRef] = graphRefs; }); if (isPvjsonBurr(pvjsonEntity)) { finalSortedStream = hl(getPvjsonEntityLatestByGraphId(isAttachedTo)).map(function (referencedEntity) { if (isPvjsonNode(referencedEntity)) { const { attachmentDisplay } = pvjsonEntity; const [relativeOffsetScalarX, relativeOffsetScalarY] = attachmentDisplay.relativeOffset; attachmentDisplay.offset = [ relativeOffsetScalarX * referencedEntity.width, relativeOffsetScalarY * referencedEntity.height ]; pvjsonEntity.attachmentDisplay = 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 (isPvjsonEdge(pvjsonEntity)) { try { const pvjsonEdge = postprocessEdgePVJSON(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(` Unexpected entity type. Only Edge or Burr should return true for isPvjsonEdgeOrBurr( ${JSON.stringify(pvjsonEntity, null, " ")} ) `)); } } else if (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. const containedCount = pvjsonEntity.contains.length; if (containedCount === 0 || pvjsonEntity.hasOwnProperty("groupRef")) { if (containedCount === 0) { return hl.fromError(new Error(` Encountered empty Group: ${JSON.stringify(pvjsonEntity, null, " ")} `)); } if (pvjsonEntity.hasOwnProperty("groupRef")) { return hl.fromError(new Error(` Encountered nested Group: ${JSON.stringify(pvjsonEntity, null, " ")} `)); } finalSortedStream = hl([processor.output]); } else { const graphIdOfGroup = pvjsonEntity.id; try { finalSortedStream = hl(pvjsonEntity.contains.map(containedId => processor.output.entitiesById[containedId])) .filter(groupedEntity => groupedEntity.kaavioType !== "Group") .collect() .map(function (groupedEntities) { const pvjsonGroup = postprocessGroupPVJSON(groupedEntities, pvjsonEntity); const graphIdToZIndex = processor.graphIdToZIndex; pvjsonGroup.contains = sortBy([ function (thisEntityId) { return graphIdToZIndex[thisEntityId]; } ], groupedEntities.map(x => x.id)); const { id, x, y } = pvjsonGroup; const groupedEntitiesFinal = groupedEntities.map(function (groupedEntity) { if (isPvjsonEdge(groupedEntity)) { groupedEntity.points = map(function (point) { point.x -= x; point.y -= y; return point; }, groupedEntity.points); } else if (isPvjsonSingleFreeNode(groupedEntity)) { groupedEntity.height; groupedEntity.x -= x; groupedEntity.y -= y; } else { return hl.fromError(new Error(` Encountered unexpected entity ${JSON.stringify(groupedEntity, null, " ")} in Group ${JSON.stringify(pvjsonGroup, null, " ")} `)); } // 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 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(difference(contains, groupedEntitiesFinal.map(x => 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(); }); const openControlledVocabularyStream = hl(cxmlSources["/Pathway/Biopax/bp:openControlledVocabulary"]) .map(processPropertiesAndType("openControlledVocabulary")) .map(function (openControlledVocabulary) { const vocabularyName = openControlledVocabulary.ontology; let vocabularyIRI = VOCABULARY_NAME_TO_IRI[vocabularyName]; if (!vocabularyIRI) { return hl.fromError(new Error(` Encountered unexpected name "${vocabularyName}" for openControlledVocabulary, with xrefIdentifier "${openControlledVocabulary.xrefIdentifier}" `)); /* 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) { const { pathway, entitiesById } = o; openControlledVocabularies.forEach(function (openControlledVocabulary) { const { id } = openControlledVocabulary; entitiesById[id] = openControlledVocabulary; if (openControlledVocabulary.ontology === "Pathway Ontology") { pathway.type.push(id); } }); return o; }); return processor.output; }); const 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) { const yearA = parseInt(a.year); const 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} `); }); // // 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; }, []); } //*/ } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9Qdmpzb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvMjAxM2EvdG9Qdmpzb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyw2QkFBNkIsQ0FBQztBQUNyQyx1RUFBdUU7QUFFdkUsT0FBTyxFQUFFLE1BQU0sSUFBSSxPQUFPLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDM0MsT0FBTyxFQUNMLE1BQU0sRUFJTixLQUFLLEVBRUwsVUFBVSxFQUdWLElBQUksRUFDSixZQUFZLEVBQ1osT0FBTyxFQUVQLFFBQVEsRUFDUixHQUFHLEVBQ0gsSUFBSSxFQUNKLFNBQVMsRUFFVCxNQUFNLEVBQ04sU0FBUyxFQUNULFNBQVMsRUFFVixNQUFNLFdBQVcsQ0FBQztBQUNuQixPQUFPLEtBQUssRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUMvQixPQUFPLEtBQUssTUFBTSxNQUFNLFFBQVEsQ0FBQztBQUVqQyxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFrQm5ELE9BQU8sS0FBSyxTQUFTLE1BQU0sc0NBQXNDLENBQUM7QUFFbEUsT0FBTyxLQUFLLFlBQVksTUFBTSxpQkFBaUIsQ0FBQztBQUVoRCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ3pDLE9BQU8sRUFDTCxjQUFjLElBQUksa0JBQWtCLEVBQ3BDLGlCQUFpQixJQUFJLHFCQUFxQixFQUMzQyxNQUFNLGNBQWMsQ0FBQztBQUN0QixPQUFPLEVBQ0wsY0FBYyxJQUFJLG1CQUFtQixFQUNyQyxpQkFBaUIsSUFBSSxzQkFBc0IsRUFDNUMsTUFBTSxVQUFVLENBQUM7QUFDbEIsT0FBTyxFQUNMLFFBQVEsRUFFUixpQkFBaUIsRUFDakIsYUFBYSxFQUNiLFlBQVksRUFDWixZQUFZLEVBQ1osYUFBYSxFQUNiLHNCQUFzQixFQUN0QixZQUFZLEVBQ1osa0JBQWtCLEVBQ2xCLFNBQVMsRUFDVCxtQkFBbUIsRUFDbkIsUUFBUSxFQUNU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