comparison env/lib/python3.7/site-packages/networkx/readwrite/gml.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 # encoding: utf-8
2 # Copyright (C) 2008-2019 by
3 # Aric Hagberg <hagberg@lanl.gov>
4 # Dan Schult <dschult@colgate.edu>
5 # Pieter Swart <swart@lanl.gov>
6 # All rights reserved.
7 # BSD license.
8 #
9 # Author: Aric Hagberg (hagberg@lanl.gov)
10 """
11 Read graphs in GML format.
12
13 "GML, the Graph Modelling Language, is our proposal for a portable
14 file format for graphs. GML's key features are portability, simple
15 syntax, extensibility and flexibility. A GML file consists of a
16 hierarchical key-value lists. Graphs can be annotated with arbitrary
17 data structures. The idea for a common file format was born at the
18 GD'95; this proposal is the outcome of many discussions. GML is the
19 standard file format in the Graphlet graph editor system. It has been
20 overtaken and adapted by several other systems for drawing graphs."
21
22 GML files are stored using a 7-bit ASCII encoding with any extended
23 ASCII characters (iso8859-1) appearing as HTML character entities.
24 You will need to give some thought into how the exported data should
25 interact with different languages and even different Python versions.
26 Re-importing from gml is also a concern.
27
28 Without specifying a `stringizer`/`destringizer`, the code is capable of
29 handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
30 specification. For other data types, you need to explicitly supply a
31 `stringizer`/`destringizer`.
32
33 For better interoperability of data generated by Python 2 and Python 3,
34 we've provided `literal_stringizer` and `literal_destringizer`.
35
36 For additional documentation on the GML file format, please see the
37 `GML website <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
38
39 Several example graphs in GML format may be found on Mark Newman's
40 `Network data page <http://www-personal.umich.edu/~mejn/netdata/>`_.
41 """
42 try:
43 try:
44 from cStringIO import StringIO
45 except ImportError:
46 from StringIO import StringIO
47 except ImportError:
48 from io import StringIO
49 from ast import literal_eval
50 from collections import defaultdict
51 import networkx as nx
52 from networkx.exception import NetworkXError
53 from networkx.utils import open_file
54
55 import re
56 try:
57 import htmlentitydefs
58 except ImportError:
59 # Python 3.x
60 import html.entities as htmlentitydefs
61
62 __all__ = ['read_gml', 'parse_gml', 'generate_gml', 'write_gml']
63
64
65 try:
66 long
67 except NameError:
68 long = int
69 try:
70 unicode
71 except NameError:
72 unicode = str
73 try:
74 unichr
75 except NameError:
76 unichr = chr
77 try:
78 literal_eval(r"u'\u4444'")
79 except SyntaxError:
80 # Remove 'u' prefixes in unicode literals in Python 3
81 def rtp_fix_unicode(s): return s[1:]
82 else:
83 rtp_fix_unicode = None
84
85
86 def escape(text):
87 """Use XML character references to escape characters.
88
89 Use XML character references for unprintable or non-ASCII
90 characters, double quotes and ampersands in a string
91 """
92 def fixup(m):
93 ch = m.group(0)
94 return '&#' + str(ord(ch)) + ';'
95
96 text = re.sub('[^ -~]|[&"]', fixup, text)
97 return text if isinstance(text, str) else str(text)
98
99
100 def unescape(text):
101 """Replace XML character references with the referenced characters"""
102 def fixup(m):
103 text = m.group(0)
104 if text[1] == '#':
105 # Character reference
106 if text[2] == 'x':
107 code = int(text[3:-1], 16)
108 else:
109 code = int(text[2:-1])
110 else:
111 # Named entity
112 try:
113 code = htmlentitydefs.name2codepoint[text[1:-1]]
114 except KeyError:
115 return text # leave unchanged
116 try:
117 return chr(code) if code < 256 else unichr(code)
118 except (ValueError, OverflowError):
119 return text # leave unchanged
120
121 return re.sub("&(?:[0-9A-Za-z]+|#(?:[0-9]+|x[0-9A-Fa-f]+));", fixup, text)
122
123
124 def literal_destringizer(rep):
125 """Convert a Python literal to the value it represents.
126
127 Parameters
128 ----------
129 rep : string
130 A Python literal.
131
132 Returns
133 -------
134 value : object
135 The value of the Python literal.
136
137 Raises
138 ------
139 ValueError
140 If `rep` is not a Python literal.
141 """
142 if isinstance(rep, (str, unicode)):
143 orig_rep = rep
144 if rtp_fix_unicode is not None:
145 rep = rtp_fix_unicode(rep)
146 try:
147 return literal_eval(rep)
148 except SyntaxError:
149 raise ValueError('%r is not a valid Python literal' % (orig_rep,))
150 else:
151 raise ValueError('%r is not a string' % (rep,))
152
153
154 @open_file(0, mode='rb')
155 def read_gml(path, label='label', destringizer=None):
156 """Read graph in GML format from `path`.
157
158 Parameters
159 ----------
160 path : filename or filehandle
161 The filename or filehandle to read from.
162
163 label : string, optional
164 If not None, the parsed nodes will be renamed according to node
165 attributes indicated by `label`. Default value: 'label'.
166
167 destringizer : callable, optional
168 A `destringizer` that recovers values stored as strings in GML. If it
169 cannot convert a string to a value, a `ValueError` is raised. Default
170 value : None.
171
172 Returns
173 -------
174 G : NetworkX graph
175 The parsed graph.
176
177 Raises
178 ------
179 NetworkXError
180 If the input cannot be parsed.
181
182 See Also
183 --------
184 write_gml, parse_gml, literal_destringizer
185
186 Notes
187 -----
188 GML files are stored using a 7-bit ASCII encoding with any extended
189 ASCII characters (iso8859-1) appearing as HTML character entities.
190 Without specifying a `stringizer`/`destringizer`, the code is capable of
191 handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
192 specification. For other data types, you need to explicitly supply a
193 `stringizer`/`destringizer`.
194
195 For additional documentation on the GML file format, please see the
196 `GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
197
198 See the module docstring :mod:`networkx.readwrite.gml` for more details.
199
200 Examples
201 --------
202 >>> G = nx.path_graph(4)
203 >>> nx.write_gml(G, 'test.gml')
204 >>> H = nx.read_gml('test.gml')
205 """
206 def filter_lines(lines):
207 for line in lines:
208 try:
209 line = line.decode('ascii')
210 except UnicodeDecodeError:
211 raise NetworkXError('input is not ASCII-encoded')
212 if not isinstance(line, str):
213 lines = str(lines)
214 if line and line[-1] == '\n':
215 line = line[:-1]
216 yield line
217
218 G = parse_gml_lines(filter_lines(path), label, destringizer)
219 return G
220
221
222 def parse_gml(lines, label='label', destringizer=None):
223 """Parse GML graph from a string or iterable.
224
225 Parameters
226 ----------
227 lines : string or iterable of strings
228 Data in GML format.
229
230 label : string, optional
231 If not None, the parsed nodes will be renamed according to node
232 attributes indicated by `label`. Default value: 'label'.
233
234 destringizer : callable, optional
235 A `destringizer` that recovers values stored as strings in GML. If it
236 cannot convert a string to a value, a `ValueError` is raised. Default
237 value : None.
238
239 Returns
240 -------
241 G : NetworkX graph
242 The parsed graph.
243
244 Raises
245 ------
246 NetworkXError
247 If the input cannot be parsed.
248
249 See Also
250 --------
251 write_gml, read_gml, literal_destringizer
252
253 Notes
254 -----
255 This stores nested GML attributes as dictionaries in the NetworkX graph,
256 node, and edge attribute structures.
257
258 GML files are stored using a 7-bit ASCII encoding with any extended
259 ASCII characters (iso8859-1) appearing as HTML character entities.
260 Without specifying a `stringizer`/`destringizer`, the code is capable of
261 handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
262 specification. For other data types, you need to explicitly supply a
263 `stringizer`/`destringizer`.
264
265 For additional documentation on the GML file format, please see the
266 `GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
267
268 See the module docstring :mod:`networkx.readwrite.gml` for more details.
269 """
270 def decode_line(line):
271 if isinstance(line, bytes):
272 try:
273 line.decode('ascii')
274 except UnicodeDecodeError:
275 raise NetworkXError('input is not ASCII-encoded')
276 if not isinstance(line, str):
277 line = str(line)
278 return line
279
280 def filter_lines(lines):
281 if isinstance(lines, (str, unicode)):
282 lines = decode_line(lines)
283 lines = lines.splitlines()
284 for line in lines:
285 yield line
286 else:
287 for line in lines:
288 line = decode_line(line)
289 if line and line[-1] == '\n':
290 line = line[:-1]
291 if line.find('\n') != -1:
292 raise NetworkXError('input line contains newline')
293 yield line
294
295 G = parse_gml_lines(filter_lines(lines), label, destringizer)
296 return G
297
298
299 def parse_gml_lines(lines, label, destringizer):
300 """Parse GML `lines` into a graph.
301 """
302 def tokenize():
303 patterns = [
304 r'[A-Za-z][0-9A-Za-z_]*\b', # keys
305 # reals
306 r'[+-]?(?:[0-9]*\.[0-9]+|[0-9]+\.[0-9]*)(?:[Ee][+-]?[0-9]+)?',
307 r'[+-]?[0-9]+', # ints
308 r'".*?"', # strings
309 r'\[', # dict start
310 r'\]', # dict end
311 r'#.*$|\s+' # comments and whitespaces
312 ]
313 tokens = re.compile(
314 '|'.join('(' + pattern + ')' for pattern in patterns))
315 lineno = 0
316 for line in lines:
317 length = len(line)
318 pos = 0
319 while pos < length:
320 match = tokens.match(line, pos)
321 if match is not None:
322 for i in range(len(patterns)):
323 group = match.group(i + 1)
324 if group is not None:
325 if i == 0: # keys
326 value = group.rstrip()
327 elif i == 1: # reals
328 value = float(group)
329 elif i == 2: # ints
330 value = int(group)
331 else:
332 value = group
333 if i != 6: # comments and whitespaces
334 yield (i, value, lineno + 1, pos + 1)
335 pos += len(group)
336 break
337 else:
338 raise NetworkXError('cannot tokenize %r at (%d, %d)' %
339 (line[pos:], lineno + 1, pos + 1))
340 lineno += 1
341 yield (None, None, lineno + 1, 1) # EOF
342
343 def unexpected(curr_token, expected):
344 category, value, lineno, pos = curr_token
345 raise NetworkXError(
346 'expected %s, found %s at (%d, %d)' %
347 (expected, repr(value) if value is not None else 'EOF', lineno,
348 pos))
349
350 def consume(curr_token, category, expected):
351 if curr_token[0] == category:
352 return next(tokens)
353 unexpected(curr_token, expected)
354
355 def parse_kv(curr_token):
356 dct = defaultdict(list)
357 while curr_token[0] == 0: # keys
358 key = curr_token[1]
359 curr_token = next(tokens)
360 category = curr_token[0]
361 if category == 1 or category == 2: # reals or ints
362 value = curr_token[1]
363 curr_token = next(tokens)
364 elif category == 3: # strings
365 value = unescape(curr_token[1][1:-1])
366 if destringizer:
367 try:
368 value = destringizer(value)
369 except ValueError:
370 pass
371 curr_token = next(tokens)
372 elif category == 4: # dict start
373 curr_token, value = parse_dict(curr_token)
374 else:
375 # Allow for string convertible id and label values
376 if key in ("id", "label", "source", "target"):
377 try:
378 # String convert the token value
379 value = unescape(str(curr_token[1]))
380 if destringizer:
381 try:
382 value = destringizer(value)
383 except ValueError:
384 pass
385 curr_token = next(tokens)
386 except Exception:
387 msg = "an int, float, string, '[' or string" + \
388 " convertable ASCII value for node id or label"
389 unexpected(curr_token, msg)
390 else: # Otherwise error out
391 unexpected(curr_token, "an int, float, string or '['")
392 dct[key].append(value)
393 dct = {key: (value if not isinstance(value, list) or len(value) != 1
394 else value[0]) for key, value in dct.items()}
395 return curr_token, dct
396
397 def parse_dict(curr_token):
398 curr_token = consume(curr_token, 4, "'['") # dict start
399 curr_token, dct = parse_kv(curr_token)
400 curr_token = consume(curr_token, 5, "']'") # dict end
401 return curr_token, dct
402
403 def parse_graph():
404 curr_token, dct = parse_kv(next(tokens))
405 if curr_token[0] is not None: # EOF
406 unexpected(curr_token, 'EOF')
407 if 'graph' not in dct:
408 raise NetworkXError('input contains no graph')
409 graph = dct['graph']
410 if isinstance(graph, list):
411 raise NetworkXError('input contains more than one graph')
412 return graph
413
414 tokens = tokenize()
415 graph = parse_graph()
416
417 directed = graph.pop('directed', False)
418 multigraph = graph.pop('multigraph', False)
419 if not multigraph:
420 G = nx.DiGraph() if directed else nx.Graph()
421 else:
422 G = nx.MultiDiGraph() if directed else nx.MultiGraph()
423 G.graph.update((key, value) for key, value in graph.items()
424 if key != 'node' and key != 'edge')
425
426 def pop_attr(dct, category, attr, i):
427 try:
428 return dct.pop(attr)
429 except KeyError:
430 raise NetworkXError(
431 "%s #%d has no '%s' attribute" % (category, i, attr))
432
433 nodes = graph.get('node', [])
434 mapping = {}
435 node_labels = set()
436 for i, node in enumerate(nodes if isinstance(nodes, list) else [nodes]):
437 id = pop_attr(node, 'node', 'id', i)
438 if id in G:
439 raise NetworkXError('node id %r is duplicated' % (id,))
440 if label is not None and label != 'id':
441 node_label = pop_attr(node, 'node', label, i)
442 if node_label in node_labels:
443 raise NetworkXError('node label %r is duplicated' %
444 (node_label,))
445 node_labels.add(node_label)
446 mapping[id] = node_label
447 G.add_node(id, **node)
448
449 edges = graph.get('edge', [])
450 for i, edge in enumerate(edges if isinstance(edges, list) else [edges]):
451 source = pop_attr(edge, 'edge', 'source', i)
452 target = pop_attr(edge, 'edge', 'target', i)
453 if source not in G:
454 raise NetworkXError(
455 'edge #%d has an undefined source %r' % (i, source))
456 if target not in G:
457 raise NetworkXError(
458 'edge #%d has an undefined target %r' % (i, target))
459 if not multigraph:
460 if not G.has_edge(source, target):
461 G.add_edge(source, target, **edge)
462 else:
463 msg = "edge #%d (%r%s%r) is duplicated.\n"
464 msg2 = 'Hint: If multigraph add "multigraph 1" to file header.'
465 info = (i, source, '->' if directed else '--', target)
466 raise nx.NetworkXError((msg % info) + msg2)
467 else:
468 key = edge.pop('key', None)
469 if key is not None and G.has_edge(source, target, key):
470 raise nx.NetworkXError(
471 'edge #%d (%r%s%r, %r) is duplicated' %
472 (i, source, '->' if directed else '--', target, key))
473 G.add_edge(source, target, key, **edge)
474
475 if label is not None and label != 'id':
476 G = nx.relabel_nodes(G, mapping)
477 return G
478
479
480 def literal_stringizer(value):
481 """Convert a `value` to a Python literal in GML representation.
482
483 Parameters
484 ----------
485 value : object
486 The `value` to be converted to GML representation.
487
488 Returns
489 -------
490 rep : string
491 A double-quoted Python literal representing value. Unprintable
492 characters are replaced by XML character references.
493
494 Raises
495 ------
496 ValueError
497 If `value` cannot be converted to GML.
498
499 Notes
500 -----
501 `literal_stringizer` is largely the same as `repr` in terms of
502 functionality but attempts prefix `unicode` and `bytes` literals with
503 `u` and `b` to provide better interoperability of data generated by
504 Python 2 and Python 3.
505
506 The original value can be recovered using the
507 :func:`networkx.readwrite.gml.literal_destringizer` function.
508 """
509 def stringize(value):
510 if isinstance(value, (int, long, bool)) or value is None:
511 if value is True: # GML uses 1/0 for boolean values.
512 buf.write(str(1))
513 elif value is False:
514 buf.write(str(0))
515 else:
516 buf.write(str(value))
517 elif isinstance(value, unicode):
518 text = repr(value)
519 if text[0] != 'u':
520 try:
521 value.encode('latin1')
522 except UnicodeEncodeError:
523 text = 'u' + text
524 buf.write(text)
525 elif isinstance(value, (float, complex, str, bytes)):
526 buf.write(repr(value))
527 elif isinstance(value, list):
528 buf.write('[')
529 first = True
530 for item in value:
531 if not first:
532 buf.write(',')
533 else:
534 first = False
535 stringize(item)
536 buf.write(']')
537 elif isinstance(value, tuple):
538 if len(value) > 1:
539 buf.write('(')
540 first = True
541 for item in value:
542 if not first:
543 buf.write(',')
544 else:
545 first = False
546 stringize(item)
547 buf.write(')')
548 elif value:
549 buf.write('(')
550 stringize(value[0])
551 buf.write(',)')
552 else:
553 buf.write('()')
554 elif isinstance(value, dict):
555 buf.write('{')
556 first = True
557 for key, value in value.items():
558 if not first:
559 buf.write(',')
560 else:
561 first = False
562 stringize(key)
563 buf.write(':')
564 stringize(value)
565 buf.write('}')
566 elif isinstance(value, set):
567 buf.write('{')
568 first = True
569 for item in value:
570 if not first:
571 buf.write(',')
572 else:
573 first = False
574 stringize(item)
575 buf.write('}')
576 else:
577 raise ValueError(
578 '%r cannot be converted into a Python literal' % (value,))
579
580 buf = StringIO()
581 stringize(value)
582 return buf.getvalue()
583
584
585 def generate_gml(G, stringizer=None):
586 r"""Generate a single entry of the graph `G` in GML format.
587
588 Parameters
589 ----------
590 G : NetworkX graph
591 The graph to be converted to GML.
592
593 stringizer : callable, optional
594 A `stringizer` which converts non-int/non-float/non-dict values into
595 strings. If it cannot convert a value into a string, it should raise a
596 `ValueError` to indicate that. Default value: None.
597
598 Returns
599 -------
600 lines: generator of strings
601 Lines of GML data. Newlines are not appended.
602
603 Raises
604 ------
605 NetworkXError
606 If `stringizer` cannot convert a value into a string, or the value to
607 convert is not a string while `stringizer` is None.
608
609 See Also
610 --------
611 literal_stringizer
612
613 Notes
614 -----
615 Graph attributes named 'directed', 'multigraph', 'node' or
616 'edge', node attributes named 'id' or 'label', edge attributes
617 named 'source' or 'target' (or 'key' if `G` is a multigraph)
618 are ignored because these attribute names are used to encode the graph
619 structure.
620
621 GML files are stored using a 7-bit ASCII encoding with any extended
622 ASCII characters (iso8859-1) appearing as HTML character entities.
623 Without specifying a `stringizer`/`destringizer`, the code is capable of
624 handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
625 specification. For other data types, you need to explicitly supply a
626 `stringizer`/`destringizer`.
627
628 For additional documentation on the GML file format, please see the
629 `GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
630
631 See the module docstring :mod:`networkx.readwrite.gml` for more details.
632
633 Examples
634 --------
635 >>> G = nx.Graph()
636 >>> G.add_node("1")
637 >>> print("\n".join(nx.generate_gml(G)))
638 graph [
639 node [
640 id 0
641 label "1"
642 ]
643 ]
644 >>> G = nx.OrderedMultiGraph([("a", "b"), ("a", "b")])
645 >>> print("\n".join(nx.generate_gml(G)))
646 graph [
647 multigraph 1
648 node [
649 id 0
650 label "a"
651 ]
652 node [
653 id 1
654 label "b"
655 ]
656 edge [
657 source 0
658 target 1
659 key 0
660 ]
661 edge [
662 source 0
663 target 1
664 key 1
665 ]
666 ]
667 """
668 valid_keys = re.compile('^[A-Za-z][0-9A-Za-z]*$')
669
670 def stringize(key, value, ignored_keys, indent, in_list=False):
671 if not isinstance(key, (str, unicode)):
672 raise NetworkXError('%r is not a string' % (key,))
673 if not valid_keys.match(key):
674 raise NetworkXError('%r is not a valid key' % (key,))
675 if not isinstance(key, str):
676 key = str(key)
677 if key not in ignored_keys:
678 if isinstance(value, (int, long, bool)):
679 if key == 'label':
680 yield indent + key + ' "' + str(value) + '"'
681 elif value is True:
682 # python bool is an instance of int
683 yield indent + key + ' 1'
684 elif value is False:
685 yield indent + key + ' 0'
686 # GML only supports signed 32-bit integers
687 elif value < -2**31 or value >= 2**31:
688 yield indent + key + ' "' + str(value) + '"'
689 else:
690 yield indent + key + ' ' + str(value)
691 elif isinstance(value, float):
692 text = repr(value).upper()
693 # GML requires that a real literal contain a decimal point, but
694 # repr may not output a decimal point when the mantissa is
695 # integral and hence needs fixing.
696 epos = text.rfind('E')
697 if epos != -1 and text.find('.', 0, epos) == -1:
698 text = text[:epos] + '.' + text[epos:]
699 if key == 'label':
700 yield indent + key + ' "' + text + '"'
701 else:
702 yield indent + key + ' ' + text
703 elif isinstance(value, dict):
704 yield indent + key + ' ['
705 next_indent = indent + ' '
706 for key, value in value.items():
707 for line in stringize(key, value, (), next_indent):
708 yield line
709 yield indent + ']'
710 elif isinstance(value, (list, tuple)) and key != 'label' \
711 and value and not in_list:
712 next_indent = indent + ' '
713 for val in value:
714 for line in stringize(key, val, (), next_indent, True):
715 yield line
716 else:
717 if stringizer:
718 try:
719 value = stringizer(value)
720 except ValueError:
721 raise NetworkXError(
722 '%r cannot be converted into a string' % (value,))
723 if not isinstance(value, (str, unicode)):
724 raise NetworkXError('%r is not a string' % (value,))
725 yield indent + key + ' "' + escape(value) + '"'
726
727 multigraph = G.is_multigraph()
728 yield 'graph ['
729
730 # Output graph attributes
731 if G.is_directed():
732 yield ' directed 1'
733 if multigraph:
734 yield ' multigraph 1'
735 ignored_keys = {'directed', 'multigraph', 'node', 'edge'}
736 for attr, value in G.graph.items():
737 for line in stringize(attr, value, ignored_keys, ' '):
738 yield line
739
740 # Output node data
741 node_id = dict(zip(G, range(len(G))))
742 ignored_keys = {'id', 'label'}
743 for node, attrs in G.nodes.items():
744 yield ' node ['
745 yield ' id ' + str(node_id[node])
746 for line in stringize('label', node, (), ' '):
747 yield line
748 for attr, value in attrs.items():
749 for line in stringize(attr, value, ignored_keys, ' '):
750 yield line
751 yield ' ]'
752
753 # Output edge data
754 ignored_keys = {'source', 'target'}
755 kwargs = {'data': True}
756 if multigraph:
757 ignored_keys.add('key')
758 kwargs['keys'] = True
759 for e in G.edges(**kwargs):
760 yield ' edge ['
761 yield ' source ' + str(node_id[e[0]])
762 yield ' target ' + str(node_id[e[1]])
763 if multigraph:
764 for line in stringize('key', e[2], (), ' '):
765 yield line
766 for attr, value in e[-1].items():
767 for line in stringize(attr, value, ignored_keys, ' '):
768 yield line
769 yield ' ]'
770 yield ']'
771
772
773 @open_file(1, mode='wb')
774 def write_gml(G, path, stringizer=None):
775 """Write a graph `G` in GML format to the file or file handle `path`.
776
777 Parameters
778 ----------
779 G : NetworkX graph
780 The graph to be converted to GML.
781
782 path : filename or filehandle
783 The filename or filehandle to write. Files whose names end with .gz or
784 .bz2 will be compressed.
785
786 stringizer : callable, optional
787 A `stringizer` which converts non-int/non-float/non-dict values into
788 strings. If it cannot convert a value into a string, it should raise a
789 `ValueError` to indicate that. Default value: None.
790
791 Raises
792 ------
793 NetworkXError
794 If `stringizer` cannot convert a value into a string, or the value to
795 convert is not a string while `stringizer` is None.
796
797 See Also
798 --------
799 read_gml, generate_gml, literal_stringizer
800
801 Notes
802 -----
803 Graph attributes named 'directed', 'multigraph', 'node' or
804 'edge', node attributes named 'id' or 'label', edge attributes
805 named 'source' or 'target' (or 'key' if `G` is a multigraph)
806 are ignored because these attribute names are used to encode the graph
807 structure.
808
809 GML files are stored using a 7-bit ASCII encoding with any extended
810 ASCII characters (iso8859-1) appearing as HTML character entities.
811 Without specifying a `stringizer`/`destringizer`, the code is capable of
812 handling `int`/`float`/`str`/`dict`/`list` data as required by the GML
813 specification. For other data types, you need to explicitly supply a
814 `stringizer`/`destringizer`.
815
816 Note that while we allow non-standard GML to be read from a file, we make
817 sure to write GML format. In particular, underscores are not allowed in
818 attribute names.
819 For additional documentation on the GML file format, please see the
820 `GML url <http://www.infosun.fim.uni-passau.de/Graphlet/GML/gml-tr.html>`_.
821
822 See the module docstring :mod:`networkx.readwrite.gml` for more details.
823
824 Examples
825 --------
826 >>> G = nx.path_graph(4)
827 >>> nx.write_gml(G, "test.gml")
828
829 Filenames ending in .gz or .bz2 will be compressed.
830
831 >>> nx.write_gml(G, "test.gml.gz")
832 """
833 for line in generate_gml(G, stringizer):
834 path.write((line + '\n').encode('ascii'))
835
836
837 # fixture for pytest
838 def teardown_module(module):
839 import os
840 for fname in ['test.gml', 'test.gml.gz']:
841 if os.path.isfile(fname):
842 os.unlink(fname)