Repository: sciunto-org/python-bibtexparser

Issue:
Cannot match if there is a space between `@inproceedings` and `{`
for this bibtex copied from usenix website: (notice the space in `@inproceedings {xxx`)
```
@inproceedings {251572,
author = {Shravan Narayan and Craig Disselkoen and Tal Garfinkel and Nathan Froyd and Eric Rahm and Sorin Lerner and Hovav Shacham and Deian Stefan},
title = {Retrofitting Fine Grain Isolation in the Firefox Renderer},
booktitle = {29th USENIX Security Symposium (USENIX Security 20)},
year = {2020},
isbn = {978-1-939133-17-5},
pages = {699--716},
url = {https://www.usenix.org/conference/usenixsecurity20/presentation/narayan},
publisher = {USENIX Association},
month = aug,
abstract = {Firefox and other major browsers rely on dozens of third-party libraries to render audio, video, images, and other content. These libraries are a frequent source of vulnerabilities. To mitigate this threat, we are migrating Firefox to an architecture that isolates these libraries in lightweight sandboxes, dramatically reducing the impact of a compromise. Retrofitting isolation can be labor-intensive, very prone to security bugs, and requires critical attention to performance. To help, we developed RLBox, a framework that minimizes the burden of converting Firefox to securely and efficiently use untrusted code. To enable this, RLBox employs static information flow enforcement, and lightweight dynamic checks, expressed directly in the C++ type system. RLBox supports efficient sandboxing through either software-based-fault isolation or multi-core process isolation. Performance overheads are modest and transient, and have only minor impact on page latency. We demonstrate this by sandboxing performance-sensitive image decoding libraries (libjpeg and libpng), video decoding libraries (libtheora and libvpx), the libvorbis audio decoding library, and the zlib decompression library. RLBox, using a WebAssembly sandbox, has been integrated into production Firefox to sandbox the libGraphite font shaping library.}
}
```

Everything becomes `ImplicitComment`.

![image](https://github.com/sciunto-org/python-bibtexparser/assets/23314210/2b37e54a-0143-496f-93cf-caddf0c26614)

I don't know if this is expected, but there is also no warning or error reported.

Discussion:

Patch:
diff --git a/bibtexparser/splitter.py b/bibtexparser/splitter.py
index ce2e55f..fd831a9 100644
--- a/bibtexparser/splitter.py
+++ b/bibtexparser/splitter.py
@@ -231,7 +231,7 @@ class Splitter:
             The library with the added blocks.
         """
         self._markiter = re.finditer(
-            r"(?<!\\)[\{\}\",=\n]|(?<=\n)@[\w]*(?={)", self.bibstr, re.MULTILINE
+            r"(?<!\\)[\{\}\",=\n]|(?<=\n)@[\w]*( |\t)*(?={)", self.bibstr, re.MULTILINE
         )
 
         if library is None:
@@ -337,7 +337,7 @@ class Splitter:
     def _handle_entry(self, m, m_val) -> Union[Entry, ParsingFailedBlock]:
         """Handle entry block. Return end index"""
         start_line = self._current_line
-        entry_type = m_val[1:]
+        entry_type = m_val[1:].strip()
         start_bracket_mark = self._next_mark(accept_eof=False)
         if start_bracket_mark.group(0) != "{":
             self._unaccepted_mark = start_bracket_mark
diff --git a/dev-utilities/bibtex-nameparsing/parsename.py b/dev-utilities/bibtex-nameparsing/parsename.py
index d3a5809..0c80e89 100644
--- a/dev-utilities/bibtex-nameparsing/parsename.py
+++ b/dev-utilities/bibtex-nameparsing/parsename.py
@@ -12,11 +12,14 @@ try:
     from tempfile import TemporaryDirectory
 except ImportError:
     from tempfile import mkdtemp
+
     class TemporaryDirectory(object):
         def __init__(self):
             self.name = mkdtemp()
+
         def __enter__(self):
             return self.name
+
         def __exit__(self, exc, value, tb):
             shutil.rmtree(self.name)
 
@@ -42,12 +45,16 @@ def evaluate_bibtex_parsename(tempdir, names):
     # Write entries for each string in the list.
     with open(os.path.join(tempdir, "parsename.bib"), "w") as bibfile:
         for i, name in enumerate(names):
-            bibfile.write('@parsename{{case{0:d}, author={{{1:s}}}}}\n'.format(i, name))
+            bibfile.write("@parsename{{case{0:d}, author={{{1:s}}}}}\n".format(i, name))
 
     # Run BibTeX.
-    proc = subprocess.Popen(["bibtex", "parsename"], cwd=tempdir,
-                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
-                            universal_newlines=True)
+    proc = subprocess.Popen(
+        ["bibtex", "parsename"],
+        cwd=tempdir,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT,
+        universal_newlines=True,
+    )
     proc.wait()
 
     # Error.
diff --git a/dev-utilities/bibtex-nameparsing/splitnames.py b/dev-utilities/bibtex-nameparsing/splitnames.py
index e3c8839..ea64534 100644
--- a/dev-utilities/bibtex-nameparsing/splitnames.py
+++ b/dev-utilities/bibtex-nameparsing/splitnames.py
@@ -12,11 +12,14 @@ try:
     from tempfile import TemporaryDirectory
 except ImportError:
     from tempfile import mkdtemp
+
     class TemporaryDirectory(object):
         def __init__(self):
             self.name = mkdtemp()
+
         def __enter__(self):
             return self.name
+
         def __exit__(self, exc, value, tb):
             shutil.rmtree(self.name)
 
@@ -42,12 +45,18 @@ def evaluate_bibtex_splitnames(tempdir, names):
     # Write entries for each string in the list.
     with open(os.path.join(tempdir, "splitnames.bib"), "w") as bibfile:
         for i, name in enumerate(names):
-            bibfile.write('@splitnames{{case{0:d}, author={{{1:s}}}}}\n'.format(i, name))
+            bibfile.write(
+                "@splitnames{{case{0:d}, author={{{1:s}}}}}\n".format(i, name)
+            )
 
     # Run BibTeX.
-    proc = subprocess.Popen(["bibtex", "splitnames"], cwd=tempdir,
-                            stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
-                            universal_newlines=True)
+    proc = subprocess.Popen(
+        ["bibtex", "splitnames"],
+        cwd=tempdir,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.STDOUT,
+        universal_newlines=True,
+    )
     proc.wait()
 
     # Error.


Test patch:
diff --git a/tests/splitter_tests/test_splitter_entry.py b/tests/splitter_tests/test_splitter_entry.py
index c0f26db..9f2806d 100644
--- a/tests/splitter_tests/test_splitter_entry.py
+++ b/tests/splitter_tests/test_splitter_entry.py
@@ -196,3 +196,38 @@ def test_entry_without_fields(entry_without_fields: str):
 
     assert library.entries[1].key == "subsequentArticle"
     assert len(library.entries[1].fields) == 1
+
+
+@pytest.mark.parametrize(
+    "entry",
+    [
+        # common in revtex, see issue #384
+        pytest.param(
+            "@Article {articleTestKey, title = {Some title}}", id="single whitespace"
+        ),
+        pytest.param(
+            "@Article  {articleTestKey, title = {Some title}}", id="double whitespace"
+        ),
+        pytest.param("@Article\t{articleTestKey, title = {Some title}}", id="tab"),
+        pytest.param(
+            "@Article \t {articleTestKey, title = {Some title}}",
+            id="tab and whitespaces",
+        ),
+    ],
+)
+def test_entry_with_space_before_bracket(entry: str):
+    """For motivation why we need this, please see issue #391"""
+    some_previous_entry = "@article{normal_entry, title = {The first title}, author = {The first author} }"
+
+    full_bibtex = f"{some_previous_entry}\n\n{entry}\n\n"
+    library: Library = Splitter(full_bibtex).split()
+    assert len(library.blocks) == 2
+    assert len(library.entries) == 2
+    assert len(library.failed_blocks) == 0
+
+    assert library.entries[0].key == "normal_entry"
+    assert len(library.entries[0].fields) == 2
+
+    assert library.entries[1].entry_type == "article"
+    assert library.entries[1].key == "articleTestKey"
+    assert len(library.entries[1].fields) == 1