使用 File_MARC 解析 MARC 记录的简单界面

v3.0.0 2024-02-27 22:45 UTC

This package is auto-updated.

Last update: 2024-08-27 23:52:25 UTC


README

Coverage StyleCI Code Climate Latest Stable Version Total Downloads

scriptotek/marc

此包提供了一个简单的界面,使用优秀的 File_MARCMARCspec 包来处理 MARC21 记录。它本身不执行任何繁重的操作,而是

  • 通过自动确定你传递给它的是二进制 MARC 还是 MARCXML(命名空间 XML 或不是),一系列记录在一个容器中或单个记录来简化加载数据。
  • 为 MARCspec 添加了一些额外的便利方法和流畅的接口。

如果你不需要这些功能,你可能希望直接使用 File_MARC。

想要为这个项目贡献力量?请参阅 CONTRIBUTING.md

使用 Composer 安装

如果你已经安装了 Composer,可以通过运行以下命令来安装此包:

composer require scriptotek/marc

读取记录

使用 Collection::fromFileCollection::fromStringCollection::fromSimpleXMLElement 从文件或字符串中读取一个或多个 MARC 记录。这些方法会自动检测数据格式(二进制 XML 或 MARCXML)以及 XML 是否有命名空间。

use Scriptotek\Marc\Collection;

$collection = Collection::fromFile($someFileName);
foreach ($collection as $record) {
    echo $record->getField('250')->getSubfield('a')->getData() . "\n";
}

$collection 对象是一个迭代器。如果你需要一个常规数组,例如为了计算记录数,你可以通过 $collection->toArray() 获取。

加载器可以从任何容器 XML 中提取 MARC 记录,因此你可以直接传递 SRU 或 OAI-PMH 响应。

$response = file_get_contents('http://lx2.loc.gov:210/lcdb?' . http_build_query([
    'operation'      => 'searchRetrieve',
    'recordSchema'   => 'marcxml',
    'version'        => '1.1',
    'maximumRecords' => '10',
    'query'          => 'bath.isbn=0761532692',
]));

$records = Collection::fromString($response);
foreach ($records as $record) {
    ...
}

如果你只有一个记录,你也可以使用 Record::fromFileRecord::fromStringRecord::fromSimpleXMLElement。这些方法在内部使用 Collection 方法,但返回一个单个 Record 对象。

use Scriptotek\Marc\Record;

$record = Record::fromFile($someFileName);

编辑记录

可以使用 File_MARC 的编辑功能来编辑记录(API 文档)。查看 示例 以开始。

使用 MARCspec 查询

使用 MARCspec 语言(在 php-marc-spec 包 中实现)通过 Record::query() 方法查询记录。该方法返回一个 QueryResult 对象,这是一个围绕 File_MARC_Reference 的小型包装器。

示例:遍历所有 650 字段且 $2 noubomn

foreach ($record->query('650{$2=\noubomn}') as $field) {
   echo $field->getSubfield('a')->getData();
}

或我们可以直接引用子字段,如下所示

foreach ($record->query('650$a{$2=\noubomn}') as $subfield) {
   echo $subfield->getData();
}

你可以使用 first() 检索单个结果,它返回第一个匹配项,如果没有找到匹配项则返回 null

$record->query('250$a')->first();

同样,text() 返回第一个匹配项的数据内容,如果没有找到匹配项则返回 null

$record->query('250$a')->text();

Record 类上的便利方法

Record 类扩展了 File_MARC_Record,并添加了一些方便的方法来从常用字段中获取数据。除了 getType() 方法外,这些方法都返回一个对象或对象数组,对象类型属于字段类之一(位于 src/Fields)。例如,getIsbns() 返回一个 Scriptotek\Marc\Isbn 对象的数组。所有字段类至少实现了一个 __toString() 方法,因此您可以轻松地获取用于展示的字段字符串表示。

请注意,所有获取方法都可以通过 PHP 魔法(__get)作为属性访问。因此,您可以使用简写形式 $record->id 而不是调用 $record->getId()

类型

$record->getType()$record->type 根据控制字段中第六个字符的值返回 'Bibliographic'、'Authority' 或 'Holdings'。有关支持常量的信息,请参阅 Marc21.php

if ($record->type == Marc21::BIBLIOGRAPHIC) {
    // ...
}

编目形式

$record->getCatalogingForm()$record->catalogingForm 返回 LDR/18 的值。有关支持常量的信息,请参阅 Marc21.php

标识

$record->getId()$record->id 返回来自 001 控制字段的记录标识。

ISBN

$record->getIsbns()$record->isbns 返回来自 020 字段的 Isbn 对象数组。

use Scriptotek\Marc\Record;

$record = Record::fromString('<?xml version="1.0" encoding="UTF-8" ?>
  <record xmlns="""http://www.loc.gov/MARC21/slim">
    <leader>99999cam a2299999 u 4500</leader>
    <controlfield tag="001">98218834x</controlfield>
    <datafield tag="020" ind1=" " ind2=" ">
      <subfield code="a">8200424421</subfield>
      <subfield code="q">h.</subfield>
      <subfield code="c">Nkr 98.00</subfield>
    </datafield>
  </record>');
$isbn = $record->isbns[0];

// Get the string representation of the field:
echo $isbn . "\n";  // '8200424421'

// Get the value of $q using the standard FILE_MARC interface:
echo $isbn->getSubfield('q')->getData() . "\n";  // 'h.'

// or using the shorthand `sf()` method from the Field class:
echo $isbn->sf('q') . "\n";  // 'h.'

标题

$record->getTitle()$record->title 返回来自 245 字段的 Title 对象,如果不存在此类字段,则返回 null。

请注意,默认的字符串表示可能或可能不适合您的需求。它目前是 $a(标题)、$b(标题剩余部分)、$n(部分号)和 $p(部分标题)的连接。对于剩余的子字段(如 $f$g$k),我尚未决定是否处理它们。

不幸的是,副标题的编码方式使得无法以安全的方式识别它们,这意味着如果您不想包含它们,也没有安全的方式删除它们。1

我将剪除任何最后的 '/' ISBD 标记。我非常希望能够也剪除最后的点,但这并不简单,原因与识别副标题相同——没有安全的方法来判断最后的点是否是 ISBD 标记或标题的一部分。2 由于美国传统编目的记录中包含显式的 ISBD 标记,而英国传统编目的记录中不包含,因此两种传统记录的混合看起来会很奇怪。

主题

$record->getSubjects($vocabulary, $tag)$record->subjects 返回来自所有 6XX 字段SubjectUncontrolledSubject 对象数组。getSubjects() 方法有两个可选参数,您可以使用这些参数来限制词汇表和/或标记。

foreach ($record->getSubjects('mesh', Subject::TOPICAL_TERM) as $subject) {
    echo "{$subject->vocabulary} {$subject->type} {$subject}";
}

静态选项

  • Subject::glue(默认::)定义了用于将子字段连接在一起的字符串。例如,650 $aPhysics $xHistory $yHistory 在使用 : 作为粘合剂时变为 Physics : History : 20th century,或使用 '--' 时变为 Physics--History--20th century
  • Subject::chopPunctuation(默认:true)定义了是否在主题的末尾剪除结束标点(.:,;/)。通常,任何结束标点都是一个可以安全剪除的 ISBD 字符,但它也可能表示缩写,而且不幸的是,没有方法可以确定。

备注

在尝试在终端用户应用程序中展示MARC记录中的数据时,很容易出错。一个通过例子学习的开发者可能会错误地认为300 $a是“页数”的一个子字段。3只需快速浏览一下,例如LC的MARC文档,就能证明这是错误的,但在其他情况下,如果没有深入了解编目规则和实践,很难避免做出错误的假设。

1 这可能会在未来改变。但即使我决定删除平行标题,我也不太确定如何安全地做到这一点。平行标题是通过前面的= ISBD标记来识别的。如果这个标记位于子字段$a的末尾,我们可以确定它是一个ISBD标记,但是由于$a$c子字段是不可重复的,多个标题只是添加到$c子字段。所以如果我们遇到在$c中某个地方的中间的=符号,我们怎么知道它是一个ISBD标记,还是标题(如虚构书籍"$aEating the right way : The 2 + 2 = 5 diet")中等于符号的一部分呢?某种形式的转义会使得这一点变得清晰,但ISBD原则似乎并没有要求这样做,这让我们完全处于迷茫之中。真的很令人烦恼 😩

2 根据ISBD原则,“字段245以句号结束,即使存在其他标点符号,除非字段中的最后一个单词是缩写、首字母或以标点符号结束的数据。”确定某物是否是“缩写、首字母或以标点符号结束的数据”对除了人类和人工智能之外的所有事物来说都不是一件容易的任务。

3 我们旧的OPAC曾经输出类似“页数:一盒影碟(DVD)……”的内容用于DVD – 显然开发者只是假设300 $a的内容在所有情况下都可以表示为“页数”。虽然这听起来很愚蠢,但从MARC记录中获取(实际有页面的)文档的页数可能会非常困难;你可以安全地从像149 p.(英语)、149 s.(挪威语)等字符串中提取数字,但你必须忽略像10 boxes11 v.(卷)等字符串中的数字。所以首先你需要一份包含所有相关语言的“页数”有效缩写列表。然后还有更复杂的情况,比如1 score (16 p.) – 初看起来我们似乎可以将它分解为(数字,单位)对,如("1 score", "16 p."),并只接受有允许单位的项(如p.)。但是突然出现一个像"74 p. of ill., 15 p."的情况,我们会将其转换为("74 p. of ill.", "15 p."),接受15 p.,而不是正确的74 p.。所以我们咬了一口草,开始编写规则;如果找到有效的匹配作为字符串的开始,则接受它,否则如果……,否则尝试标记化,等等……它很快就会变得混乱,并且肯定会在某些情况下失败。遗憾的是,在图书馆几年后,我仍然没有找到一种使用图书馆数据提取文档页数的一般方法。