From 64e3a636df2b5d94ce8ab0a1cbb5baa6cd4594ae Mon Sep 17 00:00:00 2001 From: Sebastian Kalinowski Date: Sat, 14 Mar 2015 20:10:51 +0100 Subject: [PATCH 1/2] Adjusted quoting to DOT lang specification There was some differences between how pydot quotes IDs (names, attrs) and how it is defined in DOT language specification (and how pygraphviz does it). New test with covered cases has been added and new regexpes for checking IDs has been also added. Fixes #36 --- pydot_ng/__init__.py | 33 +++++++++++++++------------------ test/test_pydot.py | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/pydot_ng/__init__.py b/pydot_ng/__init__.py index 1c4e4d1..7f97ccd 100644 --- a/pydot_ng/__init__.py +++ b/pydot_ng/__init__.py @@ -198,15 +198,13 @@ class frozendict(dict): return "frozendict(%s)" % dict.__repr__(self) -dot_keywords = ['graph', 'subgraph', 'digraph', 'node', 'edge', 'strict'] - -id_re_alpha_nums = re.compile('^[_a-zA-Z][a-zA-Z0-9_,]*$', re.UNICODE) -id_re_alpha_nums_with_ports = re.compile( - '^[_a-zA-Z][a-zA-Z0-9_,:\"]*[a-zA-Z0-9_,\"]+$', re.UNICODE) -id_re_num = re.compile('^[0-9,]+$', re.UNICODE) -id_re_with_port = re.compile('^([^:]*):([^:]*)$', re.UNICODE) -id_re_dbl_quoted = re.compile('^\".*\"$', re.S | re.UNICODE) -id_re_html = re.compile('^<.*>$', re.S | re.UNICODE) +# cases when no qoutes needed, from http://www.graphviz.org/doc/info/lang.html +dot_keywords = ('graph', 'subgraph', 'digraph', 'node', 'edge', 'strict') +id_alpha_num = re.compile(r'^[_a-zA-Z\200-\377][_a-zA-Z0-9\200-\377]*$', + re.UNICODE) +id_num = re.compile(r'^[-]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)$', re.UNICODE) +id_html = re.compile(r'^<.*>$', re.DOTALL | re.UNICODE) +id_quoted = re.compile(r'^".*"$', re.DOTALL | re.UNICODE) def needs_quotes(s): @@ -228,19 +226,18 @@ def needs_quotes(s): if s in dot_keywords: return False - chars = [ord(c) for c in s if ord(c) > 0x7f or ord(c) == 0] - if chars and not id_re_dbl_quoted.match(s) and not id_re_html.match(s): - return True - for test_re in [ - id_re_alpha_nums, id_re_num, id_re_dbl_quoted, - id_re_html, id_re_alpha_nums_with_ports]: + id_alpha_num, + id_num, + id_html, + id_quoted, + ]: if test_re.match(s): return False - m = id_re_with_port.match(s) - if m: - return needs_quotes(m.group(1)) or needs_quotes(m.group(2)) + chars = [ord(c) for c in s if ord(c) > 0x7f or ord(c) == 0] + if chars and not id_quoted.match(s) and not id_html.match(s): + return True return True diff --git a/test/test_pydot.py b/test/test_pydot.py index 409d6dc..a6f59af 100644 --- a/test/test_pydot.py +++ b/test/test_pydot.py @@ -285,6 +285,31 @@ class TestGraphAPI(unittest.TestCase): data = g.create(format='jpe') self.assertEqual(len(data) > 0, True) + +class TestQuoting(unittest.TestCase): + + def test_quote_cases(self): + checks = ( + ('A:', '"A:"'), + (':B', '":B"'), + ('A:B', '"A:B"'), + ('1A', '"1A"'), + ('A', 'A'), + ('11', '11'), + ('_xyz', '_xyz'), + ('.11', '.11'), + ('-.09', '-.09'), + ('1.8', '1.8'), + ('', '""'), + ('"1abc"', '"1abc"'), + ('@', '"@"'), + ('ÿ', 'ÿ'), + ) + + for input, expected in checks: + self.assertEqual(pydot.quote_if_necessary(input), expected) + + if __name__ == '__main__': suite = unittest.TestLoader().loadTestsFromTestCase(TestGraphAPI) unittest.TextTestRunner(verbosity=2).run(suite) From 9d2f8fa5581028e1b316883fd20a5a8f219f3327 Mon Sep 17 00:00:00 2001 From: Sebastian Kalinowski Date: Sat, 14 Mar 2015 21:17:17 +0100 Subject: [PATCH 2/2] Add test case for #24 --- test/test_pydot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_pydot.py b/test/test_pydot.py index a6f59af..51f3329 100644 --- a/test/test_pydot.py +++ b/test/test_pydot.py @@ -304,6 +304,8 @@ class TestQuoting(unittest.TestCase): ('"1abc"', '"1abc"'), ('@', '"@"'), ('ÿ', 'ÿ'), + ('$GUID__/ffb73e1c-7495-40b3-9618-9e5462fc89c7', + '"$GUID__/ffb73e1c-7495-40b3-9618-9e5462fc89c7"') ) for input, expected in checks: