| [ PHPXref.com ] | [ Generated: Sun Jul 20 19:19:39 2008 ] | [ PEAR 1.4.5 ] |
| [ Index ] [ Variables ] [ Functions ] [ Classes ] [ Constants ] [ Statistics ] | ||
[Summary view] [Print] [Text view]
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@php.net> | 17 // +----------------------------------------------------------------------+ 18 // 19 // $Id: Contact_Vcard_Parse.php,v 1.4 2005/05/28 15:40:17 pmjones 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@php.net> 50 * 51 * @package Contact_Vcard_Parse 52 * 53 * @version 1.31 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 * \, => , 279 * literal \n => newline 280 * 281 * @access public 282 * 283 * @param mixed $text The text to unescape. 284 * 285 * @return void 286 * 287 */ 288 289 function unescape(&$text) 290 { 291 if (is_array($text)) { 292 foreach ($text as $key => $val) { 293 $this->unescape($val); 294 $text[$key] = $val; 295 } 296 } else { 297 $text = str_replace('\:', ':', $text); 298 $text = str_replace('\;', ';', $text); 299 $text = str_replace('\,', ',', $text); 300 $text = str_replace('\n', "\n", $text); 301 } 302 } 303 304 305 /** 306 * 307 * Emulated destructor. 308 * 309 * @access private 310 * @return boolean true 311 * 312 */ 313 314 function _Contact_Vcard_Parse() 315 { 316 return true; 317 } 318 319 320 /** 321 * 322 * Parses an array of source lines and returns an array of vCards. 323 * Each element of the array is itself an array expressing the types, 324 * parameters, and values of each part of the vCard. Processes both 325 * 2.1 and 3.0 vCard sources. 326 * 327 * @access private 328 * 329 * @param array $source An array of lines to be read for vCard 330 * information. 331 * 332 * @return array An array of of vCard information extracted from the 333 * source array. 334 * 335 */ 336 337 function _fromArray($source, $decode_qp = true) 338 { 339 // the info array will hold all resulting vCard information. 340 $info = array(); 341 342 // tells us whether the source text indicates the beginning of a 343 // new vCard with a BEGIN:VCARD tag. 344 $begin = false; 345 346 // holds information about the current vCard being read from the 347 // source text. 348 $card = array(); 349 350 // loop through each line in the source array 351 foreach ($source as $line) { 352 353 // if the line is blank, skip it. 354 if (trim($line) == '') { 355 continue; 356 } 357 358 // find the first instance of ':' on the line. The part 359 // to the left of the colon is the type and parameters; 360 // the part to the right of the colon is the value data. 361 $pos = strpos($line, ':'); 362 363 // if there is no colon, skip the line. 364 if ($pos === false) { 365 continue; 366 } 367 368 // get the left and right portions 369 $left = trim(substr($line, 0, $pos)); 370 $right = trim(substr($line, $pos+1, strlen($line))); 371 372 // have we started yet? 373 if (! $begin) { 374 375 // nope. does this line indicate the beginning of 376 // a new vCard? 377 if (strtoupper($left) == 'BEGIN' && 378 strtoupper($right) == 'VCARD') { 379 380 // tell the loop that we've begun a new card 381 $begin = true; 382 } 383 384 // regardless, loop to the next line of source. if begin 385 // is still false, the next loop will check the line. if 386 // begin has now been set to true, the loop will start 387 // collecting card info. 388 continue; 389 390 } else { 391 392 // yep, we've started, but we don't know how far along 393 // we are in the card. is this the ending line of the 394 // current vCard? 395 if (strtoupper($left) == 'END' && 396 strtoupper($right) == 'VCARD') { 397 398 // yep, we're done. keep the info from the current 399 // card... 400 $info[] = $card; 401 402 // ...and reset to grab a new card if one exists in 403 // the source array. 404 $begin = false; 405 $card = array(); 406 407 } else { 408 409 // we're not on an ending line, so collect info from 410 // this line into the current card. split the 411 // left-portion of the line into a type-definition 412 // (the kind of information) and parameters for the 413 // type. 414 $typedef = $this->_getTypeDef($left); 415 $params = $this->_getParams($left); 416 417 // if we are decoding quoted-printable, do so now. 418 // QUOTED-PRINTABLE is not allowed in version 3.0, 419 // but we don't check for versioning, so we do it 420 // regardless. ;-) 421 $this->_decode_qp($params, $right); 422 423 // now get the value-data from the line, based on 424 // the typedef 425 switch ($typedef) { 426 427 case 'N': 428 // structured name of the person 429 $value = $this->_parseN($right); 430 break; 431 432 case 'ADR': 433 // structured address of the person 434 $value = $this->_parseADR($right); 435 break; 436 437 case 'NICKNAME': 438 // nicknames 439 $value = $this->_parseNICKNAME($right); 440 break; 441 442 case 'ORG': 443 // organizations the person belongs to 444 $value = $this->_parseORG($right); 445 break; 446 447 case 'CATEGORIES': 448 // categories to which this card is assigned 449 $value = $this->_parseCATEGORIES($right); 450 break; 451 452 case 'GEO': 453 // geographic coordinates 454 $value = $this->_parseGEO($right); 455 break; 456 457 default: 458 // by default, just grab the plain value. keep 459 // as an array to make sure *all* values are 460 // arrays. for consistency. ;-) 461 $value = array(array($right)); 462 break; 463 } 464 465 // add the type, parameters, and value to the 466 // current card array. note that we allow multiple 467 // instances of the same type, which might be dumb 468 // in some cases (e.g., N). 469 $card[$typedef][] = array( 470 'param' => $params, 471 'value' => $value 472 ); 473 } 474 } 475 } 476 477 $this->unescape($info); 478 return $info; 479 } 480 481 482 /** 483 * 484 * Takes a vCard line and extracts the Type-Definition for the line. 485 * 486 * @access private 487 * 488 * @param string $text A left-part (before-the-colon part) from a 489 * vCard line. 490 * 491 * @return string The type definition for the line. 492 * 493 */ 494 495 function _getTypeDef($text) 496 { 497 // split the text by semicolons 498 $split = $this->splitBySemi($text); 499 500 // only return first element (the typedef) 501 return strtoupper($split[0]); 502 } 503 504 505 /** 506 * 507 * Finds the Type-Definition parameters for a vCard line. 508 * 509 * @access private 510 * 511 * @param string $text A left-part (before-the-colon part) from a 512 * vCard line. 513 * 514 * @return mixed An array of parameters. 515 * 516 */ 517 518 function _getParams($text) 519 { 520 // split the text by semicolons into an array 521 $split = $this->splitBySemi($text); 522 523 // drop the first element of the array (the type-definition) 524 array_shift($split); 525 526 // set up an array to retain the parameters, if any 527 $params = array(); 528 529 // loop through each parameter. the params may be in the format... 530 // "TYPE=type1,type2,type3" 531 // ...or... 532 // "TYPE=type1;TYPE=type2;TYPE=type3" 533 foreach ($split as $full) { 534 535 // split the full parameter at the equal sign so we can tell 536 // the parameter name from the parameter value 537 $tmp = explode("=", $full); 538 539 // the key is the left portion of the parameter (before 540 // '='). if in 2.1 format, the key may in fact be the 541 // parameter value, not the parameter name. 542 $key = strtoupper(trim($tmp[0])); 543 544 // get the parameter name by checking to see if it's in 545 // vCard 2.1 or 3.0 format. 546 $name = $this->_getParamName($key); 547 548 // list of all parameter values 549 $listall = trim($tmp[1]); 550 551 // if there is a value-list for this parameter, they are 552 // separated by commas, so split them out too. 553 $list = $this->splitByComma($listall); 554 555 // now loop through each value in the parameter and retain 556 // it. if the value is blank, that means it's a 2.1-style 557 // param, and the key itself is the value. 558 foreach ($list as $val) { 559 if (trim($val) != '') { 560 // 3.0 formatted parameter 561 $params[$name][] = trim($val); 562 } else { 563 // 2.1 formatted parameter 564 $params[$name][] = $key; 565 } 566 } 567 568 // if, after all this, there are no parameter values for the 569 // parameter name, retain no info about the parameter (saves 570 // ram and checking-time later). 571 if (count($params[$name]) == 0) { 572 unset($params[$name]); 573 } 574 } 575 576 // return the parameters array. 577 return $params; 578 } 579 580 581 /** 582 * 583 * Looks at the parameters of a vCard line; if one of them is 584 * ENCODING[] => QUOTED-PRINTABLE then decode the text in-place. 585 * 586 * @access private 587 * 588 * @param array $params A parameter array from a vCard line. 589 * 590 * @param string $text A right-part (after-the-colon part) from a 591 * vCard line. 592 * 593 * @return void 594 * 595 */ 596 597 function _decode_qp(&$params, &$text) 598 { 599 // loop through each parameter 600 foreach ($params as $param_key => $param_val) { 601 602 // check to see if it's an encoding param 603 if (trim(strtoupper($param_key)) == 'ENCODING') { 604 605 // loop through each encoding param value 606 foreach ($param_val as $enc_key => $enc_val) { 607 608 // if any of the values are QP, decode the text 609 // in-place and return 610 if (trim(strtoupper($enc_val)) == 'QUOTED-PRINTABLE') { 611 $text = quoted_printable_decode($text); 612 return; 613 } 614 } 615 } 616 } 617 } 618 619 620 /** 621 * 622 * Returns parameter names from 2.1-formatted vCards. 623 * 624 * The vCard 2.1 specification allows parameter values without a 625 * name. The parameter name is then determined from the unique 626 * parameter value. 627 * 628 * Shamelessly lifted from Frank Hellwig <frank@hellwig.org> and his 629 * vCard PHP project <http://vcardphp.sourceforge.net>. 630 * 631 * @access private 632 * 633 * @param string $value The first element in a parameter name-value 634 * pair. 635 * 636 * @return string The proper parameter name (TYPE, ENCODING, or 637 * VALUE). 638 * 639 */ 640 641 function _getParamName($value) 642 { 643 static $types = array ( 644 'DOM', 'INTL', 'POSTAL', 'PARCEL','HOME', 'WORK', 645 'PREF', 'VOICE', 'FAX', 'MSG', 'CELL', 'PAGER', 646 'BBS', 'MODEM', 'CAR', 'ISDN', 'VIDEO', 647 'AOL', 'APPLELINK', 'ATTMAIL', 'CIS', 'EWORLD', 648 'INTERNET', 'IBMMAIL', 'MCIMAIL', 649 'POWERSHARE', 'PRODIGY', 'TLX', 'X400', 650 'GIF', 'CGM', 'WMF', 'BMP', 'MET', 'PMB', 'DIB', 651 'PICT', 'TIFF', 'PDF', 'PS', 'JPEG', 'QTIME', 652 'MPEG', 'MPEG2', 'AVI', 653 'WAVE', 'AIFF', 'PCM', 654 'X509', 'PGP' 655 ); 656 657 // CONTENT-ID added by pmj 658 static $values = array ( 659 'INLINE', 'URL', 'CID', 'CONTENT-ID' 660 ); 661 662 // 8BIT added by pmj 663 static $encodings = array ( 664 '7BIT', '8BIT', 'QUOTED-PRINTABLE', 'BASE64' 665 ); 666 667 // changed by pmj to the following so that the name defaults to 668 // whatever the original value was. Frank Hellwig's original 669 // code was "$name = 'UNKNOWN'". 670 $name = $value; 671 672 if (in_array($value, $types)) { 673 $name = 'TYPE'; 674 } elseif (in_array($value, $values)) { 675 $name = 'VALUE'; 676 } elseif (in_array($value, $encodings)) { 677 $name = 'ENCODING'; 678 } 679 680 return $name; 681 } 682 683 684 /** 685 * 686 * Parses a vCard line value identified as being of the "N" 687 * (structured name) type-defintion. 688 * 689 * @access private 690 * 691 * @param string $text The right-part (after-the-colon part) of a 692 * vCard line. 693 * 694 * @return array An array of key-value pairs where the key is the 695 * portion-name and the value is the portion-value. The value itself 696 * may be an array as well if multiple comma-separated values were 697 * indicated in the vCard source. 698 * 699 */ 700 701 function _parseN($text) 702 { 703 // make sure there are always at least 5 elements 704 $tmp = array_pad($this->splitBySemi($text), 5, ''); 705 return array( 706 $this->splitByComma($tmp[0]), // family (last) 707 $this->splitByComma($tmp[1]), // given (first) 708 $this->splitByComma($tmp[2]), // addl (middle) 709 $this->splitByComma($tmp[3]), // prefix 710 $this->splitByComma($tmp[4]) // suffix 711 ); 712 } 713 714 715 /** 716 * 717 * Parses a vCard line value identified as being of the "ADR" 718 * (structured address) type-defintion. 719 * 720 * @access private 721 * 722 * @param string $text The right-part (after-the-colon part) of a 723 * vCard line. 724 * 725 * @return array An array of key-value pairs where the key is the 726 * portion-name and the value is the portion-value. The value itself 727 * may be an array as well if multiple comma-separated values were 728 * indicated in the vCard source. 729 * 730 */ 731 732 function _parseADR($text) 733 { 734 // make sure there are always at least 7 elements 735 $tmp = array_pad($this->splitBySemi($text), 7, ''); 736 return array( 737 $this->splitByComma($tmp[0]), // pob 738 $this->splitByComma($tmp[1]), // extend 739 $this->splitByComma($tmp[2]), // street 740 $this->splitByComma($tmp[3]), // locality (city) 741 $this->splitByComma($tmp[4]), // region (state) 742 $this->splitByComma($tmp[5]), // postcode (ZIP) 743 $this->splitByComma($tmp[6]) // country 744 ); 745 } 746 747 748 /** 749 * 750 * Parses a vCard line value identified as being of the "NICKNAME" 751 * (informal or descriptive name) type-defintion. 752 * 753 * @access private 754 * 755 * @param string $text The right-part (after-the-colon part) of a 756 * vCard line. 757 * 758 * @return array An array of nicknames. 759 * 760 */ 761 762 function _parseNICKNAME($text) 763 { 764 return array($this->splitByComma($text)); 765 } 766 767 768 /** 769 * 770 * Parses a vCard line value identified as being of the "ORG" 771 * (organizational info) type-defintion. 772 * 773 * @access private 774 * 775 * @param string $text The right-part (after-the-colon part) of a 776 * vCard line. 777 * 778 * @return array An array of organizations; each element of the array 779 * is itself an array, which indicates primary organization and 780 * sub-organizations. 781 * 782 */ 783 784 function _parseORG($text) 785 { 786 $tmp = $this->splitbySemi($text); 787 $list = array(); 788 foreach ($tmp as $val) { 789 $list[] = array($val); 790 } 791 792 return $list; 793 } 794 795 796 /** 797 * 798 * Parses a vCard line value identified as being of the "CATEGORIES" 799 * (card-category) type-defintion. 800 * 801 * @access private 802 * 803 * @param string $text The right-part (after-the-colon part) of a 804 * vCard line. 805 * 806 * @return mixed An array of categories. 807 * 808 */ 809 810 function _parseCATEGORIES($text) 811 { 812 return array($this->splitByComma($text)); 813 } 814 815 816 /** 817 * 818 * Parses a vCard line value identified as being of the "GEO" 819 * (geographic coordinate) type-defintion. 820 * 821 * @access private 822 * 823 * @param string $text The right-part (after-the-colon part) of a 824 * vCard line. 825 * 826 * @return mixed An array of lat-lon geocoords. 827 * 828 */ 829 830 function _parseGEO($text) 831 { 832 // make sure there are always at least 2 elements 833 $tmp = array_pad($this->splitBySemi($text), 2, ''); 834 return array( 835 array($tmp[0]), // lat 836 array($tmp[1]) // lon 837 ); 838 } 839 } 840 841 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| [ Powered by PHPXref - Served by Debian GNU/Linux ] |