Browse code

Handle xdot backslashes correctly.

Irrespectively of graphviz version.

Fixes https://github.com/jrfonseca/xdot.py/issues/92

Jose Fonseca authored on 28/09/2021 12:19:49
Showing 4 changed files

1 1
new file mode 100644
... ...
@@ -0,0 +1,3 @@
1
+digraph {
2
+   1 [label="a\\00"]
3
+}
0 4
new file mode 100644
... ...
@@ -0,0 +1,3 @@
1
+digraph {
2
+   1 [label="a\\b"]
3
+}
... ...
@@ -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