Repository: RDFLib/rdflib

Issue:
Turtle parser fails with leading dot in decimal value
Input file `test.ttl`:
```
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> 

<http://qudt.org/vocab/unit/MilliM-PER-YR>
  a <http://qudt.org/schema/qudt/Unit> ;
  <http://qudt.org/schema/qudt/conversionMultiplier> .171e-11 ;
  <http://qudt.org/schema/qudt/conversionOffset> 0e+00 ;
  <http://qudt.org/schema/qudt/description> "0.001-fold of the SI base unit metre divided by the unit year" ;
  <http://qudt.org/schema/qudt/hasQuantityKind> <http://qudt.org/vocab/quantitykind/Velocity> ;
  <http://qudt.org/schema/qudt/iec61360Code> "0112/2///62720#UAA868" ;
  <http://qudt.org/schema/qudt/uneceCommonCode> "H66" ;
  rdfs:isDefinedBy <http://qudt.org/2.1/vocab/unit> ;
  rdfs:isDefinedBy <http://qudt.org/vocab/unit> ;
  rdfs:label "MilliM PER YR" ;
  <http://www.w3.org/2004/02/skos/core#prefLabel> "millimetre per year" ;
.
```
Traceback:
```
>>> from rdflib import Graph
INFO:rdflib:RDFLib Version: 4.2.2
>>> g = Graph().parse('test.ttl', format='turtle')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.7/site-packages/rdflib/graph.py", line 1043, in parse
    parser.parse(source, self, **args)
  File "/usr/local/lib/python3.7/site-packages/rdflib/plugins/parsers/notation3.py", line 1870, in parse
    p.loadStream(source.getByteStream())
  File "/usr/local/lib/python3.7/site-packages/rdflib/plugins/parsers/notation3.py", line 434, in loadStream
    return self.loadBuf(stream.read())    # Not ideal
  File "/usr/local/lib/python3.7/site-packages/rdflib/plugins/parsers/notation3.py", line 440, in loadBuf
    self.feed(buf)
  File "/usr/local/lib/python3.7/site-packages/rdflib/plugins/parsers/notation3.py", line 466, in feed
    i = self.directiveOrStatement(s, j)
  File "/usr/local/lib/python3.7/site-packages/rdflib/plugins/parsers/notation3.py", line 489, in directiveOrStatement
    return self.checkDot(argstr, j)
  File "/usr/local/lib/python3.7/site-packages/rdflib/plugins/parsers/notation3.py", line 1159, in checkDot
    "expected '.' or '}' or ']' at end of statement")
  File "/usr/local/lib/python3.7/site-packages/rdflib/plugins/parsers/notation3.py", line 1615, in BadSyntax
    raise BadSyntax(self._thisDoc, self.lines, argstr, i, msg)
rdflib.plugins.parsers.notation3.BadSyntax: at line 5 of <>:
Bad syntax (expected '.' or '}' or ']' at end of statement) at ^ in:
"...b' ;\n  <http://qudt.org/schema/qudt/conversionMultiplier> .171'^b'e-11 ;\n  <http://qudt.org/schema/qudt/conversionOffset> 0e+0'..."
>>> exit()
```
The parser is having issue with this line in the input file: `<http://qudt.org/schema/qudt/conversionMultiplier> .171e-11 ;`

Not very familiar with the code but probably some additional checks are required in `directiveOrStatement()` or `checkDot()`. 

Discussion:

Patch:
diff --git a/rdflib/plugins/parsers/notation3.py b/rdflib/plugins/parsers/notation3.py
index 4b6ff5d1..c57f5bcf 100755
--- a/rdflib/plugins/parsers/notation3.py
+++ b/rdflib/plugins/parsers/notation3.py
@@ -349,9 +349,7 @@ ws = re.compile(r'[ \t]*')                       # Whitespace not including NL
 signed_integer = re.compile(r'[-+]?[0-9]+')      # integer
 integer_syntax = re.compile(r'[-+]?[0-9]+')
 decimal_syntax = re.compile(r'[-+]?[0-9]*\.[0-9]+')
-exponent_syntax = re.compile(r'[-+]?(?:[0-9]+\.[0-9]*(?:e|E)[-+]?[0-9]+|'+
-                             r'\.[0-9](?:e|E)[-+]?[0-9]+|'+
-                             r'[0-9]+(?:e|E)[-+]?[0-9]+)')
+exponent_syntax = re.compile(r'[-+]?(?:[0-9]+\.[0-9]*|\.[0-9]+|[0-9]+)(?:e|E)[-+]?[0-9]+')
 digitstring = re.compile(r'[0-9]+')              # Unsigned integer
 interesting = re.compile(r"""[\\\r\n\"\']""")
 langcode = re.compile(r'[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*')
diff --git a/setup.py b/setup.py
index d10513be..d611fe45 100644
--- a/setup.py
+++ b/setup.py
@@ -6,11 +6,12 @@ from setuptools import setup, find_packages
 
 kwargs = {}
 kwargs['install_requires'] = [ 'six', 'isodate', 'pyparsing']
-kwargs['tests_require'] = ['html5lib', 'networkx']
+kwargs['tests_require'] = ['html5lib', 'networkx', 'nose', 'doctest-ignore-unicode']
 kwargs['test_suite'] = "nose.collector"
 kwargs['extras_require'] = {
     'html': ['html5lib'],
     'sparql': ['requests'],
+    'tests': kwargs['tests_require'],
     'docs': ['sphinx < 3', 'sphinxcontrib-apidoc']
     }
 


Test patch:
diff --git a/test/test_n3.py b/test/test_n3.py
index 5d447732..5cdc74b5 100644
--- a/test/test_n3.py
+++ b/test/test_n3.py
@@ -1,7 +1,8 @@
 from rdflib.graph import Graph, ConjunctiveGraph
 import unittest
 from rdflib.term import Literal, URIRef
-from rdflib.plugins.parsers.notation3 import BadSyntax
+from rdflib.plugins.parsers.notation3 import BadSyntax, exponent_syntax
+import itertools
 
 from six import b
 from six.moves.urllib.error import URLError
@@ -164,6 +165,31 @@ foo-bar:Ex foo-bar:name "Test" . """
         g = Graph()
         g.parse("test/n3/issue156.n3", format="n3")
 
+    def testIssue999(self):
+        """
+        Make sure the n3 parser does recognize exponent and leading dot in ".171e-11"
+        """
+        data = """
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+<http://qudt.org/vocab/unit/MilliM-PER-YR>
+  a <http://qudt.org/schema/qudt/Unit> ;
+  <http://qudt.org/schema/qudt/conversionMultiplier> .171e-11 ;
+  <http://qudt.org/schema/qudt/conversionOffset> 0e+00 ;
+  <http://qudt.org/schema/qudt/description> "0.001-fold of the SI base unit metre divided by the unit year" ;
+  <http://qudt.org/schema/qudt/hasQuantityKind> <http://qudt.org/vocab/quantitykind/Velocity> ;
+  <http://qudt.org/schema/qudt/iec61360Code> "0112/2///62720#UAA868" ;
+  <http://qudt.org/schema/qudt/uneceCommonCode> "H66" ;
+  rdfs:isDefinedBy <http://qudt.org/2.1/vocab/unit> ;
+  rdfs:isDefinedBy <http://qudt.org/vocab/unit> ;
+  rdfs:label "MilliM PER YR" ;
+  <http://www.w3.org/2004/02/skos/core#prefLabel> "millimetre per year" ;
+.
+        """
+        g = Graph()
+        g.parse(data=data, format="n3")
+        g.parse(data=data, format="turtle")
+
     def testDotInPrefix(self):
         g = Graph()
         g.parse(
@@ -226,5 +252,24 @@ foo-bar:Ex foo-bar:name "Test" . """
             g2), 'Document with declared empty prefix must match default #'
 
 
+class TestRegularExpressions(unittest.TestCase):
+    def testExponents(self):
+        signs = ("", "+", "-")
+        mantissas = ("1", "1.", ".1",
+                     "12", "12.", "1.2", ".12",
+                     "123", "123.", "12.3", "1.23", ".123")
+        es = "eE"
+        exps = ("1", "12", "+1", "-1", "+12", "-12")
+        for parts in itertools.product(signs, mantissas, es, exps):
+            expstring = "".join(parts)
+            self.assertTrue(exponent_syntax.match(expstring))
+
+    def testInvalidExponents(self):
+        # Add test cases as needed
+        invalid = (".e1",)
+        for expstring in invalid:
+            self.assertFalse(exponent_syntax.match(expstring))
+
+
 if __name__ == '__main__':
     unittest.main()