gpml2pvjson
Version:
convert GPML (XML) to pvjson (json)
748 lines (747 loc) • 60.7 kB
JavaScript
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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