CFPropertyList.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <?php
  2. namespace CFPropertyList;
  3. use \Iterator, \DOMDocument, \DOMException, DOMImplementation, DOMNode;
  4. require_once(__DIR__ . '/IOException.php');
  5. require_once(__DIR__ . '/PListException.php');
  6. require_once(__DIR__ . '/CFType.php');
  7. require_once(__DIR__ . '/CFBinaryPropertyList.php');
  8. require_once(__DIR__ . '/CFTypeDetector.php');
  9. class CFPropertyList extends CFBinaryPropertyList implements Iterator
  10. {
  11. const FORMAT_BINARY = 1;
  12. const FORMAT_XML = 2;
  13. const FORMAT_AUTO = 0;
  14. protected $file = null;
  15. protected $detectedFormat = null;
  16. protected $format = null;
  17. protected $value = array();
  18. protected $iteratorPosition = 0;
  19. protected $iteratorKeys = null;
  20. protected static $types = array(
  21. 'string' => 'CFString',
  22. 'real' => 'CFNumber',
  23. 'integer' => 'CFNumber',
  24. 'date' => 'CFDate',
  25. 'true' => 'CFBoolean',
  26. 'false' => 'CFBoolean',
  27. 'data' => 'CFData',
  28. 'array' => 'CFArray',
  29. 'dict' => 'CFDictionary'
  30. );
  31. public function __construct($file = null, $format = self::FORMAT_AUTO)
  32. {
  33. $this->file = $file;
  34. $this->format = $format;
  35. $this->detectedFormat = $format;
  36. if ($this->file) $this->load();
  37. }
  38. public function loadXML($file = null)
  39. {
  40. $this->load($file, CFPropertyList::FORMAT_XML);
  41. }
  42. public function loadXMLStream($stream)
  43. {
  44. if (($contents = stream_get_contents($stream)) === FALSE) throw IOException::notReadable('<stream>');
  45. $this->parse($contents, CFPropertyList::FORMAT_XML);
  46. }
  47. public function loadBinary($file = null)
  48. {
  49. $this->load($file, CFPropertyList::FORMAT_BINARY);
  50. }
  51. public function loadBinaryStream($stream)
  52. {
  53. if (($contents = stream_get_contents($stream)) === FALSE) throw IOException::notReadable('<stream>');
  54. $this->parse($contents, CFPropertyList::FORMAT_BINARY);
  55. }
  56. public function load($file = null, $format = null)
  57. {
  58. $file = $file ? $file : $this->file;
  59. $format = $format !== null ? $format : $this->format;
  60. $this->value = array();
  61. if (!is_readable($file)) throw IOException::notReadable($file);
  62. switch ($format) {
  63. case CFPropertyList::FORMAT_BINARY:
  64. $this->readBinary($file);
  65. break;
  66. case CFPropertyList::FORMAT_AUTO:
  67. $fd = fopen($file, "rb");
  68. if (($magic_number = fread($fd, 8)) === false) throw IOException::notReadable($file);
  69. fclose($fd);
  70. $filetype = substr($magic_number, 0, 6);
  71. $version = substr($magic_number, -2);
  72. if ($filetype == "bplist") {
  73. if ($version != "00") throw new PListException("Wrong file format version! Expected 00, got $version!");
  74. $this->detectedFormat = CFPropertyList::FORMAT_BINARY;
  75. $this->readBinary($file);
  76. break;
  77. }
  78. $this->detectedFormat = CFPropertyList::FORMAT_XML;
  79. case CFPropertyList::FORMAT_XML:
  80. $doc = new DOMDocument();
  81. if (!$doc->load($file)) throw new DOMException();
  82. $this->import($doc->documentElement, $this);
  83. break;
  84. }
  85. }
  86. public function parse($str = NULL, $format = NULL)
  87. {
  88. $format = $format !== null ? $format : $this->format;
  89. $str = $str !== null ? $str : $this->content;
  90. $this->value = array();
  91. switch ($format) {
  92. case CFPropertyList::FORMAT_BINARY:
  93. $this->parseBinary($str);
  94. break;
  95. case CFPropertyList::FORMAT_AUTO:
  96. if (($magic_number = substr($str, 0, 8)) === false) throw IOException::notReadable("<string>");
  97. $filetype = substr($magic_number, 0, 6);
  98. $version = substr($magic_number, -2);
  99. if ($filetype == "bplist") {
  100. if ($version != "00") throw new PListException("Wrong file format version! Expected 00, got $version!");
  101. $this->detectedFormat = CFPropertyList::FORMAT_BINARY;
  102. $this->parseBinary($str);
  103. break;
  104. }
  105. $this->detectedFormat = CFPropertyList::FORMAT_XML;
  106. case CFPropertyList::FORMAT_XML:
  107. $doc = new DOMDocument();
  108. if (!$doc->loadXML($str)) throw new DOMException();
  109. $this->import($doc->documentElement, $this);
  110. break;
  111. }
  112. }
  113. protected function import(DOMNode $node, $parent)
  114. {
  115. if (!$node->childNodes->length) return;
  116. foreach ($node->childNodes as $n) {
  117. if (!isset(self::$types[$n->nodeName])) continue;
  118. $class = 'CFPropertyList\\' . self::$types[$n->nodeName];
  119. $key = null;
  120. $ps = $n->previousSibling;
  121. while ($ps && $ps->nodeName == '#text' && $ps->previousSibling) $ps = $ps->previousSibling;
  122. if ($ps && $ps->nodeName == 'key') $key = $ps->firstChild->nodeValue;
  123. switch ($n->nodeName) {
  124. case 'date':
  125. $value = new $class(CFDate::dateValue($n->nodeValue));
  126. break;
  127. case 'data':
  128. $value = new $class($n->nodeValue, true);
  129. break;
  130. case 'string':
  131. $value = new $class($n->nodeValue);
  132. break;
  133. case 'real':
  134. case 'integer':
  135. $value = new $class($n->nodeName == 'real' ? floatval($n->nodeValue) : intval($n->nodeValue));
  136. break;
  137. case 'true':
  138. case 'false':
  139. $value = new $class($n->nodeName == 'true');
  140. break;
  141. case 'array':
  142. case 'dict':
  143. $value = new $class();
  144. $this->import($n, $value);
  145. if ($value instanceof CFDictionary) {
  146. $hsh = $value->getValue();
  147. if (isset($hsh['CF$UID']) && count($hsh) == 1) {
  148. $value = new CFUid($hsh['CF$UID']->getValue());
  149. }
  150. }
  151. break;
  152. }
  153. if ($parent instanceof CFDictionary) $parent->add($key, $value);
  154. else $parent->add($value);
  155. }
  156. }
  157. public function saveXML($file)
  158. {
  159. $this->save($file, CFPropertyList::FORMAT_XML);
  160. }
  161. public function saveBinary($file)
  162. {
  163. $this->save($file, CFPropertyList::FORMAT_BINARY);
  164. }
  165. public function save($file = null, $format = null)
  166. {
  167. $file = $file ? $file : $this->file;
  168. $format = $format ? $format : $this->format;
  169. if ($format == self::FORMAT_AUTO) $format = $this->detectedFormat;
  170. if (!in_array($format, array(self::FORMAT_BINARY, self::FORMAT_XML)))
  171. throw new PListException("format {$format} is not supported, use CFPropertyList::FORMAT_BINARY or CFPropertyList::FORMAT_XML");
  172. if (!file_exists($file)) {
  173. if (!is_writable(dirname($file))) throw IOException::notWritable($file);
  174. } else if (!is_writable($file)) throw IOException::notWritable($file);
  175. $content = $format == self::FORMAT_BINARY ? $this->toBinary() : $this->toXML();
  176. $fh = fopen($file, 'wb');
  177. fwrite($fh, $content);
  178. fclose($fh);
  179. }
  180. public function toXML($formatted = false)
  181. {
  182. $domimpl = new DOMImplementation();
  183. $dtd = $domimpl->createDocumentType('plist', '-//Apple//DTD PLIST 1.0//EN', 'http://www.apple.com/DTDs/PropertyList-1.0.dtd');
  184. $doc = $domimpl->createDocument(null, "plist", $dtd);
  185. $doc->encoding = "UTF-8";
  186. if ($formatted) {
  187. $doc->formatOutput = true;
  188. $doc->preserveWhiteSpace = true;
  189. }
  190. $plist = $doc->documentElement;
  191. $plist->setAttribute('version', '1.0');
  192. $plist->appendChild($this->getValue(true)->toXML($doc));
  193. return $doc->saveXML();
  194. }
  195. public function add(CFType $value = null)
  196. {
  197. if (!$value)
  198. $value = new CFString();
  199. $this->value[] = $value;
  200. }
  201. public function get($key)
  202. {
  203. if (isset($this->value[$key])) return $this->value[$key];
  204. return null;
  205. }
  206. public function __get($key)
  207. {
  208. return $this->get($key);
  209. }
  210. public function del($key)
  211. {
  212. if (isset($this->value[$key])) {
  213. $t = $this->value[$key];
  214. unset($this->value[$key]);
  215. return $t;
  216. }
  217. return null;
  218. }
  219. public function purge()
  220. {
  221. $t = $this->value;
  222. $this->value = array();
  223. return $t;
  224. }
  225. public function getValue($cftype = false)
  226. {
  227. if (count($this->value) === 1) {
  228. $t = array_values($this->value);
  229. return $t[0];
  230. }
  231. if ($cftype) {
  232. $t = new CFArray();
  233. foreach ($this->value as $value) {
  234. if ($value instanceof CFType) $t->add($value);
  235. }
  236. return $t;
  237. }
  238. return $this->value;
  239. }
  240. public static function guess($value, $options = array())
  241. {
  242. static $t = null;
  243. if ($t === null)
  244. $t = new CFTypeDetector($options);
  245. return $t->toCFType($value);
  246. }
  247. public function toArray()
  248. {
  249. $a = array();
  250. foreach ($this->value as $value) $a[] = $value->toArray();
  251. if (count($a) === 1) return $a[0];
  252. return $a;
  253. }
  254. public function rewind()
  255. {
  256. $this->iteratorPosition = 0;
  257. $this->iteratorKeys = array_keys($this->value);
  258. }
  259. public function current()
  260. {
  261. return $this->value[$this->iteratorKeys[$this->iteratorPosition]];
  262. }
  263. public function key()
  264. {
  265. return $this->iteratorKeys[$this->iteratorPosition];
  266. }
  267. public function next()
  268. {
  269. $this->iteratorPosition++;
  270. }
  271. public function valid()
  272. {
  273. return isset($this->iteratorKeys[$this->iteratorPosition]) && isset($this->value[$this->iteratorKeys[$this->iteratorPosition]]);
  274. }
  275. }
  276. ?>