GML ​
GML, the Graph Modelling Language, is a text format of nested key value records. graph-io reads and writes it the way NetworkX and igraph do.
At a glance ​
| Import from | @graphty/graph-io/gml |
| Format name | gml |
| Extensions | .gml |
| MIME types | text/x-gml, text/plain |
| Reads | yes |
| Writes | yes |
| Several graphs per file | yes (importAllGraphs) |
| Lists its graphs | no |
Loading and saving ​
import { readFile, writeFile } from "node:fs/promises";
import { checkExport, exportGraphToBytes, importGraph } from "@graphty/graph-io";
// got.gml was saved with sanitizeIds: "mangle": its ids are numbers, and the names are restored on reading
const { snapshot } = await importGraph(await readFile("got.gml"), { filename: "got.gml" });
console.log(`${snapshot.nodeCount} nodes; node columns: ${snapshot.nodes.names().join(", ")}`);
console.log(`node 0: id ${JSON.stringify(snapshot.ids.idOf(0))}, label ${String(snapshot.nodes.value("label", 0))}`);
// Saving it again needs the same option, because the ids are text once more
const options = { sanitizeIds: "mangle" } as const;
console.log(checkExport(snapshot, "gml", options).map((n) => n.code));
await writeFile("got-copy.gml", await exportGraphToBytes(snapshot, "gml", options));107 nodes; node columns: label
node 0: id "Aemon", label Aemon
[ 'W_ID_MANGLED' ]GML node ids are integers, so got.gml was saved with sanitizeIds: "mangle": the file numbers the nodes and keeps each character's name in a graphty_originalId key, and graph-io reads the names back as the ids. Saving the graph to GML again needs the same option, and the only note is W_ID_MANGLED.
How graph-io reads it ​
- The whole file is read as text before it is parsed.
- Node and edge keys become columns. GML values carry their own type, so a column of
intvalues is an integer column,realvalues make a floating-point column, quoted strings make text, a nested[ ]record is kept as JSON, and a key repeated within one node is a list. NetworkX's_networkx_list_startmarker and its"[]"empty list are understood. directed 1makes a directed graph; a file withoutdirectedis undirected. Thedirectedkey as the file wrote it is kept insnapshot.meta.extra.gml.directed(absent when the file has none), so you can telldirected 0from a file that relies on the default.- An edge's
valuekey is its weight (theweightFromoption changes the key). - The graph block's own keys become graph attributes (
snapshot.graph). Itsnamekey is also the graph's name,snapshot.meta.name, whichgraphNamematches in a file with several graphs. - An integer beyond 2^53 is stored as the nearest JavaScript number, with a
W_PRECISIONwarning. Passlong: "string"to keep every digit: that key's values are then stored as text. - A node's
graphics [ x y z ]becomes its position; the othergraphicskeys are kept as JSON. Passpositions: falseto keep the wholegraphicsrecord as JSON. - The GML specification makes node ids integers. NetworkX and Gephi also write text ids; graph-io reads them under the
idsoption with oneW_GML_STRING_IDwarning per file. nodeIdFrom: "label"or"index"takes node ids from the labels or from the node order, for files whoseidkeys are meaningless numbers. Every node still needs itsidkey, because edges name their ends by it: a node without one is skipped withE_MISSING_ID.#comments,+INF,-INFandNANare read. A file can hold severalgraph [ ]blocks; see Files that hold several graphs.- Under
nodeIdFrom: "label"or"index"the integeridkeys are not kept, andreport.lossyholds aW_GML_ID_DROPPEDnote that says so. - Text columns with many repeated values are stored as dictionaries to save memory; pass
dictionaries: falseto store them as plain text.
What a saved file keeps and loses ​
What does not survive:
- One direction per file. A graph with both kinds of edges needs
onMixedDirection. - Node ids must be integers. Other ids need
sanitizeIds: "mangle", which numbers the nodes and keeps each original id in agraphty_originalIdkey that graph-io restores. - Column names must be GML keys: a letter followed by letters, digits or underscores, and not one of GML's own keys. Other names make the export throw (
E_GML_INVALID_KEY,E_GML_RESERVED_KEY) unless you passsanitizeKeys: "mangle", which rewrites them. - Inside a JSON record, GML cannot tell integers from reals, writes booleans as 1 and 0, and has no null (
W_GML_RECORD_NUMBER_TYPEand related notes). - Edge ids are kept; time columns, visual columns other than the position, and nesting are not.
- The graph's name is written only from a
namegraph attribute, which a GML import has. A name from another format (for example,snapshot.meta.nameof a DOT graph) is not written.
What a saved file can hold (the capabilities explain each row):
| Capability | Value |
|---|---|
mixedDirection | no |
multiEdges | yes |
selfLoops | yes |
edgeIds | optional |
idCharset | integer |
dtypes | i32, f64, string, dict, json |
components | no |
lists | yes |
json | yes |
defaults | no |
options | no |
hierarchy | no |
temporal | none |
graphAttributes | yes |
positions | yes |
viz | no |
Import options ​
These come on top of the options every importer takes. A file can hold several graphs: pick one with the graphIndex or graphName option of importGraph(), as Files that hold several graphs shows.
| Option | Type | Default |
|---|---|---|
positions | boolean | true |
dictionaries | boolean | true |
positions: Read a node'sgraphics [ x y z ]record as its position; false keeps the whole record as a JSON attribute namedgraphics.dictionaries: Store a text attribute whose values repeat a lot (fewer distinct values than half the rows) as a dictionary column, which uses less memory and reads the same. Such a column reportsmeta.dtype"dict" instead of "string". A column with a role, such as thelabelcolumn, always stays "string".
Export options ​
These come on top of the options every exporter takes.
| Option | Type | Default |
|---|---|---|
weightKey | string | as read, else "value" |
sanitizeKeys | "error" | "mangle" | "error" |
weightKey: The edge key the weights are written under. The default is the key a GML import read them from, elsevalue.sanitizeKeys: What to do with an attribute name or record key that GML cannot write: one that is not a GML key (letters, digits and_, starting with a letter, soEdge Labelis not one), or one that GML uses itself in that record:idfor a node attribute;source,targetanddirectedfor an edge attribute;node,edge,directedandmultigraphfor a graph attribute. A node attribute namedsourceis fine. "error" makes the save fail, "mangle" rewrites the name (.and other characters become_, a clash gets a_2suffix) and checkExport() lists each rename. An options object shared by saves to several formats that include GML should set it.
Import issue codes ​
The codes this format's import report can hold. They are also exported as GML_ISSUE from @graphty/graph-io/gml, keyed by the code without its E_ / W_ and GML_ prefixes.
E_SYNTAX(error): The text breaks GML's syntax: a word that is not a key or a value, an unclosed string or[, a stray], or a key without a value. The import stops.E_INVALID_UTF8(error): The input is not valid UTF-8. The import stops.E_INVALID_ENCODING(error): Some bytes are not valid in the encoding that was chosen (by a byte order mark, the file's declaration or theencodingoption). The import stops.W_ENCODING_FALLBACK(warning): Bytes that are not UTF-8 and declare no encoding were read as windows-1252.W_UNKNOWN_ENCODING(warning): A declared encoding the platform cannot decode was ignored.E_NO_GRAPH(error): There is nograph [block. The import stops.W_MULTIPLE_GRAPHS(warning): The file holds more than onegraphblock and only the first was read. It is not added whengraphIndexorgraphNamechose the graph.E_GRAPH_NOT_FOUND(error):graphIndexorgraphNamenames no graph of the file; the message lists the graphs it holds. The import stops.E_AMBIGUOUS_GRAPH_NAME(error):graphNamematches more than one graph; passgraphIndex. The import stops.E_MISSING_ID(error): A node without anid.E_GML_MISSING_LABEL(error): A node without alabelunder nodeIdFrom "label".E_MISSING_ENDPOINT(error): An edge withoutsourceortarget.E_GML_ID_TYPE(error): A node id, source or target that is neither an integer nor a string.W_GML_STRING_ID(warning): Node ids, sources or targets are text, where GML expects integers. They are read under theidsoption. Reported once per file.W_DUPLICATE_NODE(warning): A node id declared twice (later keys overwrite).E_GML_REPEATED_KEY(error): A structural key repeated in one element.E_GML_ELEMENT_TYPE(error): Anode/edgekey whose value is not a record.E_GML_FLAG_TYPE(error): Adirected/multigraphflag that is not an integer.W_GML_FLAG_VALUE(warning): Adirected/multigraphflag outside 0 / 1, or repeated.W_GML_UNKNOWN_ENTITY(warning): A named entity no table decodes, or a numeric reference beyond U+10FFFF; kept as written.W_PRECISION(warning): An integer beyond 2^53 was stored as the nearest 64-bit float; passlong: "string"to keep every digit.W_GML_GRAPHICS(warning): A node's graphics value that cannot give a position as written; kept in the graphics json column.W_GML_NESTED_ELEMENT(warning): A graph, node or edge record nested in a node or edge; kept as json, not read as structure.W_GML_GROUPS(warning): YEd's isGroup / gid keys, kept as plain columns; the hierarchy is not read as containment.W_WIDENED(warning): An attribute's type was widened because a later value did not fit: an integer above 2^31 in an integer column, or two declared types for one attribute.W_COLUMN_RENAMED(warning): An attribute was renamed<name>#<suffix>because another attribute already has its name, for example two attributes declared with the same name.W_ROLE_TAKEN(warning): You read into a graph builder that already has an id, label or position attribute, so this file's one is kept as a plain attribute.W_ID_MERGED(warning): Two different id texts became the same number becauseidsis "number", so their nodes were merged.W_OPTION_IGNORED(warning): You set an option this format does not use; it had no effect. The message names the option.W_SINK_OPTION(warning): You read into your own graph builder, which was created with a differentaddMissingNodes,duplicateEdges,selfLoopsorweightDtypethan the option you passed; the builder's setting applies.W_DIRECTION_REFUSED(warning): You read into a graph builder whose direction is already set, or which already holds edges, so the file is read with the builder's direction instead of its own.W_DIRECTION_FORCED(warning): Edges of the other direction were read with the directiononMixedDirectionchose.E_MIXED_DIRECTION(error): The graph has both directed and undirected edges andonMixedDirectionis "error". An import stops; a save to a format that holds one direction per file fails withE_DIRECTED. Pass "directed" or "undirected" to read or write it anyway.W_GML_ID_DROPPED(warning): UndernodeIdFrom: "label"or"index"the file's integer ids are not kept. It is listed inreport.lossy, not inreport.issues.
Like every format, it can also record the codes for unreadable input and for elements the graph refuses: E_EMPTY_INPUT, E_TOO_LARGE, W_ENCODING_CONFLICT, W_CONTROL_CHARACTER, E_FOREIGN_FORMAT, W_ISSUES_SUPPRESSED, E_INVALID_ID, E_UNKNOWN_NODE, E_INVALID_WEIGHT, E_DUPLICATE_EDGE, E_SELF_LOOP, E_DUPLICATE_EDGE_ID.
Loss codes ​
The codes checkExport(snapshot, "gml", options) can return before a save, also exported as GML_LOSS from @graphty/graph-io/gml. An E_ code means the save throws unless you change the graph or the options.
W_GML_RECORD_NUMBER_TYPE(warning): A json column holds numbers; GML records cannot keep int versus real.W_GML_RECORD_BOOLEAN(warning): A json column holds booleans, written 1 / 0.W_GML_RECORD_NULL(warning): A json column holds nulls, omitted.E_GML_NESTED_ARRAY(error, the save throws): An attribute holds an array inside an array, which GML cannot write; the save fails.W_GML_JSON_ARRAY(warning): A json row that is an array, written as repeated keys.E_GML_INVALID_KEY(error, the save throws): An attribute name GML cannot use as a key (keys are letters and digits, starting with a letter). The save fails unlesssanitizeKeysis "mangle".E_GML_RESERVED_KEY(error, the save throws): A column named like a structural key.W_GML_KEY_MANGLED(warning): A key rewritten under sanitizeKeys "mangle".W_GML_POSITION_COMPONENTS(warning): A position column with more than three components.W_GML_GRAPHICS_OVERRIDDEN(warning): A graphics record whose x / y / z the position column overrides.E_GML_GRAPHICS_CONFLICT(error, the save throws): A graphics record that cannot hold the position.
When the graph has something this format cannot hold, it can also return the shared loss codes: E_ID_CHARSET, E_ID_TEXT_COLLISION, E_MIXED_DIRECTION, E_XML_ILLEGAL_CHAR, W_COLUMN_DROPPED, W_COLUMN_NAME_CHANGED, W_COMPONENTS_FLATTENED, W_DEFAULT_DROPPED, W_DTYPE_UNSUPPORTED, W_DYNAMIC_VALUES_DROPPED, W_EDGE_IDS_DROPPED, W_EDGE_IDS_GENERATED, W_EMPTY_COLUMN_DROPPED, W_EXTENSION_TABLE_DROPPED, W_GRAPH_ATTRIBUTES_DROPPED, W_HIERARCHY_DROPPED, W_ID_MANGLED, W_ID_RENUMBERED, W_ID_TEXT_TYPE, W_INTEGRAL_F64_AS_I32, W_JSON_UNSUPPORTED, W_LIST_UNSUPPORTED, W_MIXED_DIRECTION, W_MULTI_EDGES, W_MUTUAL_AS_UNDIRECTED, W_MUTUAL_EXPANDED, W_NONFINITE_AS_NULL, W_OPEN_INTERVAL, W_OPTIONS_DROPPED, W_OPTIONS_GAINED, W_PARENTS_DROPPED, W_POSITIONS_DROPPED, W_ROLE_ASSUMED, W_ROLE_DROPPED, W_SELF_LOOPS, W_SPELLS_DROPPED, W_STORAGE_CLASS_CHANGED, W_TEMPORAL_DROPPED, W_TEMPORAL_TEXT_DROPPED, W_TEXT_INFERRED, W_VIZ_DROPPED, W_WEIGHTS_DROPPED, W_WEIGHT_KEY_CLASH.