scriptotek / marc
使用 File_MARC 解析 MARC 记录的简单界面
Requires
- php: >=8.0
- ext-json: *
- ext-simplexml: *
- ext-xml: *
- ck/file_marc_reference: ^1.2
- pear/file_marc: @dev
Requires (Dev)
- phpunit/phpunit: ^8.0 | ^9.0
- squizlabs/php_codesniffer: ^3.3
README
scriptotek/marc
此包提供了一个简单的界面,使用优秀的 File_MARC 和 MARCspec 包来处理 MARC21 记录。它本身不执行任何繁重的操作,而是
- 通过自动确定你传递给它的是二进制 MARC 还是 MARCXML(命名空间 XML 或不是),一系列记录在一个容器中或单个记录来简化加载数据。
- 为 MARCspec 添加了一些额外的便利方法和流畅的接口。
如果你不需要这些功能,你可能希望直接使用 File_MARC。
想要为这个项目贡献力量?请参阅 CONTRIBUTING.md。
使用 Composer 安装
如果你已经安装了 Composer,可以通过运行以下命令来安装此包:
composer require scriptotek/marc
读取记录
使用 Collection::fromFile
、Collection::fromString
或 Collection::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::fromFile
、Record::fromString
或 Record::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 字段 的 Subject
和 UncontrolledSubject
对象数组。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 boxes
、11 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.
。所以我们咬了一口草,开始编写规则;如果找到有效的匹配作为字符串的开始,则接受它,否则如果……,否则尝试标记化,等等……它很快就会变得混乱,并且肯定会在某些情况下失败。遗憾的是,在图书馆几年后,我仍然没有找到一种使用图书馆数据提取文档页数的一般方法。↩