<commit_before><?php namespace Scriptotek\SimpleMarcParser;

use Illuminate\Support\Contracts\JsonableInterface;
use Danmichaelo\QuiteSimpleXmlElement\QuiteSimpleXmlElement;

/**
 * @property int       $id                 Local record identifier
 * @property string    $material
 * @property boolean   $electronic
 * @property boolean   $is_series
 * @property boolean   $is_multivolume
 * @property boolean
 * @property string    $agency
 * @property string    $lccn
 * @property string    $catalogingRules
 * @property string    $title
 * @property string    $part_no
 * @property string    $part_name
 * @property string    $part_of
 * @property string    $medium
 * @property string    $edition
 * @property string    $publisher
 * @property string    $extent
 * @property string    $contents
 * @property string    $summary
 * @property string    $cover_image
 * @property string    $description
 * @property string    $preceding
 * @property string    $succeeding
 * @property string[]  $isbns
 * @property string[]  $notes
 * @property array     $series
 * @property array     $other_form
 * @property array     $creators
 * @property array     $meetings
 * @property array     $subjects
 * @property array     $genres
 * @property array     $classifications
 * @property int       $pages
 * @property int       $year
 * @property Carbon\Carbon    $modified
 * @property Carbon\Carbon    $created
 */
class BibliographicRecord extends Record implements JsonableInterface {


    /**
     * @param string $x1
     * @param string $x2
     * @param string $default
     * @return string
     */
    public function getMaterialSubtypeFrom007($x1, $x2, $default = 'Unknown')
    {
         $f007values = array(
            'a' => array(
                'd' => 'Atlas',
                'g' => 'Diagram',
                'j' => 'Map',
                'k' => 'Profile',
                'q' => 'Model',
                'r' => 'Remote-sensing image',
                '_' => 'Map',
            ),
            'c' => array(
                'a' => 'Tape cartridge',
                'b' => 'Chip cartridge',
                'c' => 'Computer optical disc cartridge',
                'd' => 'Computer disc, type unspecified',
                'e' => 'Computer disc cartridge, type unspecified',
                'f' => 'Tape cassette',
                'h' => 'Tape reel',
                'j' => 'Magnetic disk',
                'k' => 'Computer card',
                'm' => 'Magneto-optical disc',
                'o' => 'CD-ROM',                // Optical disc
                'r' => 'Remote resource',    // n Nettdokumenter
            ),
            'f' => array(
                'a' => 'Moon',         // in the Moon writing system
                'b' => 'Braille',      // in the Braille writing system
                'c' => 'Combination ', // in a combination of two or more of the other defined types
                'd' => 'No writing system',
            ),
            'o' => array(
                'u' => 'Kit',
                '|' => 'Kit',
            ),
            's' => array(
                'd' => 'Music CD',             // v CD-er
                'e' => 'Cylinder',
                'g' => 'Sound cartridge',
                'i' => 'Sound-track film',
                'q' => 'Roll',
                's' => 'Sound cassette',
                't' => 'Sound-tape reel',
                'u' => 'Unspecified',
                'w' => 'Wire recording',
            ),
            'v' => array(
                'c' => 'Videocartridge',
                'd' => 'Videodisc',           // w DVD-er
                'f' => 'Videocassette',
                'r' => 'Videoreel',
            ),
        );
        if (isset($f007values[$x1]) && isset($f007values[$x1][$x2])) {
            return $f007values[$x1][$x2];
        }
        // TODO: LOG IT!
        return $default;
    }

    /**
     * @param \Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement $data
     */
    protected function parseMaterial(QuiteSimpleXmlElement $data)
    {
        // See http://www.loc.gov/marc/ldr06guide.html

        // The Leader/06 (Type of record) character position contains a one-character 
        // alphabetic code that differentiates MARC records created for various types of
        // content and materials.

        // The 008 field contains character positions that provide coded information about
        // the record as a whole and about special bibliographic aspects of the item cataloged.

        // Field 006 (Fixed-length data elements - Additional material characteristics) 
        // permits coding for additional aspects of an electronic resource (including any 
        // computer file aspects) if the 007 and 008 fields do not adequately describe it.

        // leader[6] : Type of record
        // leader[7] : Bibliographic level
        // Field 008/18-34 Configuration

        // LDR/06
        $recordTypes = array(
            'a' => 'Language material',
            'c' => 'Notated music',
            'd' => 'Manuscript notated music',
            'e' => 'Cartographic material',
            'f' => 'Manuscript cartographic material',
            'g' => 'Projected medium',
            'i' => 'Nonmusical sound recording',
            'j' => 'Musical sound recording',
            'k' => 'Two-dimensional nonprojectable graphic',
            'm' => 'Computer file',
            'o' => 'Kit',
            'p' => 'Mixed materials',
            'r' => 'Three-dimensional artifact or naturally occurring object',
            't' => 'Manuscript language material',
        );

        // LDR/07
        $bibliographicLevels = array(
            'a' => 'Monographic component part',
            'b' => 'Serial component part',
            'c' => 'Collection',
            'd' => 'Subunit',
            'i' => 'Integrating resource',
            'm' => 'Monograph/Item',
            's' => 'Serial',
        );

        // 007/00 (Category of material)
        $materialCategories = array(
            'a' => 'Map',
            'c' => 'Electronic resource',
            'd' => 'Globe',
            'f' => 'Tactile material',
            'g' => 'Projected graphic',
            'h' => 'Microform',
            'k' => 'Nonprojected graphic',
            'm' => 'Motion picture',
            'o' => 'Kit',
            'q' => 'Notated music',
            'r' => 'Remote-sensing image',
            's' => 'Sound recording',
            't' => 'Text',                 // p Trykt materiale
            'v' => 'Videorecording',
            'z' => 'Unspecified',
        );

        // 008/24-27 - Nature of contents (006/07-10)
        $natureOfContents = array(
            'a' => 'Abstract',
            'b' => 'Bibliography',
            'c' => 'Catalog',
            'd' => 'Dictionary',
            'e' => 'Encyclopedia',
            'f' => 'Handbook',
            'g' => 'Legal article',
            'i' => 'Index',
            'j' => 'Patent document',
            'k' => 'Discography',
            'l' => 'Legislation',
            'm' => 'Thesis',
            'n' => 'Surveys of literature in a subject area',
            'o' => 'Review',
            'p' => 'Programmed text',
            'q' => 'Filmography',
            'r' => 'Directory',
            's' => 'Statistics',
            't' => 'Technical report',
            'u' => 'Standards/specification',
            'v' => 'Legal cases and case notes',
            'w' => 'Law reports and digests',
            'y' => 'Yearbook',
            'z' => 'Treaty',
            '2' => 'Offprint',
            '5' => 'Calendar',
            '6' => 'Comics/graphic novel',
        );

        $videoFormats = array(
            'a' => 'Beta (1/2 in., videocassette)',
            'b' => 'VHS (1/2 in., videocassette)',
            'c' => 'U-matic (3/4 in., videocasstte)',
            'd' => 'EIAJ (1/2 in., reel)',
            'e' => 'Type C (1 in., reel)',
            'f' => 'Quadruplex (1 in. or 2 in., reel)',
            'g' => 'Laserdisc',
            'h' => 'CED (Capacitance Electronic Disc) videodisc',
            'i' => 'Betacam (1/2 in., videocassette)',
            'j' => 'Betacam SP (1/2 in., videocassette)',
            'k' => 'Super-VHS (1/2 in., videocassette)',
            'm' => 'M-II (1/2 in., videocassette)',
            'o' => 'D-2 (3/4 in., videocassette)',
            'p' => '8 mm.',
            'q' => 'Hi-8 mm.',
            's' => 'Blu-ray',
            'u' => 'Unknown',
            'v' => 'DVD',
        );

        // If Leader/06 = a and Leader/07 = a, c, d, or m: Books
        // If Leader/06 = a and Leader/07 = b, i, or s: Continuing Resources
        // If Leader/06 = t: Books
        // If Leader/06 = c, d, i, or j: Music
        // If Leader/06 = e, or f: Maps
        // If Leader/06 = g, k, o, or r: Visual Materials
        // If Leader/06 = m: Computer Files
        // If Leader/06 = p: Mixed Materials

        $ldr = str_split($data->text('marc:leader'));
        $f007 = str_split($data->text('marc:controlfield[@tag="007"]'));
        $f008 = str_split($data->text('marc:controlfield[@tag="008"]'));

        $material = 'Unknown';
        $this->material = $material;
        $this->electronic = false;

        if (count($ldr) < 8) return;
        if (count($f007) < 2) return;

        switch ($ldr[6]) {

            case 'a':
                if (in_array($ldr[7], array('a','c','d','m'))) {
                    $material = 'Book';
                }
                if (in_array($ldr[7], array('b','i','s'))) {
                    $material = 'Series';
                }
                break;

            case 't':
                $material = 'Book';
                break;

            case 'c':
            case 'd':
            case 'i':
            case 'j':
                $material = 'Music';
                break;

            case 'e':
            case 'f':
                $material = 'Map';
                break;

            case 'g':
            case 'k':
            case 'o':
            case 'r':
                $material = 'Visual';
                break;

            case 'm':
                $material = 'File';
                // used for computer software, numeric data, not for e-books or e-journals!
                break;

            case 'p':
                $material = 'Mixed';
                break;

        }

        $online = ($f007[0] == 'c' && $f007[1] == 'r');

        if ($material == 'File') {
            $material = $this->getMaterialSubtypeFrom007($f007[0], $f007[1], $material);


        } else if ($material == 'Visual') {
            $material = $this->getMaterialSubtypeFrom007($f007[0], $f007[1], $material);


            if (isset($f007[4]) && isset($videoFormats[$f007[4]])) {
                $material = $videoFormats[$f007[4]]; // DVD, Blu-ray            
            }

        } else if ($material == 'Music') {
            if ($f007[0] == 't') {
                $material = 'Sheet music';
            } else {
                $material = $this->getMaterialSubtypeFrom007($f007[0], $f007[1], $material);
            }

        } else if ($material == 'Series') {
            switch ($f008[21]) {
                case 'p':
                    $material = 'Periodical';
                    break;

                case 'm':
                    $material = 'Series';  // Monographic series (merk: skiller ikke mellom 'flerbindsverk' og 'serieinnførsel')
                    break;

            }
        } else if ($material == 'Book') {
            if (isset($natureOfContents[$f008[24]])) {
                // Slight simplification
                $material = $natureOfContents[$f008[24]];
            }
        }

        $this->material = $material;
        $this->electronic = $online;
    }

    /**
     * Parses common elements in subject added entry fields 600-655
     *
     * @param \Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement $node
     */
    function parseSubjectAddedEntry(QuiteSimpleXmlElement &$node) {
        $out = array('term' => '');
        $vocabularies = array(
            '0' => 'lcsh',
            '1' => 'lccsh', // LC subject headings for children's literature
            '2' => 'mesh', // Medical Subject Headings
            '3' => 'atg', // National Agricultural Library subject authority file (?)
            // 4 : unknown
            '5' => 'cash', // Canadian Subject Headings
            '6' => 'rvm', // Répertoire de vedettes-matière
            // 7: Source specified in subfield $2
        );
        $ind2 = $node->attr('ind2');

        $id = $node->text('marc:subfield[@code="0"]');
        $out['id'] = empty($id) ? null : $id;

        if (isset($vocabularies[$ind2])) {
            $out['vocabulary'] = $vocabularies[$ind2];
        } elseif ($ind2 == '7') {
            $vocab = $node->text('marc:subfield[@code="2"]');
            if (!empty($vocab)) {
                $out['vocabulary'] = $vocab;
            }
        } elseif ($ind2 == '4') {
            $this->parseAuthority($node->text('marc:subfield[@code="0"]'), $out);
        }

        $out['parts'] = array();
        $subdivtypes = array(
            'v' => 'form',
            'x' => 'general',
            'y' => 'chronological',
            'z' => 'geographic',
        );
        foreach ($node->all('marc:subfield') as $subdiv) {
            $code = $subdiv->attr('code');
            if (in_array($code, array_keys($subdivtypes))) {
                $subdiv = trim($subdiv, '.');
                $out['parts'][] = array('value' => $subdiv, 'type' => $subdivtypes[$code]);
                $out['term'] .= self::$subfieldSeparator . $subdiv;
            }
        }
        return $out;
    }

    /**
     * @param \Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement $data
     */
    public function __construct(QuiteSimpleXmlElement $data) {

        $this->parseMaterial($data);

        // Control fields
        $this->id = $data->text('marc:controlfield[@tag="001"]');

        // 003: MARC code for the agency whose system control number is
        // contained in field 001 (Control Number)
        // See http://www.loc.gov/marc/authority/ecadorg.html
        $this->agency = $data->text('marc:controlfield[@tag="003"]');

        // 005: Modified
        $this->modified = $this->parseDateTime($data->text('marc:controlfield[@tag="005"]'));

        // 008: Extract *some* information
        $f008 = $data->text('marc:controlfield[@tag="008"]');
        $this->created = $this->parseDateTime(substr($f008, 0, 6));

        $creators = array();
        $meetings = array();
        $subjects = array();
        $genres = array();
        $classifications = array();
        $series = array();
        $notes = array();
        $isbns = array();

        // Relationships
        $preceding = array();
        $succeeding = array();
        $part_of = array();
        $other_form = array();

        $this->is_series = false;
        $this->is_multivolume = false;

        foreach ($data->all('marc:datafield') as $node) {
            $marcfield = intval($node->attributes()->tag);
            switch ($marcfield) {

                // 010 - Library of Congress Control Number (NR)
                case 10:
                    $this->lccn = $node->text('marc:subfield[@code="a"]');
                    break;

                // 020 - International Standard Book Number (R)
                case 20:                                                            // Test added
                    $isbn = $node->text('marc:subfield[@code="a"]');
                    $isbn = preg_replace('/^([0-9\-xX]+).*$/', '\1', $isbn);
                    if (empty($isbn)) break;
                    array_push($isbns, $isbn);
                    break;

                // 040 - Cataloging Source (NR)
                case 40:                                                            // Test added
                    $x = $node->text('marc:subfield[@code="e"]');
                    if ($x) $this->catalogingRules = $x;
                    // Value from http://www.loc.gov/standards/sourcelist/descriptive-conventions.html
                    break;

                // 060 - National Library of Medicine Call Number (R)
                case 60:
                    $cl = array('system' => 'nlm', 'edition' => null, 'assigner' => null);

                    $map = array(
                        'a' => 'number'
                    );
                    foreach ($map as $key => $val) {
                        $t = $node->text('marc:subfield[@code="' . $key . '"]');
                        if (!is_array($val)) $val = array($val);
                        if (count($val) > 2) $t = preg_replace('/' . $val[1] . '/', $val[2], $t);
                        if (!empty($t)) $cl[$val[0]] = $t;
                    }

                    $ind2 = $node->attr('ind2');
                    $cl['assigner'] = ($ind2 == '0') ? 'DNLM' : null;
                    $classifications[] = $cl;
                    break;

                // 080 - Universal Decimal Classification Number (R)
                case 80:
                    $cl = array('system' => 'udc', 'edition' => null, 'assigner' => null);

                    $map = array(
                        'a' => array('number', '^.*?([0-9.\/:()]+).*$', '\1'),
                        '2' => 'edition',
                    );
                    foreach ($map as $key => $val) {
                        $t = $node->text('marc:subfield[@code="' . $key . '"]');
                        if (!is_array($val)) $val = array($val);
                        if (count($val) > 2) $t = preg_replace('/' . $val[1] . '/', $val[2], $t);
                        if (!empty($t)) $cl[$val[0]] = $t;
                    }

                    $classifications[] = $cl;
                    break;

                // 082 - Dewey Decimal Classification Number (R)
                case 82:
                    $cl = array('system' => 'ddc', 'edition' => null, 'assigner' => null);

                    $map = array(
                        'a' => array('number', '^.*?([0-9.]+)\/?([0-9.]*).*$', '\1\2'),
                        '2' => 'edition',
                        'q' => 'assigner'
                    );
                    foreach ($map as $key => $val) {
                        $t = $node->text('marc:subfield[@code="' . $key . '"]');
                        if (!is_array($val)) $val = array($val);
                        if (count($val) > 2) $t = preg_replace('/' . $val[1] . '/', $val[2], $t);
                        if (!empty($t)) $cl[$val[0]] = $t;
                    }
                    if (isset($cl['number'])) {
                        $classifications[] = $cl;
                    } else {
                        // Invalid value in $a, should we trigger some event to allow logging?
                    }
                    break;

                // 084 - Other Classification Number (R)
                case 84:
                    $cl = array('edition' => null, 'assigner' => null);

                    $map = array(
                        'a' => 'number',
                        '2' => 'system',
                        'q' => 'assigner'
                    );
                    foreach ($map as $key => $val) {
                        $t = $node->text('marc:subfield[@code="' . $key . '"]');
                        if (!is_array($val)) $val = array($val);
                        if (count($val) > 2) $t = preg_replace('/' . $val[1] . '/', $val[2], $t);
                        if (!empty($t)) $cl[$val[0]] = $t;
                    }

                    // Only add classifications with a system assigned. 
                    // "Local classification" is deprecated!
                    if (isset($cl['system'])) {
                        $classifications[] = $cl;
                    }
                    break;

                /*
                case 89:
                    if (!isset($this->klass)) $this->klass = array();
                    $klass = $node->text('marc:subfield[@code="a"]');
                    $klass = preg_replace('/[^0-9.]/', '', $klass);
                    foreach ($this->klass as $kitem) {
                        if (($kitem['kode'] == $klass) && ($kitem['system'] == 'dewey')) {
                            continue 3;
                        }
                    }
                    array_push($this->klass, array('kode' => $klass, 'system' => 'dewey'));
                    break;
                */

                case 100:
                    $author = array(
                        'name' => $node->text('marc:subfield[@code="a"]'),<commit_msg>Add a general method to handle the various classification fields<commit_after><?php namespace Scriptotek\SimpleMarcParser;

use Illuminate\Support\Contracts\JsonableInterface;
use Danmichaelo\QuiteSimpleXmlElement\QuiteSimpleXmlElement;
use Carbon\Carbon;

/**
 * @property int       $id                 Local record identifier
 * @property string    $material
 * @property boolean   $electronic
 * @property boolean   $is_series
 * @property boolean   $is_multivolume
 * @property boolean
 * @property string    $agency
 * @property string    $lccn
 * @property string    $catalogingRules
 * @property string    $title
 * @property string    $part_no
 * @property string    $part_name
 * @property string    $part_of
 * @property string    $medium
 * @property string    $edition
 * @property string    $publisher
 * @property string    $extent
 * @property string    $contents
 * @property string    $summary
 * @property string    $cover_image
 * @property string    $description
 * @property string    $preceding
 * @property string    $succeeding
 * @property string[]  $isbns
 * @property string[]  $notes
 * @property array     $series
 * @property array     $other_form
 * @property array     $creators
 * @property array     $meetings
 * @property array     $subjects
 * @property array     $genres
 * @property array     $classifications
 * @property int       $pages
 * @property int       $year
 * @property Carbon    $modified
 * @property Carbon    $created
 */
class BibliographicRecord extends Record implements JsonableInterface {


    /**
     * @param string $x1
     * @param string $x2
     * @param string $default
     * @return string
     */
    public function getMaterialSubtypeFrom007($x1, $x2, $default = 'Unknown')
    {
         $f007values = array(
            'a' => array(
                'd' => 'Atlas',
                'g' => 'Diagram',
                'j' => 'Map',
                'k' => 'Profile',
                'q' => 'Model',
                'r' => 'Remote-sensing image',
                '_' => 'Map',
            ),
            'c' => array(
                'a' => 'Tape cartridge',
                'b' => 'Chip cartridge',
                'c' => 'Computer optical disc cartridge',
                'd' => 'Computer disc, type unspecified',
                'e' => 'Computer disc cartridge, type unspecified',
                'f' => 'Tape cassette',
                'h' => 'Tape reel',
                'j' => 'Magnetic disk',
                'k' => 'Computer card',
                'm' => 'Magneto-optical disc',
                'o' => 'CD-ROM',                // Optical disc
                'r' => 'Remote resource',    // n Nettdokumenter
            ),
            'f' => array(
                'a' => 'Moon',         // in the Moon writing system
                'b' => 'Braille',      // in the Braille writing system
                'c' => 'Combination ', // in a combination of two or more of the other defined types
                'd' => 'No writing system',
            ),
            'o' => array(
                'u' => 'Kit',
                '|' => 'Kit',
            ),
            's' => array(
                'd' => 'Music CD',             // v CD-er
                'e' => 'Cylinder',
                'g' => 'Sound cartridge',
                'i' => 'Sound-track film',
                'q' => 'Roll',
                's' => 'Sound cassette',
                't' => 'Sound-tape reel',
                'u' => 'Unspecified',
                'w' => 'Wire recording',
            ),
            'v' => array(
                'c' => 'Videocartridge',
                'd' => 'Videodisc',           // w DVD-er
                'f' => 'Videocassette',
                'r' => 'Videoreel',
            ),
        );
        if (isset($f007values[$x1]) && isset($f007values[$x1][$x2])) {
            return $f007values[$x1][$x2];
        }
        // TODO: LOG IT!
        return $default;
    }

    /**
     * @param \Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement $data
     */
    protected function parseMaterial(QuiteSimpleXmlElement $data)
    {
        // See http://www.loc.gov/marc/ldr06guide.html

        // The Leader/06 (Type of record) character position contains a one-character 
        // alphabetic code that differentiates MARC records created for various types of
        // content and materials.

        // The 008 field contains character positions that provide coded information about
        // the record as a whole and about special bibliographic aspects of the item cataloged.

        // Field 006 (Fixed-length data elements - Additional material characteristics) 
        // permits coding for additional aspects of an electronic resource (including any 
        // computer file aspects) if the 007 and 008 fields do not adequately describe it.

        // leader[6] : Type of record
        // leader[7] : Bibliographic level
        // Field 008/18-34 Configuration

        // LDR/06
        $recordTypes = array(
            'a' => 'Language material',
            'c' => 'Notated music',
            'd' => 'Manuscript notated music',
            'e' => 'Cartographic material',
            'f' => 'Manuscript cartographic material',
            'g' => 'Projected medium',
            'i' => 'Nonmusical sound recording',
            'j' => 'Musical sound recording',
            'k' => 'Two-dimensional nonprojectable graphic',
            'm' => 'Computer file',
            'o' => 'Kit',
            'p' => 'Mixed materials',
            'r' => 'Three-dimensional artifact or naturally occurring object',
            't' => 'Manuscript language material',
        );

        // LDR/07
        $bibliographicLevels = array(
            'a' => 'Monographic component part',
            'b' => 'Serial component part',
            'c' => 'Collection',
            'd' => 'Subunit',
            'i' => 'Integrating resource',
            'm' => 'Monograph/Item',
            's' => 'Serial',
        );

        // 007/00 (Category of material)
        $materialCategories = array(
            'a' => 'Map',
            'c' => 'Electronic resource',
            'd' => 'Globe',
            'f' => 'Tactile material',
            'g' => 'Projected graphic',
            'h' => 'Microform',
            'k' => 'Nonprojected graphic',
            'm' => 'Motion picture',
            'o' => 'Kit',
            'q' => 'Notated music',
            'r' => 'Remote-sensing image',
            's' => 'Sound recording',
            't' => 'Text',                 // p Trykt materiale
            'v' => 'Videorecording',
            'z' => 'Unspecified',
        );

        // 008/24-27 - Nature of contents (006/07-10)
        $natureOfContents = array(
            'a' => 'Abstract',
            'b' => 'Bibliography',
            'c' => 'Catalog',
            'd' => 'Dictionary',
            'e' => 'Encyclopedia',
            'f' => 'Handbook',
            'g' => 'Legal article',
            'i' => 'Index',
            'j' => 'Patent document',
            'k' => 'Discography',
            'l' => 'Legislation',
            'm' => 'Thesis',
            'n' => 'Surveys of literature in a subject area',
            'o' => 'Review',
            'p' => 'Programmed text',
            'q' => 'Filmography',
            'r' => 'Directory',
            's' => 'Statistics',
            't' => 'Technical report',
            'u' => 'Standards/specification',
            'v' => 'Legal cases and case notes',
            'w' => 'Law reports and digests',
            'y' => 'Yearbook',
            'z' => 'Treaty',
            '2' => 'Offprint',
            '5' => 'Calendar',
            '6' => 'Comics/graphic novel',
        );

        $videoFormats = array(
            'a' => 'Beta (1/2 in., videocassette)',
            'b' => 'VHS (1/2 in., videocassette)',
            'c' => 'U-matic (3/4 in., videocasstte)',
            'd' => 'EIAJ (1/2 in., reel)',
            'e' => 'Type C (1 in., reel)',
            'f' => 'Quadruplex (1 in. or 2 in., reel)',
            'g' => 'Laserdisc',
            'h' => 'CED (Capacitance Electronic Disc) videodisc',
            'i' => 'Betacam (1/2 in., videocassette)',
            'j' => 'Betacam SP (1/2 in., videocassette)',
            'k' => 'Super-VHS (1/2 in., videocassette)',
            'm' => 'M-II (1/2 in., videocassette)',
            'o' => 'D-2 (3/4 in., videocassette)',
            'p' => '8 mm.',
            'q' => 'Hi-8 mm.',
            's' => 'Blu-ray',
            'u' => 'Unknown',
            'v' => 'DVD',
        );

        // If Leader/06 = a and Leader/07 = a, c, d, or m: Books
        // If Leader/06 = a and Leader/07 = b, i, or s: Continuing Resources
        // If Leader/06 = t: Books
        // If Leader/06 = c, d, i, or j: Music
        // If Leader/06 = e, or f: Maps
        // If Leader/06 = g, k, o, or r: Visual Materials
        // If Leader/06 = m: Computer Files
        // If Leader/06 = p: Mixed Materials

        $ldr = str_split($data->text('marc:leader'));
        $f007 = str_split($data->text('marc:controlfield[@tag="007"]'));
        $f008 = str_split($data->text('marc:controlfield[@tag="008"]'));

        $material = 'Unknown';
        $this->material = $material;
        $this->electronic = false;

        if (count($ldr) < 8) return;
        if (count($f007) < 2) return;

        switch ($ldr[6]) {

            case 'a':
                if (in_array($ldr[7], array('a','c','d','m'))) {
                    $material = 'Book';
                }
                if (in_array($ldr[7], array('b','i','s'))) {
                    $material = 'Series';
                }
                break;

            case 't':
                $material = 'Book';
                break;

            case 'c':
            case 'd':
            case 'i':
            case 'j':
                $material = 'Music';
                break;

            case 'e':
            case 'f':
                $material = 'Map';
                break;

            case 'g':
            case 'k':
            case 'o':
            case 'r':
                $material = 'Visual';
                break;

            case 'm':
                $material = 'File';
                // used for computer software, numeric data, not for e-books or e-journals!
                break;

            case 'p':
                $material = 'Mixed';
                break;

        }

        $online = ($f007[0] == 'c' && $f007[1] == 'r');

        if ($material == 'File') {
            $material = $this->getMaterialSubtypeFrom007($f007[0], $f007[1], $material);


        } else if ($material == 'Visual') {
            $material = $this->getMaterialSubtypeFrom007($f007[0], $f007[1], $material);


            if (isset($f007[4]) && isset($videoFormats[$f007[4]])) {
                $material = $videoFormats[$f007[4]]; // DVD, Blu-ray            
            }

        } else if ($material == 'Music') {
            if ($f007[0] == 't') {
                $material = 'Sheet music';
            } else {
                $material = $this->getMaterialSubtypeFrom007($f007[0], $f007[1], $material);
            }

        } else if ($material == 'Series') {
            switch ($f008[21]) {
                case 'p':
                    $material = 'Periodical';
                    break;

                case 'm':
                    $material = 'Series';  // Monographic series (merk: skiller ikke mellom 'flerbindsverk' og 'serieinnførsel')
                    break;

            }
        } else if ($material == 'Book') {
            if (isset($natureOfContents[$f008[24]])) {
                // Slight simplification
                $material = $natureOfContents[$f008[24]];
            }
        }

        $this->material = $material;
        $this->electronic = $online;
    }

    function addClassification($node, &$classifications, $fields, $system = null, $edition = null, $assigner = null)
    {
        $cl = array('system' => $system, 'edition' => $edition, 'number' => null, 'assigner' => $assigner);
        foreach ($fields as $key => $val) {
            $t = $node->text('marc:subfield[@code="' . $key . '"]');
            if (!is_array($val)) $val = array($val);
            if (count($val) > 2) $t = preg_replace('/' . $val[1] . '/', $val[2], $t);
            if (!empty($t)) $cl[$val[0]] = $t;
        }

        if (is_null($cl['system'])) {
            return;
            // Invalid value in $a, should we trigger some event to allow logging?
        }

        if (is_null($cl['number'])) {
            return;
            // Invalid value in $a, should we trigger some event to allow logging?
        }

        $classifications[] = $cl;
    }

    /**
     * Parses common elements in subject added entry fields 600-655
     *
     * @param \Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement $node
     * @return array
     */
    function parseSubjectAddedEntry(QuiteSimpleXmlElement &$node) {
        $out = array('term' => '');
        $vocabularies = array(
            '0' => 'lcsh',
            '1' => 'lccsh', // LC subject headings for children's literature
            '2' => 'mesh', // Medical Subject Headings
            '3' => 'atg', // National Agricultural Library subject authority file (?)
            // 4 : unknown
            '5' => 'cash', // Canadian Subject Headings
            '6' => 'rvm', // Répertoire de vedettes-matière
            // 7: Source specified in subfield $2
        );
        $ind2 = $node->attr('ind2');

        $id = $node->text('marc:subfield[@code="0"]');
        $out['id'] = empty($id) ? null : $id;

        if (isset($vocabularies[$ind2])) {
            $out['vocabulary'] = $vocabularies[$ind2];
        } elseif ($ind2 == '7') {
            $vocab = $node->text('marc:subfield[@code="2"]');
            if (!empty($vocab)) {
                $out['vocabulary'] = $vocab;
            }
        } elseif ($ind2 == '4') {
            $this->parseAuthority($node->text('marc:subfield[@code="0"]'), $out);
        }

        $out['parts'] = array();
        $subdivtypes = array(
            'v' => 'form',
            'x' => 'general',
            'y' => 'chronological',
            'z' => 'geographic',
        );
        foreach ($node->all('marc:subfield') as $subdiv) {
            $code = $subdiv->attr('code');
            if (in_array($code, array_keys($subdivtypes))) {
                $subdiv = trim($subdiv, '.');
                $out['parts'][] = array('value' => $subdiv, 'type' => $subdivtypes[$code]);
                $out['term'] .= self::$subfieldSeparator . $subdiv;
            }
        }
        return $out;
    }

    /**
     * @param \Danmichaelo\QuiteSimpleXMLElement\QuiteSimpleXMLElement $data
     */
    public function __construct(QuiteSimpleXmlElement $data) {

        $this->parseMaterial($data);

        // Control fields
        $this->id = $data->text('marc:controlfield[@tag="001"]');

        // 003: MARC code for the agency whose system control number is
        // contained in field 001 (Control Number)
        // See http://www.loc.gov/marc/authority/ecadorg.html
        $this->agency = $data->text('marc:controlfield[@tag="003"]');

        // 005: Modified
        $this->modified = $this->parseDateTime($data->text('marc:controlfield[@tag="005"]'));

        // 008: Extract *some* information
        $f008 = $data->text('marc:controlfield[@tag="008"]');
        $this->created = $this->parseDateTime(substr($f008, 0, 6));

        $creators = array();
        $meetings = array();
        $subjects = array();
        $genres = array();
        $classifications = array();
        $series = array();
        $notes = array();
        $isbns = array();

        // Relationships
        $preceding = array();
        $succeeding = array();
        $part_of = array();
        $other_form = array();

        $this->is_series = false;
        $this->is_multivolume = false;

        foreach ($data->all('marc:datafield') as $node) {
            $marcfield = intval($node->attributes()->tag);
            switch ($marcfield) {

                // 010 - Library of Congress Control Number (NR)
                case 10:
                    $this->lccn = $node->text('marc:subfield[@code="a"]');
                    break;

                // 020 - International Standard Book Number (R)
                case 20:                                                            // Test added
                    $isbn = $node->text('marc:subfield[@code="a"]');
                    $isbn = preg_replace('/^([0-9\-xX]+).*$/', '\1', $isbn);
                    if (empty($isbn)) break;
                    array_push($isbns, $isbn);
                    break;

                // 040 - Cataloging Source (NR)
                case 40:                                                            // Test added
                    $x = $node->text('marc:subfield[@code="e"]');
                    if ($x) $this->catalogingRules = $x;
                    // Value from http://www.loc.gov/standards/sourcelist/descriptive-conventions.html
                    break;

                // 060 - National Library of Medicine Call Number (R)
                case 60:
                    $this->addClassification(
                        $node,
                        $classifications,
                        array(
                            'a' => 'number'
                        ),
                        $system = 'nlm',
                        $assigner = ($node->attr('ind2') == '0') ? 'DNLM' : null
                    );
                    break;

                // 080 - Universal Decimal Classification Number (R)
                case 80:
                    $this->addClassification(
                        $node,
                        $classifications,
                        array(
                            'a' => array('number', '^.*?([0-9.\/:()]+).*$', '\1'),
                            '2' => 'edition',
                        ),
                        $system = 'udc'
                    );
                    break;

                // 082 - Dewey Decimal Classification Number (R)
                case 82:
                    $this->addClassification(
                        $node,
                        $classifications,
                        array(
                            'a' => array('number', '^.*?([0-9.]+)\/?([0-9.]*).*$', '\1\2'),
                            '2' => 'edition',
                            'q' => 'assigner'
                        ),
                        $system = 'ddc'
                    );
                    break;

                // 084 - Other Classification Number (R)
                case 84:
                    $this->addClassification(
                        $node,
                        $classifications,
                        array(
                            'a' => 'number',
                            '2' => 'system',
                            'q' => 'assigner'
                        )
                    );
                    break;

                /*
                case 89:
                    if (!isset($this->klass)) $this->klass = array();
                    $klass = $node->text('marc:subfield[@code="a"]');
                    $klass = preg_replace('/[^0-9.]/', '', $klass);
                    foreach ($this->klass as $kitem) {
                        if (($kitem['kode'] == $klass) && ($kitem['system'] == 'dewey')) {
                            continue 3;
                        }
                    }
                    array_push($this->klass, array('kode' => $klass, 'system' => 'dewey'));
                    break;
                */

                case 100:
                    $author = array(
                        'name' => $node->text('marc:subfield[@code="a"]'),
{'fraction_non_alphanumeric': 0.15914301915633244, 'fraction_numerical': 0.01676487266093045, 'mean_word_length': 1.3630643751820566, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 427, 'https://': 0, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4909813', 'n_tokens_mistral': 12234, 'n_tokens_neox': 11174, 'n_words': 2803}