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 |
|
|
?> |