Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
824
venv/Lib/site-packages/networkx/readwrite/gml.py
Normal file
824
venv/Lib/site-packages/networkx/readwrite/gml.py
Normal file
|
@ -0,0 +1,824 @@
|
|||
"""
|
||||
Read graphs in GML format.
|
||||
|
||||
"GML, the Graph Modelling Language, is our proposal for a portable
|
||||
file format for graphs. GML's key features are portability, simple
|
||||
syntax, extensibility and flexibility. A GML file consists of a
|
||||
hierarchical key-value lists. Graphs can be annotated with arbitrary
|
||||
data structures. The idea for a common file format was born at the
|
||||
GD'95; this proposal is the outcome of many discussions. GML is the
|
||||
standard file format in the Graphlet graph editor system. It has been
|
||||
overtaken and adapted by several other systems for drawing graphs."
|
||||
|
||||
GML files are stored using a 7-bit ASCII encoding with any extended
|
||||
ASCII characters (iso8859-1) appearing as HTML character entities.
|
||||
You will need to give some thought into how the exported data should
|
||||
interact with different languages and even different Python versions.
|
||||
Re-importing from gml is also a concern.
|
||||
|
||||
Without specifying a `stringizer`/`destringizer`, the code is capable of
|
||||
handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
|
||||
specification. For other data types, you need to explicitly supply a
|
||||
`stringizer`/`destringizer`.
|
||||
|
||||
For additional documentation on the GML file format, please see the
|
||||
`GML website <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
|
||||
|
||||
Several example graphs in GML format may be found on Mark Newman's
|
||||
`Network data page <http://www-personal.umich.edu/~mejn/netdata/>`_.
|
||||
"""
|
||||
from io import StringIO
|
||||
from ast import literal_eval
|
||||
from collections import defaultdict
|
||||
from enum import Enum
|
||||
from typing import Any, NamedTuple
|
||||
import networkx as nx
|
||||
from networkx.exception import NetworkXError
|
||||
from networkx.utils import open_file
|
||||
|
||||
import warnings
|
||||
import re
|
||||
import html.entities as htmlentitydefs
|
||||
|
||||
__all__ = ["read_gml", "parse_gml", "generate_gml", "write_gml"]
|
||||
|
||||
|
||||
def escape(text):
|
||||
"""Use XML character references to escape characters.
|
||||
|
||||
Use XML character references for unprintable or non-ASCII
|
||||
characters, double quotes and ampersands in a string
|
||||
"""
|
||||
|
||||
def fixup(m):
|
||||
ch = m.group(0)
|
||||
return "&#" + str(ord(ch)) + ";"
|
||||
|
||||
text = re.sub('[^ -~]|[&"]', fixup, text)
|
||||
return text if isinstance(text, str) else str(text)
|
||||
|
||||
|
||||
def unescape(text):
|
||||
"""Replace XML character references with the referenced characters"""
|
||||
|
||||
def fixup(m):
|
||||
text = m.group(0)
|
||||
if text[1] == "#":
|
||||
# Character reference
|
||||
if text[2] == "x":
|
||||
code = int(text[3:-1], 16)
|
||||
else:
|
||||
code = int(text[2:-1])
|
||||
else:
|
||||
# Named entity
|
||||
try:
|
||||
code = htmlentitydefs.name2codepoint[text[1:-1]]
|
||||
except KeyError:
|
||||
return text # leave unchanged
|
||||
try:
|
||||
return chr(code)
|
||||
except (ValueError, OverflowError):
|
||||
return text # leave unchanged
|
||||
|
||||
return re.sub("&(?:[0-9A-Za-z]+|#(?:[0-9]+|x[0-9A-Fa-f]+));", fixup, text)
|
||||
|
||||
|
||||
def literal_destringizer(rep):
|
||||
"""Convert a Python literal to the value it represents.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rep : string
|
||||
A Python literal.
|
||||
|
||||
Returns
|
||||
-------
|
||||
value : object
|
||||
The value of the Python literal.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If `rep` is not a Python literal.
|
||||
"""
|
||||
msg = "literal_destringizer is deprecated and will be removed in 3.0."
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
if isinstance(rep, str):
|
||||
orig_rep = rep
|
||||
try:
|
||||
return literal_eval(rep)
|
||||
except SyntaxError as e:
|
||||
raise ValueError(f"{orig_rep!r} is not a valid Python literal") from e
|
||||
else:
|
||||
raise ValueError(f"{rep!r} is not a string")
|
||||
|
||||
|
||||
@open_file(0, mode="rb")
|
||||
def read_gml(path, label="label", destringizer=None):
|
||||
"""Read graph in GML format from `path`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : filename or filehandle
|
||||
The filename or filehandle to read from.
|
||||
|
||||
label : string, optional
|
||||
If not None, the parsed nodes will be renamed according to node
|
||||
attributes indicated by `label`. Default value: 'label'.
|
||||
|
||||
destringizer : callable, optional
|
||||
A `destringizer` that recovers values stored as strings in GML. If it
|
||||
cannot convert a string to a value, a `ValueError` is raised. Default
|
||||
value : None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : NetworkX graph
|
||||
The parsed graph.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the input cannot be parsed.
|
||||
|
||||
See Also
|
||||
--------
|
||||
write_gml, parse_gml
|
||||
|
||||
Notes
|
||||
-----
|
||||
GML files are stored using a 7-bit ASCII encoding with any extended
|
||||
ASCII characters (iso8859-1) appearing as HTML character entities.
|
||||
Without specifying a `stringizer`/`destringizer`, the code is capable of
|
||||
handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
|
||||
specification. For other data types, you need to explicitly supply a
|
||||
`stringizer`/`destringizer`.
|
||||
|
||||
For additional documentation on the GML file format, please see the
|
||||
`GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
|
||||
|
||||
See the module docstring :mod:`networkx.readwrite.gml` for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> nx.write_gml(G, "test.gml")
|
||||
>>> H = nx.read_gml("test.gml")
|
||||
"""
|
||||
|
||||
def filter_lines(lines):
|
||||
for line in lines:
|
||||
try:
|
||||
line = line.decode("ascii")
|
||||
except UnicodeDecodeError as e:
|
||||
raise NetworkXError("input is not ASCII-encoded") from e
|
||||
if not isinstance(line, str):
|
||||
lines = str(lines)
|
||||
if line and line[-1] == "\n":
|
||||
line = line[:-1]
|
||||
yield line
|
||||
|
||||
G = parse_gml_lines(filter_lines(path), label, destringizer)
|
||||
return G
|
||||
|
||||
|
||||
def parse_gml(lines, label="label", destringizer=None):
|
||||
"""Parse GML graph from a string or iterable.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lines : string or iterable of strings
|
||||
Data in GML format.
|
||||
|
||||
label : string, optional
|
||||
If not None, the parsed nodes will be renamed according to node
|
||||
attributes indicated by `label`. Default value: 'label'.
|
||||
|
||||
destringizer : callable, optional
|
||||
A `destringizer` that recovers values stored as strings in GML. If it
|
||||
cannot convert a string to a value, a `ValueError` is raised. Default
|
||||
value : None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
G : NetworkX graph
|
||||
The parsed graph.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If the input cannot be parsed.
|
||||
|
||||
See Also
|
||||
--------
|
||||
write_gml, read_gml
|
||||
|
||||
Notes
|
||||
-----
|
||||
This stores nested GML attributes as dictionaries in the NetworkX graph,
|
||||
node, and edge attribute structures.
|
||||
|
||||
GML files are stored using a 7-bit ASCII encoding with any extended
|
||||
ASCII characters (iso8859-1) appearing as HTML character entities.
|
||||
Without specifying a `stringizer`/`destringizer`, the code is capable of
|
||||
handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
|
||||
specification. For other data types, you need to explicitly supply a
|
||||
`stringizer`/`destringizer`.
|
||||
|
||||
For additional documentation on the GML file format, please see the
|
||||
`GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
|
||||
|
||||
See the module docstring :mod:`networkx.readwrite.gml` for more details.
|
||||
"""
|
||||
|
||||
def decode_line(line):
|
||||
if isinstance(line, bytes):
|
||||
try:
|
||||
line.decode("ascii")
|
||||
except UnicodeDecodeError as e:
|
||||
raise NetworkXError("input is not ASCII-encoded") from e
|
||||
if not isinstance(line, str):
|
||||
line = str(line)
|
||||
return line
|
||||
|
||||
def filter_lines(lines):
|
||||
if isinstance(lines, str):
|
||||
lines = decode_line(lines)
|
||||
lines = lines.splitlines()
|
||||
yield from lines
|
||||
else:
|
||||
for line in lines:
|
||||
line = decode_line(line)
|
||||
if line and line[-1] == "\n":
|
||||
line = line[:-1]
|
||||
if line.find("\n") != -1:
|
||||
raise NetworkXError("input line contains newline")
|
||||
yield line
|
||||
|
||||
G = parse_gml_lines(filter_lines(lines), label, destringizer)
|
||||
return G
|
||||
|
||||
|
||||
class Pattern(Enum):
|
||||
""" encodes the index of each token-matching pattern in `tokenize`. """
|
||||
|
||||
KEYS = 0
|
||||
REALS = 1
|
||||
INTS = 2
|
||||
STRINGS = 3
|
||||
DICT_START = 4
|
||||
DICT_END = 5
|
||||
COMMENT_WHITESPACE = 6
|
||||
|
||||
|
||||
class Token(NamedTuple):
|
||||
category: Pattern
|
||||
value: Any
|
||||
line: int
|
||||
position: int
|
||||
|
||||
|
||||
LIST_START_VALUE = "_networkx_list_start"
|
||||
|
||||
|
||||
def parse_gml_lines(lines, label, destringizer):
|
||||
"""Parse GML `lines` into a graph.
|
||||
"""
|
||||
|
||||
def tokenize():
|
||||
patterns = [
|
||||
r"[A-Za-z][0-9A-Za-z_]*\b", # keys
|
||||
# reals
|
||||
r"[+-]?(?:[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)(?:[Ee][+-]?[0-9]+)?",
|
||||
r"[+-]?[0-9]+", # ints
|
||||
r'".*?"', # strings
|
||||
r"\[", # dict start
|
||||
r"\]", # dict end
|
||||
r"#.*$|\s+", # comments and whitespaces
|
||||
]
|
||||
tokens = re.compile("|".join(f"({pattern})" for pattern in patterns))
|
||||
lineno = 0
|
||||
for line in lines:
|
||||
length = len(line)
|
||||
pos = 0
|
||||
while pos < length:
|
||||
match = tokens.match(line, pos)
|
||||
if match is None:
|
||||
m = f"cannot tokenize {line[pos:]} at ({lineno + 1}, {pos + 1})"
|
||||
raise NetworkXError(m)
|
||||
for i in range(len(patterns)):
|
||||
group = match.group(i + 1)
|
||||
if group is not None:
|
||||
if i == 0: # keys
|
||||
value = group.rstrip()
|
||||
elif i == 1: # reals
|
||||
value = float(group)
|
||||
elif i == 2: # ints
|
||||
value = int(group)
|
||||
else:
|
||||
value = group
|
||||
if i != 6: # comments and whitespaces
|
||||
yield Token(Pattern(i), value, lineno + 1, pos + 1)
|
||||
pos += len(group)
|
||||
break
|
||||
lineno += 1
|
||||
yield Token(None, None, lineno + 1, 1) # EOF
|
||||
|
||||
def unexpected(curr_token, expected):
|
||||
category, value, lineno, pos = curr_token
|
||||
value = repr(value) if value is not None else "EOF"
|
||||
raise NetworkXError(f"expected {expected}, found {value} at ({lineno}, {pos})")
|
||||
|
||||
def consume(curr_token, category, expected):
|
||||
if curr_token.category == category:
|
||||
return next(tokens)
|
||||
unexpected(curr_token, expected)
|
||||
|
||||
def parse_kv(curr_token):
|
||||
dct = defaultdict(list)
|
||||
while curr_token.category == Pattern.KEYS:
|
||||
key = curr_token.value
|
||||
curr_token = next(tokens)
|
||||
category = curr_token.category
|
||||
if category == Pattern.REALS or category == Pattern.INTS:
|
||||
value = curr_token.value
|
||||
curr_token = next(tokens)
|
||||
elif category == Pattern.STRINGS:
|
||||
value = unescape(curr_token.value[1:-1])
|
||||
if destringizer:
|
||||
try:
|
||||
value = destringizer(value)
|
||||
except ValueError:
|
||||
pass
|
||||
curr_token = next(tokens)
|
||||
elif category == Pattern.DICT_START:
|
||||
curr_token, value = parse_dict(curr_token)
|
||||
else:
|
||||
# Allow for string convertible id and label values
|
||||
if key in ("id", "label", "source", "target"):
|
||||
try:
|
||||
# String convert the token value
|
||||
value = unescape(str(curr_token.value))
|
||||
if destringizer:
|
||||
try:
|
||||
value = destringizer(value)
|
||||
except ValueError:
|
||||
pass
|
||||
curr_token = next(tokens)
|
||||
except Exception:
|
||||
msg = (
|
||||
"an int, float, string, '[' or string"
|
||||
+ " convertable ASCII value for node id or label"
|
||||
)
|
||||
unexpected(curr_token, msg)
|
||||
else: # Otherwise error out
|
||||
unexpected(curr_token, "an int, float, string or '['")
|
||||
dct[key].append(value)
|
||||
|
||||
def clean_dict_value(value):
|
||||
if not isinstance(value, list):
|
||||
return value
|
||||
if len(value) == 1:
|
||||
return value[0]
|
||||
if value[0] == LIST_START_VALUE:
|
||||
return value[1:]
|
||||
return value
|
||||
|
||||
dct = {key: clean_dict_value(value) for key, value in dct.items()}
|
||||
return curr_token, dct
|
||||
|
||||
def parse_dict(curr_token):
|
||||
# dict start
|
||||
curr_token = consume(curr_token, Pattern.DICT_START, "'['")
|
||||
# dict contents
|
||||
curr_token, dct = parse_kv(curr_token)
|
||||
# dict end
|
||||
curr_token = consume(curr_token, Pattern.DICT_END, "']'")
|
||||
return curr_token, dct
|
||||
|
||||
def parse_graph():
|
||||
curr_token, dct = parse_kv(next(tokens))
|
||||
if curr_token.category is not None: # EOF
|
||||
unexpected(curr_token, "EOF")
|
||||
if "graph" not in dct:
|
||||
raise NetworkXError("input contains no graph")
|
||||
graph = dct["graph"]
|
||||
if isinstance(graph, list):
|
||||
raise NetworkXError("input contains more than one graph")
|
||||
return graph
|
||||
|
||||
tokens = tokenize()
|
||||
graph = parse_graph()
|
||||
|
||||
directed = graph.pop("directed", False)
|
||||
multigraph = graph.pop("multigraph", False)
|
||||
if not multigraph:
|
||||
G = nx.DiGraph() if directed else nx.Graph()
|
||||
else:
|
||||
G = nx.MultiDiGraph() if directed else nx.MultiGraph()
|
||||
graph_attr = {k: v for k, v in graph.items() if k not in ("node", "edge")}
|
||||
G.graph.update(graph_attr)
|
||||
|
||||
def pop_attr(dct, category, attr, i):
|
||||
try:
|
||||
return dct.pop(attr)
|
||||
except KeyError as e:
|
||||
raise NetworkXError(f"{category} #{i} has no '{attr}' attribute") from e
|
||||
|
||||
nodes = graph.get("node", [])
|
||||
mapping = {}
|
||||
node_labels = set()
|
||||
for i, node in enumerate(nodes if isinstance(nodes, list) else [nodes]):
|
||||
id = pop_attr(node, "node", "id", i)
|
||||
if id in G:
|
||||
raise NetworkXError(f"node id {id!r} is duplicated")
|
||||
if label is not None and label != "id":
|
||||
node_label = pop_attr(node, "node", label, i)
|
||||
if node_label in node_labels:
|
||||
raise NetworkXError(f"node label {node_label!r} is duplicated")
|
||||
node_labels.add(node_label)
|
||||
mapping[id] = node_label
|
||||
G.add_node(id, **node)
|
||||
|
||||
edges = graph.get("edge", [])
|
||||
for i, edge in enumerate(edges if isinstance(edges, list) else [edges]):
|
||||
source = pop_attr(edge, "edge", "source", i)
|
||||
target = pop_attr(edge, "edge", "target", i)
|
||||
if source not in G:
|
||||
raise NetworkXError(f"edge #{i} has undefined source {source!r}")
|
||||
if target not in G:
|
||||
raise NetworkXError(f"edge #{i} has undefined target {target!r}")
|
||||
if not multigraph:
|
||||
if not G.has_edge(source, target):
|
||||
G.add_edge(source, target, **edge)
|
||||
else:
|
||||
arrow = "->" if directed else "--"
|
||||
msg = f"edge #{i} ({source!r}{arrow}{target!r}) is duplicated"
|
||||
raise nx.NetworkXError(msg)
|
||||
else:
|
||||
key = edge.pop("key", None)
|
||||
if key is not None and G.has_edge(source, target, key):
|
||||
arrow = "->" if directed else "--"
|
||||
msg = f"edge #{i} ({source!r}{arrow}{target!r}, {key!r})"
|
||||
msg2 = 'Hint: If multigraph add "multigraph 1" to file header.'
|
||||
raise nx.NetworkXError(msg + " is duplicated\n" + msg2)
|
||||
G.add_edge(source, target, key, **edge)
|
||||
|
||||
if label is not None and label != "id":
|
||||
G = nx.relabel_nodes(G, mapping)
|
||||
return G
|
||||
|
||||
|
||||
def literal_stringizer(value):
|
||||
"""Convert a `value` to a Python literal in GML representation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : object
|
||||
The `value` to be converted to GML representation.
|
||||
|
||||
Returns
|
||||
-------
|
||||
rep : string
|
||||
A double-quoted Python literal representing value. Unprintable
|
||||
characters are replaced by XML character references.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If `value` cannot be converted to GML.
|
||||
|
||||
Notes
|
||||
-----
|
||||
`literal_stringizer` is largely the same as `repr` in terms of
|
||||
functionality but attempts prefix `unicode` and `bytes` literals with
|
||||
`u` and `b` to provide better interoperability of data generated by
|
||||
Python 2 and Python 3.
|
||||
|
||||
The original value can be recovered using the
|
||||
:func:`networkx.readwrite.gml.literal_destringizer` function.
|
||||
"""
|
||||
msg = "literal_stringizer is deprecated and will be removed in 3.0."
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
|
||||
def stringize(value):
|
||||
if isinstance(value, (int, bool)) or value is None:
|
||||
if value is True: # GML uses 1/0 for boolean values.
|
||||
buf.write(str(1))
|
||||
elif value is False:
|
||||
buf.write(str(0))
|
||||
else:
|
||||
buf.write(str(value))
|
||||
elif isinstance(value, str):
|
||||
text = repr(value)
|
||||
if text[0] != "u":
|
||||
try:
|
||||
value.encode("latin1")
|
||||
except UnicodeEncodeError:
|
||||
text = "u" + text
|
||||
buf.write(text)
|
||||
elif isinstance(value, (float, complex, str, bytes)):
|
||||
buf.write(repr(value))
|
||||
elif isinstance(value, list):
|
||||
buf.write("[")
|
||||
first = True
|
||||
for item in value:
|
||||
if not first:
|
||||
buf.write(",")
|
||||
else:
|
||||
first = False
|
||||
stringize(item)
|
||||
buf.write("]")
|
||||
elif isinstance(value, tuple):
|
||||
if len(value) > 1:
|
||||
buf.write("(")
|
||||
first = True
|
||||
for item in value:
|
||||
if not first:
|
||||
buf.write(",")
|
||||
else:
|
||||
first = False
|
||||
stringize(item)
|
||||
buf.write(")")
|
||||
elif value:
|
||||
buf.write("(")
|
||||
stringize(value[0])
|
||||
buf.write(",)")
|
||||
else:
|
||||
buf.write("()")
|
||||
elif isinstance(value, dict):
|
||||
buf.write("{")
|
||||
first = True
|
||||
for key, value in value.items():
|
||||
if not first:
|
||||
buf.write(",")
|
||||
else:
|
||||
first = False
|
||||
stringize(key)
|
||||
buf.write(":")
|
||||
stringize(value)
|
||||
buf.write("}")
|
||||
elif isinstance(value, set):
|
||||
buf.write("{")
|
||||
first = True
|
||||
for item in value:
|
||||
if not first:
|
||||
buf.write(",")
|
||||
else:
|
||||
first = False
|
||||
stringize(item)
|
||||
buf.write("}")
|
||||
else:
|
||||
msg = "{value!r} cannot be converted into a Python literal"
|
||||
raise ValueError(msg)
|
||||
|
||||
buf = StringIO()
|
||||
stringize(value)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def generate_gml(G, stringizer=None):
|
||||
r"""Generate a single entry of the graph `G` in GML format.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
The graph to be converted to GML.
|
||||
|
||||
stringizer : callable, optional
|
||||
A `stringizer` which converts non-int/non-float/non-dict values into
|
||||
strings. If it cannot convert a value into a string, it should raise a
|
||||
`ValueError` to indicate that. Default value: None.
|
||||
|
||||
Returns
|
||||
-------
|
||||
lines: generator of strings
|
||||
Lines of GML data. Newlines are not appended.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If `stringizer` cannot convert a value into a string, or the value to
|
||||
convert is not a string while `stringizer` is None.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Graph attributes named 'directed', 'multigraph', 'node' or
|
||||
'edge', node attributes named 'id' or 'label', edge attributes
|
||||
named 'source' or 'target' (or 'key' if `G` is a multigraph)
|
||||
are ignored because these attribute names are used to encode the graph
|
||||
structure.
|
||||
|
||||
GML files are stored using a 7-bit ASCII encoding with any extended
|
||||
ASCII characters (iso8859-1) appearing as HTML character entities.
|
||||
Without specifying a `stringizer`/`destringizer`, the code is capable of
|
||||
handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
|
||||
specification. For other data types, you need to explicitly supply a
|
||||
`stringizer`/`destringizer`.
|
||||
|
||||
For additional documentation on the GML file format, please see the
|
||||
`GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
|
||||
|
||||
See the module docstring :mod:`networkx.readwrite.gml` for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.Graph()
|
||||
>>> G.add_node("1")
|
||||
>>> print("\n".join(nx.generate_gml(G)))
|
||||
graph [
|
||||
node [
|
||||
id 0
|
||||
label "1"
|
||||
]
|
||||
]
|
||||
>>> G = nx.OrderedMultiGraph([("a", "b"), ("a", "b")])
|
||||
>>> print("\n".join(nx.generate_gml(G)))
|
||||
graph [
|
||||
multigraph 1
|
||||
node [
|
||||
id 0
|
||||
label "a"
|
||||
]
|
||||
node [
|
||||
id 1
|
||||
label "b"
|
||||
]
|
||||
edge [
|
||||
source 0
|
||||
target 1
|
||||
key 0
|
||||
]
|
||||
edge [
|
||||
source 0
|
||||
target 1
|
||||
key 1
|
||||
]
|
||||
]
|
||||
"""
|
||||
valid_keys = re.compile("^[A-Za-z][0-9A-Za-z_]*$")
|
||||
|
||||
def stringize(key, value, ignored_keys, indent, in_list=False):
|
||||
if not isinstance(key, str):
|
||||
raise NetworkXError(f"{key!r} is not a string")
|
||||
if not valid_keys.match(key):
|
||||
raise NetworkXError(f"{key!r} is not a valid key")
|
||||
if not isinstance(key, str):
|
||||
key = str(key)
|
||||
if key not in ignored_keys:
|
||||
if isinstance(value, (int, bool)):
|
||||
if key == "label":
|
||||
yield indent + key + ' "' + str(value) + '"'
|
||||
elif value is True:
|
||||
# python bool is an instance of int
|
||||
yield indent + key + " 1"
|
||||
elif value is False:
|
||||
yield indent + key + " 0"
|
||||
# GML only supports signed 32-bit integers
|
||||
elif value < -(2 ** 31) or value >= 2 ** 31:
|
||||
yield indent + key + ' "' + str(value) + '"'
|
||||
else:
|
||||
yield indent + key + " " + str(value)
|
||||
elif isinstance(value, float):
|
||||
text = repr(value).upper()
|
||||
# GML requires that a real literal contain a decimal point, but
|
||||
# repr may not output a decimal point when the mantissa is
|
||||
# integral and hence needs fixing.
|
||||
epos = text.rfind("E")
|
||||
if epos != -1 and text.find(".", 0, epos) == -1:
|
||||
text = text[:epos] + "." + text[epos:]
|
||||
if key == "label":
|
||||
yield indent + key + ' "' + text + '"'
|
||||
else:
|
||||
yield indent + key + " " + text
|
||||
elif isinstance(value, dict):
|
||||
yield indent + key + " ["
|
||||
next_indent = indent + " "
|
||||
for key, value in value.items():
|
||||
yield from stringize(key, value, (), next_indent)
|
||||
yield indent + "]"
|
||||
elif (
|
||||
isinstance(value, (list, tuple))
|
||||
and key != "label"
|
||||
and value
|
||||
and not in_list
|
||||
):
|
||||
if len(value) == 1:
|
||||
yield indent + key + " " + f'"{LIST_START_VALUE}"'
|
||||
for val in value:
|
||||
yield from stringize(key, val, (), indent, True)
|
||||
else:
|
||||
if stringizer:
|
||||
try:
|
||||
value = stringizer(value)
|
||||
except ValueError as e:
|
||||
raise NetworkXError(
|
||||
f"{value!r} cannot be converted into a string"
|
||||
) from e
|
||||
if not isinstance(value, str):
|
||||
raise NetworkXError(f"{value!r} is not a string")
|
||||
yield indent + key + ' "' + escape(value) + '"'
|
||||
|
||||
multigraph = G.is_multigraph()
|
||||
yield "graph ["
|
||||
|
||||
# Output graph attributes
|
||||
if G.is_directed():
|
||||
yield " directed 1"
|
||||
if multigraph:
|
||||
yield " multigraph 1"
|
||||
ignored_keys = {"directed", "multigraph", "node", "edge"}
|
||||
for attr, value in G.graph.items():
|
||||
yield from stringize(attr, value, ignored_keys, " ")
|
||||
|
||||
# Output node data
|
||||
node_id = dict(zip(G, range(len(G))))
|
||||
ignored_keys = {"id", "label"}
|
||||
for node, attrs in G.nodes.items():
|
||||
yield " node ["
|
||||
yield " id " + str(node_id[node])
|
||||
yield from stringize("label", node, (), " ")
|
||||
for attr, value in attrs.items():
|
||||
yield from stringize(attr, value, ignored_keys, " ")
|
||||
yield " ]"
|
||||
|
||||
# Output edge data
|
||||
ignored_keys = {"source", "target"}
|
||||
kwargs = {"data": True}
|
||||
if multigraph:
|
||||
ignored_keys.add("key")
|
||||
kwargs["keys"] = True
|
||||
for e in G.edges(**kwargs):
|
||||
yield " edge ["
|
||||
yield " source " + str(node_id[e[0]])
|
||||
yield " target " + str(node_id[e[1]])
|
||||
if multigraph:
|
||||
yield from stringize("key", e[2], (), " ")
|
||||
for attr, value in e[-1].items():
|
||||
yield from stringize(attr, value, ignored_keys, " ")
|
||||
yield " ]"
|
||||
yield "]"
|
||||
|
||||
|
||||
@open_file(1, mode="wb")
|
||||
def write_gml(G, path, stringizer=None):
|
||||
"""Write a graph `G` in GML format to the file or file handle `path`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
G : NetworkX graph
|
||||
The graph to be converted to GML.
|
||||
|
||||
path : filename or filehandle
|
||||
The filename or filehandle to write. Files whose names end with .gz or
|
||||
.bz2 will be compressed.
|
||||
|
||||
stringizer : callable, optional
|
||||
A `stringizer` which converts non-int/non-float/non-dict values into
|
||||
strings. If it cannot convert a value into a string, it should raise a
|
||||
`ValueError` to indicate that. Default value: None.
|
||||
|
||||
Raises
|
||||
------
|
||||
NetworkXError
|
||||
If `stringizer` cannot convert a value into a string, or the value to
|
||||
convert is not a string while `stringizer` is None.
|
||||
|
||||
See Also
|
||||
--------
|
||||
read_gml, generate_gml
|
||||
|
||||
Notes
|
||||
-----
|
||||
Graph attributes named 'directed', 'multigraph', 'node' or
|
||||
'edge', node attributes named 'id' or 'label', edge attributes
|
||||
named 'source' or 'target' (or 'key' if `G` is a multigraph)
|
||||
are ignored because these attribute names are used to encode the graph
|
||||
structure.
|
||||
|
||||
GML files are stored using a 7-bit ASCII encoding with any extended
|
||||
ASCII characters (iso8859-1) appearing as HTML character entities.
|
||||
Without specifying a `stringizer`/`destringizer`, the code is capable of
|
||||
handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
|
||||
specification. For other data types, you need to explicitly supply a
|
||||
`stringizer`/`destringizer`.
|
||||
|
||||
Note that while we allow non-standard GML to be read from a file, we make
|
||||
sure to write GML format. In particular, underscores are not allowed in
|
||||
attribute names.
|
||||
For additional documentation on the GML file format, please see the
|
||||
`GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
|
||||
|
||||
See the module docstring :mod:`networkx.readwrite.gml` for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> G = nx.path_graph(4)
|
||||
>>> nx.write_gml(G, "test.gml")
|
||||
|
||||
Filenames ending in .gz or .bz2 will be compressed.
|
||||
|
||||
>>> nx.write_gml(G, "test.gml.gz")
|
||||
"""
|
||||
for line in generate_gml(G, stringizer):
|
||||
path.write((line + "\n").encode("ascii"))
|
Loading…
Add table
Add a link
Reference in a new issue