/[webpac]/openisis/0.9.9e/php/Isis/Rec.php
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /openisis/0.9.9e/php/Isis/Rec.php

Parent Directory Parent Directory | Revision Log Revision Log


Revision 604 - (show annotations)
Mon Dec 27 21:49:01 2004 UTC (19 years, 4 months ago) by dpavlin
File size: 17857 byte(s)
import of new openisis release, 0.9.9e

1 <?php
2 /*
3 The Malete project - the Z39.2/Z39.50 database framework of OpenIsis.
4 Version 0.9.x (patchlevel see file Version)
5 Copyright (C) 2001-2004 by Erik Grziwotz, erik@openisis.org
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
21 see README for more information
22 EOH */
23
24 // $Id: Rec.php,v 1.2 2004/11/03 13:57:15 kripke Exp $
25
26 /** FIELD mode replaces newlines with tabs.
27 On deserializing, these tabs are not converted back to newline.
28 Do not use if you need to retain newline information.
29 */
30 define( 'ISIS_REC_FIELD', "\t" );
31 /** TEXT mode replaces newlines with vertical tabs.
32 Vertical tabs are converted back to newlines only when explicitly
33 deserializing in TEXT mode, since it's not transparent to binary data.
34 */
35 define( 'ISIS_REC_TEXT', "\013" );
36 /**
37 the basic ISIS formatting mode.
38 "Data Mode" MDL doesn't make a significant difference,
39 and uppercase can better be achieved using strtoupper
40 (given the right locale ...).
41 */
42 define( 'ISIS_REC_MHL', 'MHL' );
43 // sorry, no other way to save this ...
44 $GLOBALS['ISIS_REC_MHL_PAT'] = array(
45 '/(<[^=>]*)=[^>]+>/', # dump <a=b> substitutions
46 '/></', # replace >< pairs
47 '/[><]/', # nuke other ><
48 '/^\^./', # kill initial subfield spec
49 '/\^a/', # ^a -> ;
50 '/\^[b-i]/', # ^[b-i] -> ,
51 '/\^./' # others -> .
52 );
53 $GLOBALS['ISIS_REC_MHL_REPL'] = array(
54 '\1>', '; ', '', '', '; ', ', ', '. '
55 );
56
57 /**
58 An ISIS(/IIF/Z39.2/ISO2709)-style record in pure PHP.
59
60 This is only loosely connected to an Isis Database,
61 most functions can be used without having a DB.
62
63 Note: Most getter methods work regardless of the type of tags.
64 However, the setters are strongly biased towards numeric tags.
65
66 @version $Revision: 1.2 $
67 @license LGPL
68 @package Isis
69 */
70 class Isis_Rec {
71 /**
72 The associated database, if any. Better be an Isis_Db.
73 @var object $db
74 */
75 var $db = 0;
76 /**
77 The record's MFN ("master file number", a.k.a. rowid).
78 A value of 0 means the record did not yet have a number in it's db,
79 a write will be handled as insert.
80 @var int $mfn
81 */
82 var $mfn = 0;
83 /**
84 The record's header.
85 For data records, this is mfn[@pos][\tleader],
86 where leader can any be any MARC leader data.
87 */
88 var $head = '';
89 /**
90 Array of tags for the record. Keys and values better be integers
91 (others might be ignored or yield unexpected results in some contexts).
92 The count of the tag array is taken as length of the record.
93 The tag array may have holes (unassigned ints) if unset is used.
94 @var array $tag
95 */
96 var $tag;
97 /**
98 Array of values for the record. Values better be strings
99 (i.e. will be forced to strings by some operations).
100 @var array $tag
101 */
102 var $val;
103 /**
104 last tag accessed using function v
105 */
106 var $v = null;
107
108
109
110 // ////////////////////////////////////////////////////////////
111 // static
112 //
113
114 /** static function to format a value.
115 maybe used as Isis_Rec::fmt.
116 @param string $val a value to be formatted
117 @param string $fmt a format specification, defaults to 'MHL'
118 - if $fmt is null,
119 the value is returned unchanged
120 - if $fmt starts with '&' or '%',
121 that character is stripped and htmlspecialchars or urlencode, resp.,
122 is applied to the (each) value as last step
123 - if $fmt starts with 'MHL' (the constant ISIS_REC_MHL),
124 that is stripped and the classical MHL ISIS formatting applied to values
125 (before & or % mangling, rarely needed with subfields)
126 - if $fmt is (now) empty,
127 the complete value is used
128 - else we're going for subfields:
129 - if $fmt starts with a hat,
130 the hat is stripped an used as subfield delimiter (TAB otherwise)
131 - if $fmt matches /^([^(]*)\((\d*)(\.\.(\d*))?/,
132 the part from the first '(' on is stripped as occurence selector
133 (note that an optional closing ')' and additional chars are ignored)
134 - the (remaining) characters in $fmt are subfield names,
135 '*' selects any subfield
136 (including the initial unnamed, even if it's empty !),
137 '' any without stripping subfield names
138 - if there is a occurence or range selected,
139 for every character in the $fmt the specified occurences are used
140 (counted from 0). If either bound is empty, 0 is used.
141 If a range is specified (.. given), an upper bound of 0 means up to end.
142 By default, only the first occurence (0) is used.
143 @return if there is only a single character (remaining) in $fmt,
144 and only a single occurence selectedi (no ..), a string is returned.
145 Else you've been asking for an array,
146 and so an array is returned even if it contains only a single value.
147 If more than one subfield name was specified or the '*',
148 the names are used ('' for the initial).
149 If a range is selected, the index is used (in addition).
150 Fields are added by first looping over subfield names,
151 then occurences. PHP may or may not loop the array in that order.
152 Example:
153 '^ab' gives keys 'a' and 'b', 'b(1..' gives keys '1','2'...m
154 '^cab(..', gives 'c0', 'c1', ... 'a0', ...
155 */
156 function fmt ( $val, $fmt = ISIS_REC_MHL )
157 {
158 global $ISIS_REC_MHL_PAT, $ISIS_REC_MHL_REPL;
159 if ( null == $fmt )
160 return $val;
161 if ( '&' != $fmt{0} && '%' != $fmt{0} )
162 $mode = '';
163 else {
164 $mode = '&' == $fmt{0} ? 'htmlspecialchars' : 'urlencode';
165 $fmt = substr($fmt,1);
166 }
167 if ($mhl = ('M' == $fmt{0} && ISIS_REC_MHL == substr($fmt,0,3)) )
168 $fmt = substr($fmt,3);
169 if ( '' == $fmt ) {
170 if ( $mhl )
171 $val = preg_replace($ISIS_REC_MHL_PAT, $ISIS_REC_MHL_REPL, $val);
172 return $mode ? $mode($val) : $val;
173 }
174 if ( '^' != $fmt{0} )
175 $sep = "\t";
176 else {
177 $sep = '^';
178 $fmt = substr($fmt,1);
179 }
180 $sub = explode($sep,$val); // this really is not for performance ...
181 $n = count($sub);
182 if ( false !== strpos($fmt,'(') // quick check
183 && preg_match( '/^([^(]*)\((\d*)(\.\.(\d*))?/', $fmt, $m )
184 // actually matches everything with a ( in it
185 ) {
186 $fmt = $m[1]; // initial part
187 if (!($fst = $m[2]) )
188 $fst = 0;
189 if (!($range = $m[3]))
190 $lst = $fst;
191 elseif (!($lst = $m[4]))
192 $lst = $n;
193 } else
194 $fst = $range = 0;
195 $strip = $l = strlen($fmt);
196 if (!$strip) { // plain mode
197 $fmt = '*'; $l = 1;
198 }
199 if ( !($single = 1 == $l && !$range) )
200 $ret = array();
201 // echo "sub '$fmt'($fst..$lst) range $range single $single strip $strip n $n\n";
202 for ($i = 0; $i < $l; $i++ ) {
203 $s = $fmt{$i};
204 $sel = '*' == $s ? 0 : 1;
205 $o = -1;
206 for ($j = $sel; $j < $n; $j++ ) {
207 // echo "test '$s'($fst..$lst) $o\n";
208 if ( ($sel && $s != $sub[$j]{0}) || ++$o < $fst )
209 continue;
210 if ( $lst < $o )
211 break;
212 // ok, it's in range
213 $v = ($strip && $j) ? substr($sub[$j],1) : $sub[$j];
214 if ( $mhl )
215 $v = preg_replace($ISIS_REC_MHL_PAT, $ISIS_REC_MHL_REPL, $v);
216 if ( $mode )
217 $v = $mode($v);
218 if ( $single )
219 return $v;
220 $key = $sel ? (1 == $l ? '' : $s)
221 : $strip && $j ? $sub[$j]{0} : '';
222 if ($range) $key .= $o;
223 $ret[$key] = $v;
224 if ( !$range )
225 break;
226 }
227 }
228 return $ret;
229 }
230
231
232 // ////////////////////////////////////////////////////////////
233 // ctor
234 //
235
236 /**
237 create an ISIS record.
238 @return object Isis_Rec a new ISIS record
239 */
240 function Isis_Rec ()
241 {
242 $this->tag = array();
243 $this->val = array();
244 if ( func_num_args() )
245 $this->add( func_get_args() );
246 }
247
248
249
250 // ////////////////////////////////////////////////////////////
251 // getters
252 //
253
254 /**
255 @return the number of fields
256 */
257 function len ()
258 {
259 return count($this->tag);
260 }
261
262
263 /**
264 try to look up non-numeric tags in the fdt
265 */
266 function fdt ( &$tag )
267 {
268 if ( !is_int($tag) ) {
269 if (is_numeric($tag))
270 $tag = (int)$tag;
271 else if ($this->db && $this->db->fdt && is_int($this->db->fdt[$tag]))
272 $tag = $this->db->fdt[$tag];
273 }
274 }
275
276
277 /**
278 reset tag and val array pointers
279 */
280 function res ()
281 {
282 reset($this->tag);
283 reset($this->val);
284 }
285
286
287 /** get all values for tag as array
288 */
289 function get ( $tag )
290 {
291 $ret = array();
292 $this->fdt($tag);
293 reset($this->tag);
294 while (list($p,$t) = each($this->tag))
295 if ( $tag == $t )
296 $ret[] = $this->val[$p];
297 return $ret;
298 }
299
300
301 /** v is for value
302 -- get the value of the next occurence of tag in the record.
303 The position is reset by res or when using v with a different tag.
304 @param mixed $fmt format to apply
305 - if $fmt is null (or the value is null),
306 the value is returned unmodified
307 - if $fmt is a string,
308 the value is formatted by Isis_Rec::fmt (returns string or array).
309 - other values of $fmt are reserved for future extensions
310 and currently return null.
311 */
312 function v ( $tag = null, $fmt = null )
313 {
314 if ( !is_int($tag) )
315 $this->fdt($tag);
316 if ( $this->v != $tag ) {
317 reset($this->tag); // no each on val needed
318 $this->v = $tag;
319 }
320 $v = null;
321 if ( is_null($tag) ) {
322 if (list($p,$t) = each($this->tag))
323 $v = $this->val[$p];
324 } else
325 while (list($p,$t) = each($this->tag))
326 if ( $tag == $t ) {
327 $v = $this->val[$p];
328 break;
329 }
330 if (is_null($v) || is_null($fmt))
331 return $v;
332 if ( is_string($fmt) )
333 return Isis_Rec::fmt($v,$fmt);
334 return null;
335 }
336
337 /**
338 return an array of subrecords
339 */
340 function recs ( $db = null )
341 {
342 $ret = array();
343 $n = 0;
344 reset($this->tag);
345 while (list($p,$t) = each($this->tag))
346 if ( 0 > $t ) {
347 $r = &$ret[$n++];
348 $r = new Isis_Rec();
349 $r->db = $db;
350 $r->head = $this->val[$p];
351 for ( $i=-(int)$t-1; $i-- && list($p,$t) = each($this->tag); ) {
352 $r->tag[] = $t;
353 $r->val[] = $this->val[$p];
354 }
355 }
356 return $ret;
357 }
358
359
360 /** same as v($tag,'&'.$fmt)
361 */
362 function h ( $tag = null, $fmt = '' )
363 {
364 return $this->v($tag,'&'.$fmt);
365 }
366
367
368 /** same as v($tag,'&MHL')
369 */
370 function mhl ( $tag = null )
371 {
372 return $this->v($tag,'&MHL');
373 }
374
375
376
377 // ////////////////////////////////////////////////////////////
378 // setters
379 //
380
381 /**
382 append a new field (tag-value-pair) to the end of the record.
383 @param int $tag tag to use in the field. fdt is applied
384 @param string $val the new fields value.
385 The string type is not enforced here.
386 @return the new value
387 */
388 function append ( $tag, $val )
389 {
390 if ( !is_int($tag) )
391 $this->fdt($tag);
392 // echo "0\tappending $tag ",gettype($val),"\n";
393 if ( is_string($val) || is_numeric($val) ) {
394 $this->tag[] = $tag;
395 $this->val[] = $val;
396 } elseif ( is_array($val) ) {
397 foreach ($val as $v)
398 $this->append($tag, $v);
399 } elseif ( is_object($val) )
400 $this->embed($val);
401 return $val;
402 }
403
404 /**
405 add an array to the record.
406 @param mixed $argv an array either as a single parameter
407 or as a variable number of arguments.
408 The array is processed as follows:
409 - if an item is an int, it is appended, with the following item as value.
410 - if an item is an array, add is called recursively on this array
411 - if an item is '-db' or '-mfn', the corresponding properties are set
412 - if we have an fdt which maps the item to an int,
413 the following item as value is added with the tag given by the fdt.
414 - if an item is ISIS_REC_TEXT, the following item is parsed in text mode.
415 - else the item is parsed in standard mode.
416 */
417 function add ( $argv )
418 {
419 $added = 0;
420 $fdt = $this->db ? $this->db->fdt : null;
421 if (1 < func_num_args() || !is_array($argv))
422 $argv = func_get_args();
423 // standardized to having an array
424 for ( $i = reset($argv); $i || !is_bool($i); $i = next($argv) ) {
425 if ( is_int($i) ) {
426 if ( !is_null($this->append($i, next($argv))) )
427 $added++;
428 } elseif ( is_array($i) )
429 $added += $this->add($i);
430 elseif ( '-mfn' == $i )
431 $this->mfn = next($argv);
432 elseif ( '-db' == $i ) {
433 // they don't grok $fdt = ($this->db = next($argv))->fdt;
434 $this->db = next($argv);
435 $fdt = $this->db->fdt;
436 } elseif ($fdt && is_int($fdt[$i])) {
437 if ( !is_null($this->append($fdt[$i], next($argv))) )
438 $added++;
439 } elseif ( ISIS_REC_TEXT == $i )
440 $added += $this->parse( next($argv), ISIS_REC_TEXT );
441 else
442 $added += $this->parse( $i );
443 }
444 }
445
446
447 function addglobals ()
448 {
449 if ( $this->db && $this->db->fdt )
450 foreach ($this->db->fdt as $name => $tag)
451 $this->append($tag, $GLOBALS[$name]);
452 }
453
454 /** recompact after unsetting
455 */
456 function pack ()
457 {
458 $this->tag = array_values( $this->tag );
459 $this->val = array_values( $this->val );
460 }
461
462
463 /** remove a field at given pos, poking a hole in the field list.
464 */
465 function rm ( $pos, $pack = FALSE )
466 {
467 unset( $this->tag[$pos] );
468 unset( $this->val[$pos] );
469 if ( $pack )
470 $this->pack();
471 }
472
473
474 /** remove all fields or all with a given tag.
475 */
476 function del ( $tag = null, $pack = FALSE )
477 {
478 if ( is_null($tag) ) {
479 $this->tag = array();
480 $this->val = array();
481 } else {
482 if ( !is_int($tag) )
483 $this->fdt($tag);
484 foreach ($this->tag as $p => $t)
485 if ($tag == $t)
486 $this->rm($p);
487 if ( $pack )
488 $this->pack();
489 }
490 }
491
492
493 /** set fields with tag to values.
494 set( 42, 'foo', 'bar', 'baz' ) will change
495 the first three occurences of 42 to 'foo', 'bar' and 'baz', resp.
496 - if there are less than three occurences,
497 the remaining values are appended
498 - if there are more than three occurences,
499 the remaining occurences are deleted
500 - if a value is the integer 0,
501 processing stops (i.e. remaining occurences are left unchanged)
502 - if a value is a positive integer n,
503 processing skips n occurences (letting them unchanged)
504 - if a value is an array,
505 it's elements are used (it is flattened out non-recursively)
506 @param mixed $tag tag by int or name
507 @param mixed values... variable number of values
508 */
509 function set ( $tag )
510 {
511 if ( !is_int($tag) )
512 $this->fdt($tag);
513 $argv = func_get_args();
514 reset($argv);
515 each($argv); // eat $tag
516 reset($this->tag);
517 $ary = null;
518 $more = 1;
519 for (;;) {
520 if ( $ary ) {
521 if ( !(list($k,$arg) = each($ary)) ) {
522 $ary = null;
523 continue;
524 }
525 } else {
526 if ( !(list($k,$arg) = each($argv)) )
527 break;
528 if ( is_array($arg) ) {
529 $ary = $arg;
530 reset($ary);
531 continue;
532 }
533 }
534 // echo "setting '$arg'\n";
535 if (is_int($arg)) {
536 if (!$arg)
537 return;
538 while ($more && 0 < $arg--)
539 while (($more = (list($k,$v) = each($this->tag))) && $tag != $v)
540 ;
541 continue;
542 }
543 // now arg is a value to set/add
544 if ($more) {
545 while (($more = (list($k,$v) = each($this->tag))) && $tag != $v)
546 ;
547 if ($more) {
548 $this->val[$k] = $arg;
549 continue;
550 }
551 }
552 $this->append($tag,$arg);
553 }
554 if ( $more )
555 while (list($k,$v) = each($this->tag))
556 if ($tag == $v)
557 $this->rm($k);
558 }
559
560
561 /* transparently embed a record.
562 */
563 function embed ( $that )
564 {
565 $this->append( -($i = $that->len())-1, $that->head );
566 for ( $t = reset($that->tag), $v = reset($that->val);
567 $i--;
568 $t = next($that->tag), $v = next($that->val)
569 ) {
570 $this->tag[] = $t;
571 $this->val[] = $v;
572 }
573 }
574
575
576 // ////////////////////////////////////////////////////////////
577 // conversion to/from other representations
578 //
579
580 /**
581 serialize record to a string.
582 After each field, including the last one, a newline is added.
583 @param string $mode replacement value for newlines.
584 suggested is one of the predefined constants.
585 defaults to ISIS_REC_TEXT.
586 @return the string representation of the record
587 */
588 function toString ( $mode = ISIS_REC_TEXT )
589 {
590 $s = '';
591 if ( strlen($this->head) ) {
592 if ( '0'<=$this->head{0} && $this->head{0}<='9' )
593 $s .= "W\t";
594 $s .= $this->head."\n";
595 }
596 // why in heaven is next a pre-increment ???
597 $t = reset($this->tag);
598 $v = reset($this->val);
599 for ( $i = count($this->tag);
600 $i--;
601 $t = next($this->tag), $v = next($this->val)
602 )
603 $s .= $t . "\t" . str_replace("\n",$mode,$v) . "\n";
604 return $s;
605 }
606
607
608 /** parse text as record fields to add.
609 For each non-empty line, initial digits are used as tag (empty == 0),
610 an optional following tab is skipped, and the rest used as value,
611 after replacing $repl, if given, with newlines.
612 If the line starts with a tab and the record is not empty,
613 a newline and the value are appended to the last field,
614 else a new field is appended to the record.
615 @param string $repl string to be converted back to newlines.
616 use ISIS_REC_TEXT, if you know text is from toString(ISIS_REC_TEXT)
617 @return number of fields added
618 */
619 function parse ( $text, $repl = null )
620 {
621 // need compact array in order to reliably know last index
622 $conv = 0;
623 $lines = explode("\n",$text);
624 if ( $lines && strlen($lines[0]) ) {
625 $line = $lines[0];
626 if ( $line{0}<'0' || '9'<$line{0} ) {
627 $this->head = $line;
628 array_shift($lines);
629 }
630 }
631 foreach ($lines as $line) {
632 if ('' == $line) // blank line or trailing newline
633 continue;
634 $dig = strspn($line,'0123456789-');
635 $t = $dig ? (int)substr($line,0,$dig) : 0;
636 $v = substr( $line, $dig + ("\t" == $line{$dig} ? 1 : 0) );
637 if ( $repl )
638 $v = str_replace($repl,"\n",$v);
639 $this->tag[] = $t;
640 $this->val[] = $v;
641 $conv++;
642 }
643 return $conv;
644 } // parse
645
646
647 /**
648 return the "data fork" of this record by mapping a function
649 to tags and values in parallel.
650 @param function $func defaults to null,
651 resulting in an array of fields, each an array [0] => $tag, [1] => $val.
652 @return a new array as of array_map
653 */
654 function map ($func = null)
655 {
656 return array_map($func,$this->tag,$this->val);
657 }
658 } // class Isis_Rec
659 ?>

  ViewVC Help
Powered by ViewVC 1.1.26