From 25a382558d499a84258818d5cb03868db343dccd Mon Sep 17 00:00:00 2001 From: damien-git Date: Thu, 10 Oct 2024 09:39:49 -0400 Subject: [PATCH] Improved RIS export (#3954) --- .../src/VuFind/RecordDriver/DefaultRecord.php | 19 ++- module/VuFind/src/VuFind/RecordDriver/EDS.php | 27 +++- .../Feature/MarcAdvancedTrait.php | 136 +++++++++++++++-- .../VuFind/tests/fixtures/marc/marctraits.xml | 16 ++ .../src/VuFindTest/RecordDriver/EDSTest.php | 12 +- .../Feature/MarcAdvancedTraitTest.php | 142 ++++++++++++++++++ .../AbstractBase/export-ris.phtml | 47 +++++- 7 files changed, 374 insertions(+), 25 deletions(-) diff --git a/module/VuFind/src/VuFind/RecordDriver/DefaultRecord.php b/module/VuFind/src/VuFind/RecordDriver/DefaultRecord.php index aaf4cdbc4b2..6a3ddb08a76 100644 --- a/module/VuFind/src/VuFind/RecordDriver/DefaultRecord.php +++ b/module/VuFind/src/VuFind/RecordDriver/DefaultRecord.php @@ -124,6 +124,23 @@ public function getAllSubjectHeadings($extended = false) return array_map($callback, array_unique($headings)); } + /** + * Get the subject headings as a flat array of strings. + * + * @return array Subject headings + */ + public function getAllSubjectHeadingsFlattened() + { + $headings = []; + $subjects = $this->getAllSubjectHeadings(); + if (is_array($subjects)) { + foreach ($subjects as $subj) { + $headings[] = implode(' -- ', $subj); + } + } + return $headings; + } + /** * Get all record links related to the current record. Each link is returned as * array. @@ -243,7 +260,7 @@ public function getCallNumber() */ public function getCallNumbers() { - return (array)($this->fields['callnumber-raw'] ?? []); + return array_unique((array)($this->fields['callnumber-raw'] ?? [])); } /** diff --git a/module/VuFind/src/VuFind/RecordDriver/EDS.php b/module/VuFind/src/VuFind/RecordDriver/EDS.php index 24be3057f4b..e22b99c73c8 100644 --- a/module/VuFind/src/VuFind/RecordDriver/EDS.php +++ b/module/VuFind/src/VuFind/RecordDriver/EDS.php @@ -115,6 +115,18 @@ public function getItemsAbstract() return $abstract[0]['Data'] ?? ''; } + /** + * Get the abstract notes. + * For EDS, returns the abstract in an array or an empty array. + * + * @return array + */ + public function getAbstractNotes() + { + $abstract = $this->getItems(null, null, 'Ab'); + return (array)($abstract[0]['Data'] ?? []); + } + /** * Get the access level of the record. * @@ -431,19 +443,20 @@ public function getLinkedFullTextLink() } /** - * Get the subject data of the record. + * Get the subject headings as a flat array of strings. * - * @return string + * @return array Subject headings */ - public function getItemsSubjects() + public function getAllSubjectHeadingsFlattened() { - $subjects = array_map( + $subject_arrays = array_map( function ($data) { - return $data['Data']; + $str = preg_replace('/>\s*[;,]\s*|<', $data['Data']); + return explode('|', rtrim(strip_tags($str), '.')); }, $this->getItems(null, null, 'Su') ); - return empty($subjects) ? '' : implode(', ', $subjects); + return array_merge(...$subject_arrays); } /** @@ -682,7 +695,7 @@ public function getCleanDOI() { $doi = $this->getItems(null, null, null, 'DOI'); if (isset($doi[0]['Data'])) { - return $doi[0]['Data']; + return strip_tags($doi[0]['Data']); } $dois = $this->getFilteredIdentifiers(['doi']); return $dois[0] ?? false; diff --git a/module/VuFind/src/VuFind/RecordDriver/Feature/MarcAdvancedTrait.php b/module/VuFind/src/VuFind/RecordDriver/Feature/MarcAdvancedTrait.php index 3e7444ffc4b..30c2e32e696 100644 --- a/module/VuFind/src/VuFind/RecordDriver/Feature/MarcAdvancedTrait.php +++ b/module/VuFind/src/VuFind/RecordDriver/Feature/MarcAdvancedTrait.php @@ -558,16 +558,6 @@ protected function getSeriesFromMARC($fieldInfo) return $matches; } - /** - * Get an array of summary strings for the record. - * - * @return array - */ - public function getSummary() - { - return $this->getFieldArray('520'); - } - /** * Get an array of technical details on the item represented by the record. * @@ -1170,4 +1160,130 @@ public function getTextualHoldings() { return $this->getFieldArray('866'); } + + /** + * Check if an array of indicator filters match the provided marc data + * + * @param array $marc_data MARC data for a specific field + * @param array $indFilter Array with up to 2 keys ('1', and '2') with an array as their value + * containing what to match on in the marc indicator. + * ex: ['1' => ['0','1']] would filter ind1 with 0 or 1 + * + * @return bool + */ + protected function checkIndicatorFilter($marc_data, $indFilter): bool + { + foreach (range(1, 2) as $indNum) { + if (isset($indFilter[$indNum])) { + if (!in_array(trim(($marc_data['i' . $indNum] ?? '')), (array)$indFilter[$indNum])) { + return false; + } + } + } + // If we got this far, no non-matching filters were encountered. + return true; + } + + /** + * Check if the indicator filters match the provided marc data + * + * @param array $marc_data MARC data for a specific field + * @param array $indData Indicator filters as described in getMarcFieldWithInd() + * + * @return bool + */ + protected function checkIndicatorFilters($marc_data, $indData): bool + { + foreach ($indData as $indFilter) { + if ($this->checkIndicatorFilter($marc_data, $indFilter)) { + return true; + } + } + // If we got this far, either $indData is empty (no filters defined -- return true) + // or it is non-empty (no filters matched -- return false) + return empty($indData); + } + + /** + * Takes a Marc field that notes are stored in (ex: 950) and a list of + * sub fields (ex: ['a','b']) optionally as well as what indicator + * numbers and values to filter for and concatenates the subfields + * together and returns the fields back as an array + * (ex: ['subA subB subC', 'field2SubA field2SubB']) + * + * @param string $field Marc field to search within + * @param ?array $subfield Sub-fields to return or empty for all + * @param array $indData Array of filter arrays, each in the format indicator number => + * array of allowed indicator values. If any one of the filter arrays fully matches the indicator + * values in the field, data will be returned. If no filter arrays are defined, data will always + * be returned regardless of indicators. + * ex: [['1' => ['1', '2']], ['2' => ['']]] would filter fields ind1 = 1 or 2 or ind2 = blank + * ex: [['1' => ['1'], '2' => ['7']]] would filter fields with ind1 = 1 and ind2 = 7 + * ex: [] would apply no filtering based on indicators + * + * @return array The values within the subfields under the field + */ + public function getMarcFieldWithInd( + string $field, + ?array $subfield = null, + array $indData = [] + ) { + $vals = []; + $marc = $this->getMarcReader(); + $marc_fields = $marc->getFields($field, $subfield); + foreach ($marc_fields as $marc_data) { + $field_vals = []; + if ($this->checkIndicatorFilters($marc_data, $indData)) { + $subfields = $marc_data['subfields']; + foreach ($subfields as $subfield) { + $field_vals[] = $subfield['data']; + } + } + $newVal = implode(' ', $field_vals); + if (!empty($field_vals) && !in_array($newVal, $vals)) { + $vals[] = $newVal; + } + } + return $vals; + } + + /** + * Get the location of other archival materials notes + * + * @return array Note fields from the MARC record + */ + public function getLocationOfArchivalMaterialsNotes() + { + return $this->getMarcFieldWithInd('544', range('a', 'z'), [[1 => ['', '0']]]); + } + + /** + * Get an array of summary strings for the record with only the 'a' subfield. + * + * @return array + */ + public function getSummary() + { + return $this->getMarcFieldWithInd('520', ['a'], [[1 => ['', '0', '2', '8']]]); + } + + /** + * Get the summary note + * + * @return array Note fields from the MARC record + */ + public function getSummaryNotes() + { + return $this->getMarcFieldWithInd('520', range('a', 'z'), [[1 => ['', '0', '2', '8']]]); + } + + /** + * Get the abstract notes + * + * @return array Note fields from the MARC record + */ + public function getAbstractNotes() + { + return $this->getMarcFieldWithInd('520', range('a', 'z'), [[1 => ['3']]]); + } } diff --git a/module/VuFind/tests/fixtures/marc/marctraits.xml b/module/VuFind/tests/fixtures/marc/marctraits.xml index 8af2c699fdc..046a8180e7f 100644 --- a/module/VuFind/tests/fixtures/marc/marctraits.xml +++ b/module/VuFind/tests/fixtures/marc/marctraits.xml @@ -135,12 +135,28 @@ Summary. Expanded. + + Review Note. + Expanded. + http://expanded. + + + Abstract. + Expanded. + + + Content Advice. + Expanded. + Developers Data in UTF-8 + + Location of archival materials + Finding aid available diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/EDSTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/EDSTest.php index 34730db3e17..655a34a1596 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/EDSTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/EDSTest.php @@ -564,17 +564,19 @@ public function testGetLinkedFullTextLink(): void } /** - * Test getItemsSubjects for a record. + * Test getAllSubjectHeadingsFlattened for a record. * * @return void */ - public function testGetItemsSubjects(): void + public function testGetAllSubjectHeadingsFlattened(): void { $driver = $this->getDriver('valid-eds-record'); $this->assertEquals( - 'PSYCHOTHERAPY, ' . - 'METAPHOR.', - $driver->getItemsSubjects() + [ + 'PSYCHOTHERAPY', + 'METAPHOR', + ], + $driver->getAllSubjectHeadingsFlattened() ); } diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcAdvancedTraitTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcAdvancedTraitTest.php index 139dce0779e..0abd637b9ba 100644 --- a/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcAdvancedTraitTest.php +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/RecordDriver/Feature/MarcAdvancedTraitTest.php @@ -204,4 +204,146 @@ public function testMarcAdvancedTraitAltScript(): void $obj->getTitleSectionsAltScript() ); } + + /** + * Test getMarcFieldWithInd when a single indicator value is sent + * + * @return void + */ + public function testGetMarcFieldWithIndOneValue(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + // Test when a single ind value is passed + $this->assertEquals( + ['upc'], + $obj->getMarcFieldWithInd('024', null, [['1' => ['1']]]) + ); + } + + /** + * Test getMarcFieldWithInd when multiple values for the indicator are sent + * + * @return void + */ + public function testGetMarcFieldWithIndTwoValues(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + // Test when multiple values for the same ind are passed + $this->assertEquals( + ['upc', 'ismn'], + $obj->getMarcFieldWithInd('024', null, [['1' => ['1', '2']]]) + ); + } + + /** + * Test getMarcFieldWithInd when multiple indicators are requested + * as AND conditions + * + * @return void + */ + public function testGetMarcFieldWithIndMultAndInds(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + // Test when different ind values are passed for each ind + $this->assertEquals( + ['spa'], + $obj->getMarcFieldWithInd('041', null, [['1' => ['1'], '2' => ['7']]]) + ); + } + + /** + * Test getMarcFieldWithInd when multiple indicators are requested + * as OR conditions + * + * @return void + */ + public function testGetMarcFieldWithIndMultOrInds(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + // Test when different ind values are passed for each ind + $this->assertEquals( + ['ger', 'spa'], + $obj->getMarcFieldWithInd('041', null, [['1' => ['0']], ['2' => ['7']]]) + ); + } + + /** + * Test getMarcFieldWithInd when no indicator filters are sent + * + * @return void + */ + public function testGetMarcFieldWithIndNoValues(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + // Test when no indicator is passed + $this->assertEquals( + ['upc', 'ismn', 'ian'], + $obj->getMarcFieldWithInd('024', null, []) + ); + } + + /** + * Test calling getSummary to get expected marc data + * + * @return void + */ + public function testGetSummary(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + $this->assertEquals( + ['Summary.'], + $obj->getSummary() + ); + } + + /** + * Test calling getSummaryNotes to get expected marc data + * + * @return void + */ + public function testGetSummaryNotes(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + $this->assertEquals( + ['Summary. Expanded.'], + $obj->getSummaryNotes() + ); + } + + /** + * Test calling getAbstractNotes to get expected marc data + * + * @return void + */ + public function testGetAbstractNotes(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + $this->assertEquals( + ['Abstract. Expanded.'], + $obj->getAbstractNotes() + ); + } + + /** + * Test calling getLocationOfArchivalMaterialsNotes to get expected marc data + * + * @return void + */ + public function testGetLocationOfArchivalMaterialsNotes(): void + { + $obj = $this->getMockDriverFromFixture('marc/marctraits.xml'); + + $this->assertEquals( + ['Location of archival materials'], + $obj->getLocationOfArchivalMaterialsNotes() + ); + } } diff --git a/themes/root/templates/RecordDriver/AbstractBase/export-ris.phtml b/themes/root/templates/RecordDriver/AbstractBase/export-ris.phtml index db3cfcbc5d6..dc934505bc2 100644 --- a/themes/root/templates/RecordDriver/AbstractBase/export-ris.phtml +++ b/themes/root/templates/RecordDriver/AbstractBase/export-ris.phtml @@ -29,6 +29,12 @@ if (!empty($title)) { echo 'TI - ' . "$title\r\n"; } +$shortTitle = $this->driver->getShortTitle(); +if (!empty($shortTitle)) { + $shortTitle = rtrim(rtrim($shortTitle, ' /'), ' :'); + echo 'ST - ' . "$shortTitle\r\n"; +} + if (!empty($journalTitle)) { if (empty($title)) { echo 'TI - ' . "$journalTitle\r\n"; @@ -111,7 +117,7 @@ if (is_array($genres)) { } } -$topics = $this->driver->tryMethod('getTopics'); +$topics = $this->driver->tryMethod('getAllSubjectHeadingsFlattened'); if (is_array($topics)) { foreach ($topics as $topic) { echo 'KW - ' . "$topic\r\n"; @@ -149,12 +155,49 @@ if (is_array($notes)) { } } +echo 'L4 - ' . $this->serverUrl($this->recordLinker()->getUrl($this->driver)) . "\r\n"; + foreach ($this->record($this->driver)->getUrlList() as $url) { echo 'UR - ' . "$url\r\n"; } if ($doi = $this->driver->tryMethod('getCleanDOI')) { - echo "DO - $doi\r\n"; + echo 'DO - ' . "$doi\r\n"; +} + +$summary = $this->driver->tryMethod('getSummaryNotes'); +if (is_array($summary)) { + foreach ($summary as $note) { + echo 'AB - ' . "$note\r\n"; + } +} + +$abstract = $this->driver->tryMethod('getAbstractNotes'); +if (is_array($abstract)) { + foreach ($abstract as $note) { + echo 'AB - ' . "$note\r\n"; + } +} + +$notes = $this->driver->tryMethod('getLocationOfArchivalMaterialsNotes'); +if (is_array($notes)) { + foreach ($notes as $note) { + echo 'AV - ' . "$note\r\n"; + } +} + +$call_numbers = $this->driver->tryMethod('getCallNumbers'); +if (is_array($call_numbers)) { + foreach ($call_numbers as $call_number) { + echo 'CN - ' . "$call_number\r\n"; + } +} + +$results = $this->driver->tryMethod('getFullTitlesAltScript'); +if (is_array($results)) { + foreach ($results as $result) { + echo 'J2 - ' . "$result\r\n"; + } } // End of Record: