]> git.sur5r.net Git - contagged/blob - Contact_Vcard_Parse.php
Italian translation
[contagged] / Contact_Vcard_Parse.php
1 <?php
2 /* vim: set expandtab tabstop=4 softtabstop=4 shiftwidth=4: */ 
3 // +----------------------------------------------------------------------+ 
4 // | PHP version 4                                                        | 
5 // +----------------------------------------------------------------------+ 
6 // | Copyright (c) 1997-2002 The PHP Group                                | 
7 // +----------------------------------------------------------------------+ 
8 // | This source file is subject to version 2.0 of the PHP license,       | 
9 // | that is bundled with this package in the file LICENSE, and is        | 
10 // | available at through the world-wide-web at                           | 
11 // | http://www.php.net/license/2_02.txt.                                 | 
12 // | If you did not receive a copy of the PHP license and are unable to   | 
13 // | obtain it through the world-wide-web, please send a note to          | 
14 // | license@php.net so we can mail you a copy immediately.               | 
15 // +----------------------------------------------------------------------+ 
16 // | Authors: Paul M. Jones <pmjones@ciaweb.net>                          | 
17 // +----------------------------------------------------------------------+ 
18 // 
19 // $Id: Contact_Vcard_Parse.php,v 1.1 2004/06/01 08:48:59 gohr Exp $ 
20
21
22 /**
23
24 * Parser for vCards.
25 *
26 * This class parses vCard 2.1 and 3.0 sources from file or text into a
27 * structured array.
28
29 * Usage:
30
31 * <code>
32 *     // include this class file
33 *     require_once 'Contact_Vcard_Parse.php';
34 *     
35 *     // instantiate a parser object
36 *     $parse = new Contact_Vcard_Parse();
37 *     
38 *     // parse a vCard file and store the data
39 *     // in $cardinfo
40 *     $cardinfo = $parse->fromFile('sample.vcf');
41 *     
42 *     // view the card info array
43 *     echo '<pre>';
44 *     print_r($cardinfo);
45 *     echo '</pre>';
46 * </code>
47
48 *
49 * @author Paul M. Jones <pmjones@ciaweb.net>
50
51 * @package Contact_Vcard_Parse
52
53 * @version 1.30
54
55 */
56
57 class Contact_Vcard_Parse {
58     
59     
60     /**
61     * 
62     * Reads a file for parsing, then sends it to $this->fromText()
63     * and returns the results.
64     * 
65     * @access public
66     * 
67     * @param array $filename The filename to read for vCard information.
68     * 
69     * @return array An array of of vCard information extracted from the
70     * file.
71     * 
72     * @see Contact_Vcard_Parse::fromText()
73     * 
74     * @see Contact_Vcard_Parse::_fromArray()
75     * 
76     */
77     
78     function fromFile($filename, $decode_qp = true)
79     {
80         $text = $this->fileGetContents($filename);
81         
82         if ($text === false) {
83             return false;
84         } else {
85             // dump to, and get return from, the fromText() method.
86             return $this->fromText($text, $decode_qp);
87         }
88     }
89     
90     
91     /**
92     * 
93     * Reads the contents of a file.  Included for users whose PHP < 4.3.0.
94     * 
95     * @access public
96     * 
97     * @param array $filename The filename to read for vCard information.
98     * 
99     * @return string|bool The contents of the file if it exists and is
100     * readable, or boolean false if not.
101     * 
102     * @see Contact_Vcard_Parse::fromFile()
103     * 
104     */
105     
106     function fileGetContents($filename)
107     {
108         if (file_exists($filename) &&
109             is_readable($filename)) {
110             
111             $text = '';
112             $len = filesize($filename);
113             
114             $fp = fopen($filename, 'r');
115             while ($line = fread($fp, filesize($filename))) {
116                 $text .= $line;
117             }
118             fclose($fp);
119             
120             return $text;
121             
122         } else {
123         
124             return false;
125             
126         }
127     }
128     
129     
130     /**
131     * 
132     * Prepares a block of text for parsing, then sends it through and
133     * returns the results from $this->fromArray().
134     * 
135     * @access public
136     * 
137     * @param array $text A block of text to read for vCard information.
138     * 
139     * @return array An array of vCard information extracted from the
140     * source text.
141     * 
142     * @see Contact_Vcard_Parse::_fromArray()
143     * 
144     */
145     
146     function fromText($text, $decode_qp = true)
147     {
148         // convert all kinds of line endings to Unix-standard and get
149         // rid of double blank lines.
150         $this->convertLineEndings($text);
151         
152         // unfold lines.  concat two lines where line 1 ends in \n and
153         // line 2 starts with a whitespace character.  only removes
154         // the first whitespace character, leaves others in place.
155         $fold_regex = '(\n)([ |\t])';
156         $text = preg_replace("/$fold_regex/i", "", $text);
157         
158         // massage for Macintosh OS X Address Book (remove nulls that
159         // Address Book puts in for unicode chars)
160         $text = str_replace("\x00", '', $text);
161         
162         // convert the resulting text to an array of lines
163         $lines = explode("\n", $text);
164         
165         // parse the array of lines and return vCard info
166         return $this->_fromArray($lines, $decode_qp);
167     }
168     
169     
170     /**
171     * 
172     * Converts line endings in text.
173     * 
174     * Takes any text block and converts all line endings to UNIX
175     * standard. DOS line endings are \r\n, Mac are \r, and UNIX is \n.
176     *
177     * NOTE: Acts on the text block in-place; does not return a value.
178     * 
179     * @access public
180     * 
181     * @param string $text The string on which to convert line endings.
182     * 
183     * @return void
184     * 
185     */
186     
187     function convertLineEndings(&$text)
188     {
189         // DOS
190         $text = str_replace("\r\n", "\n", $text);
191         
192         // Mac
193         $text = str_replace("\r", "\n", $text);
194     }
195     
196     
197     /**
198     * 
199     * Splits a string into an array at semicolons.  Honors backslash-
200     * escaped semicolons (i.e., splits at ';' not '\;').
201     * 
202     * @access public
203     * 
204     * @param string $text The string to split into an array.
205     * 
206     * @param bool $convertSingle If splitting the string results in a
207     * single array element, return a string instead of a one-element
208     * array.
209     * 
210     * @return mixed An array of values, or a single string.
211     * 
212     */
213     
214     function splitBySemi($text, $convertSingle = false)
215     {
216         // we use these double-backs (\\) because they get get converted
217         // to single-backs (\) by preg_split.  the quad-backs (\\\\) end
218         // up as as double-backs (\\), which is what preg_split requires
219         // to indicate a single backslash (\). what a mess.
220         $regex = '(?<!\\\\)(\;)';
221         $tmp = preg_split("/$regex/i", $text);
222         
223         // if there is only one array-element and $convertSingle is
224         // true, then return only the value of that one array element
225         // (instead of returning the array).
226         if ($convertSingle && count($tmp) == 1) {
227             return $tmp[0];
228         } else {
229             return $tmp;
230         }
231     }
232     
233     
234     /**
235     * 
236     * Splits a string into an array at commas.  Honors backslash-
237     * escaped commas (i.e., splits at ',' not '\,').
238     * 
239     * @access public
240     * 
241     * @param string $text The string to split into an array.
242     * 
243     * @param bool $convertSingle If splitting the string results in a
244     * single array element, return a string instead of a one-element
245     * array.
246     * 
247     * @return mixed An array of values, or a single string.
248     * 
249     */
250     
251     function splitByComma($text, $convertSingle = false)
252     {
253         // we use these double-backs (\\) because they get get converted
254         // to single-backs (\) by preg_split.  the quad-backs (\\\\) end
255         // up as as double-backs (\\), which is what preg_split requires
256         // to indicate a single backslash (\). ye gods, how ugly.
257         $regex = '(?<!\\\\)(\,)';
258         $tmp = preg_split("/$regex/i", $text);
259         
260         // if there is only one array-element and $convertSingle is
261         // true, then return only the value of that one array element
262         // (instead of returning the array).
263         if ($convertSingle && count($tmp) == 1) {
264             return $tmp[0];
265         } else {
266             return $tmp;
267         }
268     }
269     
270     
271     /**
272     * 
273     * Used to make string human-readable after being a vCard value.
274     * 
275     * Converts...
276     *     \; => ;
277     *     \, => ,
278     *     literal \n => newline
279     * 
280     * @access public
281     * 
282     * @param mixed $text The text to unescape.
283     * 
284     * @return void
285     * 
286     */
287     
288     function unescape(&$text)
289     {
290         if (is_array($text)) {
291             foreach ($text as $key => $val) {
292                 $this->unescape($val);
293                 $text[$key] = $val;
294             }
295         } else {
296             $text = str_replace('\;', ';', $text);
297             $text = str_replace('\,', ',', $text);
298             $text = str_replace('\n', "\n", $text);
299         }
300     }
301     
302     
303     /**
304     *
305     * Emulated destructor.
306     *
307     * @access private
308     * @return boolean true
309     *
310     */
311     
312     function _Contact_Vcard_Parse()
313     {
314         return true;
315     }
316     
317     
318     /**
319     * 
320     * Parses an array of source lines and returns an array of vCards.
321     * Each element of the array is itself an array expressing the types,
322     * parameters, and values of each part of the vCard. Processes both
323     * 2.1 and 3.0 vCard sources.
324     *
325     * @access private
326     * 
327     * @param array $source An array of lines to be read for vCard
328     * information.
329     * 
330     * @return array An array of of vCard information extracted from the
331     * source array.
332     * 
333     */
334     
335     function _fromArray($source, $decode_qp = true)
336     {
337         // the info array will hold all resulting vCard information.
338         $info = array();
339         
340         // tells us whether the source text indicates the beginning of a
341         // new vCard with a BEGIN:VCARD tag.
342         $begin = false;
343         
344         // holds information about the current vCard being read from the
345         // source text.
346         $card = array();
347         
348         // loop through each line in the source array
349         foreach ($source as $line) {
350             
351             // if the line is blank, skip it.
352             if (trim($line) == '') {
353                 continue;
354             }
355             
356             // find the first instance of ':' on the line.  The part
357             // to the left of the colon is the type and parameters;
358             // the part to the right of the colon is the value data.
359             $pos = strpos($line, ':');
360             
361             // if there is no colon, skip the line.
362             if ($pos === false) {
363                 continue;
364             }
365             
366             // get the left and right portions
367             $left = trim(substr($line, 0, $pos));
368             $right = trim(substr($line, $pos+1, strlen($line)));
369             
370             // have we started yet?
371             if (! $begin) {
372                 
373                 // nope.  does this line indicate the beginning of
374                 // a new vCard?
375                 if (strtoupper($left) == 'BEGIN' &&
376                     strtoupper($right) == 'VCARD') {
377                     
378                     // tell the loop that we've begun a new card
379                     $begin = true;
380                 }
381                 
382                 // regardless, loop to the next line of source. if begin
383                 // is still false, the next loop will check the line. if
384                 // begin has now been set to true, the loop will start
385                 // collecting card info.
386                 continue;
387                 
388             } else {
389                 
390                 // yep, we've started, but we don't know how far along
391                 // we are in the card. is this the ending line of the
392                 // current vCard?
393                 if (strtoupper($left) == 'END' &&
394                     strtoupper($right) == 'VCARD') {
395                     
396                     // yep, we're done. keep the info from the current
397                     // card...
398                     $info[] = $card;
399                     
400                     // ...and reset to grab a new card if one exists in
401                     // the source array.
402                     $begin = false;
403                     $card = array();
404                     
405                 } else {
406                     
407                     // we're not on an ending line, so collect info from
408                     // this line into the current card. split the
409                     // left-portion of the line into a type-definition
410                     // (the kind of information) and parameters for the
411                     // type.
412                     $typedef = $this->_getTypeDef($left);
413                     $params = $this->_getParams($left);
414                     
415                     // if we are decoding quoted-printable, do so now.
416                     // QUOTED-PRINTABLE is not allowed in version 3.0,
417                     // but we don't check for versioning, so we do it
418                     // regardless.  ;-)
419                     $this->_decode_qp($params, $right);
420                     
421                     // now get the value-data from the line, based on
422                     // the typedef
423                     switch ($typedef) {
424                         
425                     case 'N':
426                         // structured name of the person
427                         $value = $this->_parseN($right);
428                         break;
429                         
430                     case 'ADR':
431                         // structured address of the person
432                         $value = $this->_parseADR($right);
433                         break;
434                         
435                     case 'NICKNAME':
436                         // nicknames
437                         $value = $this->_parseNICKNAME($right);
438                         break;
439                         
440                     case 'ORG':
441                         // organizations the person belongs to
442                         $value = $this->_parseORG($right);
443                         break;
444                         
445                     case 'CATEGORIES':
446                         // categories to which this card is assigned
447                         $value = $this->_parseCATEGORIES($right);
448                         break;
449                         
450                     case 'GEO':
451                         // geographic coordinates
452                         $value = $this->_parseGEO($right);
453                         break;
454                         
455                     default:
456                         // by default, just grab the plain value. keep
457                         // as an array to make sure *all* values are
458                         // arrays.  for consistency. ;-)
459                         $value = array(array($right));
460                         break;
461                     }
462                     
463                     // add the type, parameters, and value to the
464                     // current card array.  note that we allow multiple
465                     // instances of the same type, which might be dumb
466                     // in some cases (e.g., N).
467                     $card[$typedef][] = array(
468                         'param' => $params,
469                         'value' => $value
470                     );
471                 }
472             }
473         }
474         
475         $this->unescape($info);
476         return $info;
477     }
478     
479     
480     /**
481     *
482     * Takes a vCard line and extracts the Type-Definition for the line.
483     * 
484     * @access private
485     *
486     * @param string $text A left-part (before-the-colon part) from a
487     * vCard line.
488     * 
489     * @return string The type definition for the line.
490     * 
491     */
492     
493     function _getTypeDef($text)
494     {
495         // split the text by semicolons
496         $split = $this->splitBySemi($text);
497         
498         // only return first element (the typedef)
499         return $split[0];
500     }
501     
502     
503     /**
504     *
505     * Finds the Type-Definition parameters for a vCard line.
506     * 
507     * @access private
508     * 
509     * @param string $text A left-part (before-the-colon part) from a
510     * vCard line.
511     * 
512     * @return mixed An array of parameters.
513     * 
514     */
515     
516     function _getParams($text)
517     {
518         // split the text by semicolons into an array
519         $split = $this->splitBySemi($text);
520         
521         // drop the first element of the array (the type-definition)
522         array_shift($split);
523         
524         // set up an array to retain the parameters, if any
525         $params = array();
526         
527         // loop through each parameter.  the params may be in the format...
528         // "TYPE=type1,type2,type3"
529         //    ...or...
530         // "TYPE=type1;TYPE=type2;TYPE=type3"
531         foreach ($split as $full) {
532             
533             // split the full parameter at the equal sign so we can tell
534             // the parameter name from the parameter value
535             $tmp = explode("=", $full);
536             
537             // the key is the left portion of the parameter (before
538             // '='). if in 2.1 format, the key may in fact be the
539             // parameter value, not the parameter name.
540             $key = strtoupper(trim($tmp[0]));
541             
542             // get the parameter name by checking to see if it's in
543             // vCard 2.1 or 3.0 format.
544             $name = $this->_getParamName($key);
545             
546             // list of all parameter values
547             $listall = trim($tmp[1]);
548             
549             // if there is a value-list for this parameter, they are
550             // separated by commas, so split them out too.
551             $list = $this->splitByComma($listall);
552             
553             // now loop through each value in the parameter and retain
554             // it.  if the value is blank, that means it's a 2.1-style
555             // param, and the key itself is the value.
556             foreach ($list as $val) {
557                 if (trim($val) != '') {
558                     // 3.0 formatted parameter
559                     $params[$name][] = trim($val);
560                 } else {
561                     // 2.1 formatted parameter
562                     $params[$name][] = $key;
563                 }
564             }
565             
566             // if, after all this, there are no parameter values for the
567             // parameter name, retain no info about the parameter (saves
568             // ram and checking-time later).
569             if (count($params[$name]) == 0) {
570                 unset($params[$name]);
571             }
572         }
573         
574         // return the parameters array.
575         return $params;
576     }
577     
578     
579     /**
580     *
581     * Looks at the parameters of a vCard line; if one of them is
582     * ENCODING[] => QUOTED-PRINTABLE then decode the text in-place.
583     * 
584     * @access private
585     * 
586     * @param array $params A parameter array from a vCard line.
587     * 
588     * @param string $text A right-part (after-the-colon part) from a
589     * vCard line.
590     *
591     * @return void
592     * 
593     */
594     
595     function _decode_qp(&$params, &$text)
596     {
597         // loop through each parameter
598         foreach ($params as $param_key => $param_val) {
599             
600             // check to see if it's an encoding param
601             if (trim(strtoupper($param_key)) == 'ENCODING') {
602             
603                 // loop through each encoding param value
604                 foreach ($param_val as $enc_key => $enc_val) {
605                 
606                     // if any of the values are QP, decode the text
607                     // in-place and return
608                     if (trim(strtoupper($enc_val)) == 'QUOTED-PRINTABLE') {
609                         $text = quoted_printable_decode($text);
610                         return;
611                     }
612                 }
613             }
614         }
615     }
616     
617     
618     /**
619     * 
620     * Returns parameter names from 2.1-formatted vCards.
621     *
622     * The vCard 2.1 specification allows parameter values without a
623     * name. The parameter name is then determined from the unique
624     * parameter value.
625     * 
626     * Shamelessly lifted from Frank Hellwig <frank@hellwig.org> and his
627     * vCard PHP project <http://vcardphp.sourceforge.net>.
628     * 
629     * @access private
630     * 
631     * @param string $value The first element in a parameter name-value
632     * pair.
633     * 
634     * @return string The proper parameter name (TYPE, ENCODING, or
635     * VALUE).
636     * 
637     */
638      
639     function _getParamName($value)
640     {
641         static $types = array (
642             'DOM', 'INTL', 'POSTAL', 'PARCEL','HOME', 'WORK',
643             'PREF', 'VOICE', 'FAX', 'MSG', 'CELL', 'PAGER',
644             'BBS', 'MODEM', 'CAR', 'ISDN', 'VIDEO',
645             'AOL', 'APPLELINK', 'ATTMAIL', 'CIS', 'EWORLD',
646             'INTERNET', 'IBMMAIL', 'MCIMAIL',
647             'POWERSHARE', 'PRODIGY', 'TLX', 'X400',
648             'GIF', 'CGM', 'WMF', 'BMP', 'MET', 'PMB', 'DIB',
649             'PICT', 'TIFF', 'PDF', 'PS', 'JPEG', 'QTIME',
650             'MPEG', 'MPEG2', 'AVI',
651             'WAVE', 'AIFF', 'PCM',
652             'X509', 'PGP'
653         );
654         
655         // CONTENT-ID added by pmj
656         static $values = array (
657             'INLINE', 'URL', 'CID', 'CONTENT-ID'
658         );
659         
660         // 8BIT added by pmj
661         static $encodings = array (
662             '7BIT', '8BIT', 'QUOTED-PRINTABLE', 'BASE64'
663         );
664         
665         // changed by pmj to the following so that the name defaults to
666         // whatever the original value was.  Frank Hellwig's original
667         // code was "$name = 'UNKNOWN'".
668         $name = $value;
669         
670         if (in_array($value, $types)) {
671             $name = 'TYPE';
672         } elseif (in_array($value, $values)) {
673             $name = 'VALUE';
674         } elseif (in_array($value, $encodings)) {
675             $name = 'ENCODING';
676         }
677         
678         return $name;
679     }
680     
681     
682     /**
683     *
684     * Parses a vCard line value identified as being of the "N"
685     * (structured name) type-defintion.
686     *
687     * @access private
688     *
689     * @param string $text The right-part (after-the-colon part) of a
690     * vCard line.
691     * 
692     * @return array An array of key-value pairs where the key is the
693     * portion-name and the value is the portion-value.  The value itself
694     * may be an array as well if multiple comma-separated values were
695     * indicated in the vCard source.
696     *
697     */
698     
699     function _parseN($text)
700     {
701         $tmp = $this->splitBySemi($text);
702         return array(
703             $this->splitByComma($tmp[0]), // family (last)
704             $this->splitByComma($tmp[1]), // given (first)
705             $this->splitByComma($tmp[2]), // addl (middle)
706             $this->splitByComma($tmp[3]), // prefix
707             $this->splitByComma($tmp[4])  // suffix
708         );
709     }
710     
711     
712     /**
713     *
714     * Parses a vCard line value identified as being of the "ADR"
715     * (structured address) type-defintion.
716     *
717     * @access private
718     *
719     * @param string $text The right-part (after-the-colon part) of a
720     * vCard line.
721     * 
722     * @return array An array of key-value pairs where the key is the
723     * portion-name and the value is the portion-value.  The value itself
724     * may be an array as well if multiple comma-separated values were
725     * indicated in the vCard source.
726     *
727     */
728     
729     function _parseADR($text)
730     {
731         $tmp = $this->splitBySemi($text);
732         return array(
733             $this->splitByComma($tmp[0]), // pob
734             $this->splitByComma($tmp[1]), // extend
735             $this->splitByComma($tmp[2]), // street
736             $this->splitByComma($tmp[3]), // locality (city)
737             $this->splitByComma($tmp[4]), // region (state)
738             $this->splitByComma($tmp[5]), // postcode (ZIP)
739             $this->splitByComma($tmp[6])  // country
740         );
741     }
742     
743     
744     /**
745     * 
746     * Parses a vCard line value identified as being of the "NICKNAME"
747     * (informal or descriptive name) type-defintion.
748     *
749     * @access private
750     * 
751     * @param string $text The right-part (after-the-colon part) of a
752     * vCard line.
753     * 
754     * @return array An array of nicknames.
755     *
756     */
757     
758     function _parseNICKNAME($text)
759     {
760         return array($this->splitByComma($text));
761     }
762     
763     
764     /**
765     * 
766     * Parses a vCard line value identified as being of the "ORG"
767     * (organizational info) type-defintion.
768     *
769     * @access private
770     *
771     * @param string $text The right-part (after-the-colon part) of a
772     * vCard line.
773     * 
774     * @return array An array of organizations; each element of the array
775     * is itself an array, which indicates primary organization and
776     * sub-organizations.
777     *
778     */
779     
780     function _parseORG($text)
781     {
782         $tmp = $this->splitbySemi($text);
783         $list = array();
784         foreach ($tmp as $val) {
785             $list[] = array($val);
786         }
787         
788         return $list;
789     }
790     
791     
792     /**
793     * 
794     * Parses a vCard line value identified as being of the "CATEGORIES"
795     * (card-category) type-defintion.
796     *
797     * @access private
798     * 
799     * @param string $text The right-part (after-the-colon part) of a
800     * vCard line.
801     * 
802     * @return mixed An array of categories.
803     *
804     */
805     
806     function _parseCATEGORIES($text)
807     {
808         return array($this->splitByComma($text));
809     }
810     
811     
812     /**
813     * 
814     * Parses a vCard line value identified as being of the "GEO"
815     * (geographic coordinate) type-defintion.
816     *
817     * @access private
818     *
819     * @param string $text The right-part (after-the-colon part) of a
820     * vCard line.
821     * 
822     * @return mixed An array of lat-lon geocoords.
823     *
824     */
825     
826     function _parseGEO($text)
827     {
828         $tmp = $this->splitBySemi($text);
829         return array(
830             array($tmp[0]), // lat
831             array($tmp[1])  // lon
832         );
833     }
834 }
835
836 ?>