1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10:
11:
12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25:
26: class Horde_Xml_Element implements ArrayAccess
27: {
28: 29: 30:
31: protected static $_namespaces = array(
32: 'opensearch' => 'http://a9.com/-/spec/opensearchrss/1.0/',
33: 'atom' => 'http://www.w3.org/2005/Atom',
34: 'rss' => 'http://blogs.law.harvard.edu/tech/rss',
35: 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns',
36: );
37:
38: 39: 40: 41: 42: 43: 44: 45: 46: 47:
48: public static function lookupNamespace($prefix)
49: {
50: return isset(self::$_namespaces[$prefix]) ?
51: self::$_namespaces[$prefix] :
52: $prefix;
53: }
54:
55: 56: 57: 58: 59: 60: 61: 62: 63: 64:
65: public static function registerNamespace($prefix, $namespaceURI)
66: {
67: self::$_namespaces[$prefix] = $namespaceURI;
68: }
69:
70: 71: 72:
73: protected $_element;
74:
75: 76: 77: 78: 79: 80:
81: protected $_serialized;
82:
83: 84: 85:
86: protected $_parentElement;
87:
88: 89: 90:
91: protected $_children = null;
92:
93: 94: 95:
96: protected $_appended = true;
97:
98: 99: 100: 101: 102: 103:
104: public function __construct($element)
105: {
106: $this->_element = $element;
107: $this->__wakeup();
108: }
109:
110: 111: 112: 113: 114: 115: 116: 117:
118: public function getDom()
119: {
120: return $this->_element;
121: }
122:
123: 124: 125: 126: 127: 128: 129: 130: 131:
132: public function setDom(DOMElement $element)
133: {
134: $this->_element = $this->_element->ownerDocument->importNode($element, true);
135: }
136:
137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152:
153: public function fromArray($array)
154: {
155: foreach ($array as $key => $value) {
156: $element = null;
157: $attribute = null;
158:
159: $hash_position = strpos($key, '#');
160: if ($hash_position === false) {
161: $element = $key;
162: } elseif ($hash_position === 0) {
163: $attribute = substr($key, 1);
164: } else {
165: list($element, $attribute) = explode('#', $key, 2);
166: }
167:
168: if (!is_null($element)) {
169: if (!is_null($attribute)) {
170: $this->{$element}[$attribute] = $value;
171: } else {
172: if (is_array($value)) {
173:
174:
175: $firstKey = key($value);
176: if ($firstKey === 0) {
177: if (strpos($element, ':') !== false) {
178: list($ns) = explode(':', $element, 2);
179: $baseNode = $this->_element->ownerDocument->createElementNS(Horde_Xml_Element::lookupNamespace($ns), $element);
180: } else {
181: $baseNode = $this->_element->ownerDocument->createElement($element);
182: }
183:
184: foreach ($value as $v) {
185: $node = $baseNode->cloneNode();
186: if (is_array($v)) {
187: $e = new Horde_Xml_Element($node);
188: $e->fromArray($v);
189: } else {
190: $node->nodeValue = $v;
191: $e = new Horde_Xml_Element($node);
192: }
193: $this->appendChild($e);
194: }
195: } else {
196: $this->$element->fromArray($value);
197: }
198: } else {
199: $this->$element = $value;
200: }
201: }
202: } elseif (!is_null($attribute)) {
203: $this[$attribute] = $value;
204: }
205: }
206: }
207:
208: 209: 210: 211: 212:
213: public function appendChild(Horde_Xml_Element $element)
214: {
215: $element->setParent($this);
216: $element->_ensureAppended();
217: $this->_expireCachedChildren();
218: }
219:
220: 221: 222: 223: 224: 225: 226: 227:
228: public function saveXml($formatted = false)
229: {
230:
231: $doc = new DOMDocument($this->_element->ownerDocument->version,
232: $this->_element->ownerDocument->actualEncoding);
233: $doc->formatOutput = $formatted;
234: $doc->appendChild($doc->importNode($this->_element, true));
235: return $doc->saveXML();
236: }
237:
238: 239: 240: 241: 242: 243: 244:
245: public function saveXmlFragment($formatted = false)
246: {
247: $oldFormatted = $this->_element->ownerDocument->formatOutput;
248: $this->_element->ownerDocument->formatOutput = $formatted;
249: $xml = $this->_element->ownerDocument->saveXML($this->_element);
250: $this->_element->ownerDocument->formatOutput = $oldFormatted;
251: return $xml;
252: }
253:
254: 255: 256: 257:
258: public function __wakeup()
259: {
260: if ($this->_element instanceof DOMElement) {
261: return true;
262: }
263:
264: if ($this->_element instanceof Horde_Xml_Element) {
265: $this->_element = $this->_element->getDom();
266: return true;
267: }
268:
269: if ($this->_serialized) {
270: $this->_element = $this->_serialized;
271: $this->_serialized = null;
272: }
273:
274: if (is_string($this->_element)) {
275: $doc = new DOMDocument();
276: $doc->preserveWhiteSpace = false;
277:
278: $extract = false;
279: if (substr($this->_element, 0, 5) != '<?xml') {
280: $extract = true;
281: $preamble = '<?xml version="1.0" encoding="UTF-8" ?><root ';
282: foreach (self::$_namespaces as $prefix => $nsUri) {
283: $preamble .= " xmlns:$prefix=\"$nsUri\"";
284: }
285: $preamble .= '>';
286: $this->_element = $preamble . $this->_element . '</root>';
287: }
288:
289: $loaded = @$doc->loadXML($this->_element);
290: if (!$loaded) {
291: throw new Horde_Xml_Element_Exception('DOMDocument cannot parse XML: ', error_get_last());
292: }
293:
294: if ($extract) {
295: $newDoc = new DOMDocument();
296: $this->_element = $newDoc->importNode($doc->documentElement->childNodes->item(0), true);
297: } else {
298: $this->_element = $doc->documentElement;
299: }
300:
301: return true;
302: }
303:
304: throw new InvalidArgumentException('Horde_Xml_Element initialization value must be a DOMElement, a Horde_Xml_Element, or a non-empty string; '
305: . (gettype($this->_element) == 'object' ? get_class($this->_element) : gettype($this->_element))
306: . ' given');
307: }
308:
309: 310: 311: 312: 313:
314: public function __sleep()
315: {
316: $this->_serialized = $this->saveXml();
317: return array('_serialized');
318: }
319:
320: 321: 322: 323: 324: 325: 326: 327: 328: 329:
330: public function __get($var)
331: {
332: $nodes = $this->_children($var);
333: $length = count($nodes);
334:
335: if ($length == 1) {
336: if ($nodes[0] instanceof Horde_Xml_Element) {
337: return $nodes[0];
338: }
339: return new Horde_Xml_Element($nodes[0]);
340: } elseif ($length > 1) {
341: if ($nodes[0] instanceof Horde_Xml_Element) {
342: return $nodes;
343: }
344: return array_map(create_function('$e', 'return new Horde_Xml_Element($e);'), $nodes);
345: } else {
346:
347:
348:
349:
350:
351:
352: if (strpos($var, ':') !== false) {
353: list($ns, $elt) = explode(':', $var, 2);
354: $node = $this->_element->ownerDocument->createElementNS(Horde_Xml_Element::lookupNamespace($ns), $elt);
355: } else {
356: $node = $this->_element->ownerDocument->createElement($var);
357: }
358: $node = new Horde_Xml_Element($node);
359: $node->setParent($this);
360: return $node;
361: }
362: }
363:
364: 365: 366: 367: 368: 369:
370: public function __set($var, $val)
371: {
372: if (!is_scalar($val)) {
373: throw new InvalidArgumentException('Element values must be scalars, ' . gettype($val) . ' given');
374: }
375:
376: $this->_ensureAppended();
377:
378: $nodes = $this->_children($var);
379: if (!$nodes) {
380: if (strpos($var, ':') !== false) {
381: list($ns) = explode(':', $var, 2);
382: $node = $this->_element->ownerDocument->createElementNS(Horde_Xml_Element::lookupNamespace($ns), $var, $val);
383: $this->_element->appendChild($node);
384: } else {
385: $node = $this->_element->ownerDocument->createElement($var, $val);
386: $this->_element->appendChild($node);
387: }
388:
389: $this->_expireCachedChildren();
390: } elseif (count($nodes) > 1) {
391: throw new Horde_Xml_Element_Exception('Cannot set the value of multiple nodes simultaneously.');
392: } else {
393: $nodes[0]->nodeValue = $val;
394: }
395: }
396:
397: 398: 399:
400: public function __isset($var)
401: {
402: return (boolean)$this->_children($var);
403: }
404:
405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415:
416: public function __call($var, $unused)
417: {
418: $nodes = $this->_children($var);
419:
420: if (!$nodes) {
421: return null;
422: } elseif (count($nodes) > 1) {
423: if ($nodes[0] instanceof Horde_Xml_Element) {
424: return $nodes;
425: }
426: return array_map(create_function('$e', 'return new Horde_Xml_Element($e);'), $nodes);
427: } else {
428: if ($nodes[0] instanceof Horde_Xml_Element) {
429: return (string)$nodes[0];
430: } else {
431: return $nodes[0]->nodeValue;
432: }
433: }
434: }
435:
436: 437: 438:
439: public function __unset($var)
440: {
441: $nodes = $this->_children($var);
442: foreach ($nodes as $node) {
443: $parent = $node->parentNode;
444: $parent->removeChild($node);
445: }
446:
447: $this->_expireCachedChildren();
448: }
449:
450: 451: 452: 453: 454: 455:
456: public function __toString()
457: {
458: return $this->_element->nodeValue;
459: }
460:
461: 462: 463: 464: 465: 466:
467: public function setParent(Horde_Xml_Element $element)
468: {
469: $this->_parentElement = $element;
470: $this->_appended = false;
471: }
472:
473: 474: 475: 476: 477:
478: protected function _ensureAppended()
479: {
480: if (!$this->_appended) {
481: $parentDom = $this->_parentElement->getDom();
482: if (!$parentDom->ownerDocument->isSameNode($this->_element->ownerDocument)) {
483: $this->_element = $parentDom->ownerDocument->importNode($this->_element, true);
484: }
485:
486: $parentDom->appendChild($this->_element);
487: $this->_appended = true;
488: $this->_parentElement->_ensureAppended();
489: }
490: }
491:
492: 493: 494: 495: 496: 497: 498: 499:
500: protected function _children($var)
501: {
502: if (is_null($this->_children)) {
503: $this->_cacheChildren();
504: }
505:
506:
507:
508:
509: $varMethod = 'get' . ucfirst($var);
510: if (method_exists($this, $varMethod)) {
511: $children = call_user_func(array($this, $varMethod));
512: if (is_null($children)) {
513: $this->_children[$var] = array();
514: } elseif (!is_array($children)) {
515: $this->_children[$var] = array($children);
516: } else {
517: $this->_children[$var] = $children;
518: }
519: }
520:
521: if (!isset($this->_children[$var])) {
522: $this->_children[$var] = array();
523: }
524:
525: return $this->_children[$var];
526: }
527:
528: 529: 530:
531: protected function _cacheChildren()
532: {
533: foreach ($this->_element->childNodes as $child) {
534: if (!isset($this->_children[$child->localName]))
535: $this->_children[$child->localName] = array();
536: $this->_children[$child->localName][] = $child;
537:
538: if ($child->prefix) {
539: if (!isset($this->_children[$child->prefix . ':' . $child->localName]))
540: $this->_children[$child->prefix . ':' . $child->localName] = array();
541: $this->_children[$child->prefix . ':' . $child->localName][] = $child;
542: }
543: }
544: }
545:
546: 547: 548:
549: protected function _expireCachedChildren()
550: {
551: $this->_children = null;
552: }
553:
554: 555: 556: 557: 558:
559: public function offsetExists($offset)
560: {
561: if (strpos($offset, ':') !== false) {
562: list($ns, $attr) = explode(':', $offset, 2);
563: return $this->_element->hasAttributeNS(Horde_Xml_Element::lookupNamespace($ns), $attr);
564: } else {
565: return $this->_element->hasAttribute($offset);
566: }
567: }
568:
569: 570: 571: 572: 573:
574: public function offsetGet($offset)
575: {
576: if (strpos($offset, ':') !== false) {
577: list($ns, $attr) = explode(':', $offset, 2);
578: return $this->_element->getAttributeNS(Horde_Xml_Element::lookupNamespace($ns), $attr);
579: } else {
580: return $this->_element->getAttribute($offset);
581: }
582: }
583:
584: 585: 586: 587: 588:
589: public function offsetSet($offset, $value)
590: {
591: if (!is_scalar($value)) {
592: throw new InvalidArgumentException('Element values must be scalars, ' . gettype($value) . ' given');
593: }
594:
595: $this->_ensureAppended();
596:
597: if (strpos($offset, ':') !== false) {
598: list($ns) = explode(':', $offset, 2);
599: $result = $this->_element->setAttributeNS(Horde_Xml_Element::lookupNamespace($ns), $offset, $value);
600: } else {
601: $result = $this->_element->setAttribute($offset, $value);
602: }
603:
604: if ($result) {
605: $this->_expireCachedChildren();
606: return true;
607: } else {
608: return false;
609: }
610: }
611:
612: 613: 614: 615: 616:
617: public function offsetUnset($offset)
618: {
619: if (strpos($offset, ':') !== false) {
620: list($ns, $attr) = explode(':', $offset, 2);
621: $result = $this->_element->removeAttributeNS(Horde_Xml_Element::lookupNamespace($ns), $attr);
622: } else {
623: $result = $this->_element->removeAttribute($offset);
624: }
625:
626: if ($result) {
627: $this->_expireCachedChildren();
628: return true;
629: } else {
630: return false;
631: }
632: }
633:
634: }
635: