Repository: sciunto-org/python-bibtexparser
Pull request #456 (2024-02-06 08:50:05)

Bugfix name split
Fixes https://github.com/sciunto-org/python-bibtexparser/issues/441


Patch:
diff --git a/bibtexparser/entrypoint.py b/bibtexparser/entrypoint.py
index 5ecf871..0d77607 100644
--- a/bibtexparser/entrypoint.py
+++ b/bibtexparser/entrypoint.py
@@ -76,14 +76,19 @@ def parse_string(
 ):
     """Parse a BibTeX string.
 
-    Args:
-        bibtex_str (str): BibTeX string to parse
-        parse_stack (Optional[Iterable[Middleware]], optional): List of middleware to apply to the database after splitting. If None (default), a default stack will be used providing simple standard functionality will be used.
-        append_middleware (Optional[Iterable[Middleware]], optional): List of middleware to append to the default stack (ignored if a not-None parse_stack is passed).
-        library (Optional[Library], optional): Library to add entries to. If None (default), a new library will be created.
-
-    Returns:
-        Library: Parsed BibTeX database
+    :param bibtex_str: BibTeX string to parse
+    :param parse_stack:
+        List of middleware to apply to the database after splitting.
+        If ``None`` (default), a default stack will be used providing simple standard functionality.
+
+    :param append_middleware:
+        List of middleware to append to the default stack
+        (ignored if a not-``None`` parse_stack is passed).
+
+    :param library:
+        Library to add entries to. If ``None`` (default), a new library will be created.
+
+    :return: Library: Parsed BibTeX database
     """
     splitter = Splitter(bibstr=bibtex_str)
     library = splitter.split(library=library)
@@ -103,14 +108,17 @@ def parse_file(
 ) -> Library:
     """Parse a BibTeX file
 
-    Args:
-        path (str): Path to BibTeX file
-        parse_stack (Optional[Iterable[Middleware]], optional): List of middleware to apply to the database after splitting. If None (default), a default stack will be used providing simple standard functionality will be used.
-        append_middleware (Optional[Iterable[Middleware]], optional): List of middleware to append to the default stack (ignored if a not-None parse_stack is passed).
-        encoding: Encoding of the .bib file. Default encoding is "UTF-8".
+    :param path: Path to BibTeX file
+    :param parse_stack:
+        List of middleware to apply to the database after splitting.
+        If ``None`` (default), a default stack will be used providing simple standard functionality.
+
+    :param append_middleware:
+        List of middleware to append to the default stack
+        (ignored if a not-``None`` parse_stack is passed).
 
-    Returns:
-        Library: Parsed BibTeX library
+    :param encoding: Encoding of the .bib file. Default encoding is ``"UTF-8"``.
+    :return: Library: Parsed BibTeX library
     """
     with open(path, encoding=encoding) as f:
         bibtex_str = f.read()
diff --git a/bibtexparser/exceptions.py b/bibtexparser/exceptions.py
index cfbcd34..161c78d 100644
--- a/bibtexparser/exceptions.py
+++ b/bibtexparser/exceptions.py
@@ -40,8 +40,8 @@ class ParserStateException(ParsingException):
 class RegexMismatchException(ParserStateException):
     """Raised when regex matches are inconsistent, implying a bug in the parser.
 
-    For example, raised when first match `@string{`
-    is not followed by an overlapping match `}`.
+    For example, raised when first match ``@string{``
+    is not followed by an overlapping match ``}``.
     """
 
     def __init__(self, first_match, expected_match, second_match):
diff --git a/bibtexparser/middlewares/names.py b/bibtexparser/middlewares/names.py
index 11d4d42..7152870 100644
--- a/bibtexparser/middlewares/names.py
+++ b/bibtexparser/middlewares/names.py
@@ -574,6 +574,9 @@ def split_multiple_persons_names(names):
 
         # Change in brace level.
         if char == "{":
+            if step == NEXT_WORD:
+                spans[-1].append(possible_end)
+                spans.append([pos - 1])
             bracelevel += 1
             step = START_WHITESPACE
             continue
diff --git a/bibtexparser/model.py b/bibtexparser/model.py
index 2e37b00..fc57a28 100644
--- a/bibtexparser/model.py
+++ b/bibtexparser/model.py
@@ -5,7 +5,8 @@ from typing import Any, Dict, List, Optional, Set
 class Block(abc.ABC):
     """A abstract superclass of all top-level building blocks of a bibtex file.
 
-    E.g. a `@string` block, a `@preamble` block, an `@entry` block, a comment, etc."""
+    E.g. a ``@string`` block, a ``@preamble`` block, an ``@entry`` block, a comment, etc.
+    """
 
     def __init__(
         self,
@@ -51,15 +52,15 @@ class Block(abc.ABC):
         return self._parser_metadata
 
     def get_parser_metadata(self, key: str) -> Optional[Any]:
-        """EXPERIMENTAL: get auxiliary information stored in `parser_metadata`.
+        """EXPERIMENTAL: get auxiliary information stored in ``parser_metadata``.
 
-        See attribute `parser_metadata` for more information."""
+        See attribute ``parser_metadata`` for more information."""
         return self._parser_metadata.get(key, None)
 
     def set_parser_metadata(self, key: str, value: Any):
-        """EXPERIMENTAL: set auxiliary information stored in `parser_metadata`.
+        """EXPERIMENTAL: set auxiliary information stored in ``parser_metadata``.
 
-        See attribute `parser_metadata` for more information."""
+        See attribute ``parser_metadata`` for more information."""
         self._parser_metadata[key] = value
 
     def __eq__(self, other):
@@ -72,7 +73,7 @@ class Block(abc.ABC):
 
 
 class String(Block):
-    """Bibtex Blocks of the `@string` type, e.g. @string{me = "My Name"}."""
+    """Bibtex Blocks of the ``@string`` type, e.g. ``@string{me = "My Name"}``."""
 
     def __init__(
         self,
@@ -87,7 +88,7 @@ class String(Block):
 
     @property
     def key(self) -> str:
-        """The key of the string, e.g. `me` in `@string{me = "My Name"}`."""
+        """The key of the string, e.g. ``me`` in ``@string{me = "My Name"}``."""
         return self._key
 
     @key.setter
@@ -96,7 +97,7 @@ class String(Block):
 
     @property
     def value(self) -> str:
-        """The value of the string, e.g. `"My Name"` in `@string{me = "My Name"}`."""
+        """The value of the string, e.g. ``"My Name"`` in ``@string{me = "My Name"}``."""
         return self._value
 
     @value.setter
@@ -114,7 +115,7 @@ class String(Block):
 
 
 class Preamble(Block):
-    """Bibtex Blocks of the `@preamble` type, e.g. @preamble{This is a preamble}."""
+    """Bibtex Blocks of the ``@preamble`` type, e.g. ``@preamble{This is a preamble}``."""
 
     def __init__(
         self, value: str, start_line: Optional[int] = None, raw: Optional[str] = None
@@ -124,7 +125,7 @@ class Preamble(Block):
 
     @property
     def value(self) -> str:
-        """The value of the preamble, e.g. `blabla` in `@preamble{blabla}`."""
+        """The value of the preamble, e.g. ``blabla`` in ``@preamble{blabla}``."""
         return self._value
 
     @value.setter
@@ -142,7 +143,7 @@ class Preamble(Block):
 
 
 class ExplicitComment(Block):
-    """Bibtex Blocks of the `@comment` type, e.g. @comment{This is a comment}."""
+    """Bibtex Blocks of the ``@comment`` type, e.g. ``@comment{This is a comment}``."""
 
     def __init__(
         self, comment: str, start_line: Optional[int] = None, raw: Optional[str] = None
@@ -152,7 +153,7 @@ class ExplicitComment(Block):
 
     @property
     def comment(self) -> str:
-        """The value of the comment, e.g. `blabla` in `@comment{blabla}`."""
+        """The value of the comment, e.g. ``blabla`` in ``@comment{blabla}``."""
         return self._comment
 
     @comment.setter
@@ -170,7 +171,7 @@ class ExplicitComment(Block):
 
 
 class ImplicitComment(Block):
-    """Bibtex outside of an @{...} block, which is treated as a comment."""
+    """Bibtex outside of an ``@{...}`` block, which is treated as a comment."""
 
     def __init__(
         self, comment: str, start_line: Optional[int] = None, raw: Optional[str] = None
@@ -198,7 +199,7 @@ class ImplicitComment(Block):
 
 
 class Field:
-    """A field of a Bibtex entry, e.g. `author = {John Doe}`."""
+    """A field of a Bibtex entry, e.g. ``author = {John Doe}``."""
 
     def __init__(self, key: str, value: Any, start_line: Optional[int] = None):
         self._start_line = start_line
@@ -207,7 +208,7 @@ class Field:
 
     @property
     def key(self) -> str:
-        """The key of the field, e.g. `author` in `author = {John Doe}`."""
+        """The key of the field, e.g. ``author`` in ``author = {John Doe}``."""
         return self._key
 
     @key.setter
@@ -216,7 +217,7 @@ class Field:
 
     @property
     def value(self) -> Any:
-        """The value of the field, e.g. `{John Doe}` in `author = {John Doe}`."""
+        """The value of the field, e.g. ``{John Doe}`` in ``author = {John Doe}``."""
         return self._value
 
     @value.setter
@@ -247,7 +248,7 @@ class Field:
 
 
 class Entry(Block):
-    """Bibtex Blocks of the `@entry` type, e.g. @article{Cesar2013, ...}."""
+    """Bibtex Blocks of the ``@entry`` type, e.g. ``@article{Cesar2013, ...}``."""
 
     def __init__(
         self,
@@ -264,7 +265,7 @@ class Entry(Block):
 
     @property
     def entry_type(self):
-        """The type of the entry, e.g. `article` in `@article{Cesar2013, ...}`."""
+        """The type of the entry, e.g. ``article`` in ``@article{Cesar2013, ...}``."""
         return self._entry_type
 
     @entry_type.setter
@@ -273,7 +274,7 @@ class Entry(Block):
 
     @property
     def key(self):
-        """The key of the entry, e.g. `Cesar2013` in `@article{Cesar2013, ...}`."""
+        """The key of the entry, e.g. ``Cesar2013`` in ``@article{Cesar2013, ...}``."""
         return self._key
 
     @key.setter
@@ -282,7 +283,7 @@ class Entry(Block):
 
     @property
     def fields(self) -> List[Field]:
-        """The key-value attributes of an entry, as `Field` instances."""
+        """The key-value attributes of an entry, as ``Field`` instances."""
         return self._fields
 
     @fields.setter
@@ -436,7 +437,7 @@ class DuplicateBlockKeyBlock(ParsingFailedBlock):
 
     @property
     def key(self) -> str:
-        """The key of the entry, e.g. `Cesar2013` in `@article{Cesar2013, ...}`."""
+        """The key of the entry, e.g. ``Cesar2013`` in ``@article{Cesar2013, ...}``."""
         return self._key
 
     @key.setter


Test patch:
diff --git a/tests/middleware_tests/test_names.py b/tests/middleware_tests/test_names.py
index 8dde269..d2f3ff1 100644
--- a/tests/middleware_tests/test_names.py
+++ b/tests/middleware_tests/test_names.py
@@ -47,6 +47,18 @@ from tests.middleware_tests.middleware_test_util import (
         ("P. M. Sutherland And\nSmith, J.", ["P. M. Sutherland", "Smith, J."]),
         ("P. M. Sutherland aNd\tSmith, J.", ["P. M. Sutherland", "Smith, J."]),
         ("P. M. Sutherland aNd\nSmith, J.", ["P. M. Sutherland", "Smith, J."]),
+        (
+            r"{Delgado de Molina}, A. and Vign{\'e}, M. and {de Geus}, Tom",
+            ["{Delgado de Molina}, A.", r"Vign{\'e}, M.", "{de Geus}, Tom"],
+        ),
+        (
+            r"{Delgado de Molina}, A. and {de Geus}, Tom and Vign{\'e}, M.",
+            ["{Delgado de Molina}, A.", "{de Geus}, Tom", r"Vign{\'e}, M."],
+        ),
+        (
+            r"{Delgado de Molina}, A. and {de Geus}, Tom and {Vign{\'e}, M.}",
+            ["{Delgado de Molina}, A.", "{de Geus}, Tom", r"{Vign{\'e}, M.}"],
+        ),
         (
             "Fake Name an{d brace in an}d and Somebody Else",
             ["Fake Name an{d brace in an}d", "Somebody Else"],
@@ -238,6 +250,10 @@ REGULAR_NAME_PARTS_PARSING_TEST_CASES = (
         {"first": ["A."], "von": [], "last": ["{Delgado de Molina}"], "jr": []},
     ),
     (r"M. Vign{\'e}", {"first": ["M."], "von": [], "last": [r"Vign{\'e}"], "jr": []}),
+    (
+        r"Tom {de Geus}",
+        {"first": ["Tom"], "von": [], "last": ["{de Geus}"], "jr": []},
+    ),
     ###############################################################################
     #
     # Test cases from