goldrush 0.0.1__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.
- goldrush-0.0.1/LICENSE +25 -0
- goldrush-0.0.1/PKG-INFO +67 -0
- goldrush-0.0.1/README.md +31 -0
- goldrush-0.0.1/pyproject.toml +26 -0
- goldrush-0.0.1/src/goldrush/__init__.py +219 -0
- goldrush-0.0.1/src/goldrush/py.typed +0 -0
- goldrush-0.0.1/src/goldrush/record.py +292 -0
goldrush-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Redistribution and use in source and binary forms, with or without
|
|
2
|
+
modification, are permitted provided that the following conditions are met:
|
|
3
|
+
|
|
4
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
5
|
+
list of conditions and the following disclaimer.
|
|
6
|
+
|
|
7
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
8
|
+
this list of conditions and the following disclaimer in the documentation
|
|
9
|
+
and/or other materials provided with the distribution.
|
|
10
|
+
|
|
11
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
12
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
13
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
14
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
15
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
16
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
17
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
18
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
19
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
20
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
21
|
+
|
|
22
|
+
Copyright for this project is held by its many contributors, including:
|
|
23
|
+
|
|
24
|
+
Jane Sandberg <js7389@princeton.edu>
|
|
25
|
+
Ed Summers <ehs@pobox.com>
|
goldrush-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: goldrush
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Generate Gold Rush key for pymarc records
|
|
5
|
+
Author: Jane Sandberg, Ed Summers
|
|
6
|
+
Author-email: Jane Sandberg <js7389@princeton.edu>, Ed Summers <ehs@pobox.com>
|
|
7
|
+
License: Redistribution and use in source and binary forms, with or without
|
|
8
|
+
modification, are permitted provided that the following conditions are met:
|
|
9
|
+
|
|
10
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
11
|
+
list of conditions and the following disclaimer.
|
|
12
|
+
|
|
13
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
14
|
+
this list of conditions and the following disclaimer in the documentation
|
|
15
|
+
and/or other materials provided with the distribution.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
18
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
19
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
20
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
21
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
22
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
23
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
24
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
25
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
26
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
27
|
+
|
|
28
|
+
Copyright for this project is held by its many contributors, including:
|
|
29
|
+
|
|
30
|
+
Jane Sandberg <js7389@princeton.edu>
|
|
31
|
+
Ed Summers <ehs@pobox.com>
|
|
32
|
+
Requires-Dist: pymarc>=5.3.1
|
|
33
|
+
Requires-Dist: unidecode>=1.4.0
|
|
34
|
+
Requires-Python: >=3.13
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# goldrush
|
|
38
|
+
|
|
39
|
+
**Note: This is alpha software. If you rely on this library you should do so with the
|
|
40
|
+
understanding that you might find errors in the Gold Rush key that is generated.
|
|
41
|
+
Lets make it better together!**
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
[](https://gitlab.com/pymarc/goldrush/-/commits/main)
|
|
46
|
+
|
|
47
|
+
*goldrush* is a Python implementation of the [Gold Rush match key algorithm]
|
|
48
|
+
for identifying "duplicate" MARC records, or records that appear to be about
|
|
49
|
+
the same book. It provides a function that you pass a pymarc Record and get
|
|
50
|
+
back the Gold Rush key as a string.
|
|
51
|
+
|
|
52
|
+
goldrush was largely adapted from [pymarc_dedupe], which included other
|
|
53
|
+
functionality unrelated to Gold Rush, and was not available on PyPI as a
|
|
54
|
+
dependency.
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
>>> from goldrush import goldrush
|
|
60
|
+
>>> from pymarc import MARCReader
|
|
61
|
+
>>> record = next(MARCReader(open('marc.dat', 'rb')))
|
|
62
|
+
>>> goldrush(record)
|
|
63
|
+
'pragmaticprogrammerfromjourneymantomaster_____________________________2000321____addisa_______________________________________________________hunt________________p'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
[Gold Rush match key algorithm]: https://coalliance.org/sites/default/files/GoldRush-Match_KeyJanuary2024_0.doc
|
|
67
|
+
[pymarc_dedupe]: https://github.com/pulibrary/pymarc_dedupe
|
goldrush-0.0.1/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# goldrush
|
|
2
|
+
|
|
3
|
+
**Note: This is alpha software. If you rely on this library you should do so with the
|
|
4
|
+
understanding that you might find errors in the Gold Rush key that is generated.
|
|
5
|
+
Lets make it better together!**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
[](https://gitlab.com/pymarc/goldrush/-/commits/main)
|
|
10
|
+
|
|
11
|
+
*goldrush* is a Python implementation of the [Gold Rush match key algorithm]
|
|
12
|
+
for identifying "duplicate" MARC records, or records that appear to be about
|
|
13
|
+
the same book. It provides a function that you pass a pymarc Record and get
|
|
14
|
+
back the Gold Rush key as a string.
|
|
15
|
+
|
|
16
|
+
goldrush was largely adapted from [pymarc_dedupe], which included other
|
|
17
|
+
functionality unrelated to Gold Rush, and was not available on PyPI as a
|
|
18
|
+
dependency.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
>>> from goldrush import goldrush
|
|
24
|
+
>>> from pymarc import MARCReader
|
|
25
|
+
>>> record = next(MARCReader(open('marc.dat', 'rb')))
|
|
26
|
+
>>> goldrush(record)
|
|
27
|
+
'pragmaticprogrammerfromjourneymantomaster_____________________________2000321____addisa_______________________________________________________hunt________________p'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
[Gold Rush match key algorithm]: https://coalliance.org/sites/default/files/GoldRush-Match_KeyJanuary2024_0.doc
|
|
31
|
+
[pymarc_dedupe]: https://github.com/pulibrary/pymarc_dedupe
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "goldrush"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Generate Gold Rush key for pymarc records"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = {file = "LICENSE"}
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Jane Sandberg", email = "js7389@princeton.edu" },
|
|
9
|
+
{ name = "Ed Summers", email = "ehs@pobox.com" }
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.13"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"pymarc>=5.3.1",
|
|
14
|
+
"unidecode>=1.4.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["uv_build>=0.11.6,<0.12.0"]
|
|
19
|
+
build-backend = "uv_build"
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=9.0.3",
|
|
24
|
+
"ruff>=0.15.11",
|
|
25
|
+
"ty>=0.0.31",
|
|
26
|
+
]
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from functools import cache
|
|
3
|
+
from unidecode import unidecode
|
|
4
|
+
|
|
5
|
+
from pymarc import Record
|
|
6
|
+
|
|
7
|
+
from goldrush.record import GoldrushRecord
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def goldrush(pymarc_rec: Record) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Pass in a pymarc.Record object and get back a Gold Rush key as a string.
|
|
13
|
+
"""
|
|
14
|
+
rec = GoldrushRecord(pymarc_rec)
|
|
15
|
+
return (
|
|
16
|
+
_title(rec)
|
|
17
|
+
+ (_publication_year(rec))
|
|
18
|
+
+ _pagination(rec)
|
|
19
|
+
+ _edition(rec)
|
|
20
|
+
+ _publisher_name(rec)
|
|
21
|
+
+ rec.type_of()
|
|
22
|
+
+ _title_part(rec)
|
|
23
|
+
+ _title_number(rec)
|
|
24
|
+
+ _title_inclusive_dates(rec)
|
|
25
|
+
+ _author(rec)
|
|
26
|
+
+ _gov_doc_number(rec)
|
|
27
|
+
+ _format_character(rec)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _title(rec: GoldrushRecord) -> str:
|
|
32
|
+
some_string = _normalize_title(rec.title())
|
|
33
|
+
return _pad(some_string, 70)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _title_part(rec: GoldrushRecord) -> str:
|
|
37
|
+
some_string = _normalize_title(rec.title_part())
|
|
38
|
+
return _pad(some_string, 30)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _publication_year(rec: GoldrushRecord) -> str:
|
|
42
|
+
some_string = str(rec.publication_year() or "")
|
|
43
|
+
return _pad(some_string, 4)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _pagination(rec: GoldrushRecord) -> str:
|
|
47
|
+
try:
|
|
48
|
+
nums = re.findall(r"\d+", rec.pagination())[0]
|
|
49
|
+
except (TypeError, IndexError):
|
|
50
|
+
nums = ""
|
|
51
|
+
return _pad(nums, 4)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _edition(rec: GoldrushRecord) -> str:
|
|
55
|
+
lowercase_ed = ""
|
|
56
|
+
try:
|
|
57
|
+
lowercase_ed = rec.edition().lower()
|
|
58
|
+
chars = re.findall(r"\d+", lowercase_ed)[0]
|
|
59
|
+
except (TypeError, IndexError):
|
|
60
|
+
try:
|
|
61
|
+
chars = re.findall(r"\w+", lowercase_ed)[0]
|
|
62
|
+
chars = chars[0:3]
|
|
63
|
+
for ordinal, numeral in _edition_dictionary().items():
|
|
64
|
+
chars = chars.replace(ordinal, numeral)
|
|
65
|
+
except (TypeError, IndexError):
|
|
66
|
+
chars = ""
|
|
67
|
+
return unidecode(_pad(chars, 3))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _publisher_name(rec: GoldrushRecord) -> str:
|
|
71
|
+
publisher_name = rec.publisher_name().lower()
|
|
72
|
+
publisher_name = publisher_name.translate(
|
|
73
|
+
str.maketrans(_title_translation_dictionary())
|
|
74
|
+
)
|
|
75
|
+
return _pad(publisher_name, 5)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _title_number(rec: GoldrushRecord) -> str:
|
|
79
|
+
title_number = rec.title_number() or ""
|
|
80
|
+
return _pad(title_number, 10)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _title_inclusive_dates(rec: GoldrushRecord) -> str:
|
|
84
|
+
title_dates = rec.title_inclusive_dates() or ""
|
|
85
|
+
title_dates = _strip_punctuation(title_dates)
|
|
86
|
+
return _pad(title_dates, 15)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _author(rec: GoldrushRecord) -> str:
|
|
90
|
+
author = rec.author() or ""
|
|
91
|
+
author = _strip_punctuation(author)
|
|
92
|
+
return _pad(author, 5)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _gov_doc_number(rec: GoldrushRecord) -> str:
|
|
96
|
+
gov_doc_number = rec.gov_doc_number() or ""
|
|
97
|
+
gov_doc_number = _strip_punctuation(gov_doc_number)
|
|
98
|
+
return _pad(gov_doc_number, 15)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _format_character(rec: GoldrushRecord) -> str:
|
|
102
|
+
if rec.is_electronic_resource():
|
|
103
|
+
return "e"
|
|
104
|
+
return "p"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _normalize_title(some_string: str) -> str:
|
|
108
|
+
some_string = some_string.strip().lower()
|
|
109
|
+
# remove beginning English articles
|
|
110
|
+
some_string = re.sub("^the +", "", some_string)
|
|
111
|
+
some_string = re.sub("^a +", "", some_string)
|
|
112
|
+
some_string = re.sub("^an +", "", some_string)
|
|
113
|
+
some_string = some_string.translate(str.maketrans(_title_translation_dictionary()))
|
|
114
|
+
return unidecode(some_string)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _pad(string: str, length: int) -> str:
|
|
118
|
+
return string.ljust(length, "_")[0:length]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _strip_punctuation(some_string: str) -> str:
|
|
122
|
+
some_string = re.sub(" ", " ", some_string).strip().lower()
|
|
123
|
+
some_string = re.sub("^the +", "", some_string)
|
|
124
|
+
some_string = some_string.translate(str.maketrans(_translation_dictionary()))
|
|
125
|
+
return unidecode(some_string)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@cache
|
|
129
|
+
def _edition_dictionary() -> dict[str, str]:
|
|
130
|
+
return {
|
|
131
|
+
"fir": "1__",
|
|
132
|
+
"sec": "2__",
|
|
133
|
+
"thr": "3__",
|
|
134
|
+
"fou": "4__",
|
|
135
|
+
"fiv": "5__",
|
|
136
|
+
"six": "6__",
|
|
137
|
+
"sev": "7__",
|
|
138
|
+
"eig": "8__",
|
|
139
|
+
"nin": "9__",
|
|
140
|
+
"ten": "10_",
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@cache
|
|
145
|
+
def _title_translation_dictionary() -> dict[str, str]:
|
|
146
|
+
return {
|
|
147
|
+
"&": "and",
|
|
148
|
+
"%": "",
|
|
149
|
+
"'": "",
|
|
150
|
+
"{": "",
|
|
151
|
+
"}": "",
|
|
152
|
+
" ": "",
|
|
153
|
+
"!": "",
|
|
154
|
+
'"': "",
|
|
155
|
+
"#": "",
|
|
156
|
+
"$": "",
|
|
157
|
+
"(": "",
|
|
158
|
+
")": "",
|
|
159
|
+
"*": "",
|
|
160
|
+
"+": "",
|
|
161
|
+
",": "",
|
|
162
|
+
"-": "",
|
|
163
|
+
".": "",
|
|
164
|
+
"/": "",
|
|
165
|
+
":": "",
|
|
166
|
+
";": "",
|
|
167
|
+
"<": "",
|
|
168
|
+
"=": "",
|
|
169
|
+
">": "",
|
|
170
|
+
"?": "",
|
|
171
|
+
"@": "",
|
|
172
|
+
"[": "",
|
|
173
|
+
"\\": "",
|
|
174
|
+
"]": "",
|
|
175
|
+
"^": "",
|
|
176
|
+
"`": "",
|
|
177
|
+
"|": "",
|
|
178
|
+
"~": "",
|
|
179
|
+
"©": "",
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@cache
|
|
184
|
+
def _translation_dictionary() -> dict[str, str]:
|
|
185
|
+
return {
|
|
186
|
+
"&": "and",
|
|
187
|
+
"%": "_",
|
|
188
|
+
"'": "",
|
|
189
|
+
"{": "",
|
|
190
|
+
"}": "",
|
|
191
|
+
" ": "_",
|
|
192
|
+
"!": "_",
|
|
193
|
+
'"': "_",
|
|
194
|
+
"#": "_",
|
|
195
|
+
"$": "_",
|
|
196
|
+
"(": "_",
|
|
197
|
+
")": "_",
|
|
198
|
+
"*": "_",
|
|
199
|
+
"+": "_",
|
|
200
|
+
",": "_",
|
|
201
|
+
"-": "_",
|
|
202
|
+
".": "_",
|
|
203
|
+
"/": "_",
|
|
204
|
+
":": "_",
|
|
205
|
+
";": "_",
|
|
206
|
+
"<": "_",
|
|
207
|
+
"=": "_",
|
|
208
|
+
">": "_",
|
|
209
|
+
"?": "_",
|
|
210
|
+
"@": "_",
|
|
211
|
+
"[": "_",
|
|
212
|
+
"\\": "_",
|
|
213
|
+
"]": "_",
|
|
214
|
+
"^": "_",
|
|
215
|
+
"`": "_",
|
|
216
|
+
"|": "_",
|
|
217
|
+
"~": "_",
|
|
218
|
+
"©": "_",
|
|
219
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import string
|
|
3
|
+
|
|
4
|
+
import pymarc
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GoldrushRecord:
|
|
8
|
+
def __init__(self, record):
|
|
9
|
+
self.record = record
|
|
10
|
+
|
|
11
|
+
def id(self):
|
|
12
|
+
try:
|
|
13
|
+
return self.record.get("001").data
|
|
14
|
+
except (KeyError, AttributeError):
|
|
15
|
+
return ""
|
|
16
|
+
|
|
17
|
+
def title(self):
|
|
18
|
+
if self.__vernacular_title_field():
|
|
19
|
+
title_field = self.__vernacular_title_field()
|
|
20
|
+
else:
|
|
21
|
+
title_field = self.__title_from_245()
|
|
22
|
+
try:
|
|
23
|
+
subfield_a = str(title_field.get("a") or "")
|
|
24
|
+
subfield_b = str(title_field.get("b") or "")
|
|
25
|
+
subfield_p = str(title_field.get("p") or "")
|
|
26
|
+
title = " ".join([subfield_a, subfield_b, subfield_p])
|
|
27
|
+
title = self.__strip_ending_punctuation(title)
|
|
28
|
+
return title
|
|
29
|
+
except (KeyError, AttributeError):
|
|
30
|
+
return ""
|
|
31
|
+
|
|
32
|
+
def transliterated_title(self):
|
|
33
|
+
title_field = self.__title_from_245()
|
|
34
|
+
try:
|
|
35
|
+
subfield_a = str(title_field.get("a") or "")
|
|
36
|
+
subfield_b = str(title_field.get("b") or "")
|
|
37
|
+
subfield_p = str(title_field.get("p") or "")
|
|
38
|
+
title = " ".join([subfield_a, subfield_b, subfield_p])
|
|
39
|
+
title = self.__strip_ending_punctuation(title)
|
|
40
|
+
return title
|
|
41
|
+
except (KeyError, AttributeError):
|
|
42
|
+
return ""
|
|
43
|
+
|
|
44
|
+
def __title_from_245(self):
|
|
45
|
+
try:
|
|
46
|
+
title = self.record["245"]
|
|
47
|
+
return title
|
|
48
|
+
except KeyError:
|
|
49
|
+
return ""
|
|
50
|
+
|
|
51
|
+
def __vernacular_title_field(self):
|
|
52
|
+
try:
|
|
53
|
+
return self.record.get_linked_fields(self.record["245"])[0]
|
|
54
|
+
except (KeyError, IndexError, pymarc.exceptions.MissingLinkedFields):
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
def publication_year(self):
|
|
58
|
+
pub_year = None
|
|
59
|
+
if self.date_one() and self.date_two():
|
|
60
|
+
pub_year = self.date_two()
|
|
61
|
+
elif self.date_one() and not self.date_two():
|
|
62
|
+
pub_year = self.date_one()
|
|
63
|
+
elif (
|
|
64
|
+
not self.date_one() and not self.date_two() and self.__date_of_production()
|
|
65
|
+
):
|
|
66
|
+
pub_year = self.__date_of_production()
|
|
67
|
+
elif (
|
|
68
|
+
not self.date_one()
|
|
69
|
+
and not self.date_two()
|
|
70
|
+
and not self.__date_of_production()
|
|
71
|
+
and self.__date_of_publication()
|
|
72
|
+
):
|
|
73
|
+
pub_year = self.__date_of_publication()
|
|
74
|
+
return pub_year
|
|
75
|
+
|
|
76
|
+
def pagination(self):
|
|
77
|
+
try:
|
|
78
|
+
subfield_a = self.record["300"].get("a")
|
|
79
|
+
if subfield_a:
|
|
80
|
+
return self.__normalize_extent(subfield_a)
|
|
81
|
+
return ""
|
|
82
|
+
except KeyError:
|
|
83
|
+
return ""
|
|
84
|
+
|
|
85
|
+
def edition(self):
|
|
86
|
+
try:
|
|
87
|
+
return self.__normalize_edition(self.record["250"].get("a"))
|
|
88
|
+
except KeyError:
|
|
89
|
+
return ""
|
|
90
|
+
|
|
91
|
+
def publisher_name(self):
|
|
92
|
+
try:
|
|
93
|
+
pub = self.record["264"]["b"]
|
|
94
|
+
except KeyError:
|
|
95
|
+
try:
|
|
96
|
+
pub = self.record["260"]["b"]
|
|
97
|
+
except KeyError:
|
|
98
|
+
return ""
|
|
99
|
+
return self.__strip_punctuation(pub)
|
|
100
|
+
|
|
101
|
+
def type_of(self):
|
|
102
|
+
return self.record.leader.type_of_record
|
|
103
|
+
|
|
104
|
+
def title_part(self):
|
|
105
|
+
try:
|
|
106
|
+
parts = self.record["245"].get_subfields("p")[1:]
|
|
107
|
+
return self.__strip_punctuation(" ".join(parts))
|
|
108
|
+
except KeyError:
|
|
109
|
+
return ""
|
|
110
|
+
|
|
111
|
+
def title_number(self):
|
|
112
|
+
try:
|
|
113
|
+
num = self.record["245"].get("n")
|
|
114
|
+
if num:
|
|
115
|
+
return self.__strip_punctuation(num)
|
|
116
|
+
return ""
|
|
117
|
+
except KeyError:
|
|
118
|
+
return ""
|
|
119
|
+
|
|
120
|
+
def author(self):
|
|
121
|
+
if self.__vernacular_author_field():
|
|
122
|
+
author_field = self.__vernacular_author_field()
|
|
123
|
+
else:
|
|
124
|
+
author_field = self.__author_from_1xx()
|
|
125
|
+
|
|
126
|
+
if author_field:
|
|
127
|
+
try:
|
|
128
|
+
return self.__strip_ending_punctuation(author_field.get("a"))
|
|
129
|
+
except AttributeError:
|
|
130
|
+
return ""
|
|
131
|
+
return ""
|
|
132
|
+
|
|
133
|
+
def __author_from_1xx(self):
|
|
134
|
+
try:
|
|
135
|
+
return self.record["100"]
|
|
136
|
+
except KeyError:
|
|
137
|
+
try:
|
|
138
|
+
return self.record["110"]
|
|
139
|
+
except KeyError:
|
|
140
|
+
try:
|
|
141
|
+
return self.record["111"]
|
|
142
|
+
except KeyError:
|
|
143
|
+
return ""
|
|
144
|
+
|
|
145
|
+
def __vernacular_author_field(self):
|
|
146
|
+
try:
|
|
147
|
+
return self.record.get_linked_fields(self.record["100"])[0]
|
|
148
|
+
except (KeyError, IndexError, pymarc.exceptions.MissingLinkedFields):
|
|
149
|
+
try:
|
|
150
|
+
return self.record.get_linked_fields(self.record["110"])[0]
|
|
151
|
+
except (KeyError, IndexError, pymarc.exceptions.MissingLinkedFields):
|
|
152
|
+
try:
|
|
153
|
+
return self.record.get_linked_fields(self.record["111"])[0]
|
|
154
|
+
except (KeyError, IndexError, pymarc.exceptions.MissingLinkedFields):
|
|
155
|
+
return ""
|
|
156
|
+
|
|
157
|
+
def title_inclusive_dates(self):
|
|
158
|
+
try:
|
|
159
|
+
date = self.record["245"].get("f")
|
|
160
|
+
if date:
|
|
161
|
+
return self.__strip_ending_punctuation(date)
|
|
162
|
+
return ""
|
|
163
|
+
except KeyError:
|
|
164
|
+
return ""
|
|
165
|
+
|
|
166
|
+
def gov_doc_number(self):
|
|
167
|
+
try:
|
|
168
|
+
return self.record["086"].get("a")
|
|
169
|
+
except KeyError:
|
|
170
|
+
return ""
|
|
171
|
+
|
|
172
|
+
def is_electronic_resource(self):
|
|
173
|
+
return bool(
|
|
174
|
+
self.__is_electronic_resource_from_title()
|
|
175
|
+
or self.__is_electronic_resource_from_reproduction()
|
|
176
|
+
or self.__is_electronic_resource_from_description()
|
|
177
|
+
or self.__is_electronic_resource_from_007()
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def __is_electronic_resource_from_title(self):
|
|
181
|
+
try:
|
|
182
|
+
return self.record["245"].get("h") == "[electronic resource]"
|
|
183
|
+
except KeyError:
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
def __is_electronic_resource_from_reproduction(self):
|
|
187
|
+
try:
|
|
188
|
+
return re.match(
|
|
189
|
+
"electronic reproduction", self.record["533"].get("a"), re.IGNORECASE
|
|
190
|
+
)
|
|
191
|
+
except (KeyError, TypeError):
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
def __is_electronic_resource_from_description(self):
|
|
195
|
+
try:
|
|
196
|
+
subfield_a = self.record["300"].get("a")
|
|
197
|
+
if subfield_a:
|
|
198
|
+
return bool(re.search("online resource", subfield_a, re.IGNORECASE))
|
|
199
|
+
return False
|
|
200
|
+
except KeyError:
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def __is_electronic_resource_from_007(self):
|
|
204
|
+
try:
|
|
205
|
+
return bool(self.record["007"].data[0] == "c")
|
|
206
|
+
except KeyError:
|
|
207
|
+
return False
|
|
208
|
+
|
|
209
|
+
def __normalize_edition(self, edition):
|
|
210
|
+
edition_mapping = {r"Ed\.": "Edition", r"ed\.": "edition"}
|
|
211
|
+
try:
|
|
212
|
+
for key, value in edition_mapping.items():
|
|
213
|
+
edition = re.sub(key, value, edition)
|
|
214
|
+
return self.__strip_punctuation(edition)
|
|
215
|
+
except TypeError:
|
|
216
|
+
return ""
|
|
217
|
+
|
|
218
|
+
def __normalize_extent(self, extent):
|
|
219
|
+
extent_mapping = {
|
|
220
|
+
r"p\.": "pages",
|
|
221
|
+
r"v\.": "volumes",
|
|
222
|
+
r"vol\.": "volumes",
|
|
223
|
+
r"ℓ\.": "leaves",
|
|
224
|
+
}
|
|
225
|
+
for key, value in extent_mapping.items():
|
|
226
|
+
extent = re.sub(key, value, extent)
|
|
227
|
+
return self.__strip_punctuation(extent)
|
|
228
|
+
|
|
229
|
+
def __strip_ending_punctuation(self, some_string):
|
|
230
|
+
punctuation_to_strip = string.punctuation.replace(")", "")
|
|
231
|
+
return some_string.strip(punctuation_to_strip + " ")
|
|
232
|
+
|
|
233
|
+
def __strip_punctuation(self, some_string):
|
|
234
|
+
punctuation_to_strip = string.punctuation.replace("&", "")
|
|
235
|
+
some_string = some_string.translate(str.maketrans("", "", punctuation_to_strip))
|
|
236
|
+
some_string = re.sub(" ", " ", some_string).strip()
|
|
237
|
+
return some_string
|
|
238
|
+
|
|
239
|
+
def is_valid_date(self, date_string):
|
|
240
|
+
valid = True
|
|
241
|
+
if date_string == "9999":
|
|
242
|
+
valid = False
|
|
243
|
+
elif date_string == " ":
|
|
244
|
+
valid = False
|
|
245
|
+
elif self.number_of_characters(date_string) != 4:
|
|
246
|
+
valid = False
|
|
247
|
+
try:
|
|
248
|
+
int(date_string)
|
|
249
|
+
except (ValueError, TypeError):
|
|
250
|
+
valid = False
|
|
251
|
+
return valid
|
|
252
|
+
|
|
253
|
+
def number_of_characters(self, date_string):
|
|
254
|
+
try:
|
|
255
|
+
return len(date_string)
|
|
256
|
+
except TypeError:
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
def date_one(self):
|
|
260
|
+
try:
|
|
261
|
+
date_string = self.record["008"].data[7:11]
|
|
262
|
+
return self.__as_date(date_string)
|
|
263
|
+
except KeyError:
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
def date_two(self):
|
|
267
|
+
try:
|
|
268
|
+
date_string = self.record["008"].data[11:15]
|
|
269
|
+
return self.__as_date(date_string)
|
|
270
|
+
except KeyError:
|
|
271
|
+
return None
|
|
272
|
+
|
|
273
|
+
def __date_of_production(self):
|
|
274
|
+
try:
|
|
275
|
+
date_string = self.record["264"]["c"]
|
|
276
|
+
except KeyError:
|
|
277
|
+
return None
|
|
278
|
+
return self.__as_date(date_string)
|
|
279
|
+
|
|
280
|
+
def __date_of_publication(self):
|
|
281
|
+
try:
|
|
282
|
+
date_string = self.record["260"]["c"]
|
|
283
|
+
except KeyError:
|
|
284
|
+
return ""
|
|
285
|
+
return self.__as_date(date_string)
|
|
286
|
+
|
|
287
|
+
def __as_date(self, date_string):
|
|
288
|
+
# Remove punctuation (for 260 and 264 fields)
|
|
289
|
+
date_string = self.__strip_punctuation(date_string)
|
|
290
|
+
if self.is_valid_date(date_string):
|
|
291
|
+
return int(date_string)
|
|
292
|
+
return ""
|