bibtexparser 2.0.0b5__tar.gz → 2.0.0b7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/PKG-INFO +2 -2
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/README.md +1 -1
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/__init__.py +1 -1
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/entrypoint.py +23 -15
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/exceptions.py +2 -2
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/__init__.py +1 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/enclosing.py +4 -4
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/interpolate.py +2 -2
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/middleware.py +6 -6
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/month.py +7 -7
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/names.py +98 -37
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/sorting_entry_fields.py +4 -4
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/model.py +62 -21
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser.egg-info/PKG-INFO +2 -2
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/tests/test_model.py +57 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/LICENSE +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/library.py +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/latex_encoding.py +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/parsestack.py +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/sorting_blocks.py +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/splitter.py +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/writer.py +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser.egg-info/SOURCES.txt +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser.egg-info/dependency_links.txt +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser.egg-info/requires.txt +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser.egg-info/top_level.txt +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/setup.cfg +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/setup.py +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/tests/test_entrypoint.py +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/tests/test_library.py +0 -0
- {bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/tests/test_writer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bibtexparser
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0b7
|
|
4
4
|
Summary: Bibtex parser for python 3
|
|
5
5
|
Home-page: https://github.com/sciunto-org/python-bibtexparser
|
|
6
6
|
Author: Michael Weiss and other contributors
|
|
@@ -120,7 +120,7 @@ Writes the content of a bibtex library to a `.bib` file. Optional formatting par
|
|
|
120
120
|
|
|
121
121
|
## About
|
|
122
122
|
|
|
123
|
-
Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)).
|
|
123
|
+
Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)). In 2024, Tom de Geus ([@tdegeus](https://github.com/tdegeus)) joined as co-maintainer.
|
|
124
124
|
|
|
125
125
|
Credits and thanks to the many contributors who helped creating this library, including
|
|
126
126
|
François Boulogne ([@sciunto](https://github.com/sciunto/), creator of the first version) and Olivier Mangin ([@omangin](https://github.com/omangin/), long-term contributor).
|
|
@@ -90,7 +90,7 @@ Writes the content of a bibtex library to a `.bib` file. Optional formatting par
|
|
|
90
90
|
|
|
91
91
|
## About
|
|
92
92
|
|
|
93
|
-
Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)).
|
|
93
|
+
Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)). In 2024, Tom de Geus ([@tdegeus](https://github.com/tdegeus)) joined as co-maintainer.
|
|
94
94
|
|
|
95
95
|
Credits and thanks to the many contributors who helped creating this library, including
|
|
96
96
|
François Boulogne ([@sciunto](https://github.com/sciunto/), creator of the first version) and Olivier Mangin ([@omangin](https://github.com/omangin/), long-term contributor).
|
|
@@ -76,14 +76,19 @@ def parse_string(
|
|
|
76
76
|
):
|
|
77
77
|
"""Parse a BibTeX string.
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
:param bibtex_str: BibTeX string to parse
|
|
80
|
+
:param parse_stack:
|
|
81
|
+
List of middleware to apply to the database after splitting.
|
|
82
|
+
If ``None`` (default), a default stack will be used providing simple standard functionality.
|
|
83
|
+
|
|
84
|
+
:param append_middleware:
|
|
85
|
+
List of middleware to append to the default stack
|
|
86
|
+
(ignored if a not-``None`` parse_stack is passed).
|
|
87
|
+
|
|
88
|
+
:param library:
|
|
89
|
+
Library to add entries to. If ``None`` (default), a new library will be created.
|
|
90
|
+
|
|
91
|
+
:return: Library: Parsed BibTeX database
|
|
87
92
|
"""
|
|
88
93
|
splitter = Splitter(bibstr=bibtex_str)
|
|
89
94
|
library = splitter.split(library=library)
|
|
@@ -103,14 +108,17 @@ def parse_file(
|
|
|
103
108
|
) -> Library:
|
|
104
109
|
"""Parse a BibTeX file
|
|
105
110
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
:param path: Path to BibTeX file
|
|
112
|
+
:param parse_stack:
|
|
113
|
+
List of middleware to apply to the database after splitting.
|
|
114
|
+
If ``None`` (default), a default stack will be used providing simple standard functionality.
|
|
115
|
+
|
|
116
|
+
:param append_middleware:
|
|
117
|
+
List of middleware to append to the default stack
|
|
118
|
+
(ignored if a not-``None`` parse_stack is passed).
|
|
111
119
|
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
:param encoding: Encoding of the .bib file. Default encoding is ``"UTF-8"``.
|
|
121
|
+
:return: Library: Parsed BibTeX library
|
|
114
122
|
"""
|
|
115
123
|
with open(path, encoding=encoding) as f:
|
|
116
124
|
bibtex_str = f.read()
|
|
@@ -40,8 +40,8 @@ class ParserStateException(ParsingException):
|
|
|
40
40
|
class RegexMismatchException(ParserStateException):
|
|
41
41
|
"""Raised when regex matches are inconsistent, implying a bug in the parser.
|
|
42
42
|
|
|
43
|
-
For example, raised when first match
|
|
44
|
-
is not followed by an overlapping match
|
|
43
|
+
For example, raised when first match ``@string{``
|
|
44
|
+
is not followed by an overlapping match ``}``.
|
|
45
45
|
"""
|
|
46
46
|
|
|
47
47
|
def __init__(self, first_match, expected_match, second_match):
|
|
@@ -7,6 +7,7 @@ from bibtexparser.middlewares.latex_encoding import (
|
|
|
7
7
|
LatexDecodingMiddleware,
|
|
8
8
|
LatexEncodingMiddleware,
|
|
9
9
|
)
|
|
10
|
+
from bibtexparser.middlewares.middleware import BlockMiddleware, LibraryMiddleware
|
|
10
11
|
from bibtexparser.middlewares.month import (
|
|
11
12
|
MonthAbbreviationMiddleware,
|
|
12
13
|
MonthIntMiddleware,
|
|
@@ -37,8 +37,8 @@ class RemoveEnclosingMiddleware(BlockMiddleware):
|
|
|
37
37
|
)
|
|
38
38
|
|
|
39
39
|
# docstr-coverage: inherited
|
|
40
|
-
@
|
|
41
|
-
def metadata_key() -> str:
|
|
40
|
+
@classmethod
|
|
41
|
+
def metadata_key(cls) -> str:
|
|
42
42
|
return REMOVED_ENCLOSING_KEY
|
|
43
43
|
|
|
44
44
|
@staticmethod
|
|
@@ -109,8 +109,8 @@ class AddEnclosingMiddleware(BlockMiddleware):
|
|
|
109
109
|
self._enclose_integers = enclose_integers
|
|
110
110
|
|
|
111
111
|
# docstr-coverage: inherited
|
|
112
|
-
@
|
|
113
|
-
def metadata_key() -> str:
|
|
112
|
+
@classmethod
|
|
113
|
+
def metadata_key(cls) -> str:
|
|
114
114
|
return "remove_enclosing"
|
|
115
115
|
|
|
116
116
|
def _enclose(
|
|
@@ -28,8 +28,8 @@ class ResolveStringReferencesMiddleware(LibraryMiddleware):
|
|
|
28
28
|
super().__init__(allow_inplace_modification)
|
|
29
29
|
|
|
30
30
|
# docstr-coverage: inherited
|
|
31
|
-
@
|
|
32
|
-
def metadata_key() -> str:
|
|
31
|
+
@classmethod
|
|
32
|
+
def metadata_key(cls) -> str:
|
|
33
33
|
return "ResolveStringReferences"
|
|
34
34
|
|
|
35
35
|
# docstr-coverage: inherited
|
|
@@ -21,7 +21,9 @@ class Middleware(abc.ABC):
|
|
|
21
21
|
or LibraryMiddleware"""
|
|
22
22
|
|
|
23
23
|
def __init__(
|
|
24
|
-
self,
|
|
24
|
+
self,
|
|
25
|
+
allow_parallel_execution: bool = True,
|
|
26
|
+
allow_inplace_modification: bool = True,
|
|
25
27
|
):
|
|
26
28
|
"""
|
|
27
29
|
|
|
@@ -62,14 +64,12 @@ class BlockMiddleware(Middleware, abc.ABC):
|
|
|
62
64
|
except if `allow_inplace_modification` is true.
|
|
63
65
|
"""
|
|
64
66
|
|
|
65
|
-
@
|
|
66
|
-
|
|
67
|
-
def metadata_key() -> str:
|
|
67
|
+
@classmethod
|
|
68
|
+
def metadata_key(cls) -> str:
|
|
68
69
|
"""Identifier of the middleware.
|
|
69
|
-
|
|
70
70
|
This key is used to identify the middleware in a blocks metadata.
|
|
71
71
|
"""
|
|
72
|
-
|
|
72
|
+
return cls.__name__
|
|
73
73
|
|
|
74
74
|
# docstr-coverage: inherited
|
|
75
75
|
def transform(self, library: "Library") -> "Library":
|
|
@@ -12,7 +12,7 @@ class _MonthInterpolator(BlockMiddleware, abc.ABC):
|
|
|
12
12
|
"""Abstract class to handle month-conversions."""
|
|
13
13
|
|
|
14
14
|
# docstr-coverage: inherited
|
|
15
|
-
def __init__(self, allow_inplace_modification: bool):
|
|
15
|
+
def __init__(self, allow_inplace_modification: bool = True):
|
|
16
16
|
super().__init__(
|
|
17
17
|
allow_inplace_modification=allow_inplace_modification,
|
|
18
18
|
allow_parallel_execution=True,
|
|
@@ -80,8 +80,8 @@ class MonthLongStringMiddleware(_MonthInterpolator):
|
|
|
80
80
|
and unenclosed."""
|
|
81
81
|
|
|
82
82
|
# docstr-coverage: inherited
|
|
83
|
-
@
|
|
84
|
-
def metadata_key() -> str:
|
|
83
|
+
@classmethod
|
|
84
|
+
def metadata_key(cls) -> str:
|
|
85
85
|
return "MonthLongStringMiddleware"
|
|
86
86
|
|
|
87
87
|
# docstr-coverage: inherited
|
|
@@ -124,8 +124,8 @@ class MonthAbbreviationMiddleware(_MonthInterpolator):
|
|
|
124
124
|
The created abbreviations are always lowercase and unenclosed."""
|
|
125
125
|
|
|
126
126
|
# docstr-coverage: inherited
|
|
127
|
-
@
|
|
128
|
-
def metadata_key() -> str:
|
|
127
|
+
@classmethod
|
|
128
|
+
def metadata_key(cls) -> str:
|
|
129
129
|
return "MonthAbbreviationMiddleware"
|
|
130
130
|
|
|
131
131
|
# docstr-coverage: inherited
|
|
@@ -159,8 +159,8 @@ class MonthIntMiddleware(_MonthInterpolator):
|
|
|
159
159
|
The created int-months are always integers and unenclosed."""
|
|
160
160
|
|
|
161
161
|
# docstr-coverage: inherited
|
|
162
|
-
@
|
|
163
|
-
def metadata_key() -> str:
|
|
162
|
+
@classmethod
|
|
163
|
+
def metadata_key(cls) -> str:
|
|
164
164
|
return "MonthIntMiddleware"
|
|
165
165
|
|
|
166
166
|
# docstr-coverage: inherited
|
|
@@ -5,7 +5,7 @@ Much of the code is taken from Blair Bonnetts never merged v0 pull request
|
|
|
5
5
|
"""
|
|
6
6
|
import abc
|
|
7
7
|
import dataclasses
|
|
8
|
-
from typing import List, Tuple
|
|
8
|
+
from typing import List, Literal, Tuple
|
|
9
9
|
|
|
10
10
|
from bibtexparser.model import Block, Entry, Field, MiddlewareErrorBlock
|
|
11
11
|
|
|
@@ -62,8 +62,8 @@ class SeparateCoAuthors(_NameTransformerMiddleware):
|
|
|
62
62
|
"""Middleware to separate multi-person fields (e.g. co-authors, co-editors)."""
|
|
63
63
|
|
|
64
64
|
# docstr-coverage: inherited
|
|
65
|
-
@
|
|
66
|
-
def metadata_key() -> str:
|
|
65
|
+
@classmethod
|
|
66
|
+
def metadata_key(cls) -> str:
|
|
67
67
|
return "separate_coauthors"
|
|
68
68
|
|
|
69
69
|
# docstr-coverage: inherited
|
|
@@ -75,8 +75,8 @@ class MergeCoAuthors(_NameTransformerMiddleware):
|
|
|
75
75
|
"""Middleware to merge multi-person-list fields (e.g. co-authors, co-editors)."""
|
|
76
76
|
|
|
77
77
|
# docstr-coverage: inherited
|
|
78
|
-
@
|
|
79
|
-
def metadata_key() -> str:
|
|
78
|
+
@classmethod
|
|
79
|
+
def metadata_key(cls) -> str:
|
|
80
80
|
return "merge_coauthors"
|
|
81
81
|
|
|
82
82
|
# docstr-coverage: inherited
|
|
@@ -114,6 +114,35 @@ class NameParts:
|
|
|
114
114
|
]
|
|
115
115
|
)
|
|
116
116
|
|
|
117
|
+
@property
|
|
118
|
+
def merge_last_name_first(self) -> str:
|
|
119
|
+
"""Merging the name parts into a single string, last-name-first (with commas) format.
|
|
120
|
+
|
|
121
|
+
The structure of the output is: `von Last, Jr, First`
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def escape_last_slash(string: str) -> str:
|
|
125
|
+
"""Escape the last slash in a string if it is not escaped."""
|
|
126
|
+
# Find the number of trailing slashes
|
|
127
|
+
stripped = string.rstrip("\\")
|
|
128
|
+
num_slashes = len(string) - len(stripped)
|
|
129
|
+
if num_slashes % 2 == 0:
|
|
130
|
+
# Even number: everything is escaped
|
|
131
|
+
return string
|
|
132
|
+
else:
|
|
133
|
+
# Odd number: need to escape one.
|
|
134
|
+
return string + "\\"
|
|
135
|
+
|
|
136
|
+
first = " ".join(self.first) if self.first else None
|
|
137
|
+
von = " ".join(self.von) if self.von else None
|
|
138
|
+
last = " ".join(self.last) if self.last else None
|
|
139
|
+
jr = " ".join(self.jr) if self.jr else None
|
|
140
|
+
|
|
141
|
+
von_last = " ".join(name for name in [von, last] if name)
|
|
142
|
+
return ", ".join(
|
|
143
|
+
escape_last_slash(name) for name in [von_last, jr, first] if name
|
|
144
|
+
)
|
|
145
|
+
|
|
117
146
|
|
|
118
147
|
class SplitNameParts(_NameTransformerMiddleware):
|
|
119
148
|
"""Middleware to split a persons name into its parts (first, von, last, jr).
|
|
@@ -123,8 +152,8 @@ class SplitNameParts(_NameTransformerMiddleware):
|
|
|
123
152
|
"""
|
|
124
153
|
|
|
125
154
|
# docstr-coverage: inherited
|
|
126
|
-
@
|
|
127
|
-
def metadata_key() -> str:
|
|
155
|
+
@classmethod
|
|
156
|
+
def metadata_key(cls) -> str:
|
|
128
157
|
return "split_name_parts"
|
|
129
158
|
|
|
130
159
|
def _transform_field_value(self, name) -> List[NameParts]:
|
|
@@ -142,18 +171,38 @@ class MergeNameParts(_NameTransformerMiddleware):
|
|
|
142
171
|
"""Middleware to merge a persons name parts (first, von, last, jr) into a single string.
|
|
143
172
|
|
|
144
173
|
Name fields (e.g. author, editor, translator) are expected to be lists of NameParts.
|
|
174
|
+
|
|
175
|
+
The merging style indicates whether:
|
|
176
|
+
- the merging is done without commas in first-name-first order ("first"), or
|
|
177
|
+
- the merging is done with commas in last-name-first order ("last").
|
|
145
178
|
"""
|
|
146
179
|
|
|
180
|
+
def __init__(
|
|
181
|
+
self,
|
|
182
|
+
style: Literal["last", "first"] = "last",
|
|
183
|
+
allow_inplace_modification: bool = True,
|
|
184
|
+
name_fields: Tuple[str] = ("author", "editor", "translator"),
|
|
185
|
+
):
|
|
186
|
+
self.style = style
|
|
187
|
+
super().__init__(allow_inplace_modification, name_fields)
|
|
188
|
+
|
|
147
189
|
# docstr-coverage: inherited
|
|
148
|
-
@
|
|
149
|
-
def metadata_key() -> str:
|
|
190
|
+
@classmethod
|
|
191
|
+
def metadata_key(cls) -> str:
|
|
150
192
|
return "merge_name_parts"
|
|
151
193
|
|
|
152
194
|
def _transform_field_value(self, name) -> List[str]:
|
|
153
195
|
if not isinstance(name, list) and all(isinstance(n, NameParts) for n in name):
|
|
154
196
|
raise ValueError("Expected a list of NameParts, got {}. ".format(name))
|
|
155
197
|
|
|
156
|
-
|
|
198
|
+
if self.style == "last":
|
|
199
|
+
return [n.merge_last_name_first for n in name]
|
|
200
|
+
elif self.style == "first":
|
|
201
|
+
return [n.merge_first_name_first for n in name]
|
|
202
|
+
else:
|
|
203
|
+
raise ValueError(
|
|
204
|
+
"""Expected "first" or "last" style, got {}. """.format(self.style)
|
|
205
|
+
)
|
|
157
206
|
|
|
158
207
|
|
|
159
208
|
def parse_single_name_into_parts(name, strict=True):
|
|
@@ -229,33 +278,38 @@ def parse_single_name_into_parts(name, strict=True):
|
|
|
229
278
|
for char in nameiter:
|
|
230
279
|
# An escape.
|
|
231
280
|
if char == "\\":
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
281
|
+
try:
|
|
282
|
+
escaped = next(nameiter)
|
|
283
|
+
|
|
284
|
+
# BibTeX doesn't allow whitespace escaping. Copy the slash and fall
|
|
285
|
+
# through to the normal case to handle the whitespace.
|
|
286
|
+
if escaped in whitespace:
|
|
287
|
+
word.append(char)
|
|
288
|
+
char = escaped
|
|
289
|
+
|
|
290
|
+
else:
|
|
291
|
+
# Is this the first character in a brace?
|
|
292
|
+
if bracestart:
|
|
293
|
+
bracestart = False
|
|
294
|
+
controlseq = escaped.isalpha()
|
|
295
|
+
specialchar = True
|
|
296
|
+
|
|
297
|
+
# Can we use it to determine the case?
|
|
298
|
+
elif (case == -1) and escaped.isalpha():
|
|
299
|
+
if escaped.isupper():
|
|
300
|
+
case = 1
|
|
301
|
+
else:
|
|
302
|
+
case = 0
|
|
303
|
+
|
|
304
|
+
# Copy the escape to the current word and go to the next
|
|
305
|
+
# character in the input.
|
|
306
|
+
word.append(char)
|
|
307
|
+
word.append(escaped)
|
|
308
|
+
continue
|
|
309
|
+
|
|
310
|
+
# If we're at the end of the string, then the \ is just a \.
|
|
311
|
+
except StopIteration:
|
|
237
312
|
word.append(char)
|
|
238
|
-
char = escaped
|
|
239
|
-
|
|
240
|
-
else:
|
|
241
|
-
# Is this the first character in a brace?
|
|
242
|
-
if bracestart:
|
|
243
|
-
bracestart = False
|
|
244
|
-
controlseq = escaped.isalpha()
|
|
245
|
-
specialchar = True
|
|
246
|
-
|
|
247
|
-
# Can we use it to determine the case?
|
|
248
|
-
elif (case == -1) and escaped.isalpha():
|
|
249
|
-
if escaped.isupper():
|
|
250
|
-
case = 1
|
|
251
|
-
else:
|
|
252
|
-
case = 0
|
|
253
|
-
|
|
254
|
-
# Copy the escape to the current word and go to the next
|
|
255
|
-
# character in the input.
|
|
256
|
-
word.append(char)
|
|
257
|
-
word.append(escaped)
|
|
258
|
-
continue
|
|
259
313
|
|
|
260
314
|
# Start of a braced expression.
|
|
261
315
|
if char == "{":
|
|
@@ -510,12 +564,19 @@ def split_multiple_persons_names(names):
|
|
|
510
564
|
|
|
511
565
|
# Escaped character.
|
|
512
566
|
if char == "\\":
|
|
513
|
-
|
|
567
|
+
try:
|
|
568
|
+
next(namesiter)
|
|
569
|
+
# If we're at the end of the string, then the \ is just a \.
|
|
570
|
+
except StopIteration:
|
|
571
|
+
pass
|
|
514
572
|
pos += 1
|
|
515
573
|
continue
|
|
516
574
|
|
|
517
575
|
# Change in brace level.
|
|
518
576
|
if char == "{":
|
|
577
|
+
if step == NEXT_WORD:
|
|
578
|
+
spans[-1].append(possible_end)
|
|
579
|
+
spans.append([pos - 1])
|
|
519
580
|
bracelevel += 1
|
|
520
581
|
step = START_WHITESPACE
|
|
521
582
|
continue
|
{bibtexparser-2.0.0b5 → bibtexparser-2.0.0b7}/bibtexparser/middlewares/sorting_entry_fields.py
RENAMED
|
@@ -22,8 +22,8 @@ class SortFieldsAlphabeticallyMiddleware(BlockMiddleware):
|
|
|
22
22
|
return entry
|
|
23
23
|
|
|
24
24
|
# docstr-coverage: inherited
|
|
25
|
-
@
|
|
26
|
-
def metadata_key() -> str:
|
|
25
|
+
@classmethod
|
|
26
|
+
def metadata_key(cls) -> str:
|
|
27
27
|
return "sorted_fields_alphabetically"
|
|
28
28
|
|
|
29
29
|
|
|
@@ -71,6 +71,6 @@ class SortFieldsCustomMiddleware(BlockMiddleware):
|
|
|
71
71
|
return entry
|
|
72
72
|
|
|
73
73
|
# docstr-coverage: inherited
|
|
74
|
-
@
|
|
75
|
-
def metadata_key() -> str:
|
|
74
|
+
@classmethod
|
|
75
|
+
def metadata_key(cls) -> str:
|
|
76
76
|
return "sorted_fields_custom"
|
|
@@ -5,7 +5,8 @@ from typing import Any, Dict, List, Optional, Set
|
|
|
5
5
|
class Block(abc.ABC):
|
|
6
6
|
"""A abstract superclass of all top-level building blocks of a bibtex file.
|
|
7
7
|
|
|
8
|
-
E.g. a
|
|
8
|
+
E.g. a ``@string`` block, a ``@preamble`` block, an ``@entry`` block, a comment, etc.
|
|
9
|
+
"""
|
|
9
10
|
|
|
10
11
|
def __init__(
|
|
11
12
|
self,
|
|
@@ -51,15 +52,15 @@ class Block(abc.ABC):
|
|
|
51
52
|
return self._parser_metadata
|
|
52
53
|
|
|
53
54
|
def get_parser_metadata(self, key: str) -> Optional[Any]:
|
|
54
|
-
"""EXPERIMENTAL: get auxiliary information stored in
|
|
55
|
+
"""EXPERIMENTAL: get auxiliary information stored in ``parser_metadata``.
|
|
55
56
|
|
|
56
|
-
See attribute
|
|
57
|
+
See attribute ``parser_metadata`` for more information."""
|
|
57
58
|
return self._parser_metadata.get(key, None)
|
|
58
59
|
|
|
59
60
|
def set_parser_metadata(self, key: str, value: Any):
|
|
60
|
-
"""EXPERIMENTAL: set auxiliary information stored in
|
|
61
|
+
"""EXPERIMENTAL: set auxiliary information stored in ``parser_metadata``.
|
|
61
62
|
|
|
62
|
-
See attribute
|
|
63
|
+
See attribute ``parser_metadata`` for more information."""
|
|
63
64
|
self._parser_metadata[key] = value
|
|
64
65
|
|
|
65
66
|
def __eq__(self, other):
|
|
@@ -72,7 +73,7 @@ class Block(abc.ABC):
|
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
class String(Block):
|
|
75
|
-
"""Bibtex Blocks of the
|
|
76
|
+
"""Bibtex Blocks of the ``@string`` type, e.g. ``@string{me = "My Name"}``."""
|
|
76
77
|
|
|
77
78
|
def __init__(
|
|
78
79
|
self,
|
|
@@ -87,7 +88,7 @@ class String(Block):
|
|
|
87
88
|
|
|
88
89
|
@property
|
|
89
90
|
def key(self) -> str:
|
|
90
|
-
"""The key of the string, e.g.
|
|
91
|
+
"""The key of the string, e.g. ``me`` in ``@string{me = "My Name"}``."""
|
|
91
92
|
return self._key
|
|
92
93
|
|
|
93
94
|
@key.setter
|
|
@@ -96,7 +97,7 @@ class String(Block):
|
|
|
96
97
|
|
|
97
98
|
@property
|
|
98
99
|
def value(self) -> str:
|
|
99
|
-
"""The value of the string, e.g.
|
|
100
|
+
"""The value of the string, e.g. ``"My Name"`` in ``@string{me = "My Name"}``."""
|
|
100
101
|
return self._value
|
|
101
102
|
|
|
102
103
|
@value.setter
|
|
@@ -114,7 +115,7 @@ class String(Block):
|
|
|
114
115
|
|
|
115
116
|
|
|
116
117
|
class Preamble(Block):
|
|
117
|
-
"""Bibtex Blocks of the
|
|
118
|
+
"""Bibtex Blocks of the ``@preamble`` type, e.g. ``@preamble{This is a preamble}``."""
|
|
118
119
|
|
|
119
120
|
def __init__(
|
|
120
121
|
self, value: str, start_line: Optional[int] = None, raw: Optional[str] = None
|
|
@@ -124,7 +125,7 @@ class Preamble(Block):
|
|
|
124
125
|
|
|
125
126
|
@property
|
|
126
127
|
def value(self) -> str:
|
|
127
|
-
"""The value of the preamble, e.g.
|
|
128
|
+
"""The value of the preamble, e.g. ``blabla`` in ``@preamble{blabla}``."""
|
|
128
129
|
return self._value
|
|
129
130
|
|
|
130
131
|
@value.setter
|
|
@@ -142,7 +143,7 @@ class Preamble(Block):
|
|
|
142
143
|
|
|
143
144
|
|
|
144
145
|
class ExplicitComment(Block):
|
|
145
|
-
"""Bibtex Blocks of the
|
|
146
|
+
"""Bibtex Blocks of the ``@comment`` type, e.g. ``@comment{This is a comment}``."""
|
|
146
147
|
|
|
147
148
|
def __init__(
|
|
148
149
|
self, comment: str, start_line: Optional[int] = None, raw: Optional[str] = None
|
|
@@ -152,7 +153,7 @@ class ExplicitComment(Block):
|
|
|
152
153
|
|
|
153
154
|
@property
|
|
154
155
|
def comment(self) -> str:
|
|
155
|
-
"""The value of the comment, e.g.
|
|
156
|
+
"""The value of the comment, e.g. ``blabla`` in ``@comment{blabla}``."""
|
|
156
157
|
return self._comment
|
|
157
158
|
|
|
158
159
|
@comment.setter
|
|
@@ -170,7 +171,7 @@ class ExplicitComment(Block):
|
|
|
170
171
|
|
|
171
172
|
|
|
172
173
|
class ImplicitComment(Block):
|
|
173
|
-
"""Bibtex outside of an
|
|
174
|
+
"""Bibtex outside of an ``@{...}`` block, which is treated as a comment."""
|
|
174
175
|
|
|
175
176
|
def __init__(
|
|
176
177
|
self, comment: str, start_line: Optional[int] = None, raw: Optional[str] = None
|
|
@@ -198,7 +199,7 @@ class ImplicitComment(Block):
|
|
|
198
199
|
|
|
199
200
|
|
|
200
201
|
class Field:
|
|
201
|
-
"""A field of a Bibtex entry, e.g.
|
|
202
|
+
"""A field of a Bibtex entry, e.g. ``author = {John Doe}``."""
|
|
202
203
|
|
|
203
204
|
def __init__(self, key: str, value: Any, start_line: Optional[int] = None):
|
|
204
205
|
self._start_line = start_line
|
|
@@ -207,7 +208,7 @@ class Field:
|
|
|
207
208
|
|
|
208
209
|
@property
|
|
209
210
|
def key(self) -> str:
|
|
210
|
-
"""The key of the field, e.g.
|
|
211
|
+
"""The key of the field, e.g. ``author`` in ``author = {John Doe}``."""
|
|
211
212
|
return self._key
|
|
212
213
|
|
|
213
214
|
@key.setter
|
|
@@ -216,7 +217,7 @@ class Field:
|
|
|
216
217
|
|
|
217
218
|
@property
|
|
218
219
|
def value(self) -> Any:
|
|
219
|
-
"""The value of the field, e.g.
|
|
220
|
+
"""The value of the field, e.g. ``{John Doe}`` in ``author = {John Doe}``."""
|
|
220
221
|
return self._value
|
|
221
222
|
|
|
222
223
|
@value.setter
|
|
@@ -247,7 +248,7 @@ class Field:
|
|
|
247
248
|
|
|
248
249
|
|
|
249
250
|
class Entry(Block):
|
|
250
|
-
"""Bibtex Blocks of the
|
|
251
|
+
"""Bibtex Blocks of the ``@entry`` type, e.g. ``@article{Cesar2013, ...}``."""
|
|
251
252
|
|
|
252
253
|
def __init__(
|
|
253
254
|
self,
|
|
@@ -264,7 +265,7 @@ class Entry(Block):
|
|
|
264
265
|
|
|
265
266
|
@property
|
|
266
267
|
def entry_type(self):
|
|
267
|
-
"""The type of the entry, e.g.
|
|
268
|
+
"""The type of the entry, e.g. ``article`` in ``@article{Cesar2013, ...}``."""
|
|
268
269
|
return self._entry_type
|
|
269
270
|
|
|
270
271
|
@entry_type.setter
|
|
@@ -273,7 +274,7 @@ class Entry(Block):
|
|
|
273
274
|
|
|
274
275
|
@property
|
|
275
276
|
def key(self):
|
|
276
|
-
"""The key of the entry, e.g.
|
|
277
|
+
"""The key of the entry, e.g. ``Cesar2013`` in ``@article{Cesar2013, ...}``."""
|
|
277
278
|
return self._key
|
|
278
279
|
|
|
279
280
|
@key.setter
|
|
@@ -282,7 +283,7 @@ class Entry(Block):
|
|
|
282
283
|
|
|
283
284
|
@property
|
|
284
285
|
def fields(self) -> List[Field]:
|
|
285
|
-
"""The key-value attributes of an entry, as
|
|
286
|
+
"""The key-value attributes of an entry, as ``Field`` instances."""
|
|
286
287
|
return self._fields
|
|
287
288
|
|
|
288
289
|
@fields.setter
|
|
@@ -304,6 +305,30 @@ class Entry(Block):
|
|
|
304
305
|
else:
|
|
305
306
|
self._fields.append(field)
|
|
306
307
|
|
|
308
|
+
def pop(self, key: str, default=None) -> Optional[Field]:
|
|
309
|
+
"""Removes and returns the field with the given key.
|
|
310
|
+
|
|
311
|
+
:param key: The key of the field to remove.
|
|
312
|
+
:param default: The value to return if the field does not exist."""
|
|
313
|
+
try:
|
|
314
|
+
field = self.fields_dict.pop(key)
|
|
315
|
+
except KeyError:
|
|
316
|
+
return default
|
|
317
|
+
|
|
318
|
+
self._fields = [f for f in self._fields if f.key != key]
|
|
319
|
+
return field
|
|
320
|
+
|
|
321
|
+
def get(self, key: str, default=None) -> Optional[Field]:
|
|
322
|
+
"""Returns the field with the given key, or the default value if it does not exist.
|
|
323
|
+
|
|
324
|
+
:param key: The key of the field.
|
|
325
|
+
:param default: The value to return if the field does not exist."""
|
|
326
|
+
return self.fields_dict.get(key, default)
|
|
327
|
+
|
|
328
|
+
def __contains__(self, key: str) -> bool:
|
|
329
|
+
"""Dict-mimicking ``in`` operator."""
|
|
330
|
+
return key in self.fields_dict
|
|
331
|
+
|
|
307
332
|
def __getitem__(self, key: str) -> Any:
|
|
308
333
|
"""Dict-mimicking index.
|
|
309
334
|
|
|
@@ -318,6 +343,22 @@ class Entry(Block):
|
|
|
318
343
|
return self.key
|
|
319
344
|
return self.fields_dict[key].value
|
|
320
345
|
|
|
346
|
+
def __setitem__(self, key: str, value: Any):
|
|
347
|
+
"""Dict-mimicking index.
|
|
348
|
+
|
|
349
|
+
This serves for partial v1.x backwards compatibility,
|
|
350
|
+
as well as for a shorthand for `set_field`.
|
|
351
|
+
"""
|
|
352
|
+
self.set_field(Field(key, value))
|
|
353
|
+
|
|
354
|
+
def __delitem__(self, key):
|
|
355
|
+
"""Dict-mimicking index.
|
|
356
|
+
|
|
357
|
+
This serves for partial v1.x backwards compatibility,
|
|
358
|
+
as well as for a shorthand for `pop`.
|
|
359
|
+
"""
|
|
360
|
+
self.pop(key)
|
|
361
|
+
|
|
321
362
|
def items(self):
|
|
322
363
|
"""Dict-mimicking, for partial v1.x backwards compatibility.
|
|
323
364
|
|
|
@@ -410,7 +451,7 @@ class DuplicateBlockKeyBlock(ParsingFailedBlock):
|
|
|
410
451
|
|
|
411
452
|
@property
|
|
412
453
|
def key(self) -> str:
|
|
413
|
-
"""The key of the entry, e.g.
|
|
454
|
+
"""The key of the entry, e.g. ``Cesar2013`` in ``@article{Cesar2013, ...}``."""
|
|
414
455
|
return self._key
|
|
415
456
|
|
|
416
457
|
@key.setter
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bibtexparser
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0b7
|
|
4
4
|
Summary: Bibtex parser for python 3
|
|
5
5
|
Home-page: https://github.com/sciunto-org/python-bibtexparser
|
|
6
6
|
Author: Michael Weiss and other contributors
|
|
@@ -120,7 +120,7 @@ Writes the content of a bibtex library to a `.bib` file. Optional formatting par
|
|
|
120
120
|
|
|
121
121
|
## About
|
|
122
122
|
|
|
123
|
-
Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)).
|
|
123
|
+
Since 2022, `bibtexparser` is primarily written and maintained by Michael Weiss ([@MiWeiss](https://github.com/MiWeiss/)). In 2024, Tom de Geus ([@tdegeus](https://github.com/tdegeus)) joined as co-maintainer.
|
|
124
124
|
|
|
125
125
|
Credits and thanks to the many contributors who helped creating this library, including
|
|
126
126
|
François Boulogne ([@sciunto](https://github.com/sciunto/), creator of the first version) and Olivier Mangin ([@omangin](https://github.com/omangin/), long-term contributor).
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from copy import copy, deepcopy
|
|
2
2
|
from textwrap import dedent
|
|
3
3
|
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
4
6
|
from bibtexparser.model import (
|
|
5
7
|
Entry,
|
|
6
8
|
ExplicitComment,
|
|
@@ -75,6 +77,34 @@ def test_entry_deepcopy():
|
|
|
75
77
|
assert entry_1.fields_dict["field"] == entry_2.fields_dict["field"]
|
|
76
78
|
|
|
77
79
|
|
|
80
|
+
def test_entry_get():
|
|
81
|
+
entry1 = Entry(
|
|
82
|
+
"article", "key", [Field("field", "value", 1), Field("foo", "bar", 2)], 1, "raw"
|
|
83
|
+
)
|
|
84
|
+
entry2 = Entry(
|
|
85
|
+
"article", "key", [Field("field", "value", 1), Field("foo", "bar", 2)], 1, "raw"
|
|
86
|
+
)
|
|
87
|
+
assert entry1.get("other", "default") == "default"
|
|
88
|
+
assert entry1.get("foo") == Field("foo", "bar", 2)
|
|
89
|
+
assert entry1 == entry2
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_entry_pop():
|
|
93
|
+
entry1 = Entry(
|
|
94
|
+
"article", "key", [Field("field", "value", 1), Field("foo", "bar", 2)], 1, "raw"
|
|
95
|
+
)
|
|
96
|
+
entry2 = Entry("article", "key", [Field("field", "value", 1)], 1, "raw")
|
|
97
|
+
assert entry1.pop("other", "default") == "default"
|
|
98
|
+
assert entry1.pop("foo") == Field("foo", "bar", 2)
|
|
99
|
+
assert entry1 == entry2
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_entry_contains():
|
|
103
|
+
entry = Entry("article", "key", [Field("field", "value", 1)], 1, "raw")
|
|
104
|
+
assert "field" in entry
|
|
105
|
+
assert "other" not in entry
|
|
106
|
+
|
|
107
|
+
|
|
78
108
|
def test_string_equality():
|
|
79
109
|
# Equal to itself
|
|
80
110
|
string_1 = String(
|
|
@@ -290,3 +320,30 @@ def test_entry_str():
|
|
|
290
320
|
)
|
|
291
321
|
|
|
292
322
|
assert str(entry) == expected
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def test_entry_fields_shorthand():
|
|
326
|
+
entry = Entry(
|
|
327
|
+
entry_type="article",
|
|
328
|
+
key="myEntry",
|
|
329
|
+
fields=[
|
|
330
|
+
Field("myFirstField", "firstValue"),
|
|
331
|
+
Field("mySecondField", "secondValue"),
|
|
332
|
+
],
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
entry["myFirstField"] = "changed_value"
|
|
336
|
+
assert entry["myFirstField"] == "changed_value"
|
|
337
|
+
assert entry.fields_dict["myFirstField"].value == "changed_value"
|
|
338
|
+
|
|
339
|
+
entry["myNewField"] = "new_value"
|
|
340
|
+
assert entry["myNewField"] == "new_value"
|
|
341
|
+
assert entry.fields_dict["myNewField"].key == "myNewField"
|
|
342
|
+
assert entry.fields_dict["myNewField"].value == "new_value"
|
|
343
|
+
assert entry.fields_dict["myNewField"].start_line is None
|
|
344
|
+
|
|
345
|
+
del entry["myNewField"]
|
|
346
|
+
assert "myNewField" not in entry.fields_dict
|
|
347
|
+
assert len([f for f in entry.fields if f.key == "myNewField"]) == 0
|
|
348
|
+
with pytest.raises(KeyError):
|
|
349
|
+
entry["myNewField"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|