PHPackages                             scriptotek/marc - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Parsing &amp; Serialization](/categories/parsing)
4. /
5. scriptotek/marc

ActiveLibrary[Parsing &amp; Serialization](/categories/parsing)

scriptotek/marc
===============

Simple interface to parsing MARC records using File\_MARC

v3.0.0(2y ago)5570.5k—6.4%13[5 issues](https://github.com/scriptotek/php-marc/issues)[2 PRs](https://github.com/scriptotek/php-marc/pulls)4MITPHPPHP &gt;=8.0

Since Aug 25Pushed 1y ago14 watchersCompare

[ Source](https://github.com/scriptotek/php-marc)[ Packagist](https://packagist.org/packages/scriptotek/marc)[ RSS](/packages/scriptotek-marc/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (4)Versions (19)Used By (4)

[![Coverage](https://camo.githubusercontent.com/cd570647dd55106dd5ea2d9c71136f39bc951370619a1cd485e29e786a4ad1e9/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636f762f632f6769746875622f7363726970746f74656b2f7068702d6d617263)](https://codecov.io/gh/scriptotek/php-marc)[![StyleCI](https://camo.githubusercontent.com/6a8bae502d719b528d7ee41bed8384ca955850f89f6cb5dd58572117885a9590/68747470733a2f2f6769746875622e7374796c6563692e696f2f7265706f732f34313336333139392f736869656c643f6272616e63683d6d61696e)](https://styleci.io/repos/41363199)[![Code Climate](https://camo.githubusercontent.com/553a2d778ac619c5007504be871ca4de53e564485d47236714e9196c3d8ae4b0/68747470733a2f2f696d672e736869656c64732e696f2f636f6465636c696d6174652f6d61696e7461696e6162696c6974792f7363726970746f74656b2f7068702d6d617263)](https://codeclimate.com/github/scriptotek/php-marc)[![Latest Stable Version](https://camo.githubusercontent.com/31a572518784058f57db59474c904779e50792dd3ceee618e781724f8c6f8b4e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7363726970746f74656b2f6d617263)](https://packagist.org/packages/scriptotek/marc)[![Total Downloads](https://camo.githubusercontent.com/c93a3e526a311793935c6afd27b8ce3b1e7aa20558b7efab9f41e2ec2f304b00/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7363726970746f74656b2f6d617263)](https://packagist.org/packages/scriptotek/marc)

scriptotek/marc
===============

[](#scriptotekmarc)

This package provides a simple interface to work with MARC21 records using the excellent [File\_MARC](https://github.com/pear/File_MARC) and [MARCspec](http://marcspec.github.io/)packages. It doesn't do any of the heavy lifting itself, but instead

- makes it a little bit easier to load data by automatically determining what you throw at it (Binary MARC or MARCXML, namespaced XML or not, a collection of records in some container or a single record).
- adds a few extra convenience methods and a fluent interface to MARCspec.

If you don't need any of this, you might want to use File\_MARC directly instead.

Want to contribute to this project? Please see [CONTRIBUTING.md](CONTRIBUTING.md).

Installation using Composer:
----------------------------

[](#installation-using-composer)

If you have [Composer](https://getcomposer.org/) installed, the package can be installed by running

```
composer require scriptotek/marc

```

Reading records
---------------

[](#reading-records)

Use `Collection::fromFile`, `Collection::fromString` or `Collection::fromSimpleXMLElement`to read one or more MARC records from a file or string. The methods autodetect the data format (Binary XML or MARCXML) and whether the XML is namespaced or not.

```
use Scriptotek\Marc\Collection;

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

The `$collection` object is an iterator. If you rather want a normal array, for instance in order to count the number of records, you can get that from `$collection->toArray()`.

The loader can extract MARC records from any container XML, so you can pass in an SRU or OAI-PMH response directly:

```
$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) {
    ...
}
```

If you only have a single record, you can also use `Record::fromFile`, `Record::fromString` or `Record::fromSimpleXMLElement`. These use the `Collection` methods under the hood, but returns a single `Record` object.

```
use Scriptotek\Marc\Record;

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

Editing records
---------------

[](#editing-records)

Records can be edited using the editing capabilities of File\_MARC ([API docs](https://pear.php.net/package/File_MARC/docs/latest/)). See [an example](https://github.com/scriptotek/php-marc/issues/13#issuecomment-522036879)to get started.

Querying with MARCspec
----------------------

[](#querying-with-marcspec)

Use the `Record::query()` method to query a record using the [MARCspec](http://marcspec.github.io/) language as implemented in the [php-marc-spec package](https://github.com/MARCspec/php-marc-spec) package. The method returns a `QueryResult` object, which is a small wrapper around `File_MARC_Reference`.

Example: To loop over all `650` fields having `$2 noubomn`:

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

or we could reference the subfield directly, like so:

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

You can retrieve single results using `first()`, which returns the first match, or `null` if no matches were found:

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

In the same way, `text()` returns the data content of the first match, or `null`if no matches were found:

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

Convenience methods on the Record class
---------------------------------------

[](#convenience-methods-on-the-record-class)

The `Record` class extends `File_MARC_Record` with a few convenience methods to get data from commonly used fields. Each of these methods, except `getType()`, returns an object or an array of objects of one of the field classes (located in `src/Fields`). For instance `getIsbns()` returns an array of `Scriptotek\Marc\Isbn` objects. All the field classes implements at minimum a `__toString()` method so you easily can get a string representation of the field for presentation purpose.

Note that all the get methods can also be accessed as attributes thanks to a little PHP magic (`__get`). So instead of calling `$record->getId()`, you can use the shorthand variant `$record->id`.

### type

[](#type)

`$record->getType()` or `$record->type` returns either 'Bibliographic', 'Authority' or 'Holdings' based on the value of the sixth character in the leader. See `Marc21.php` for supporting constants.

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

### catalogingForm

[](#catalogingform)

`$record->getCatalogingForm()` or `$record->catalogingForm` returns the value of LDR/18. See `Marc21.php` for supporting constants.

### id

[](#id)

`$record->getId()` or `$record->id` returns the record id from 001 control field.

### isbns

[](#isbns)

`$record->getIsbns()` or `$record->isbns` returns an array of `Isbn` objects from 020 fields.

```
use Scriptotek\Marc\Record;

$record = Record::fromString('

    99999cam a2299999 u 4500
    98218834x

      8200424421
      h.
      Nkr 98.00

  ');
$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.'
```

### title

[](#title)

`$record->getTitle()` or `$record->title` returns a `Title` objects from 245 field, or null if no such field is present.

Beware that the default string representation may or may not fit your needs. It's currently a concatenation of `$a` (title), `$b` (remainder of title), `$n`(part number) and `$p` (part title). For the remaining subfields like `$f`, `$g` and `$k`, I haven't decided whether to handle them or not.

Parallel titles are unfortunately encoded in such a way that there's no way I'm aware of to identify them in a secure manner, meaning there's also no secure way to remove them if you don't want to include them.[1](#f1)

I'm trimming off any final '`/`' ISBD marker. I would have loved to be able to also trim off final dots, but that's not trivial for the same reason identifying parallel titles is not[1](#f1) – there's just no safe way to tell if the final dot is an ISBD marker or part of the title.[2](#f2) Since explicit ISBD markers are included in records catalogued in the American tradition, but not in records catalogued in the British tradition, a mix of records from both traditions will look silly.

### subjects

[](#subjects)

`$record->getSubjects($vocabulary, $tag)` or `$record->subjects` returns an array of `Subject` and `UncontrolledSubject` objects from all [the 6XX fields](http://www.loc.gov/marc/bibliographic/bd6xx.html). The `getSubjects()` method have two optional arguments you can use to limit by vocabulary and/or tag.

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

Static options:

- `Subject::glue` (default: `:`) defines what string is used to glue the subfields together in the string representation. For instance, `650 $aPhysics $xHistory $yHistory`becomes `Physics : History : 20th century` when using `:` as glue, or `Physics--History--20th century` with `'--'`.
- `Subject::chopPunctuation` (default: `true`) defines if ending punctuation (.:,;/) is to be chopped off at the end of subjects. Usually, any ending punctuation is an ISBD character that can be safely chopped off, but it might also indicate an abbreviation, and unfortunately there is no way to know.

Notes
-----

[](#notes)

It's unfortunately easy to err when trying to present data from MARC records in end user applications. A developer learning by example might for instance assume that `300 $a` is a subfield for "number of pages".[3](#f3) A quick glance at e.g. [LC's MARC documentation](https://www.loc.gov/marc/bibliographic/bd300.html) would be enough to prove that wrong, but in other cases it's harder to avoid making false assumptions without deep familiarity with cataloguing rules and practices.

**1** That might change in the future. But even if I decide to remove parallel titles, I'm not really sure how to do it in a safe way. Parallel titles are identified by a leading `=`ISBD marker. If the marker is at the end of subfield `$a`, we can be certain it's an ISBD marker, but since the `$a` and `$c` subfields are not repeatable, multiple titles are just added to the `$c` subfield. So if we encounter an `=` sign in the middle middle of `$c` somewhere, how can we tell if it's an ISBD marker or just an equal sign part of the title (like in the fictive book `"$aEating the right way : The 2 + 2 = 5 diet"`)? Some kind of escaping would have made that clear, but the ISBD principles doesn't seem to call for that, leaving us completely in the dark. *That* is seriously annoying 😩 [↩](#a1)

**2** [According to](http://www.loc.gov/marc/bibliographic/bd245.html)ISBD principles "field 245 ends with a period, even when another mark of punctuation is present, unless the last word in the field is an abbreviation, initial/letter, or data that ends with final punctuation." Determining if something is "an abbreviation, initial/letter, or data that ends with final punctuation" is certainly not an easy task for anything but humans and AI. [↩](#a2)

**3** Our old OPAC used to output something like "Number of pages: One video disc (DVD)…" for DVDs – the developers had apparently just assumed that the content of `300 $a` could be represented as "number of pages" in all cases. While that sounds silly, getting the *number* of pages (for documents that actually have pages) from MARC records can be ridiculously hard; you can safely extract the number from strings like `149 p.` (English), `149 s.` (Norwegian), etc., but you must ignore the numbers in strings like `10 boxes`, `11 v.` (volumes) etc. So for a start you need a list of valid abbreviations for "pages" in all relevant languages. Then there's the more complicated cases like `1 score (16 p.)` – at first sight it looks like we can tokenize that into (number, unit) pairs, like `("1 score", "16 p.")` and only accept the item(s) having an allowed unit (like `p.`). But then suddenly comes a case like `"74 p. of ill., 15 p."`, which we would turn into `("74 p. of ill.", "15 p.")`, accepting `15 p.`, not the correct `74 p.`. So we bite into the grass and start writing rules; if a valid match is found as the start of the string, then accept it, else if …, else try tokenization, etc... it quickly becomes messy and it will certainly fail in some cases. Sad to say, after a few years in the library, I still haven't figured out a general way to extract the number of pages a document have using library data. [↩](#a3)

###  Health Score

46

—

FairBetter than 93% of packages

Maintenance25

Infrequent updates — may be unmaintained

Popularity44

Moderate usage in the ecosystem

Community25

Small or concentrated contributor base

Maturity77

Established project with proven stability

 Bus Factor1

Top contributor holds 88.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~182 days

Recently: every ~315 days

Total

18

Last Release

811d ago

Major Versions

v0.3.2 → v1.0.02017-07-02

v1.0.1 → v2.0.02018-10-23

v2.2.3 → v3.0.02024-02-27

PHP version history (5 changes)v0.1.0PHP &gt;=5.3

v0.2.0PHP &gt;=5.4

v0.3.2PHP &gt;=5.5

v1.0.0PHP &gt;=5.6

v2.2.2PHP &gt;=8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/87db1d80793d2adbeca18997c33acbb5cf1dec75ccc5c19f147d666dfbf2e990?d=identicon)[danmichaelo](/maintainers/danmichaelo)

---

Top Contributors

[![danmichaelo](https://avatars.githubusercontent.com/u/434495?v=4)](https://github.com/danmichaelo "danmichaelo (134 commits)")[![rudolfbyker](https://avatars.githubusercontent.com/u/10025342?v=4)](https://github.com/rudolfbyker "rudolfbyker (16 commits)")[![cKlee](https://avatars.githubusercontent.com/u/849235?v=4)](https://github.com/cKlee "cKlee (1 commits)")[![gerricom](https://avatars.githubusercontent.com/u/459469?v=4)](https://github.com/gerricom "gerricom (1 commits)")

---

Tags

marc-recordsmarc21marcxmlmarc

###  Code Quality

TestsPHPUnit

Code StylePHP\_CodeSniffer

### Embed Badge

![Health badge](/badges/scriptotek-marc/health.svg)

```
[![Health](https://phpackages.com/badges/scriptotek-marc/health.svg)](https://phpackages.com/packages/scriptotek-marc)
```

###  Alternatives

[masterminds/html5

An HTML5 parser and serializer.

1.8k242.8M229](/packages/masterminds-html5)[sabberworm/php-css-parser

Parser for CSS Files written in PHP

1.8k191.2M65](/packages/sabberworm-php-css-parser)[jms/metadata

Class/method/property metadata management in PHP

1.8k152.8M88](/packages/jms-metadata)[jms/serializer-bundle

Allows you to easily serialize, and deserialize data of any complexity

1.8k89.3M627](/packages/jms-serializer-bundle)[hassankhan/config

Lightweight configuration file loader that supports PHP, INI, XML, JSON, and YAML files

97513.5M170](/packages/hassankhan-config)[meyfa/php-svg

Read, edit, write, and render SVG files with PHP

54613.9M42](/packages/meyfa-php-svg)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
