gpml2pvjson
Version:
convert GPML (XML) to pvjson (json)
738 lines (737 loc) • 61.4 kB
JavaScript
"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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