/[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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 604 - (hide 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 dpavlin 604 <?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