Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]
1 <?php 2 /** 3 * CFPropertyList 4 * {@link http://developer.apple.com/documentation/Darwin/Reference/ManPages/man5/plist.5.html Property Lists} 5 * @author Rodney Rehm <rodney.rehm@medialize.de> 6 * @author Christian Kruse <cjk@wwwtech.de> 7 * @package plist 8 * @version $Id$ 9 * @example example-read-01.php Read an XML PropertyList 10 * @example example-read-02.php Read a Binary PropertyList 11 * @example example-read-03.php Read a PropertyList without knowing the type 12 * @example example-create-01.php Using the CFPropertyList API 13 * @example example-create-02.php Using {@link CFTypeDetector} 14 * @example example-create-03.php Using {@link CFTypeDetector} with {@link CFDate} and {@link CFData} 15 * @example example-modify-01.php Read, modify and save a PropertyList 16 */ 17 18 namespace CFPropertyList; 19 use \Iterator, \DOMDocument, \DOMException, DOMImplementation, DOMNode; 20 21 /** 22 * Require IOException, PListException, CFType and CFBinaryPropertyList 23 */ 24 require_once (__DIR__.'/IOException.php'); 25 require_once (__DIR__.'/PListException.php'); 26 require_once (__DIR__.'/CFType.php'); 27 require_once (__DIR__.'/CFBinaryPropertyList.php'); 28 require_once (__DIR__.'/CFTypeDetector.php'); 29 30 /** 31 * Property List 32 * Interface for handling reading, editing and saving Property Lists as defined by Apple. 33 * @author Rodney Rehm <rodney.rehm@medialize.de> 34 * @author Christian Kruse <cjk@wwwtech.de> 35 * @package plist 36 * @example example-read-01.php Read an XML PropertyList 37 * @example example-read-02.php Read a Binary PropertyList 38 * @example example-read-03.php Read a PropertyList without knowing the type 39 * @example example-create-01.php Using the CFPropertyList API 40 * @example example-create-02.php Using {@link CFTypeDetector} 41 * @example example-create-03.php Using {@link CFTypeDetector} with {@link CFDate} and {@link CFData} 42 * @example example-create-04.php Using and extended {@link CFTypeDetector} 43 */ 44 class CFPropertyList extends CFBinaryPropertyList implements Iterator { 45 /** 46 * Format constant for binary format 47 * @var integer 48 */ 49 const FORMAT_BINARY = 1; 50 51 /** 52 * Format constant for xml format 53 * @var integer 54 */ 55 const FORMAT_XML = 2; 56 57 /** 58 * Format constant for automatic format recognizing 59 * @var integer 60 */ 61 const FORMAT_AUTO = 0; 62 63 /** 64 * Path of PropertyList 65 * @var string 66 */ 67 protected $file = null; 68 69 /** 70 * Detected format of PropertyList 71 * @var integer 72 */ 73 protected $detectedFormat = null; 74 75 /** 76 * Path of PropertyList 77 * @var integer 78 */ 79 protected $format = null; 80 81 /** 82 * CFType nodes 83 * @var array 84 */ 85 protected $value = array(); 86 87 /** 88 * Position of iterator {@link http://php.net/manual/en/class.iterator.php} 89 * @var integer 90 */ 91 protected $iteratorPosition = 0; 92 93 /** 94 * List of Keys for numerical iterator access {@link http://php.net/manual/en/class.iterator.php} 95 * @var array 96 */ 97 protected $iteratorKeys = null; 98 99 /** 100 * List of NodeNames to ClassNames for resolving plist-files 101 * @var array 102 */ 103 protected static $types = array( 104 'string' => 'CFString', 105 'real' => 'CFNumber', 106 'integer' => 'CFNumber', 107 'date' => 'CFDate', 108 'true' => 'CFBoolean', 109 'false' => 'CFBoolean', 110 'data' => 'CFData', 111 'array' => 'CFArray', 112 'dict' => 'CFDictionary' 113 ); 114 115 116 /** 117 * Create new CFPropertyList. 118 * If a path to a PropertyList is specified, it is loaded automatically. 119 * @param string $file Path of PropertyList 120 * @param integer $format he format of the property list, see {@link FORMAT_XML}, {@link FORMAT_BINARY} and {@link FORMAT_AUTO}, defaults to {@link FORMAT_AUTO} 121 * @throws IOException if file could not be read by {@link load()} 122 * @uses $file for storing the current file, if specified 123 * @uses load() for loading the plist-file 124 */ 125 public function __construct($file=null,$format=self::FORMAT_AUTO) { 126 $this->file = $file; 127 $this->format = $format; 128 $this->detectedFormat = $format; 129 if($this->file) $this->load(); 130 } 131 132 /** 133 * Load an XML PropertyList. 134 * @param string $file Path of PropertyList, defaults to {@link $file} 135 * @return void 136 * @throws IOException if file could not be read 137 * @throws DOMException if XML-file could not be read properly 138 * @uses load() to actually load the file 139 */ 140 public function loadXML($file=null) { 141 $this->load($file,CFPropertyList::FORMAT_XML); 142 } 143 144 /** 145 * Load an XML PropertyList. 146 * @param resource $stream A stream containing the xml document. 147 * @return void 148 * @throws IOException if stream could not be read 149 * @throws DOMException if XML-stream could not be read properly 150 */ 151 public function loadXMLStream($stream) { 152 if(($contents = stream_get_contents($stream)) === FALSE) throw IOException::notReadable('<stream>'); 153 $this->parse($contents,CFPropertyList::FORMAT_XML); 154 } 155 156 /** 157 * Load an binary PropertyList. 158 * @param string $file Path of PropertyList, defaults to {@link $file} 159 * @return void 160 * @throws IOException if file could not be read 161 * @throws PListException if binary plist-file could not be read properly 162 * @uses load() to actually load the file 163 */ 164 public function loadBinary($file=null) { 165 $this->load($file,CFPropertyList::FORMAT_BINARY); 166 } 167 168 /** 169 * Load an binary PropertyList. 170 * @param stream $stream Stream containing the PropertyList 171 * @return void 172 * @throws IOException if file could not be read 173 * @throws PListException if binary plist-file could not be read properly 174 * @uses parse() to actually load the file 175 */ 176 public function loadBinaryStream($stream) { 177 if(($contents = stream_get_contents($stream)) === FALSE) throw IOException::notReadable('<stream>'); 178 $this->parse($contents,CFPropertyList::FORMAT_BINARY); 179 } 180 181 /** 182 * Load a plist file. 183 * Load and import a plist file. 184 * @param string $file Path of PropertyList, defaults to {@link $file} 185 * @param integer $format The format of the property list, see {@link FORMAT_XML}, {@link FORMAT_BINARY} and {@link FORMAT_AUTO}, defaults to {@link $format} 186 * @return void 187 * @throws PListException if file format version is not 00 188 * @throws IOException if file could not be read 189 * @throws DOMException if plist file could not be parsed properly 190 * @uses $file if argument $file was not specified 191 * @uses $value reset to empty array 192 * @uses import() for importing the values 193 */ 194 public function load($file=null,$format=null) { 195 $file = $file ? $file : $this->file; 196 $format = $format !== null ? $format : $this->format; 197 $this->value = array(); 198 199 if(!is_readable($file)) throw IOException::notReadable($file); 200 201 switch($format) { 202 case CFPropertyList::FORMAT_BINARY: 203 $this->readBinary($file); 204 break; 205 case CFPropertyList::FORMAT_AUTO: // what we now do is ugly, but neccessary to recognize the file format 206 $fd = fopen($file,"rb"); 207 if(($magic_number = fread($fd,8)) === false) throw IOException::notReadable($file); 208 fclose($fd); 209 210 $filetype = substr($magic_number,0,6); 211 $version = substr($magic_number,-2); 212 213 if($filetype == "bplist") { 214 if($version != "00") throw new PListException("Wrong file format version! Expected 00, got $version!"); 215 $this->detectedFormat = CFPropertyList::FORMAT_BINARY; 216 $this->readBinary($file); 217 break; 218 } 219 $this->detectedFormat = CFPropertyList::FORMAT_XML; 220 // else: xml format, break not neccessary 221 case CFPropertyList::FORMAT_XML: 222 $doc = new DOMDocument(); 223 if(!$doc->load($file)) throw new DOMException(); 224 $this->import($doc->documentElement, $this); 225 break; 226 } 227 } 228 229 /** 230 * Parse a plist string. 231 * Parse and import a plist string. 232 * @param string $str String containing the PropertyList, defaults to {@link $content} 233 * @param integer $format The format of the property list, see {@link FORMAT_XML}, {@link FORMAT_BINARY} and {@link FORMAT_AUTO}, defaults to {@link $format} 234 * @return void 235 * @throws PListException if file format version is not 00 236 * @throws IOException if file could not be read 237 * @throws DOMException if plist file could not be parsed properly 238 * @uses $content if argument $str was not specified 239 * @uses $value reset to empty array 240 * @uses import() for importing the values 241 */ 242 public function parse($str=NULL,$format=NULL) { 243 $format = $format !== null ? $format : $this->format; 244 $str = $str !== null ? $str : $this->content; 245 $this->value = array(); 246 247 switch($format) { 248 case CFPropertyList::FORMAT_BINARY: 249 $this->parseBinary($str); 250 break; 251 case CFPropertyList::FORMAT_AUTO: // what we now do is ugly, but neccessary to recognize the file format 252 if(($magic_number = substr($str,0,8)) === false) throw IOException::notReadable("<string>"); 253 254 $filetype = substr($magic_number,0,6); 255 $version = substr($magic_number,-2); 256 257 if($filetype == "bplist") { 258 if($version != "00") throw new PListException("Wrong file format version! Expected 00, got $version!"); 259 $this->detectedFormat = CFPropertyList::FORMAT_BINARY; 260 $this->parseBinary($str); 261 break; 262 } 263 $this->detectedFormat = CFPropertyList::FORMAT_XML; 264 // else: xml format, break not neccessary 265 case CFPropertyList::FORMAT_XML: 266 $doc = new DOMDocument(); 267 if(!$doc->loadXML($str)) throw new DOMException(); 268 $this->import($doc->documentElement, $this); 269 break; 270 } 271 } 272 273 /** 274 * Convert a DOMNode into a CFType. 275 * @param DOMNode $node Node to import children of 276 * @param CFDictionary|CFArray|CFPropertyList $parent 277 * @return void 278 */ 279 protected function import(DOMNode $node, $parent) { 280 // abort if there are no children 281 if(!$node->childNodes->length) return; 282 283 foreach($node->childNodes as $n) { 284 // skip if we can't handle the element 285 if(!isset(self::$types[$n->nodeName])) continue; 286 287 $class = 'CFPropertyList\\'.self::$types[$n->nodeName]; 288 $key = null; 289 290 // find previous <key> if possible 291 $ps = $n->previousSibling; 292 while($ps && $ps->nodeName == '#text' && $ps->previousSibling) $ps = $ps->previousSibling; 293 294 // read <key> if possible 295 if($ps && $ps->nodeName == 'key') $key = $ps->firstChild->nodeValue; 296 297 switch($n->nodeName) { 298 case 'date': 299 $value = new $class(CFDate::dateValue($n->nodeValue)); 300 break; 301 case 'data': 302 $value = new $class($n->nodeValue,true); 303 break; 304 case 'string': 305 $value = new $class($n->nodeValue); 306 break; 307 308 case 'real': 309 case 'integer': 310 $value = new $class($n->nodeName == 'real' ? floatval($n->nodeValue) : intval($n->nodeValue)); 311 break; 312 313 case 'true': 314 case 'false': 315 $value = new $class($n->nodeName == 'true'); 316 break; 317 318 case 'array': 319 case 'dict': 320 $value = new $class(); 321 $this->import($n, $value); 322 323 if($value instanceof CFDictionary) { 324 $hsh = $value->getValue(); 325 if(isset($hsh['CF$UID']) && count($hsh) == 1) { 326 $value = new CFUid($hsh['CF$UID']->getValue()); 327 } 328 } 329 330 break; 331 } 332 333 // Dictionaries need a key 334 if($parent instanceof CFDictionary) $parent->add($key, $value); 335 // others don't 336 else $parent->add($value); 337 } 338 } 339 340 /** 341 * Convert CFPropertyList to XML and save to file. 342 * @param string $file Path of PropertyList, defaults to {@link $file} 343 * @return void 344 * @throws IOException if file could not be read 345 * @uses $file if $file was not specified 346 */ 347 public function saveXML($file) { 348 $this->save($file,CFPropertyList::FORMAT_XML); 349 } 350 351 /** 352 * Convert CFPropertyList to binary format (bplist00) and save to file. 353 * @param string $file Path of PropertyList, defaults to {@link $file} 354 * @return void 355 * @throws IOException if file could not be read 356 * @uses $file if $file was not specified 357 */ 358 public function saveBinary($file) { 359 $this->save($file,CFPropertyList::FORMAT_BINARY); 360 } 361 362 /** 363 * Convert CFPropertyList to XML or binary and save to file. 364 * @param string $file Path of PropertyList, defaults to {@link $file} 365 * @param string $format Format of PropertyList, defaults to {@link $format} 366 * @return void 367 * @throws IOException if file could not be read 368 * @throws PListException if evaluated $format is neither {@link FORMAT_XML} nor {@link FORMAL_BINARY} 369 * @uses $file if $file was not specified 370 * @uses $format if $format was not specified 371 */ 372 public function save($file=null,$format=null) { 373 $file = $file ? $file : $this->file; 374 $format = $format ? $format : $this->format; 375 if($format == self::FORMAT_AUTO)$format = $this->detectedFormat; 376 377 if( !in_array( $format, array( self::FORMAT_BINARY, self::FORMAT_XML ) ) ) 378 throw new PListException( "format {$format} is not supported, use CFPropertyList::FORMAT_BINARY or CFPropertyList::FORMAT_XML" ); 379 380 if(!file_exists($file)) { 381 // dirname("file.xml") == "" and is treated as the current working directory 382 if(!is_writable(dirname($file))) throw IOException::notWritable($file); 383 } 384 else if(!is_writable($file)) throw IOException::notWritable($file); 385 386 $content = $format == self::FORMAT_BINARY ? $this->toBinary() : $this->toXML(); 387 388 $fh = fopen($file, 'wb'); 389 fwrite($fh,$content); 390 fclose($fh); 391 } 392 393 /** 394 * Convert CFPropertyList to XML 395 * @param bool $formatted Print plist formatted (i.e. with newlines and whitespace indention) if true; defaults to false 396 * @return string The XML content 397 */ 398 public function toXML($formatted=false) { 399 $domimpl = new DOMImplementation(); 400 // <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 401 $dtd = $domimpl->createDocumentType('plist', '-//Apple//DTD PLIST 1.0//EN', 'http://www.apple.com/DTDs/PropertyList-1.0.dtd'); 402 $doc = $domimpl->createDocument(null, "plist", $dtd); 403 $doc->encoding = "UTF-8"; 404 405 // format output 406 if($formatted) { 407 $doc->formatOutput = true; 408 $doc->preserveWhiteSpace = true; 409 } 410 411 // get documentElement and set attribs 412 $plist = $doc->documentElement; 413 $plist->setAttribute('version', '1.0'); 414 415 // add PropertyList's children 416 $plist->appendChild($this->getValue(true)->toXML($doc)); 417 418 return $doc->saveXML(); 419 } 420 421 422 /************************************************************************************************ 423 * M A N I P U L A T I O N 424 ************************************************************************************************/ 425 426 /** 427 * Add CFType to collection. 428 * @param CFType $value CFType to add to collection 429 * @return void 430 * @uses $value for adding $value 431 */ 432 public function add(CFType $value=null) { 433 // anything but CFType is null, null is an empty string - sad but true 434 if( !$value ) 435 $value = new CFString(); 436 437 $this->value[] = $value; 438 } 439 440 /** 441 * Get CFType from collection. 442 * @param integer $key Key of CFType to retrieve from collection 443 * @return CFType CFType found at $key, null else 444 * @uses $value for retrieving CFType of $key 445 */ 446 public function get($key) { 447 if(isset($this->value[$key])) return $this->value[$key]; 448 return null; 449 } 450 451 /** 452 * Generic getter (magic) 453 * 454 * @param integer $key Key of CFType to retrieve from collection 455 * @return CFType CFType found at $key, null else 456 * @author Sean Coates <sean@php.net> 457 * @link http://php.net/oop5.overloading 458 */ 459 public function __get($key) { 460 return $this->get($key); 461 } 462 463 /** 464 * Remove CFType from collection. 465 * @param integer $key Key of CFType to removes from collection 466 * @return CFType removed CFType, null else 467 * @uses $value for removing CFType of $key 468 */ 469 public function del($key) { 470 if(isset($this->value[$key])) { 471 $t = $this->value[$key]; 472 unset($this->value[$key]); 473 return $t; 474 } 475 476 return null; 477 } 478 479 /** 480 * Empty the collection 481 * @return array the removed CFTypes 482 * @uses $value for removing CFType of $key 483 */ 484 public function purge() { 485 $t = $this->value; 486 $this->value = array(); 487 return $t; 488 } 489 490 /** 491 * Get first (and only) child, or complete collection. 492 * @param string $cftype if set to true returned value will be CFArray instead of an array in case of a collection 493 * @return CFType|array CFType or list of CFTypes known to the PropertyList 494 * @uses $value for retrieving CFTypes 495 */ 496 public function getValue($cftype=false) { 497 if(count($this->value) === 1) { 498 $t = array_values( $this->value ); 499 return $t[0]; 500 } 501 if($cftype) { 502 $t = new CFArray(); 503 foreach( $this->value as $value ) { 504 if( $value instanceof CFType ) $t->add($value); 505 } 506 return $t; 507 } 508 return $this->value; 509 } 510 511 /** 512 * Create CFType-structure from guessing the data-types. 513 * The functionality has been moved to the more flexible {@link CFTypeDetector} facility. 514 * @param mixed $value Value to convert to CFType 515 * @param array $options Configuration for casting values [autoDictionary, suppressExceptions, objectToArrayMethod, castNumericStrings] 516 * @return CFType CFType based on guessed type 517 * @uses CFTypeDetector for actual type detection 518 * @deprecated 519 */ 520 public static function guess($value, $options=array()) { 521 static $t = null; 522 if( $t === null ) 523 $t = new CFTypeDetector( $options ); 524 525 return $t->toCFType( $value ); 526 } 527 528 529 /************************************************************************************************ 530 * S E R I A L I Z I N G 531 ************************************************************************************************/ 532 533 /** 534 * Get PropertyList as array. 535 * @return mixed primitive value of first (and only) CFType, or array of primitive values of collection 536 * @uses $value for retrieving CFTypes 537 */ 538 public function toArray() { 539 $a = array(); 540 foreach($this->value as $value) $a[] = $value->toArray(); 541 if(count($a) === 1) return $a[0]; 542 543 return $a; 544 } 545 546 547 /************************************************************************************************ 548 * I T E R A T O R I N T E R F A C E 549 ************************************************************************************************/ 550 551 /** 552 * Rewind {@link $iteratorPosition} to first position (being 0) 553 * @link http://php.net/manual/en/iterator.rewind.php 554 * @return void 555 * @uses $iteratorPosition set to 0 556 * @uses $iteratorKeys store keys of {@link $value} 557 */ 558 public function rewind() { 559 $this->iteratorPosition = 0; 560 $this->iteratorKeys = array_keys($this->value); 561 } 562 563 /** 564 * Get Iterator's current {@link CFType} identified by {@link $iteratorPosition} 565 * @link http://php.net/manual/en/iterator.current.php 566 * @return CFType current Item 567 * @uses $iteratorPosition identify current key 568 * @uses $iteratorKeys identify current value 569 */ 570 public function current() { 571 return $this->value[$this->iteratorKeys[$this->iteratorPosition]]; 572 } 573 574 /** 575 * Get Iterator's current key identified by {@link $iteratorPosition} 576 * @link http://php.net/manual/en/iterator.key.php 577 * @return string key of the current Item 578 * @uses $iteratorPosition identify current key 579 * @uses $iteratorKeys identify current value 580 */ 581 public function key() { 582 return $this->iteratorKeys[$this->iteratorPosition]; 583 } 584 585 /** 586 * Increment {@link $iteratorPosition} to address next {@see CFType} 587 * @link http://php.net/manual/en/iterator.next.php 588 * @return void 589 * @uses $iteratorPosition increment by 1 590 */ 591 public function next() { 592 $this->iteratorPosition++; 593 } 594 595 /** 596 * Test if {@link $iteratorPosition} addresses a valid element of {@link $value} 597 * @link http://php.net/manual/en/iterator.valid.php 598 * @return boolean true if current position is valid, false else 599 * @uses $iteratorPosition test if within {@link $iteratorKeys} 600 * @uses $iteratorPosition test if within {@link $value} 601 */ 602 public function valid() { 603 return isset($this->iteratorKeys[$this->iteratorPosition]) && isset($this->value[$this->iteratorKeys[$this->iteratorPosition]]); 604 } 605 606 } 607 608 # eof
title
Description
Body
title
Description
Body
title
Description
Body
title
Body