Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 202 |
|
0.00% |
0 / 21 |
CRAP | |
0.00% |
0 / 1 |
MarcReader | |
0.00% |
0 / 202 |
|
0.00% |
0 / 21 |
7140 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setData | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
90 | |||
toFormat | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getLeader | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getField | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getFields | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
90 | |||
getAllFields | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
30 | |||
getSubfield | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
getSubfields | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
getFieldsSubfields | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
90 | |||
getLinkedField | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
getLinkedFields | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
72 | |||
getLinkedFieldsSubfields | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getFieldLink | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
parseLinkageField | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
getFilteredRecord | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
30 | |||
getFilteringRulesForTag | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 | |||
filterSubfields | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
42 | |||
getWarnings | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getInternalFields | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getInternalSubfield | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | /** |
4 | * MARC record reader class. |
5 | * |
6 | * PHP version 7 |
7 | * |
8 | * Copyright (C) The National Library of Finland 2020-2022. |
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 MARC |
25 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org/wiki/development Wiki |
28 | */ |
29 | |
30 | namespace VuFind\Marc; |
31 | |
32 | use function in_array; |
33 | use function intval; |
34 | use function is_array; |
35 | use function is_string; |
36 | |
37 | /** |
38 | * MARC record reader class. |
39 | * |
40 | * @category VuFind |
41 | * @package MARC |
42 | * @author Ere Maijala <ere.maijala@helsinki.fi> |
43 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
44 | * @link https://vufind.org/wiki/development Wiki |
45 | */ |
46 | class MarcReader |
47 | { |
48 | /** |
49 | * Supported serialization formats |
50 | * |
51 | * @var array |
52 | */ |
53 | protected $serializations = [ |
54 | 'ISO2709' => Serialization\Iso2709::class, |
55 | 'JSON' => Serialization\MarcInJson::class, |
56 | 'MARCXML' => Serialization\MarcXml::class, |
57 | ]; |
58 | |
59 | /** |
60 | * MARC leader |
61 | * |
62 | * @var string |
63 | */ |
64 | protected $leader; |
65 | |
66 | /** |
67 | * MARC is stored in a multidimensional array resembling MARC-in-JSON |
68 | * specification by Ross Singer: |
69 | * [ |
70 | * 'leader' => '...', |
71 | * 'fields' => [ |
72 | * [ |
73 | * '001' => '12345' |
74 | * ], |
75 | * [ |
76 | * '245' => [ |
77 | * 'ind1' => '0', |
78 | * 'ind2' => '1', |
79 | * 'subfields' => [ |
80 | * ['a' => 'Title'], |
81 | * ['k' => 'Form'], |
82 | * ['k' => 'Another'], |
83 | * ['p' => 'Part'], |
84 | * ] |
85 | * ] |
86 | * ] |
87 | * ] |
88 | * ] |
89 | * |
90 | * @var array |
91 | * @see https://web.archive.org/web/20151112001548/http://dilettantes.code4lib.org/blog/2010/09/a-proposal-to-serialize-marc-in-json/ |
92 | */ |
93 | protected $data; |
94 | |
95 | /** |
96 | * Any warnings encountered when parsing a record |
97 | * |
98 | * @var array |
99 | */ |
100 | protected $warnings; |
101 | |
102 | /** |
103 | * Constructor |
104 | * |
105 | * @param string|array $data MARC record in one of the supported formats, or an |
106 | * associative array with 'leader' and 'fields' in the internal format |
107 | */ |
108 | public function __construct($data) |
109 | { |
110 | $this->setData($data); |
111 | } |
112 | |
113 | /** |
114 | * Set MARC record data |
115 | * |
116 | * @param string|array $data MARC record in one of the supported formats, or an |
117 | * associative array with 'leader' and 'fields' in the internal format |
118 | * |
119 | * @throws \Exception |
120 | * @return void |
121 | */ |
122 | public function setData($data): void |
123 | { |
124 | $this->warnings = []; |
125 | if (is_array($data)) { |
126 | if ( |
127 | !is_string($data['leader'] ?? null) |
128 | || !is_array($data['fields'] ?? null) |
129 | ) { |
130 | throw new \Exception('Invalid data array format provided'); |
131 | } |
132 | $this->data = $data; |
133 | return; |
134 | } |
135 | $valid = false; |
136 | foreach ($this->serializations as $serialization) { |
137 | if ($serialization::canParse($data)) { |
138 | $this->data = $serialization::fromString($data); |
139 | if (isset($this->data['warnings'])) { |
140 | $this->warnings = $this->data['warnings']; |
141 | unset($this->data['warnings']); |
142 | } |
143 | $valid = true; |
144 | break; |
145 | } |
146 | } |
147 | if (!$valid) { |
148 | throw new \Exception('MARC record format not recognized'); |
149 | } |
150 | // Make sure leader is 24 characters, and reset meaningless offsets: |
151 | if ($this->data['leader']) { |
152 | $leader = str_pad(substr($this->data['leader'] ?? '', 0, 24), 24); |
153 | $this->data['leader'] = '00000' . substr($leader, 5, 7) . '00000' |
154 | . substr($leader, 17); |
155 | } |
156 | } |
157 | |
158 | /** |
159 | * Serialize the record |
160 | * |
161 | * @param string $format Format to return (e.g. 'ISO2709' or 'MARCXML') |
162 | * |
163 | * @throws \Exception |
164 | * @return string |
165 | */ |
166 | public function toFormat(string $format): string |
167 | { |
168 | $serialization = $this->serializations[$format] ?? null; |
169 | if (null === $serialization) { |
170 | throw new \Exception("Unknown MARC format '$format' requested"); |
171 | } |
172 | return $serialization::toString($this->data); |
173 | } |
174 | |
175 | /** |
176 | * Return leader |
177 | * |
178 | * @return string |
179 | */ |
180 | public function getLeader(): string |
181 | { |
182 | return $this->data['leader']; |
183 | } |
184 | |
185 | /** |
186 | * Return an associative array for a data field, a string for a control field or |
187 | * an empty array if field does not exist |
188 | * |
189 | * @param string $fieldTag The MARC field tag to get |
190 | * @param array $subfieldCodes The MARC subfield codes to get, or empty for all |
191 | * |
192 | * @return array|string |
193 | */ |
194 | public function getField(string $fieldTag, ?array $subfieldCodes = null) |
195 | { |
196 | $results = $this->getFields($fieldTag, $subfieldCodes); |
197 | return $results[0] ?? []; |
198 | } |
199 | |
200 | /** |
201 | * Return an associative array of fields for data fields or an array of values |
202 | * for control fields |
203 | * |
204 | * @param string $fieldTag The MARC field tag to get |
205 | * @param array $subfieldCodes The MARC subfield codes to get, or empty for all. |
206 | * Ignored for control fields. |
207 | * |
208 | * @return array |
209 | */ |
210 | public function getFields(string $fieldTag, ?array $subfieldCodes = null): array |
211 | { |
212 | $result = []; |
213 | |
214 | foreach ($this->data['fields'] as $fieldData) { |
215 | if ($fieldTag && $fieldTag !== (string)key($fieldData)) { |
216 | continue; |
217 | } |
218 | $field = current($fieldData); |
219 | if (!is_array($field)) { |
220 | // Control field |
221 | $result[] = $field; |
222 | continue; |
223 | } |
224 | $subfields = []; |
225 | foreach ($field['subfields'] ?? [] as $subfield) { |
226 | if ( |
227 | $subfieldCodes |
228 | && !in_array((string)key($subfield), $subfieldCodes) |
229 | ) { |
230 | continue; |
231 | } |
232 | $subfields[] = [ |
233 | 'code' => (string)key($subfield), |
234 | 'data' => current($subfield), |
235 | ]; |
236 | } |
237 | if ($subfields) { |
238 | $result[] = [ |
239 | 'tag' => $fieldTag, |
240 | 'i1' => $field['ind1'], |
241 | 'i2' => $field['ind2'], |
242 | 'subfields' => $subfields, |
243 | ]; |
244 | } |
245 | } |
246 | |
247 | return $result; |
248 | } |
249 | |
250 | /** |
251 | * Return all fields as an array. |
252 | * |
253 | * Control fields have the following elements: |
254 | * - tag |
255 | * - data |
256 | * |
257 | * Data fields have the following elements: |
258 | * - tag |
259 | * - i1 |
260 | * - i2 |
261 | * - subfields |
262 | * |
263 | * @return array |
264 | */ |
265 | public function getAllFields() |
266 | { |
267 | $result = []; |
268 | |
269 | foreach ($this->data['fields'] as $fieldData) { |
270 | $tag = (string)key($fieldData); |
271 | $field = current($fieldData); |
272 | if (is_string($field)) { |
273 | // Control field |
274 | $result[] = [ |
275 | 'tag' => $tag, |
276 | 'data' => $field, |
277 | ]; |
278 | continue; |
279 | } |
280 | $subfields = []; |
281 | foreach ($field['subfields'] ?? [] as $subfield) { |
282 | $subfields[] = [ |
283 | 'code' => (string)key($subfield), |
284 | 'data' => current($subfield), |
285 | ]; |
286 | } |
287 | if ($subfields) { |
288 | $result[] = [ |
289 | 'tag' => $tag, |
290 | 'i1' => $field['ind1'], |
291 | 'i2' => $field['ind2'], |
292 | 'subfields' => $subfields, |
293 | ]; |
294 | } |
295 | } |
296 | |
297 | return $result; |
298 | } |
299 | |
300 | /** |
301 | * Return first subfield with the given code in the MARC field provided by |
302 | * getField or getFields |
303 | * |
304 | * @param array $field Result from MarcReader::getFields |
305 | * @param string $subfieldCode The MARC subfield code to get |
306 | * |
307 | * @return string |
308 | */ |
309 | public function getSubfield(array $field, string $subfieldCode): string |
310 | { |
311 | foreach ($field['subfields'] ?? [] as $current) { |
312 | if ($current['code'] == $subfieldCode) { |
313 | return trim($current['data']); |
314 | } |
315 | } |
316 | |
317 | return ''; |
318 | } |
319 | |
320 | /** |
321 | * Return all subfields with the given code in the MARC field provided by |
322 | * getField or getFields. Returns all subfields if subfieldCode is empty. |
323 | * |
324 | * @param array $field Result from MarcReader::getFields |
325 | * @param string $subfieldCode The MARC subfield code to get |
326 | * |
327 | * @return array |
328 | */ |
329 | public function getSubfields(array $field, string $subfieldCode = ''): array |
330 | { |
331 | $result = []; |
332 | foreach ($field['subfields'] ?? [] as $current) { |
333 | if ('' === $subfieldCode || $current['code'] == $subfieldCode) { |
334 | $result[] = trim($current['data']); |
335 | } |
336 | } |
337 | |
338 | return $result; |
339 | } |
340 | |
341 | /** |
342 | * Return an array of all values extracted from the specified field/subfield |
343 | * combination. If multiple subfields and a separator are specified, the |
344 | * subfields will be concatenated together in the order listed -- each entry in |
345 | * the array will correspond with a single MARC field. If $separator is null, |
346 | * the return array will contain separate entries for all subfields. |
347 | * |
348 | * @param string $fieldTag The MARC field tag to get |
349 | * @param array $subfieldCodes The MARC subfield codes to get |
350 | * @param string $separator Subfield separator string. Set to null to disable |
351 | * concatenation of subfields. |
352 | * |
353 | * @return array |
354 | */ |
355 | public function getFieldsSubfields( |
356 | string $fieldTag, |
357 | array $subfieldCodes, |
358 | ?string $separator = ' ' |
359 | ): array { |
360 | $result = []; |
361 | |
362 | foreach ($this->getInternalFields($fieldTag) as $field) { |
363 | if (!isset($field['subfields'])) { |
364 | continue; |
365 | } |
366 | $subfields = []; |
367 | foreach ($field['subfields'] ?? [] as $subfield) { |
368 | if ( |
369 | $subfieldCodes |
370 | && !in_array((string)key($subfield), $subfieldCodes) |
371 | ) { |
372 | continue; |
373 | } |
374 | if (null !== $separator) { |
375 | $subfields[] = current($subfield); |
376 | } else { |
377 | $result[] = current($subfield); |
378 | } |
379 | } |
380 | if (null !== $separator && $subfields) { |
381 | $result[] = implode($separator, $subfields); |
382 | } |
383 | } |
384 | |
385 | return $result; |
386 | } |
387 | |
388 | /** |
389 | * Return an associative array for a linked field such as 880 (Alternate Graphic |
390 | * Representation) or an empty array if field does not exist |
391 | * |
392 | * @param string $fieldTag The MARC field that contains the linked fields |
393 | * @param string $linkedFieldTag The linked MARC field tag to get |
394 | * @param string $occurrence The occurrence number to get; empty string for |
395 | * whatever comes first |
396 | * @param array $subfieldCodes The MARC subfield codes to get, or empty for all |
397 | * |
398 | * @return array |
399 | */ |
400 | public function getLinkedField( |
401 | string $fieldTag, |
402 | string $linkedFieldTag, |
403 | string $occurrence = '', |
404 | ?array $subfieldCodes = null |
405 | ): array { |
406 | $results |
407 | = $this->getLinkedFields($fieldTag, $linkedFieldTag, $subfieldCodes); |
408 | foreach ($results as $field) { |
409 | if (empty($occurrence) || $occurrence === $field['link']['occurrence']) { |
410 | return $field; |
411 | } |
412 | } |
413 | return []; |
414 | } |
415 | |
416 | /** |
417 | * Return an array of associative arrays for a linked field such as 880 |
418 | * (Alternate Graphic Representation) |
419 | * |
420 | * @param string $fieldTag The MARC field that contains the linked fields |
421 | * @param string $linkedFieldTag The linked MARC field tag to get |
422 | * @param array $subfieldCodes The MARC subfield codes to get, or empty for all |
423 | * |
424 | * @return array |
425 | */ |
426 | public function getLinkedFields( |
427 | string $fieldTag, |
428 | string $linkedFieldTag, |
429 | ?array $subfieldCodes = null |
430 | ): array { |
431 | $result = []; |
432 | |
433 | foreach ($this->getInternalFields($fieldTag) as $field) { |
434 | if (is_string($field)) { |
435 | // Control field |
436 | continue; |
437 | } |
438 | $link |
439 | = $this->parseLinkageField($this->getInternalSubfield($field, '6')); |
440 | if ($link['field'] !== $linkedFieldTag) { |
441 | continue; |
442 | } |
443 | $subfields = []; |
444 | foreach ($field['subfields'] ?? [] as $subfield) { |
445 | if ( |
446 | $subfieldCodes |
447 | && !in_array((string)key($subfield), $subfieldCodes) |
448 | ) { |
449 | continue; |
450 | } |
451 | $subfields[] = [ |
452 | 'code' => (string)key($subfield), |
453 | 'data' => current($subfield), |
454 | ]; |
455 | } |
456 | if ($subfields) { |
457 | $result[] = [ |
458 | 'tag' => $fieldTag, |
459 | 'i1' => $field['ind1'], |
460 | 'i2' => $field['ind2'], |
461 | 'subfields' => $subfields, |
462 | 'link' => $link, |
463 | ]; |
464 | } |
465 | } |
466 | |
467 | return $result; |
468 | } |
469 | |
470 | /** |
471 | * Return an array of all values extracted from the specified linked |
472 | * field/subfield combination. If multiple subfields and a separator are |
473 | * specified, the subfields will be concatenated together in the order listed |
474 | * -- each entry in the array will correspond with a single MARC field. If |
475 | * $separator is null, the return array will contain separate entries for all |
476 | * subfields. |
477 | * |
478 | * @param string $fieldTag The MARC field that contains the linked fields |
479 | * @param string $linkedFieldTag The linked MARC field tag to get |
480 | * @param array $subfieldCodes The MARC subfield codes to get |
481 | * @param string $separator Subfield separator string. Set to null to |
482 | * disable concatenation of subfields. |
483 | * |
484 | * @return array |
485 | */ |
486 | public function getLinkedFieldsSubfields( |
487 | string $fieldTag, |
488 | string $linkedFieldTag, |
489 | array $subfieldCodes, |
490 | ?string $separator = ' ' |
491 | ): array { |
492 | $result = []; |
493 | foreach ($this->getLinkedFields($fieldTag, $linkedFieldTag, $subfieldCodes) as $field) { |
494 | $subfields = $this->getSubfields($field); |
495 | if (null !== $separator) { |
496 | $result[] = implode($separator, $subfields); |
497 | } else { |
498 | $result = array_merge($result, $subfields); |
499 | } |
500 | } |
501 | return $result; |
502 | } |
503 | |
504 | /** |
505 | * Get linked field data from subfield 6 |
506 | * |
507 | * @param array $field Field |
508 | * |
509 | * @return array |
510 | */ |
511 | public function getFieldLink(array $field): array |
512 | { |
513 | return $this->parseLinkageField($this->getSubfield($field, '6')); |
514 | } |
515 | |
516 | /** |
517 | * Parse a linkage field |
518 | * |
519 | * @param string $link Linkage field |
520 | * |
521 | * @return array |
522 | */ |
523 | public function parseLinkageField(string $link): array |
524 | { |
525 | $linkParts = explode('/', $link, 3); |
526 | $targetParts = explode('-', $linkParts[0]); |
527 | return [ |
528 | 'field' => $targetParts[0], |
529 | 'occurrence' => $targetParts[1] ?? '', |
530 | 'script' => $linkParts[1] ?? '', |
531 | 'orientation' => $linkParts[2] ?? '', |
532 | ]; |
533 | } |
534 | |
535 | /** |
536 | * Return a copy of the record with the specified fields and/or subfields |
537 | * removed. |
538 | * |
539 | * Each rule can have the following elements: |
540 | * |
541 | * tag - Tag the rule applies to (a regular expression). |
542 | * subfields - Subfields codes to remove (a regular expression, optional). |
543 | * Default is to remove all subfields (and the field). |
544 | * |
545 | * Examples: |
546 | * |
547 | * $result = $reader->getFilteredRecord( |
548 | * [ |
549 | * [ |
550 | * 'tag' => '9..' |
551 | * ] |
552 | * ] |
553 | * ); |
554 | * |
555 | * $result = $reader->getFilteredRecord( |
556 | * [ |
557 | * [ |
558 | * 'tag' => '...', |
559 | * 'subfields' => '0' |
560 | * ] |
561 | * ] |
562 | * ); |
563 | * |
564 | * @param array $rules Array of filtering rules |
565 | * |
566 | * @return MarcReader |
567 | */ |
568 | public function getFilteredRecord(array $rules): MarcReader |
569 | { |
570 | $resultFields = []; |
571 | foreach ($this->data['fields'] as $fieldData) { |
572 | $tag = (string)key($fieldData); |
573 | $field = current($fieldData); |
574 | $fieldRules = $this->getFilteringRulesForTag($rules, $tag); |
575 | if ($fieldRules) { |
576 | if (is_string($field)) { |
577 | // Control field, filter out completely |
578 | continue; |
579 | } |
580 | $field['subfields'] = $this->filterSubfields( |
581 | $fieldRules, |
582 | $field['subfields'] |
583 | ); |
584 | if (!$field['subfields']) { |
585 | // No subfields left, drop the field |
586 | continue; |
587 | } |
588 | $resultFields[] = [$tag => $field]; |
589 | } else { |
590 | $resultFields[] = [$tag => $field]; |
591 | } |
592 | } |
593 | return new MarcReader( |
594 | [ |
595 | 'leader' => $this->data['leader'], |
596 | 'fields' => $resultFields, |
597 | ] |
598 | ); |
599 | } |
600 | |
601 | /** |
602 | * Get filtering rules matching a field tag |
603 | * |
604 | * @param array $rules Filtering rules |
605 | * @param string $tag Field tag |
606 | * |
607 | * @return array |
608 | */ |
609 | protected function getFilteringRulesForTag(array $rules, string $tag): array |
610 | { |
611 | $result = []; |
612 | foreach ($rules as $rule) { |
613 | if ( |
614 | preg_match('/' . $rule['tag'] . '/', $tag) |
615 | && (!isset($rule['subfields']) || intval($tag) >= 10) |
616 | ) { |
617 | $result[] = $rule; |
618 | } |
619 | } |
620 | return $result; |
621 | } |
622 | |
623 | /** |
624 | * Filter subfields |
625 | * |
626 | * @param array $rules Filtering rules |
627 | * @param array $subfields Subfields |
628 | * |
629 | * @return array |
630 | */ |
631 | protected function filterSubfields(array $rules, array $subfields): array |
632 | { |
633 | foreach ($rules as $rule) { |
634 | if (!isset($rule['subfields'])) { |
635 | // No subfields specified, filter out all of them |
636 | return []; |
637 | } |
638 | $remaining = []; |
639 | foreach ($subfields as $subfield) { |
640 | $code = (string)key($subfield); |
641 | if (!preg_match('/' . $rule['subfields'] . '/', $code)) { |
642 | $remaining[] = $subfield; |
643 | } |
644 | } |
645 | if (!$remaining) { |
646 | return []; |
647 | } |
648 | $subfields = $remaining; |
649 | } |
650 | |
651 | return $subfields; |
652 | } |
653 | |
654 | /** |
655 | * Get any warnings encountered when parsing a record |
656 | * |
657 | * @return array |
658 | */ |
659 | public function getWarnings(): array |
660 | { |
661 | return $this->warnings; |
662 | } |
663 | |
664 | /** |
665 | * Return fields by tag in internal format |
666 | * |
667 | * @param string $tag Field tag |
668 | * |
669 | * @return array |
670 | */ |
671 | protected function getInternalFields(string $tag): array |
672 | { |
673 | $result = []; |
674 | foreach ($this->data['fields'] as $field) { |
675 | $fieldTag = (string)key($field); |
676 | if ($fieldTag === $tag) { |
677 | $result[] = current($field); |
678 | } |
679 | } |
680 | return $result; |
681 | } |
682 | |
683 | /** |
684 | * Return first subfield with the given code in the internal MARC field |
685 | * |
686 | * @param array $field Internal MARC field |
687 | * @param string $subfieldCode The MARC subfield code to get |
688 | * |
689 | * @return string |
690 | */ |
691 | protected function getInternalSubfield( |
692 | array $field, |
693 | string $subfieldCode |
694 | ): string { |
695 | foreach ($field['subfields'] ?? [] as $subfield) { |
696 | if ((string)key($subfield) === $subfieldCode) { |
697 | return trim(current($subfield)); |
698 | } |
699 | } |
700 | return ''; |
701 | } |
702 | } |