Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.57% |
48 / 53 |
|
75.00% |
6 / 8 |
CRAP | |
0.00% |
0 / 1 |
MarcReaderTrait | |
90.57% |
48 / 53 |
|
75.00% |
6 / 8 |
27.61 | |
0.00% |
0 / 1 |
getRawMarcData | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
4.02 | |||
getMarcReader | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getFieldArray | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
getFirstFieldValue | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getPublicationInfo | |
80.00% |
16 / 20 |
|
0.00% |
0 / 1 |
9.65 | |||
getSubfield | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSubfields | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSubfieldArray | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
6 |
1 | <?php |
2 | |
3 | /** |
4 | * Functions for reading MARC records. |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2017. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License version 2, |
12 | * as published by the Free Software Foundation. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License |
20 | * along with this program; if not, write to the Free Software |
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 | * |
23 | * @category VuFind |
24 | * @package RecordDrivers |
25 | * @author Demian Katz <demian.katz@villanova.edu> |
26 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
27 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
28 | * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki |
29 | */ |
30 | |
31 | namespace VuFind\RecordDriver\Feature; |
32 | |
33 | use function array_key_exists; |
34 | use function count; |
35 | use function in_array; |
36 | use function is_array; |
37 | |
38 | /** |
39 | * Functions for reading MARC records. |
40 | * |
41 | * Assumption: raw MARC data can be found in $this->fields['fullrecord']. |
42 | * |
43 | * Assumption: VuFind config available as $this->mainConfig |
44 | * |
45 | * @category VuFind |
46 | * @package RecordDrivers |
47 | * @author Demian Katz <demian.katz@villanova.edu> |
48 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
49 | * @link https://vufind.org/wiki/development:plugins:record_drivers Wiki |
50 | */ |
51 | trait MarcReaderTrait |
52 | { |
53 | /** |
54 | * MARC reader class to use. |
55 | * |
56 | * @var string |
57 | */ |
58 | protected $marcReaderClass = \VuFind\Marc\MarcReader::class; |
59 | |
60 | /** |
61 | * MARC reader. Access only via getMarcReader() as this is initialized lazily. |
62 | */ |
63 | protected $lazyMarcReader = null; |
64 | |
65 | /** |
66 | * Retrieve the raw MARC data for this record; note that format may vary |
67 | * depending on what was indexed (e.g. XML vs. binary MARC). |
68 | * |
69 | * @return string |
70 | */ |
71 | public function getRawMarcData() |
72 | { |
73 | // Set preferred MARC field from config or default, if it's not existing |
74 | $preferredMarcFields = $this->mainConfig->Record->preferredMarcFields |
75 | ?? 'fullrecord'; |
76 | $preferredMarcFieldArray = explode(',', $preferredMarcFields); |
77 | $preferredMarcField = 'fullrecord'; |
78 | foreach ($preferredMarcFieldArray as $testField) { |
79 | if (array_key_exists($testField, $this->fields)) { |
80 | $preferredMarcField = $testField; |
81 | break; |
82 | } |
83 | } |
84 | if (empty($this->fields[$preferredMarcField])) { |
85 | throw new \Exception('Missing MARC data in record ' . $this->getUniqueId()); |
86 | } |
87 | return trim($this->fields[$preferredMarcField]); |
88 | } |
89 | |
90 | /** |
91 | * Get access to the MarcReader object. |
92 | * |
93 | * @return \VuFind\Marc\MarcReader |
94 | */ |
95 | public function getMarcReader() |
96 | { |
97 | if (null === $this->lazyMarcReader) { |
98 | $this->lazyMarcReader = new $this->marcReaderClass( |
99 | $this->getRawMarcData() |
100 | ); |
101 | } |
102 | |
103 | return $this->lazyMarcReader; |
104 | } |
105 | |
106 | /** |
107 | * Return an array of all values extracted from the specified field/subfield |
108 | * combination. If multiple subfields are specified and $concat is true, they |
109 | * will be concatenated together in the order listed -- each entry in the array |
110 | * will correspond with a single MARC field. If $concat is false, the return |
111 | * array will contain separate entries for separate subfields. |
112 | * |
113 | * @param string $field The MARC field number to read |
114 | * @param array $subfields The MARC subfield codes to read |
115 | * @param bool $concat Should we concatenate subfields? |
116 | * @param string $separator Separator string (used only when $concat === true) |
117 | * |
118 | * @return array |
119 | */ |
120 | protected function getFieldArray( |
121 | $field, |
122 | $subfields = null, |
123 | $concat = true, |
124 | $separator = ' ' |
125 | ) { |
126 | // Default to subfield a if nothing is specified. |
127 | if (!is_array($subfields)) { |
128 | $subfields = ['a']; |
129 | } |
130 | return $this->getMarcReader()->getFieldsSubfields( |
131 | $field, |
132 | $subfields, |
133 | $concat ? $separator : null |
134 | ); |
135 | } |
136 | |
137 | /** |
138 | * Get the first value matching the specified MARC field and subfields. |
139 | * If multiple subfields are specified, they will be concatenated together. |
140 | * |
141 | * @param string $field The MARC field to read |
142 | * @param array $subfields The MARC subfield codes to read |
143 | * |
144 | * @return string |
145 | */ |
146 | protected function getFirstFieldValue($field, $subfields = null) |
147 | { |
148 | $matches = $this->getFieldArray($field, $subfields); |
149 | return $matches[0] ?? ''; |
150 | } |
151 | |
152 | /** |
153 | * Get the item's publication information |
154 | * |
155 | * @param string $subfield The subfield to retrieve ('a' = location, 'c' = date) |
156 | * |
157 | * @return array |
158 | */ |
159 | protected function getPublicationInfo($subfield = 'a') |
160 | { |
161 | // Get string separator for publication information: |
162 | $separator = $this->mainConfig->Record->marcPublicationInfoSeparator ?? ' '; |
163 | |
164 | // First check old-style 260 field: |
165 | $results = $this->getFieldArray('260', [$subfield], true, $separator); |
166 | |
167 | // Now track down relevant RDA-style 264 fields; we only care about |
168 | // copyright and publication places (and ignore copyright places if |
169 | // publication places are present). This behavior is designed to be |
170 | // consistent with default SolrMarc handling of names/dates. |
171 | $pubResults = $copyResults = []; |
172 | |
173 | $fields = $this->getMarcReader()->getFields('264', [$subfield]); |
174 | foreach ($fields as $currentField) { |
175 | $currentVal = $this |
176 | ->getSubfieldArray($currentField, [$subfield], true, $separator); |
177 | if (!empty($currentVal)) { |
178 | switch ($currentField['i2']) { |
179 | case '1': |
180 | $pubResults = array_merge($pubResults, $currentVal); |
181 | break; |
182 | case '4': |
183 | $copyResults = array_merge($copyResults, $currentVal); |
184 | break; |
185 | } |
186 | } |
187 | } |
188 | $replace260 = $this->mainConfig->Record->replaceMarc260 ?? false; |
189 | if (count($pubResults) > 0) { |
190 | return $replace260 ? $pubResults : array_merge($results, $pubResults); |
191 | } elseif (count($copyResults) > 0) { |
192 | return $replace260 ? $copyResults : array_merge($results, $copyResults); |
193 | } |
194 | |
195 | return $results; |
196 | } |
197 | |
198 | /** |
199 | * Return first subfield with the given code in the provided MARC field |
200 | * |
201 | * @param array $field Result from MarcReader::getFields |
202 | * @param string $subfield The MARC subfield code to get |
203 | * |
204 | * @return string |
205 | */ |
206 | protected function getSubfield($field, $subfield) |
207 | { |
208 | return $this->getMarcReader()->getSubfield($field, $subfield); |
209 | } |
210 | |
211 | /** |
212 | * Return all subfields with the given code in the provided MARC field |
213 | * |
214 | * @param array $field Result from MarcReader::getFields |
215 | * @param string $subfield The MARC subfield code to get |
216 | * |
217 | * @return array |
218 | */ |
219 | protected function getSubfields($field, $subfield) |
220 | { |
221 | return $this->getMarcReader()->getSubfields($field, $subfield); |
222 | } |
223 | |
224 | /** |
225 | * Return an array of non-empty subfield values found in the provided MARC |
226 | * field. If $concat is true, the array will contain either zero or one |
227 | * entries (empty array if no subfields found, subfield values concatenated |
228 | * together in specified order if found). If concat is false, the array |
229 | * will contain a separate entry for each subfield value found. |
230 | * |
231 | * @param array $currentField Result from MarcReader::getFields |
232 | * @param array $subfields The MARC subfield codes to read |
233 | * @param bool $concat Should we concatenate subfields? |
234 | * @param string $separator Separator string (used only when $concat === true) |
235 | * |
236 | * @return array |
237 | */ |
238 | protected function getSubfieldArray( |
239 | $currentField, |
240 | $subfields, |
241 | $concat = true, |
242 | $separator = ' ' |
243 | ) { |
244 | // Start building a line of text for the current field |
245 | $matches = []; |
246 | |
247 | // Loop through all subfields, collecting results that match the filter; |
248 | // note that it is important to retain the original MARC order here! |
249 | foreach ($currentField['subfields'] as $currentSubfield) { |
250 | if (in_array($currentSubfield['code'], $subfields)) { |
251 | // Grab the current subfield value and act on it if it is non-empty: |
252 | $data = trim($currentSubfield['data']); |
253 | if (!empty($data)) { |
254 | $matches[] = $data; |
255 | } |
256 | } |
257 | } |
258 | |
259 | // Send back the data in a different format depending on $concat mode: |
260 | return $concat && $matches ? [implode($separator, $matches)] : $matches; |
261 | } |
262 | } |