Grima  2018-08
Whispering into Alma's ear
MARC21.php
Go to the documentation of this file.
1 <?php
2 
3 require_once "ISO2709.php";
4 
6  public function __construct( $input ) {
7  parent::__construct( $input );
8  $this->prepend = fread( $this->input, 5 );
9  if( ctype_digit( $this->prepend ) ) $this->RecordTerminator = ISO2709::RecordTerminator;
10  else if( $this->prepend === "=LDR " ) {
11  $this->RecordTerminator = "\r\n\r\n";
12  } else if( $this->prepend === "=000 " ) {
13  $this->RecordTerminator = "\r\n\r\n";
14  }
15  $this->recordClass = "MARC21Record";
16  }
17 }
18 
20  public function __construct( $input ) {
21  parent::__construct( $input );
22  $this->recordClass = "MARC21Record";
23  }
24 }
25 
26 class MARC21Record extends ISO2709Record {
27  protected $mnemonicType;
28  function __construct( ) {
29  parent::__construct();
30  $this->set_default( "identifierLength", 2 );
31  $this->set_default( "indicatorLength", 2 );
32  $this->set_default( "lengthOfLengthOfField", 4 );
33  $this->set_default( "lengthOfStartingCharacterPosition", 5 );
34  $this->set_default( "lengthOfImplementationDefined", 0 );
35  $this->mnemonicType = "LC";
36  }
37 
38  function isTagValid( $tag ) {
39  return is_string( $tag ) and ctype_digit( $tag ) and strlen( $tag ) == 3 and $tag !== "000";
40  }
41 
42  function assertTagValid( $tag ) {
43  if( !self::isTagValid( $tag ) ) {
44  $this->exception("MARC21: Tags must be numeric and must be 3 bytes long. Thus '$tag' is not allowed.");
45  return false;
46  }
47  return parent::assertTagValid( $tag );
48  }
49 
50  function isTagRepeatable( $tag ) {
51  return $tag != "001" and $tag != "005";
52  }
53 
54  // 00* ordered ASCII-wise, otherwise order by century
55  function tagOrder( $a, $b ) {
56  if(!parent::isTagValid($a) || !parent::isTagValid($b)) return 0;
57  if( $a === $b ) return 0;
58  $a0 = substr( $a, 0, 2 );
59  $b0 = substr( $b, 0, 2 );
60  if( $a0 === $b0 and $a0 === "00" ) return strcmp( $a, $b );
61  if( $a0 === "00" ) { return -1; }
62  if( $b0 === "00" ) { return 1; }
63  $a0 = substr( $a, 0, 1 );
64  $b0 = substr( $b, 0, 1 );
65  if( $a0 === "0" and $b0 === "0" ) {
66  if( $a === "049" ) return 1;
67  if( $b === "049" ) return -1;
68  }
69  return strcmp( $a0, $b0 );
70  }
71 
72  function assertTagOrder( $a, $b ) {
73  if( self::tagOrder( $a, $b ) > 0 ) {
74  $this->exception("MARC21: 00* is ordered by ASCII, and the rest by century. Thus '$a' should not precede '$b'.");
75  return false;
76  }
77  return parent::assertTagOrder( $a, $b );
78  }
79 
80  function loadFromBinaryString( $string, $fuzzy = true ) {
81  $ret = parent::loadFromBinaryString( $string );
82  if( $ret === false ) return $ret;
83  if( strlen($this->leader) != 24 ) return false;
84  if(!ctype_print($this->leader))
85  $this->exception("MARC21: Leader can only contain 0x20 through 0x7E, not 0x" . bin2hex( $this->leader ) );
86  if($this->leader[23] != "0")
87  $this->exception("MARC21: Last character of leader is supposed to be '0' not '{$this->leader[23]}'");
88  return true;
89  // Check data order
90  # MARC21
91  # 00*-00* are in numeric-alpha order in both directory and data (with no intervening in either directory or data)
92  }
93 
94  function CheckField( $field ) {
95  parent::CheckField( $field );
96  if( substr( $field->tag, 0, 2 ) === "00" ) {
97  // No extra requirements on control fields
98  } else {
99  // Indicators must be length 2, numeric, lower case, or space
100  if( !preg_match("/^[0-9a-z ]{2}$/", $field->indicators) )
101  $this->exception("MARC21 8.3.5: indicators should be length 2, and each character should be lower case, numeric, or a space. " .
102  "So field '{$field->tag}' with indicators '{$field->indicators}' is not allowed." );
103  // Subfield identifiers must be length 1, numeric, or lower case
104  foreach( $field->subfields as $subfield ) {
105  if( !preg_match("/^[0-9a-z]$/", $subfield->identifier) )
106  $this->exception("MARC21 8.3.5: subfield identifiers should be length 1, and each character should be lower case or numeric. " .
107  "So field '{$field->tag}' with subfield '{$subfield->identifier}' is not allowed." );
108  }
109  }
110  }
111 
112  function loadFromMnemonicString( $string ) {
113  $this->leader = array();
114  $this->fields = array();
115  $this->exceptions = array();
116  $this->raw = $string;
117  foreach( explode( "\r\n", $string ) as $line ) {
118  $field = $this->ParseMnemonicLine( $line );
119  if( $field !== false ) $this->AppendField( $field );
120  }
121  }
122 
123  function loadFromXMLString( $string ) {
124  $this->leader = array(); # XXX ??
125  $this->fields = array();
126  $this->exceptions = array();
127  $this->raw = $string;
128 
129  $doc = new DOMDocument();
130  $doc->loadXML($string);
131  $xpath = new DOMXpath($doc);
132 
133  $leaderfield = $xpath->query('//record/leader');
134  if ($leaderfield) { # <leader>00838mas a2200301 a 4500</leader>
135  $leader = $leaderfield[0]->nodeValue;
136  if( strlen($leader) != 24 ) return false;
137  $this->leader = $leader;
138  }
139 
140  # $field = new ISO2709Field( $tag, $data, $indicators, $subfields, $directoryEntry, $this );
141 
142  $controlfield = $xpath->query('//record/controlfield');
143  if ($controlfield) { # <controlfield tag="001"> 92005291 </controlfield>
144  foreach ($controlfield as $cfd) {
145  $tag = $cfd->getAttribute('tag');
146  $data = $cfd->nodeValue;
147  $field = new ISO2709Field( $tag, $data );
148  $this->AppendField( $field );
149  }
150  }
151 
152  $datafield = $xpath->query('//record/datafield');
153  if ($datafield) { # <datafield ind1=" " ind2=" " tag="035"><subfield code="9">AHE8121LM</subfield></datafield>
154  foreach ($datafield as $dfd) {
155  $tag = $dfd->getAttribute('tag');
156  $indicators = $dfd->getAttribute('ind1') . $dfd->getAttribute('ind2');
157  $subfields = array();
158  foreach ($dfd->childNodes as $subf) {
159  if ($subf->nodeType == '1') { # Element
160  $code = $subf->getAttribute('code');
161  $value = $subf->nodeValue;
162  $subfields[] = new ISO2709Subfield( $code, $value );
163  }
164  }
165  $field = new ISO2709Field( $tag, null, $indicators, $subfields);
166  $this->AppendField( $field );
167  }
168  }
169  }
170 
171  function asXMLString( ) {
172  $str = '<record>';
173  $str .= '<leader>' . $this->leader . '</leader>';
174  foreach( $this->fields as $field ) {
175  if ( isset($field->data ) ) {
176  $str .= "<controlfield tag=\"{$field->tag}\">{$field->data}</controlfield>";
177  } else {
178  $str .= "<datafield tag=\"{$field->tag}\">";
179  foreach( $field->subfields as $subfield ) {
180  $str .= "<subfield code=\"{$subfield->identifier}\">{$subfield->data}</subfield>";
181  }
182  $str .= '</datafield>';
183  }
184  }
185  $str .= '</record>';
186  return $str;
187  }
188 
189  function loadFromString( $string ) {
190  if (preg_match("/^ *([^ ])/",$string,$m)) {
191  $starts_with = $m[1];
192  if ($starts_with == '<') {
193  return $this->loadFromXMLString( $string );
194  }
195  if ($starts_with == '=') {
196  return $this->loadFromMnemonicString( $string );
197  }
198  if (ctype_digit($starts_with)) {
199  return $this->loadFromBinaryString( $string );
200  }
201  return false;
202  }
203  return false;
204  }
205 
206  function ParseMnemonicLine( $line ) {
207  $equ = substr( $line, 0, 1 );
208  $tag = substr( $line, 1, 3 );
209  $spa = substr( $line, 4, 2 );
210  $dat = substr( $line, 6 );
211  if( $equ !== "=" or $spa !== " " ) {
212  $this->exception("Mnemonic: unreadable line '$line'");
213  return false;
214  }
215  if( $tag == "LDR" or $tag == "000" ) {
216  $this->leader = $dat;
217  $this->indicatorLength = 2;
218  $this->identifierLength = 2;
219  $this->mnemonicType = ($tag == "LDR") ? "TRME" : "LCMB";
220  return false;
221  }
222  if( $this->mnemonicType === "TRME" ) {
223  $dat = str_replace( '$', ISO2709::SubfieldInitiator, $dat );
224  $dat = str_replace( '\\', ' ', $dat );
225  } else if( $this->mnemonicType === "LCMB" ) {
226  $dat = str_replace( '$', ISO2709::SubfieldInitiator, $dat );
227  $dat = str_replace( '#', ' ', $dat );
228  /* // These might not really be applicable
229  } else if( $this->mnemonicType === "OCLC" ) {
230  $dat = str_replace( 'ǂ', ISO2709::SubfieldInitiator, $dat );
231  // no space translation
232  } else if( $this->mnemonicType === "VGER" ) {
233  $dat = str_replace( '‡', ISO2709::SubfieldInitiator, $dat );
234  // no space translation
235  } else if( $this->mnemonicType === "ALMA" ) {
236  $dat = str_replace( '$$', ISO2709::SubfieldInitiator, $dat );
237  */
238  } else {
239  if( substr($tag,0,2) === "00" ) {
240  $dat = str_replace( '#', ' ', $dat );
241  $dat = str_replace( '\\', ' ', $dat );
242  } else {
243  if( $dat[0] == '\\' ) $dat[0] = ' ';
244  if( $dat[1] == '\\' ) $dat[1] = ' ';
245  if( $dat[0] == '#' ) $dat[0] = ' ';
246  if( $dat[1] == '#' ) $dat[1] = ' ';
247  if( strpos( $dat, ISO2709::SubfieldInitiator ) === false ) {
248  $dat = str_replace( '$', ISO2709::SubfieldInitiator, $dat );
249  }
250  }
251  }
252  return $this->ParseBinaryField( $tag, $dat, array() );
253  }
254 
255  function AppendField( $field, $reorder = false ) {
256  if( is_string( $field ) ) {
257  $field = $this->ParseMnemonicLine( $field );
258  }
259  if( is_a( $field, "ISO2709Field" ) ) return parent::AppendField( $field, $reorder );
260  }
261 
262 
263  function AsMnemonicString( $leader_tag = 'LDR', $field_initiator = '=', $tag_terminator = ' ', $identifier_initiator = '$', $field_terminator = "\r\n", $record_terminator = "\r\n", $space_replacer = ' ') {
264  if( $this->mnemonicType === "TRME" ) return parent::AsMnemonicString( "LDR", "=", " ", "\$", "\r\n", "\r\n", "\\" );
265  if( $this->mnemonicType === "LCMB" ) return parent::AsMnemonicString( "000", "=", " ", "\$", "\r\n", "\r\n", "#" );
266  if( $this->mnemonicType === "VGER" ) return parent::AsMnemonicString( "LEADER", "=", " ", "|", "\r\n", "\r\n", "#" );
267  #return parent::AsMnemonicString( "LDR", "=", " ", "\$", "\r\n", "\r\n", " " );
268  return parent::AsMnemonicString( $leader_tag, $field_initiator, $tag_terminator, $identifier_initiator, $field_terminator, $record_terminator, $space_replacer );
269  }
270 
271  function delFields( $pattern ) {
272  foreach( $this->fields as $k => $v ) {
273  $vmne = $v->AsMnemonicString( "=", " ", "\$", "\r\n", '\\' );
274  if( preg_match( "@^" . $pattern . "@", $vmne ) ) unset( $this->fields[$k] );
275  }
276  $this->fields = array_values( $this->fields );
277  }
278 
279 }
loadFromString( $string)
Definition: MARC21.php:189
__construct( $input)
Definition: MARC21.php:6
CheckField( $field)
Definition: MARC21.php:94
asXMLString()
Definition: MARC21.php:171
isTagRepeatable( $tag)
Definition: MARC21.php:50
const RecordTerminator
Definition: ISO2709.php:121
__construct( $input)
Definition: MARC21.php:20
assertTagOrder( $a, $b)
Definition: MARC21.php:72
isTagValid( $tag)
Definition: MARC21.php:38
__construct()
Definition: MARC21.php:28
loadFromMnemonicString( $string)
Definition: MARC21.php:112
tagOrder( $a, $b)
Definition: MARC21.php:55
loadFromBinaryString( $string, $fuzzy=true)
Definition: MARC21.php:80
const SubfieldInitiator
Definition: ISO2709.php:123
delFields( $pattern)
Definition: MARC21.php:271
AsMnemonicString( $leader_tag='LDR', $field_initiator='=', $tag_terminator=' ', $identifier_initiator='$', $field_terminator="\", $record_terminator="\", $space_replacer=' ')
Definition: MARC21.php:263
assertTagValid( $tag)
Definition: MARC21.php:42
loadFromXMLString( $string)
Definition: MARC21.php:123
ParseMnemonicLine( $line)
Definition: MARC21.php:206
AppendField( $field, $reorder=false)
Definition: MARC21.php:255
$mnemonicType
Definition: MARC21.php:27