Irrespectively of graphviz version.
Fixes https://github.com/jrfonseca/xdot.py/issues/92
... | ... |
@@ -14,8 +14,11 @@ |
14 | 14 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 |
# |
16 | 16 |
import colorsys |
17 |
+import re |
|
17 | 18 |
import sys |
18 | 19 |
|
20 |
+from distutils.version import LooseVersion |
|
21 |
+ |
|
19 | 22 |
from .lexer import ParseError, DotLexer |
20 | 23 |
|
21 | 24 |
from ..ui.colors import lookup_color |
... | ... |
@@ -85,7 +88,14 @@ class XDotAttrParser: |
85 | 88 |
- http://www.graphviz.org/doc/info/output.html#d:xdot |
86 | 89 |
""" |
87 | 90 |
|
88 |
- def __init__(self, parser, buf): |
|
91 |
+ def __init__(self, parser, buf, broken_backslashes): |
|
92 |
+ |
|
93 |
+ # `\` should be escaped as `\\`, but older versions of graphviz xdot |
|
94 |
+ # output failed to properly escape it. See also |
|
95 |
+ # https://github.com/jrfonseca/xdot.py/issues/92 |
|
96 |
+ if not broken_backslashes: |
|
97 |
+ buf = re.sub(br'\\(.)', br'\1', buf) |
|
98 |
+ |
|
89 | 99 |
self.parser = parser |
90 | 100 |
self.buf = buf |
91 | 101 |
self.pos = 0 |
... | ... |
@@ -427,10 +437,16 @@ class XDotParser(DotParser): |
427 | 437 |
|
428 | 438 |
XDOTVERSION = '1.7' |
429 | 439 |
|
430 |
- def __init__(self, xdotcode): |
|
440 |
+ def __init__(self, xdotcode, graphviz_version=None): |
|
431 | 441 |
lexer = DotLexer(buf=xdotcode) |
432 | 442 |
DotParser.__init__(self, lexer) |
433 | 443 |
|
444 |
+ # https://github.com/jrfonseca/xdot.py/issues/92 |
|
445 |
+ self.broken_backslashes = False |
|
446 |
+ if graphviz_version is not None and \ |
|
447 |
+ LooseVersion(graphviz_version) < LooseVersion("2.46.0"): |
|
448 |
+ self.broken_backslashes = True |
|
449 |
+ |
|
434 | 450 |
self.nodes = [] |
435 | 451 |
self.edges = [] |
436 | 452 |
self.shapes = [] |
... | ... |
@@ -480,7 +496,7 @@ class XDotParser(DotParser): |
480 | 496 |
|
481 | 497 |
for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"): |
482 | 498 |
if attr in attrs: |
483 |
- parser = XDotAttrParser(self, attrs[attr]) |
|
499 |
+ parser = XDotAttrParser(self, attrs[attr], self.broken_backslashes) |
|
484 | 500 |
self.shapes.extend(parser.parse()) |
485 | 501 |
|
486 | 502 |
def handle_node(self, id, attrs): |
... | ... |
@@ -502,7 +518,7 @@ class XDotParser(DotParser): |
502 | 518 |
shapes = [] |
503 | 519 |
for attr in ("_draw_", "_ldraw_"): |
504 | 520 |
if attr in attrs: |
505 |
- parser = XDotAttrParser(self, attrs[attr]) |
|
521 |
+ parser = XDotAttrParser(self, attrs[attr], self.broken_backslashes) |
|
506 | 522 |
shapes.extend(parser.parse()) |
507 | 523 |
try: |
508 | 524 |
url = attrs['URL'] |
... | ... |
@@ -525,7 +541,7 @@ class XDotParser(DotParser): |
525 | 541 |
shapes = [] |
526 | 542 |
for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"): |
527 | 543 |
if attr in attrs: |
528 |
- parser = XDotAttrParser(self, attrs[attr]) |
|
544 |
+ parser = XDotAttrParser(self, attrs[attr], self.broken_backslashes) |
|
529 | 545 |
shapes.extend(parser.parse()) |
530 | 546 |
if shapes: |
531 | 547 |
src = self.node_by_name[src_id] |
... | ... |
@@ -56,6 +56,7 @@ class DotWidget(Gtk.DrawingArea): |
56 | 56 |
} |
57 | 57 |
|
58 | 58 |
filter = 'dot' |
59 |
+ graphviz_version = None |
|
59 | 60 |
|
60 | 61 |
def __init__(self): |
61 | 62 |
Gtk.DrawingArea.__init__(self) |
... | ... |
@@ -100,6 +101,7 @@ class DotWidget(Gtk.DrawingArea): |
100 | 101 |
|
101 | 102 |
def set_filter(self, filter): |
102 | 103 |
self.filter = filter |
104 |
+ self.graphviz_version = None |
|
103 | 105 |
|
104 | 106 |
def run_filter(self, dotcode): |
105 | 107 |
if not self.filter: |
... | ... |
@@ -153,7 +155,14 @@ class DotWidget(Gtk.DrawingArea): |
153 | 155 |
|
154 | 156 |
def set_xdotcode(self, xdotcode, center=True): |
155 | 157 |
assert isinstance(xdotcode, bytes) |
156 |
- parser = XDotParser(xdotcode) |
|
158 |
+ if self.graphviz_version is None: |
|
159 |
+ stdout = subprocess.check_output([self.filter, '-V'], stderr=subprocess.STDOUT) |
|
160 |
+ stdout = stdout.rstrip() |
|
161 |
+ mo = re.match(br'^.* - .* version (?P<version>.*) \(.*\)$', stdout) |
|
162 |
+ assert mo |
|
163 |
+ self.graphviz_version = mo.group('version').decode('ascii') |
|
164 |
+ |
|
165 |
+ parser = XDotParser(xdotcode, graphviz_version=self.graphviz_version) |
|
157 | 166 |
self.graph = parser.parse() |
158 | 167 |
self.zoom_image(self.zoom_ratio, center=center) |
159 | 168 |
|