Grima 2018-08
Whispering into Alma's ear
ISO2709.php
Go to the documentation of this file.
1<?php
2
3
4/***********************************************************************
5**
6** Methods for dealing with ISO2709 (MARC) records and files
7*/
8
9# TODO: should fields be in directory order or data order?
10
11# TODO: Missing exceptions:
12# ISO2709
13# 001 is non-repeatable
14# 001 is first in data
15# 002-009 are next in data, but in any order
16
17class ISO2709RecordSetIterator implements Iterator {
19 public function __construct( $input ) {
20 if( is_string( $input ) ) $input = fopen( $input, "rb" );
21 if( !is_resource( $input ) ) throw new Exception("ISO2709RecordSetIterator requires a file resource, filename, or URL to be created");
22 $this->input = $input;
23 $this->recordClass = "ISO2709Record";
24 $this->RecordTerminator = ISO2709::RecordTerminator;
25 $this->prepend = "";
26 }
27 public function valid() { return $this->valid; }
28 public function current() {
30 }
31 public function key() {
33 }
34 public function next() {
35 if( $this->currentRecord === null ) $this->currentRecord = new $this->recordClass();
36 $this->currentPosition++;
37 $string = stream_get_line( $this->input, 99999, $this->RecordTerminator );
38 $this->valid = true;
39 if( $string === false ) $this->valid = false;
40 else $this->currentRecord->loadFromString( $this->prepend . $string );
41 $this->prepend = "";
42 }
43 public function rewind() {
44 if($this->currentPosition !== null) rewind( $this->input );
45 $this->currentPosition = -1;
46 $this->currentRecord = new $this->recordClass();
47 $this->next();
48 }
49}
50
51class ISO2709RecordSetArray implements ArrayAccess {
53
54 function __construct( $input, $numCache = 10 ) {
55 if( is_string( $input ) ) $input = fopen( $input, "rb" );
56 if( !is_resource( $input ) ) throw new Exception("ISO2709RecordSetArray requires a file resource, filename, or URL to be created");
57 $this->input = $input;
58 $this->records = array();
59 $this->cache = array();
60 $this->recordOffsets = array();
61 $this->recordOffsets[0] = 0;
62 $this->numCache = $numCache;
63 $this->recordClass = "ISO2709Record";
64 }
65
66 public function offsetExists( $offset ) {
67 if(!is_int($offset) or $offset < 0 or ($this->numRecords !== null and $this->numRecords <= $offset))
68 return false;
69 return $this->offsetGet( $offset ) === false;
70 }
71
72 public function offsetGet ( $offset ) {
73 # Update cache
74 foreach( array_keys( $this->cache, $offset ) as $k ) { unset($this->cache[$k]); }
75 $this->cache = array_values( $this->cache );
76 array_push( $this->cache, $offset );
77 while( count( $this->cache ) > $this->numCache ) {
78 $k = array_shift( $this->cache );
79 unset( $this->records[$k] );
80 }
81 # Now get the record
82 if( !isset( $this->records[ $offset ] ) ) {
83 if( !isset( $this->recordOffsets[$offset] ) ) {
84 for( $i = $offset - 1 ; $i > 0 ; $i-- ) {
85 if( isset( $this->recordOffsets[$i] ) ) break;
86 }
87 fseek( $this->input, $this->recordOffsets[$i], SEEK_SET );
88 while( $i < $offset ) {
89 stream_get_line( $this->input, 99999, ISO2709::RecordTerminator );
90 $i++;
91 $this->recordOffsets[ $i ] = ftell( $this->input );
92 }
93 } else {
94 fseek( $this->input, $this->recordOffsets[$offset], SEEK_SET );
95 }
96 $this->records[ $offset ] = new $this->recordClass();
97 $this->records[ $offset ]->loadFromBinaryStream( $this->input );
98 if(feof($this->input)) {
99 $this->numRecords = $offset+1;
100 } else {
101 $this->recordOffsets[$offset+1] = ftell( $this->input );
102 }
103 }
104 return $this->records[ $offset ];
105 }
106
107 public function offsetSet ( $offset, $value ) {
108 throw new Exception( "This type of record set is read-only, sorry" );
109 }
110
111 public function offsetUnset ( $offset ) {
112 throw new Exception( "This type of record set is read-only, sorry" );
113 }
114}
115
116class ISO2709 {
117 /* A record must be terminated with 0x1D,
118 the directory and each field must be terminated with 0x1E,
119 and each subfield begins with an identifier which must in turn begin
120 with 0x1F */
121 const RecordTerminator = "\035";
122 const FieldTerminator = "\036";
123 const SubfieldInitiator = "\037";
124
125};
126
127class ISO2709Exception extends Exception { };
128
130 public $leader; // string form of the leader (mnemonic marc format)
131 public $fields; // array of ISO2709 fields
132 public $exceptions; // an array of standards violations as strings
133 public $raw;
135
136 protected $defaults;
137
138 protected function set_default( $name, $value ) {
139 if( !isset( $this->defaults ) ) $this->defaults=array();
140 if( !isset( $this->defaults[$name] ) ) $this->defaults[$name] = $value;
141 }
142
143 function __construct( ) {
144 $this->set_default( "identifierLength", 0 ); // required, so if not available disable subfields
145 $this->set_default( "indicatorLength", 0 ); // advisory only
146 $this->set_default( "lengthOfLengthOfField", 4 ); // required, but no real default; use MARC21
147 $this->set_default( "lengthOfStartingCharacterPosition", 5 ); // required, but no real default; use MARC21
148 $this->set_default( "lengthOfImplementationDefined", 0 ); // required, it seems like 0 is the only sensible value; use MARC21
149 $this->leader = "";
150 $this->fields = array();
151 $this->exceptions = array();
152 }
153
154 ############################################################
155 ##
156 ## Standards conformance
157 ##
158
159 // ISO2709 4.5.1
160 function isTagValid( $tag ) {
161 return ($tag !== "000") && is_string( $tag ) && ( preg_match('/^[0-9A-Z]{3}$/',$tag) || preg_match('/^[0-9a-z]{3}$/',$tag) );
162 }
163
164 function assertTagValid( $tag ) {
165 if( !self::isTagValid( $tag ) ) {
166 $this->exception("ISO2709 4.5.1: Tags must be alphanumeric, using only one case, and must be 3 bytes long. Thus '$tag' is not allowed.");
167 return false;
168 }
169 if( !$this->isTagValid( $tag ) ) {
170 $this->exception("Unknown: Invalid tag '$tag'");
171 return false;
172 }
173 return true;
174 }
175
176 // ISO2709 4.1
177 function isTagRepeatable( $tag ) {
178 return ($tag !== "001");
179 }
180
181 function assertTagRepeatable( $tag ) {
182 if( !self::isTagRepeatable( $tag ) ) {
183 $this->exception("ISO2709 4.5.1: 001 refers to the record identifier field, which is not repeatable (4.1c)");
184 return false;
185 }
186 if( !$this->isTagRepeatable( $tag ) ) {
187 $this->exception("Unknown: Tag '$tag' is not repeatable.");
188 return false;
189 }
190 return true;
191 }
192
193 // ISO2709 4.1
194 // -1 if $a must come before $b, +1 if $a must come after $b, 0 if unspecified
195 // ISO2709 ordering is simple 001, then 00*, then ***
196 function tagOrder( $a, $b ) {
197 if(!$this->isTagValid($a) || !$this->isTagValid($b)) return 0;
198 if( $a === $b ) return 0;
199 if( $a === "001" ) return -1;
200 if( $b === "001" ) return +1;
201 $a = substr( $a, 0, 2 );
202 $b = substr( $b, 0, 2 );
203 if( $a === $b ) return 0;
204 if( $a === "00" ) return -1;
205 if( $b === "00" ) return +1;
206 return 0;
207 }
208
209 function assertTagOrder( $a, $b ) {
210 if( self::tagOrder( $a, $b ) > 0 )
211 $this->exception("ISO2709 4.1: Field 001 is first, followed by 00* in any order, followed by all other tags; hence '$a' cannot precede '$b'");
212 else if( $this->tagOrder( $a, $b ) > 0 )
213 $this->exception("Unknown: '$a' is not supposed to precede '$b'");
214 }
215
216 function exception( $string ) {
217 $this->exceptions[] = $string;
218 }
219
220 ############################################################
221 ##
222 ## Parsing a binary string
223 ##
224
225 public function loadFromBinaryStream( $filehandle ) {
226 $peek = stream_get_line( $filehandle, 99999, ISO2709::RecordTerminator );
227 if( $peek ) $peek .= ISO2709::RecordTerminator;
228 else return false;
229 /*
230 $peek = fread( $filehandle, 5 );
231 if( $peek === false ) return false;
232 if( ! ctype_digit( $peek ) ) return false;
233 $peek .= fread( $filehandle, intval( $peek ) - 5 );
234 */
235 return $this->loadFromBinaryString( $peek );
236 }
237
238 private function checkString( $name, $trueValue, $string, $start = 0, $length = false, $default = false ) {
239 $string = ( $length === false ) ? substr( $string, $start ) : substr( $string, $start, $length );
240 if( is_int( $trueValue ) ) {
241 if( !ctype_digit( $string ) or intval( $string ) !== $trueValue )
242 $this->exception(
243 "ISO2709: Value '$string' for \"$name\" does not match computed value '$trueValue'" );
244 } else {
245 if( !ctype_digit( $string ) )
246 $this->exception(
247 "ISO2709: Value '$string' for \"$name\" is not numeric" );
248 }
249 return ctype_digit($string)?intval( $string ):$default;
250 }
251
252 public function setLeader( $string ) {
253 $this->leader = $string;
254 $this->indicatorLength = $this->checkString( "Indicator Length", false, $this->leader, 10, 1, $this->defaults["indicatorLength"] );
255 $this->identifierLength = $this->checkString( "Identifier Length", false, $this->leader, 11, 1, $this->defaults["identifierLength"] );
256 }
257
258 public function loadFromBinaryString( $string, $fuzzy = true ) {
259 $this->leader = substr( $string, 0, 24 );
260 $this->fields = array();
261 $this->exceptions = array();
262 $this->raw = $string;
263
264 // We now ignore the leader as much as possible
265 $recordLength = strpos( $string, ISO2709::RecordTerminator ) + 1;
266 $baseAddressOfData = strpos( $string, ISO2709::FieldTerminator )+1;
267
268 $this->checkString( "Record Length", $recordLength, $this->leader, 0, 5 );
269 $indicatorLength = $this->checkString( "Indicator Length", false, $this->leader, 10, 1, $this->defaults["indicatorLength"] );
270 $identifierLength = $this->checkString( "Identifier Length", false, $this->leader, 11, 1, $this->defaults["identifierLength"] );
271 $this->checkString( "Base Address of Data", $baseAddressOfData, $this->leader, 12, 5 );
272 $lengthOfLengthOfField = $this->checkString( "Length of Length-Of-Field portion of directory entry", false, $this->leader, 20, 1, $this->defaults["lengthOfLengthOfField"] );
273 $lengthOfStartingCharacterPosition = $this->checkString( "Length of Starting-Character-Position portion of directory entry", false, $this->leader, 21, 1, $this->defaults["lengthOfStartingCharacterPosition"] );
274 $lengthOfImplementationDefined = $this->checkString( "Length of Implementation-Defined portion of directory entry", false, $this->leader, 22, 1, $this->defaults["lengthOfImplementationDefined"] );
275 if( "$lengthOfLengthOfField$lengthOfStartingCharacterPosition$lengthOfImplementationDefined" !== "450" ) {
276 $this->exception( "MARC21: $lengthOfLengthOfField$lengthOfStartingCharacterPosition$lengthOfImplementationDefined != 450" );
277 $lengthOfLengthOfField = 4;
278 $lengthOfStartingCharacterPosition = 5;
279 $lengthOfImplementationDefined = 0;
280 }
281
282 $this->indicatorLength = $indicatorLength; // advisory only
283 $this->identifierLength = $identifierLength; // required
284
285 $lengthOfDirectoryEntry = 3 + $lengthOfLengthOfField + $lengthOfStartingCharacterPosition + $lengthOfImplementationDefined;
286
287 if( 0 != ( ( $baseAddressOfData-25) % $lengthOfDirectoryEntry ) )
288 $this->exception( "ISO2709 4.4.1: Directory does not end on directory entry boundary ".
289 "(Directory is ". ($baseAddressOfData-25)." bytes long, " .
290 "each entry is 3 + $lengthOfLengthOfField + $lengthOfStartingCharacterPosition + $lengthOfImplementationDefined = $lengthOfDirectoryEntry bytes long, " .
291 "leaving " . (($baseAddressOfData-25)%$lengthOfDirectoryEntry) . " bytes leftover)" );
292
293 // now read the directory
294 $fieldTypeMode = 0;
295 $lastTag = false;
296 for( $i = 24 ; $i+$lengthOfDirectoryEntry+1 <= $baseAddressOfData ; $i+= $lengthOfDirectoryEntry ) {
297 $tag = substr( $string, $i, 3 );
298
299 $off = $this->checkString( "Start of field '$tag'", false, $string, $i+3+$lengthOfLengthOfField, $lengthOfStartingCharacterPosition );
300 if( $baseAddressOfData + $off > strlen($string) ) {
301 $off = substr( $string, $i+3+$lengthOfLengthOfField, $lengthOfStartingCharacterPosition );
302 $this->exception( "ISO2709: Offset '$off' of field '$tag' is beyond end of string. Losing this field." );
303 continue;
304 }
305 if( $string[ $baseAddressOfData + $off - 1 ] !== ISO2709::FieldTerminator ) {
306 $this->exception( "Jack: Data for field '$tag' does not immediately follow a field terminator" );
307 $off = strrpos( substr( $string, 0, $baseAddressOfData + $off ), ISO2709::FieldTerminator ) - $baseAddressOfData + 1;
308 }
309 $len = strpos( $string, ISO2709::FieldTerminator, $baseAddressOfData + $off ) - ( $baseAddressOfData + $off - 1);
310 $imp = substr( $string, $i + 3 + $lengthOfLengthOfField + $lengthOfStartingCharacterPosition, $lengthOfImplementationDefined );
311
312 $maxFieldLen = intval( "1" .str_repeat( $lengthOfLengthOfField, "0" ) ) - 1;
313 $calclen = 0;
314 while( substr( $string, $i+3, $lengthOfLengthOfField ) === "000" ) {
315 $calclen += $maxFieldLen;
316 $i += $lengthOfDirectoryEntry;
317 $newTag = substr( $string, $i, 3 );
318 if( $tag !== $newTag ) {
319 $this->exception("ISO2709 4.4.4: When the recorded field length is 0, each following directory entry refers to the same field. However, we went from '$tag' to '$newTag'.");
320 $i -= $lengthOfDirectoryEntry;
321 break;
322 }
323 }
324 $this->checkString( "Length of field '$tag'", $len-$calclen, $string, $i+3, $lengthOfLengthOfField );
325
326
327 // read in the field
328 $data = substr( $string, $baseAddressOfData + $off, $len - 1 );
329 $this->AppendFieldBinary( $tag, $data, array( $tag, $len, $off, $imp ) );
330 }
331 return true;
332 }
333
334 function DataNotInDirectory( ) {
335 $string = $this->raw;
336 $recordLength = strpos( $string, ISO2709::RecordTerminator ) + 1;
337 $baseAddressOfData = strpos( $string, ISO2709::FieldTerminator )+1;
338 $lengthOfLengthOfField = $this->checkString( "Length of Length-Of-Field portion of directory entry", false, $this->leader, 20, 1, $this->defaults["lengthOfLengthOfField"] );
339 $lengthOfStartingCharacterPosition = $this->checkString( "Length of Starting-Character-Position portion of directory entry", false, $this->leader, 21, 1, $this->defaults["lengthOfStartingCharacterPosition"] );
340 $lengthOfImplementationDefined = $this->checkString( "Length of Implementation-Defined portion of directory entry", false, $this->leader, 22, 1, $this->defaults["lengthOfImplementationDefined"] );
341 $lengthOfDirectoryEntry = 3 + $lengthOfLengthOfField + $lengthOfStartingCharacterPosition + $lengthOfImplementationDefined;
342
343 $map = str_repeat( "?", $recordLength );
344 for( $i = 24 ; $i+$lengthOfDirectoryEntry+1 <= $baseAddressOfData ; $i+= $lengthOfDirectoryEntry ) {
345 $tag = substr( $string, $i, 3 );
346 $off = intval( substr( $string, $i+3+$lengthOfLengthOfField, $lengthOfStartingCharacterPosition ) );
347 $off = strrpos( substr( $string, 0, $baseAddressOfData + $off ), ISO2709::FieldTerminator ) - $baseAddressOfData + 1;
348
349 $calclen = 0;
350 $maxFieldLen = intval( "1" .str_repeat( $lengthOfLengthOfField, "0" ) ) - 1;
351 while( substr( $string, $i+3, $lengthOfLengthOfField ) === "000" ) {
352 $i += $lengthOfDirectoryEntry;
353 $calclen += $maxFieldLen;
354 }
355 $len = $calclen + intval( substr( $string, $i+3, $lengthOfLengthOfField ) );
356
357 for( $j = 0 ; $j < $len ; $j++ ) {
358 $map[$baseAddressOfData+$off+$j] = " ";
359 }
360 $map = substr_replace( $map, $tag, $baseAddressOfData+$off );
361 $map[$baseAddressOfData+$off+$len-1] = "t";
362 }
363 for( $i = 0 ; $i < $recordLength ; $i++ ) {
364 if( $i < 24 ) $map[$i] = "l";
365 else if( $i+1 < $baseAddressOfData ) $map[$i] = "d";
366 else if( $string[$i] == ISO2709::FieldTerminator ) {
367 if($map[$i] != "t") $map[$i]="#";
368 else $map[$i] = "T";
369 }
370 }
371 print $map."\n";
372 }
373
374 function loadFromString( $string ) {
375 return $this->loadFromBinaryString( $string );
376 }
377
378
379 ############################################################
380 ##
381 ## Append a field to a MARC record
382 ##
383
384 function ParseBinaryField( $tag, $data, $directoryEntry ) {
385 # Check for valid tag
386 $this->assertTagValid($tag);
387
388 # Strip out indicators and subfields, handling bad data
389 # If there is a subfield identifier initiator, then everything before it is the indicators
390 # Otherwise the indicators are precisely the first indicatorLength bytes
391 if( false !== ( $subfieldStart = strpos( $data, ISO2709::SubfieldInitiator ) ) ) {
392 $indicators = substr( $data, 0, $subfieldStart );
393 $subfields = explode( ISO2709::SubfieldInitiator, substr( $data, $subfieldStart+1 ) );
394 foreach( $subfields as $k => $v ) {
395 $identifier = substr( $v, 0, $this->identifierLength - 1);
396 $subdata = substr( $v, $this->identifierLength-1 );
397 $subfields[$k] = new ISO2709Subfield( $identifier, $subdata );
398 }
399 $data = "";
400 } else {
401 if( substr( $tag, 0, 2 ) !== "00" ) {
402 $indicators = substr( $data, 0, $this->indicatorLength );
403 $data = substr( $data, $this->indicatorLength );
404 } else {
405 $indicators = "";
406 }
407 $subfields = array();
408 }
409 return new ISO2709Field( $tag, $data, $indicators, $subfields, $directoryEntry, $this );
410 }
411
412 function AppendFieldBinary( $tag, $data, $directoryEntry = array(), $reorder = false ) {
413 $field = $this->ParseBinaryField( $tag, $data, $directoryEntry );
414 $this->AppendField( $field, $reorder );
415 }
416
417 function CheckField( $field ) {
418 if( substr( $field->tag, 0, 2 ) === "00" ) {
419 if( $field->indicators !== "" )
420 $this->exception("ISO2709 4.5.4: Control fields have no indicators, but field '{$field->tag}' has indicators '{$field->indicators}'");
421 if( count( $field->subfields ) > 0 )
422 $this->exception("ISO2709 4.5.4: Control fields have no subfields, but field '{$field->tag}' has " . count( $field->subfields) . " subfields");
423 } else {
424 if( strlen( $field->indicators ) != $this->indicatorLength )
425 $this->exception("ISO2709 4.5.4d: Field '{$field->tag}' has indicators '{$field->indicators}' of the wrong length (specified length is {$this->indicatorLength})");
426 foreach( $field->subfields as $subfield ) {
427 if( strlen( $subfield->identifier ) + 1 != $this->identifierLength )
428 $this->exception("ISO2709: Field '{$field->tag}' has subfield '\${$subfield->identifier}' but that is not the right length of a subfield identifier.");
429 }
430 if( $field->data !== "" and $this->indicatorLength > 0 )
431 $this->exception("ISO2709 4.5.4: Field '{$field->tag}' has data, but should only have subfields");
432 }
433 }
434
435 function AppendField( $field, $reorder = false ) {
436 $this->checkField( $field );
437 $field->record = $this;
438
439 # Check for valid ordering
440 $numTags = count( $this->fields );
441 if( $numTags > 0 ) {
442 if( $reorder === false ) {
443 $lastTag = $this->fields[ $numTags - 1 ]->tag;
444 #$this->assertTagOrder( $lastTag, $field->tag );
445 $appendAfter = $numTags;
446 } else {
447 for( $i = $numTags - 1 ; $i >= 0 ; $i-- ) {
448 $lastTag = $this->fields[ $i ]->tag;
449 if( $this->tagOrder( $lastTag, $field->tag ) <= 0 ) {
450 #print "Append {$field->tag} after $lastTag \n";
451 break;
452 }
453 #print "Do not {$field->tag} after $lastTag \n";
454 }
455 $appendAfter = $i;
456 }
457 } else { $appendAfter = -1; }
458
459 array_splice( $this->fields, $appendAfter+1, 0, array( $field ) );
460 }
461
462 function removeField( $field ) {
463 foreach( $this->fields as $k => $v ) {
464 if( $v === $field ) unset( $this->fields[$k] );
465 }
466 $this->fields = array_values( $this->fields );
467 }
468
469 function delFields( $tag ) {
470 foreach( $this->fields as $k => $v ) {
471 if( $v->tag === $tag ) unset( $this->fields[$k] );
472 }
473 $this->fields = array_values( $this->fields );
474 }
475
476 function getFields( $tag ) {
477 $ret = array();
478 foreach( $this->fields as $v ) {
479 if( $v->tag === $tag ) $ret[] = $v;
480 }
481 return $ret;
482 }
483
484 function getTagPattern( $patt ) {
485 $ret = array();
486 foreach( $this->fields as $v ) {
487 if( preg_match("/$patt/", $v->tag ) ) $ret[] = $v;
488 }
489 return $ret;
490 }
491
492 ############################################################
493 ##
494 ## Export as binary
495 ##
496
497 public function AsBinaryString( ) {
498 // We now ignore the leader as much as possible
499 $indicatorLength = $this->indicatorLength; // advisory, we will write corrupt files if requested
501 $lengthOfLengthOfField = $this->checkString( "Length of Length-Of-Field portion of directory entry", false, $this->leader, 20, 1, $this->defaults["lengthOfLengthOfField"] );
502 $lengthOfStartingCharacterPosition = $this->checkString( "Length of Starting-Character-Position portion of directory entry", false, $this->leader, 21, 1, $this->defaults["lengthOfStartingCharacterPosition"] );
503 $lengthOfImplementationDefined = $this->checkString( "Length of Implementation-Defined portion of directory entry", false, $this->leader, 22, 1, $this->defaults["lengthOfImplementationDefined"] );
504 $lengthOfDirectoryEntry = 3 + $lengthOfLengthOfField + $lengthOfStartingCharacterPosition + $lengthOfImplementationDefined;
505
506 $directory = "";
507 $data = "";
508 $baseAddressOfData = 24 + $lengthOfDirectoryEntry * count( $this->fields ) + 1;
509 foreach( $this->fields as $v ) {
510 $tag = $v->tag;
511 $start = strlen( $data );
512 $data .= $v->indicators;
513 foreach( $v->subfields as $vv ) {
514 $data .= ISO2709::SubfieldInitiator . $vv->identifier . $vv->data;
515 }
516 $data .= $v->data;
518 $length = strlen($data) - $start;
519 $impdef = $v->directoryEntry[3];
520 $directory .= sprintf(
521 "%3.3s" .
522 "%0{$lengthOfLengthOfField}.{$lengthOfLengthOfField}d" .
523 "%0{$lengthOfStartingCharacterPosition}.{$lengthOfStartingCharacterPosition}d" .
524 "%{$lengthOfImplementationDefined}.{$lengthOfImplementationDefined}s",
525 $tag, $length, $start, $impdef );
526 }
527 $directory .= ISO2709::FieldTerminator;
529 $recordLength = $baseAddressOfData + strlen($data);
530 $leader = sprintf("%05.5d%1.1s%4.4s%1.1d%1.1d%05.5d%3.3s%1.1d%1.1d%1.1d%1.1s",
531 $recordLength, substr( $this->leader, 5, 1 ),
532 substr( $this->leader, 6, 4), $indicatorLength,
533 $identifierLength, $baseAddressOfData, substr( $this->leader, 17, 3 ),
534 $lengthOfLengthOfField,
535 $lengthOfStartingCharacterPosition,
536 $lengthOfImplementationDefined, substr( $this->leader, 23, 1 ) );
537 $this->leader = $leader;
538 $ret = $leader . $directory . $data;
539 assert( strlen( $leader ) + strlen( $directory ) === $baseAddressOfData );
540 assert( strlen( $ret ) === $recordLength );
541 return $ret;
542 }
543
544 ############################################################
545 ##
546 ## Export as Mnemonic string
547 ##
548
549
550 public function AsMnemonicString(
551 $leader_tag = "LDR",
552 $field_initiator = "=",
553 $tag_terminator = " ",
554 $identifier_initiator = "\$",
555 $field_terminator = "\r\n",
556 $record_terminator = "\r\n",
557 $space_replacer = " "
558 ) {
559 $ret = array();
560 $ret[] = sprintf( "%s%3.3s%s%s%s%s%s", $field_initiator, $leader_tag,
561 $tag_terminator, "", "", $this->leader, $field_terminator );
562 foreach( $this->fields as $v ) {
563 $ret[] = $v->AsMnemonicString( $field_initiator,$tag_terminator,$identifier_initiator,$field_terminator, $space_replacer );
564 }
565 foreach( $this->exceptions as $v ) {
566 $ret[] = sprintf( "%s%3.3s%s%s%s%s%s", $field_initiator, "XXX",
567 $tag_terminator, "xx", "\$x$v", "", $field_terminator );
568 }
569 return implode( $ret ) . $record_terminator;
570 }
571}
572
575 function __construct( $tag, $data, $indicators = "", $subfields = array(), $directoryEntry = array(), $record = null ) {
576 $this->tag=$tag;
577 $this->data=$data;
578 $this->indicators = $indicators;
579 $this->directoryEntry = $directoryEntry;
580 if( !isset( $this->directoryEntry[0] ) ) $this->directoryEntry[0] = $tag;
581 if( !isset( $this->directoryEntry[3] ) ) $this->directoryEntry[3] = "";
582 $this->record = $record;
583 $this->subfields = array();
584 foreach( $subfields as $v ) { $this->appendSubfield( $v ); }
585 }
586 function remove() {
587 return $this->record->removeField( $this );
588 }
589 function delete() {
590 return $this->record->removeField( $this );
591 }
592 function appendSubfield( $subfield, $arg2 = null ) {
593 if( is_a( $subfield, "ISO2709Subfield" ) ) {
594 $subfield->field = $this;
595 $this->subfields[] = $subfield;
596 } else {
597 $this->appendSubfield( new ISO2709Subfield( $subfield, $arg2 ) );
598 }
599 }
600
601 # maybe title more specific
602 function insertSubfieldBefore( $identifier, $subfield, $arg3 = null) {
603 if( is_a( $subfield, "ISO2709Subfield" ) ) {
604 $found = false;
605 foreach( $this->subfields as $k => $v ) {
606 if( $v->identifier === $identifier ) {
607 array_splice($this->subfields,$k,0,array($subfield));
608 $this->subfields = array_values($this->subfields);
609 $found = true;
610 if( $k > 0 ) {
611 if (preg_match("/(.*?)( *[\:\/=] *)$/",$this->subfields[$k-1]->data,$m)) {
612 $this->subfields[$k-1]->data = $m[1];
613 $this->subfields[$k]->data .= $m[2];
614 }
615 }
616 break;
617 }
618 }
619 return $found;
620 } else {
621 return $this->insertSubfieldBefore( $identifier, new ISO2709Subfield( $subfield, $arg3 ) );
622 }
623 }
624
625 function removeSubfield( \ISO2709Subfield $subfield ) {
626 foreach( $this->subfields as $k => $v ) {
627 if( $v === $subfield ) unset( $this->subfields[$k] );
628 }
629 $this->subfields = array_values( $this->subfields );
630 }
631 function getOneSubfield( $identifier ) {
632 foreach( $this->subfields as $v ) {
633 if( $v->identifier === $identifier ) return $v;
634 }
635 return false;
636 }
637 function getSubfields( $identifier ) {
638 $ret = array();
639 foreach( $this->subfields as $v ) {
640 if( $v->identifier === $identifier ) $ret[] = $v;
641 }
642 return $ret;
643 }
644
645 public function AsMnemonicString(
646 $field_initiator = "=",
647 $tag_terminator = " ",
648 $identifier_initiator = "\$",
649 $field_terminator = "\r\n",
650 $space_replacer = " "
651 ) {
652 $sub = "";
653 foreach( $this->subfields as $vv ) {
654 $sub .= $identifier_initiator . $vv->identifier . $vv->data;
655 }
656 $indicators = str_replace(" ",$space_replacer, $this->indicators );
657 $data = str_replace(" ",$space_replacer, $this->data );
658 return sprintf( "%s%3.3s%s%s%s%s%s", $field_initiator, $this->tag,
659 $tag_terminator, $indicators, $sub, $data, $field_terminator );
660 }
661}
662
666 $this->identifier = $identifier;
667 $this->data = $data;
668 }
669 function remove() {
670 return $this->field->removeSubfield( $this );
671 }
672 function delete() {
673 return $this->field->removeSubfield( $this );
674 }
675
676 function prev() {
677 $prev = null;
678 foreach( $this->field->subfields as $vv ) {
679 if ($vv == $this) {
680 return $prev;
681 } else {
682 $prev = $vv;
683 }
684 }
685 }
686
687}
insertSubfieldBefore( $identifier, $subfield, $arg3=null)
Definition: ISO2709.php:602
$directoryEntry
Definition: ISO2709.php:574
removeSubfield(\ISO2709Subfield $subfield)
Definition: ISO2709.php:625
appendSubfield( $subfield, $arg2=null)
Definition: ISO2709.php:592
AsMnemonicString( $field_initiator="=", $tag_terminator=" ", $identifier_initiator="\$", $field_terminator="\r\n", $space_replacer=" ")
Definition: ISO2709.php:645
$indicators
Definition: ISO2709.php:574
$subfields
Definition: ISO2709.php:574
__construct( $tag, $data, $indicators="", $subfields=array(), $directoryEntry=array(), $record=null)
Definition: ISO2709.php:575
getOneSubfield( $identifier)
Definition: ISO2709.php:631
getSubfields( $identifier)
Definition: ISO2709.php:637
offsetSet( $offset, $value)
Definition: ISO2709.php:107
offsetGet( $offset)
Definition: ISO2709.php:72
offsetUnset( $offset)
Definition: ISO2709.php:111
__construct( $input, $numCache=10)
Definition: ISO2709.php:54
offsetExists( $offset)
Definition: ISO2709.php:66
__construct( $input)
Definition: ISO2709.php:19
$exceptions
Definition: ISO2709.php:132
DataNotInDirectory()
Definition: ISO2709.php:334
loadFromBinaryString( $string, $fuzzy=true)
Definition: ISO2709.php:258
assertTagRepeatable( $tag)
Definition: ISO2709.php:181
AppendField( $field, $reorder=false)
Definition: ISO2709.php:435
AppendFieldBinary( $tag, $data, $directoryEntry=array(), $reorder=false)
Definition: ISO2709.php:412
setLeader( $string)
Definition: ISO2709.php:252
AsMnemonicString( $leader_tag="LDR", $field_initiator="=", $tag_terminator=" ", $identifier_initiator="\$", $field_terminator="\r\n", $record_terminator="\r\n", $space_replacer=" ")
Definition: ISO2709.php:550
getFields( $tag)
Definition: ISO2709.php:476
AsBinaryString()
Definition: ISO2709.php:497
getTagPattern( $patt)
Definition: ISO2709.php:484
loadFromString( $string)
Definition: ISO2709.php:374
$identifierLength
Definition: ISO2709.php:134
set_default( $name, $value)
Definition: ISO2709.php:138
CheckField( $field)
Definition: ISO2709.php:417
ParseBinaryField( $tag, $data, $directoryEntry)
Definition: ISO2709.php:384
loadFromBinaryStream( $filehandle)
Definition: ISO2709.php:225
isTagValid( $tag)
Definition: ISO2709.php:160
$indicatorLength
Definition: ISO2709.php:134
__construct()
Definition: ISO2709.php:143
removeField( $field)
Definition: ISO2709.php:462
isTagRepeatable( $tag)
Definition: ISO2709.php:177
tagOrder( $a, $b)
Definition: ISO2709.php:196
assertTagValid( $tag)
Definition: ISO2709.php:164
checkString( $name, $trueValue, $string, $start=0, $length=false, $default=false)
Definition: ISO2709.php:238
delFields( $tag)
Definition: ISO2709.php:469
assertTagOrder( $a, $b)
Definition: ISO2709.php:209
exception( $string)
Definition: ISO2709.php:216
__construct( $identifier, $data)
Definition: ISO2709.php:665
Definition: ISO2709.php:116
const RecordTerminator
Definition: ISO2709.php:121
const SubfieldInitiator
Definition: ISO2709.php:123
const FieldTerminator
Definition: ISO2709.php:122