<?php /** * Pica+ Field. * * This file is part of PicaRecord. * * PicaRecord is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * PicaRecord is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PicaRecord. If not, see <http://www.gnu.org/licenses/>. * * @package PicaRecord * @author David Maus <maus@hab.de> * @copyright Copyright (c) 2012-2016 by Herzog August Bibliothek Wolfenbüttel * @license http://www.gnu.org/licenses/gpl.html GNU General Public License v3 */ namespace HAB\Pica\Record; use InvalidArgumentException; class Field { /** * Regular expression matching a valid Pica+ field tag. * * @var string */ const TAG_RE = '|^[012][0-9]{2}[A-Z@]$|D'; /** * Return TRUE if argument is a valid field tag. * * @param mixed $arg Variable to check * @return boolean TRUE if argument is a valid field tag */ public static function isValidFieldTag ($arg) { return (bool)preg_match(self::TAG_RE, $arg); } /** * Return TRUE if argument is a valid field occurrence. * * Argument is casted to int iff it is either null or a numeric string. * * @param mixed $arg Variable to check * @return boolean TRUE if argument is a valid field occurrence */ public static function isValidFieldOccurrence ($arg) { if ($arg === null || ctype_digit($arg)) { $arg = (int)$arg; } return is_int($arg) && $arg >= 0 && $arg < 100; } /** * Return predicate that matches a field shorthand against a regular * expression. * * @param string $reBody Body of regular expression * @return callback Predicate */ public static function match ($reBody) { if (strpos($reBody, '#') !== false) { $reBody = str_replace('#', '\#', $reBody); } $regexp = "#{$reBody}#D"; return function (Field $field) use ($regexp) { return (bool)preg_match($regexp, $field->getShorthand()); }; } /** * Return a new field based on its array representation. * * @throws InvalidArgumentException Missing `tag', `occurrene', or `subfields' index * @param array $field Array representation of a field * @return \HAB\Pica\Record\Field A shiny new field */ public static function factory (array $field) { foreach (array('tag', 'occurrence', 'subfields') as $index) { if (!array_key_exists($index, $field)) { throw new InvalidArgumentException("Missing '{$index}' index in field array"); } } return new Field($field['tag'], $field['occurrence'], array_map(array('HAB\Pica\Record\Subfield', 'factory'), $field['subfields'])); } /// /** * The field tag. * * @var string */ protected $_tag; /** * The field level. * * @var integer */ protected $_level; /** * The field occurrence. * * @var integer */ protected $_occurrence; /** * The field shorthand. * * @var string */ protected $_shorthand; /** * Subfields. * * @var array */ protected $_subfields; /** * Constructor. * * @throws InvalidArgumentException Invalid field tag or occurrence * @param string $tag Field tag * @param integer $occurrence Field occurrence * @param array $subfields Initial set of subfields * @return void */ public function __construct ($tag, $occurrence, array $subfields = array()) { if (!self::isValidFieldTag($tag)) { throw new InvalidArgumentException("Invalid field tag: $tag"); } if (!self::isValidFieldOccurrence($occurrence)) { throw new InvalidArgumentException("Invalid field occurrence: $occurrence"); } $this->_tag = $tag; $this->_occurrence = (int)$occurrence; $this->_shorthand = sprintf('%4s/%02d', $tag, $occurrence); $this->_level = (int)$tag[0]; $this->setSubfields($subfields); } /** * Set the field subfields. * * Replaces the subfield list with subfields in argument. * * @param array $subfields Subfields * @return void */ public function setSubfields (array $subfields) { $this->_subfields = array(); foreach ($subfields as $subfield) { $this->addSubfield($subfield); } } /** * Add a subfield to the end of the subfield list. * * @throws InvalidArgumentException Subfield already present in subfield list * @param \HAB\Pica\Record\Subfield $subfield Subfield to add * @return void */ public function addSubfield (\HAB\Pica\Record\Subfield $subfield) { if (in_array($subfield, $this->getSubfields(), true)) { throw new InvalidArgumentException("Cannot add subfield: Subfield already part of the subfield list"); } $this->_subfields []= $subfield; } /** * Remove a subfield. * * @throws InvalidArgumentException Subfield is not part of the subfield list * @param \HAB\Pica\Record\Subfield $subfield Subfield to delete * @return void */ public function removeSubfield (\HAB\Pica\Record\Subfield $subfield) { $index = array_search($subfield, $this->_subfields, true); if ($index === false) { throw new InvalidArgumentException("Cannot remove subfield: Subfield not part of the subfield list"); } unset($this->_subfields[$index]); } /** * Return the field's subfields. * * Returns all subfields when called with no arguments. * * Otherwise the returned array is constructed as follows: * * Each argument is interpreted as a subfield code. The nth element of the * returned array maps to the nth argument in the function call and contains * NULL if the field does not have a subfield with the selected code, or the * subfield if it exists. In order to retrieve multiple subfields with an * identical code you repeat the subfield code in the argument list. * * @return array Subfields */ public function getSubfields () { if (func_num_args() === 0) { return $this->_subfields; } else { $selected = array(); $codes = array(); $subfields = $this->getSubfields(); array_walk($subfields, function ($value, $index) use (&$codes) { $codes[$index] = $value->getCode(); }); foreach (func_get_args() as $arg) { $index = array_search($arg, $codes, true); if ($index === false) { $selected []= null; } else { $selected []= $subfields[$index]; unset($codes[$index]); } } return $selected; } } /** * Return the nth occurrence of a subfield with specified code. * * @param string $code Subfield code * @param integer $n Zero-based subfield index * @return \HAB\Pica\record\Subfield|null The requested subfield or NULL if * none exists */ public function getNthSubfield ($code, $n) { $count = 0; foreach ($this->getSubfields() as $subfield) { if ($subfield->getCode() == $code) { if ($count == $n) { return $subfield; } $count++; } } return null; } /** * Return true if a subfield with given code exists. * * If optional argument $n return true if the field has subfield * at the specified position. * * @param string $code Subfield code * @param integer $n Optional zero-based subfield index * @return boolean */ public function hasSubfield ($code, $n = 0) { return (boolean)$this->getNthSubfield($code, $n); } /** * Return all subfields with the specified code. * * @param string $code Subfield code * @return array */ public function getSubfieldsWithCode ($code) { return array_filter($this->_subfields, function (Subfield $s) use ($code) { return $s->getCode() == $code; }); } /** * Return the field tag. * * @return string Field tag */ public function getTag () { return $this->_tag; } /** * Return the field occurrence. * * @return integer Field occurrence */ public function getOccurrence () { return $this->_occurrence; } /** * Return the field level. * * @return integer Field level */ public function getLevel () { return $this->_level; } /** * Return the field shorthand. * * @return string Field shorthand */ public function getShorthand () { return $this->_shorthand; } /** * Return TRUE if the field is empty. * * A field is empty if it contains no subfields. * * @return boolean TRUE if the field is empty */ public function isEmpty () { return empty($this->_subfields); } /** * Finalize the clone() operation. * * @return void */ public function __clone () { $this->_subfields = Helper::mapClone($this->_subfields); } /** * Return a printable representation of the field. * * The printable representation of a field is its shorthand. * * @return string Printable representation of the field */ public function __toString () { return $this->getShorthand(); } }