]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/XmlElement.php
php-5.2.0 fix for string fallback: Provide the magic method __toString
[SourceForge/phpwiki.git] / lib / XmlElement.php
1 <?php rcs_id('$Id: XmlElement.php,v 1.40 2007-01-13 23:28:56 rurban Exp $');
2 /**
3  * Code for writing XML.
4  * @package Markup
5  * @author: Jeff Dairiki,
6  *          Reini Urban (php5 tricks)
7  *
8  * WARNING: This module is very php5 sensitive. 
9  *        Fixed for 1.3.9 and 1.3.11.
10  *        With allow_call_time_pass_reference clean fixes.
11  */
12
13 /**
14  * A sequence of (zero or more) XmlElements (possibly interspersed with
15  * plain strings (CDATA).
16  */
17 class XmlContent
18 {
19     function XmlContent (/* ... */) {
20         $this->_content = array();
21         $this->_pushContent_array(func_get_args());
22     }
23
24     function pushContent ($arg /*, ...*/) {
25         if (func_num_args() > 1)
26             $this->_pushContent_array(func_get_args());
27         elseif (is_array($arg))
28             $this->_pushContent_array($arg);
29         else
30             $this->_pushContent($arg);
31     }
32
33     function _pushContent_array ($array) {
34         foreach ($array as $item) {
35             if (is_array($item))
36                 $this->_pushContent_array($item);
37             else
38                 $this->_pushContent($item);
39         }
40     }
41
42     function _pushContent ($item) {
43         if (strtolower(get_class($item)) == 'xmlcontent')
44             array_splice($this->_content, count($this->_content), 0,
45                          $item->_content);
46         else
47             $this->_content[] = $item;
48     }
49
50     function unshiftContent ($arg /*, ...*/) {
51         if (func_num_args() > 1)
52             $this->_unshiftContent_array(func_get_args());
53         elseif (is_array($arg))
54             $this->_unshiftContent_array($arg);
55         else
56             $this->_unshiftContent($arg);
57     }
58
59     function _unshiftContent_array ($array) {
60         foreach (array_reverse($array) as $item) {
61             if (is_array($item))
62                 $this->_unshiftContent_array($item);
63             else
64                 $this->_unshiftContent($item);
65         }
66     }
67
68     function _unshiftContent ($item) {
69         if (strtolower(get_class($item)) == 'xmlcontent')
70             array_splice($this->_content, 0, 0, $item->_content);
71         else
72             array_unshift($this->_content, $item);
73     }
74     
75     function getContent () {
76         return $this->_content;
77     }
78
79     function setContent ($arg /* , ... */) {
80         $this->_content = array();
81         $this->_pushContent_array(func_get_args());
82     }
83
84     function printXML () {
85         foreach ($this->_content as $item) {
86             if (is_object($item)) {
87                 if (method_exists($item, 'printXML'))
88                     $item->printXML();
89                 elseif (method_exists($item, 'asXML'))
90                     echo $item->asXML();
91                 elseif (method_exists($item, 'asString'))
92                     echo $this->_quote($item->asString());
93                 else
94                     printf("==Object(%s)==", get_class($item));
95             }
96             else
97                 echo $this->_quote((string) $item);
98         }
99     }
100
101     function asXML () {
102         $xml = '';
103         foreach ($this->_content as $item) {
104             if (is_object($item)) {
105                 if (method_exists($item, 'asXML'))
106                     $xml .= $item->asXML();
107                 elseif (method_exists($item, 'asString'))
108                     $xml .= $this->_quote($item->asString());
109                 else
110                     $xml .= sprintf("==Object(%s)==", get_class($item));
111             }
112             else
113                 $xml .= $this->_quote((string) $item);
114         }
115         return $xml;
116     }
117
118     function asPDF () {
119         $pdf = '';
120         foreach ($this->_content as $item) {
121             if (is_object($item)) {
122                 if (method_exists($item, 'asPDF'))
123                     $pdf .= $item->asPDF();
124                 elseif (method_exists($item, 'asString'))
125                     $pdf .= $this->_quote($item->asString());
126                 else
127                     $pdf .= sprintf("==Object(%s)==", get_class($item));
128             }
129             else
130                 $pdf .= $this->_quote((string) $item);
131         }
132         return $pdf;
133     }
134
135     /* php-5.2 magic */
136     function __toString () {
137         return $this->asString();
138     }
139
140     function asString () {
141         $val = '';
142         foreach ($this->_content as $item) {
143             if (is_object($item)) {
144                 if (method_exists($item, 'asString')) {
145                     $val .= (string) $item->asString();
146                 } else {
147                     $val .= sprintf("==Object(%s)==", get_class($item));
148                 }
149             }
150             else
151                 $val .= (string) $item;
152         }
153         return trim($val);
154     }
155
156
157     /**
158      * See if element is empty.
159      *
160      * Empty means it has no content.
161      * @return bool True if empty.
162      */
163     function isEmpty () {
164         if (empty($this->_content))
165             return true;
166         foreach ($this->_content as $x) {
167             if (is_string($x) ? strlen($x) : !empty($x))
168                 return false;
169         }
170         return true;
171     }
172     
173     function _quote ($string) {
174         if (!$string) return $string;
175         if (check_php_version(4,1) and isset($GLOBALS['charset']))
176             return htmlspecialchars($string, ENT_COMPAT, $GLOBALS['charset']);
177         else
178             return htmlspecialchars($string);
179     }
180 };
181
182 /**
183  * An XML element.
184  *
185  * @param $tagname string Tag of html element.
186  */
187 class XmlElement extends XmlContent
188 {
189     function XmlElement ($tagname /* , $attr_or_content , ...*/) {
190         //FIXME: php5 incompatible
191         $this->XmlContent();
192         $this->_init(func_get_args());
193     }
194
195     function _init ($args) {
196         if (!is_array($args))
197             $args = func_get_args();
198
199         assert(count($args) >= 1);
200         //assert(is_string($args[0]));
201         $this->_tag = array_shift($args);
202         
203         if ($args && is_array($args[0]))
204             $this->_attr = array_shift($args);
205         else {
206             $this->_attr = array();
207             if ($args && $args[0] === false)
208                 array_shift($args);
209         }
210
211         $this->setContent($args);
212     }
213     
214     /** Methods only needed for XmlParser,
215      *  to be fully compatible to perl Html::Element
216      */
217     // doesn't yet work with php5 as __destruct()
218     function _destruct () {
219         if ($this->hasChildren()) {
220             foreach ($this->getChildren() as $node) {
221                 $node->_destruct();
222             }
223         }
224         unset($this->_tag);
225         unset($this->_attr);
226         unset($this->_content);
227     }
228     
229     function getChildren () {
230         return $this->_children;
231     }
232
233     function hasChildren () {
234         return !empty($this->_children);
235     }
236     /* End XmlParser Methods
237      */
238
239     function getTag () {
240         return $this->_tag;
241     }
242     
243     function setAttr ($attr, $value = false) {
244         if (is_array($attr)) {
245             assert($value === false);
246             foreach ($attr as $a => $v) {
247                 $this->_attr[strtolower($a)] = $v;
248                 //$this->set($a, $v);
249             }
250             return;
251         }
252
253         assert(is_string($attr));
254             
255         if ($value === false) {
256             unset($this->_attr[$attr]);
257         }
258         else {
259             if (is_bool($value))
260                 $value = $attr;
261             $this->_attr[$attr] = (string) $value;
262         }
263
264         if ($attr == 'class')
265             unset($this->_classes);
266     }
267
268     function getAttr ($attr) {
269         if ($attr == 'class')
270             $this->_setClasses();
271
272         if (isset($this->_attr[strtolower($attr)]))
273             return $this->_attr[strtolower($attr)];
274         else
275             return false;
276     }
277
278     function _getClasses() {
279         if (!isset($this->_classes)) {
280             $this->_classes = array();
281             if (isset($this->_attr['class'])) {
282                 $classes = explode(' ', (string) $this->_attr['class']);
283                 foreach ($classes as $class) {
284                     $class = trim($class);
285                     if ($class)
286                         $this->_classes[$class] = $class;
287                 }
288             }
289         }
290         return $this->_classes;
291     }
292
293     function _setClasses() {
294         if (isset($this->_classes)) {
295             if ($this->_classes)
296                 $this->_attr['class'] = join(' ', $this->_classes);
297             else
298                 unset($this->_attr['class']);
299         }
300     }
301
302     /**
303      * Manipulate the elements CSS class membership.
304      *
305      * This adds or remove an elements membership
306      * in a give CSS class.
307      *
308      * @param $class string
309      *
310      * @param $in_class bool
311      *   If true (the default) the element is added to class $class.
312      *   If false, the element is removed from the class.
313      */
314     function setInClass($class, $in_class=true) {
315         $this->_getClasses();
316         $class = trim($class);
317         if ($in_class)
318             $this->_classes[$class] = $class;
319         else 
320             unset($this->_classes[$class]);
321     }
322
323     /**
324      * Is element in a given (CSS) class?
325      *
326      * This checks for the presence of a particular class in the
327      * elements 'class' attribute.
328      *
329      * @param $class string  The class to check for.
330      * @return bool True if the element is a member of $class.
331      */
332     function inClass($class) {
333         $this->_parseClasses();
334         return isset($this->_classes[trim($class)]);
335     }
336
337     function startTag() {
338         $start = "<" . $this->_tag;
339         $this->_setClasses();
340         foreach ($this->_attr as $attr => $val) {
341             if (is_bool($val)) {
342                 if (!$val)
343                     continue;
344                 $val = $attr;
345             }
346             $qval = str_replace("\"", '&quot;', $this->_quote((string)$val));
347             $start .= " $attr=\"$qval\"";
348         }
349         $start .= ">";
350         return $start;
351     }
352
353     function emptyTag() {
354         return substr($this->startTag(), 0, -1) . "/>";
355     }
356
357     
358     function endTag() {
359         return "</$this->_tag>";
360     }
361     
362         
363     function printXML () {
364         if ($this->isEmpty())
365             echo $this->emptyTag();
366         else {
367             echo $this->startTag();
368             // FIXME: The next two lines could be removed for efficiency
369             if (!$this->hasInlineContent())
370                 echo "\n";
371             XmlContent::printXML();
372             echo "</$this->_tag>";
373         }
374         if (!$this->isInlineElement())
375             echo "\n";
376     }
377
378     function asXML () {
379         if ($this->isEmpty()) {
380             $xml = $this->emptyTag();
381         }
382         else {
383             $xml = $this->startTag();
384             // FIXME: The next two lines could be removed for efficiency
385             if (!$this->hasInlineContent())
386                 $xml .= "\n";
387             $xml .= XmlContent::asXML();
388             $xml .= "</$this->_tag>";
389         }
390         if (!$this->isInlineElement())
391             $xml .= "\n";
392         return $xml;
393     }
394
395     /**
396      * Can this element have inline content?
397      *
398      * This is a hack, but is probably the best one can do without
399      * knowledge of the DTD...
400      */
401     function hasInlineContent () {
402         // This is a hack.
403         if (empty($this->_content))
404             return true;
405         if (is_object($this->_content[0]))
406             return false;
407         return true;
408     }
409     
410     /**
411      * Is this element part of inline content?
412      *
413      * This is a hack, but is probably the best one can do without
414      * knowledge of the DTD...
415      */
416     function isInlineElement () {
417         return false;
418     }
419     
420 };
421
422 class RawXml {
423     function RawXml ($xml_text) {
424         $this->_xml = $xml_text;
425     }
426
427     function printXML () {
428         echo $this->_xml;
429     }
430
431     function asXML () {
432         return $this->_xml;
433     }
434
435     function isEmpty () {
436         return empty($this->_xml);
437     }
438 }
439
440 class FormattedText {
441     function FormattedText ($fs /* , ... */) {
442         if ($fs !== false) {
443             $this->_init(func_get_args());
444         }
445     }
446
447     function _init ($args) {
448         $this->_fs = array_shift($args);
449
450         // PHP's sprintf doesn't support variable width specifiers,
451         // like sprintf("%*s", 10, "x"); --- so we won't either.
452         $m = array();
453         if (! preg_match_all('/(?<!%)%(\d+)\$/x', $this->_fs, $m)) {
454             $this->_args  = $args;
455         }
456         else {
457             // Format string has '%2$s' style argument reordering.
458             // PHP doesn't support this.
459             if (preg_match('/(?<!%)%[- ]?\d*[^- \d$]/x', $this->_fs)) // $fmt
460                 // literal variable name substitution only to keep locale
461                 // strings uncluttered
462                 trigger_error(sprintf(_("Can't mix '%s' with '%s' type format strings"),
463                                       '%1\$s','%s'), E_USER_WARNING);
464         
465             $this->_fs = preg_replace('/(?<!%)%\d+\$/x', '%', $this->_fs);
466
467             $this->_args = array();
468             foreach($m[1] as $argnum) {
469                 if ($argnum < 1 || $argnum > count($args))
470                     trigger_error(sprintf("%s: argument index out of range", 
471                                           $argnum), E_USER_WARNING);
472                 $this->_args[] = $args[$argnum - 1];
473             }
474         }
475     }
476
477     function asXML () {
478         // Not all PHP's have vsprintf, so...
479         $args[] = XmlElement::_quote((string)$this->_fs);
480         foreach ($this->_args as $arg)
481             $args[] = AsXML($arg);
482         return call_user_func_array('sprintf', $args);
483     }
484
485     function printXML () {
486         // Not all PHP's have vsprintf, so...
487         $args[] = XmlElement::_quote((string)$this->_fs);
488         foreach ($this->_args as $arg)
489             $args[] = AsXML($arg);
490         call_user_func_array('printf', $args);
491     }
492
493     function asString() {
494         $args[] = $this->_fs;
495         foreach ($this->_args as $arg)
496             $args[] = AsString($arg);
497         return call_user_func_array('sprintf', $args);
498     }
499 }
500
501 /**
502  * PHP5 compatibility
503  * Error[2048]: Non-static method XmlContent::_quote() should not be called statically
504  * Note: There's lot of room for performance increase if the right charset variant can 
505  * be created on load-time.
506  */
507 function XmlContent_quote ($string) {
508     if (!$string) return $string;
509     if (check_php_version(4,1) and isset($GLOBALS['charset'])
510         and (!defined('IGNORE_CHARSET_NOT_SUPPORTED_WARNING') or !IGNORE_CHARSET_NOT_SUPPORTED_WARNING))
511     {
512         return htmlspecialchars($string, ENT_COMPAT, $GLOBALS['charset']);
513     } else {
514         return htmlspecialchars($string);
515     }
516 }
517
518 function PrintXML ($val /* , ... */ ) {
519     if (func_num_args() > 1) {
520         foreach (func_get_args() as $arg)
521             PrintXML($arg);
522     }
523     elseif (is_object($val)) {
524         if (method_exists($val, 'printXML'))
525             $val->printXML();
526         elseif (method_exists($val, 'asXML')) {
527             echo $val->asXML();
528         }
529         elseif (method_exists($val, 'asString'))
530             echo XmlContent_quote($val->asString());
531         else
532             printf("==Object(%s)==", get_class($val));
533     }
534     elseif (is_array($val)) {
535         // DEPRECATED:
536         // Use XmlContent objects instead of arrays for collections of XmlElements.
537         trigger_error("Passing arrays to PrintXML() is deprecated: (" . AsXML($val, true) . ")",
538                       E_USER_NOTICE);
539         foreach ($val as $x)
540             PrintXML($x);
541     }
542     else
543         echo (string)XmlContent_quote((string)$val);
544 }
545
546 function AsXML ($val /* , ... */) {
547     static $nowarn;
548
549     if (func_num_args() > 1) {
550         $xml = '';
551         foreach (func_get_args() as $arg)
552             $xml .= AsXML($arg);
553         return $xml;
554     }
555     elseif (is_object($val)) {
556         if (method_exists($val, 'asXML'))
557             return $val->asXML();
558         elseif (method_exists($val, 'asString'))
559             return XmlContent_quote($val->asString());
560         else
561             return sprintf("==Object(%s)==", get_class($val));
562     }
563     elseif (is_array($val)) {
564         // DEPRECATED:
565         // Use XmlContent objects instead of arrays for collections of XmlElements.
566         if (empty($nowarn)) {
567             $nowarn = true;
568             trigger_error("Passing arrays to AsXML() is deprecated: (" . AsXML($val) . ")",
569                           E_USER_NOTICE);
570             unset($nowarn);
571         }
572         $xml = '';
573         foreach ($val as $x)
574             $xml .= AsXML($x);
575         return $xml;
576     }
577     else
578         return XmlContent_quote((string)$val);
579 }
580
581 function AsString ($val) {
582     if (func_num_args() > 1) {
583         $str = '';
584         foreach (func_get_args() as $arg)
585             $str .= AsString($arg);
586         return $str;
587     }
588     elseif (is_object($val)) {
589         if (method_exists($val, 'asString'))
590             return $val->asString();
591         else
592             return sprintf("==Object(%s)==", get_class($val));
593     }
594     elseif (is_array($val)) {
595         // DEPRECATED:
596         // Use XmlContent objects instead of arrays for collections of XmlElements.
597         trigger_error("Passing arrays to AsString() is deprecated", E_USER_NOTICE);
598         $str = '';
599         foreach ($val as $x)
600             $str .= AsString($x);
601         return $str;
602     }
603     
604     return (string) $val;
605 }
606
607
608 function fmt ($fs /* , ... */) {
609     $s = new FormattedText(false);
610
611     $args = func_get_args();
612     $args[0] = _($args[0]);
613     $s->_init($args);
614     return $s;
615 }
616
617 // $Log: not supported by cvs2svn $
618 // Revision 1.39  2006/08/15 13:36:23  rurban
619 // support iso-8859-2
620 //
621 // Revision 1.38  2005/10/10 19:36:09  rurban
622 // fix comment
623 //
624 // Revision 1.37  2005/01/25 07:04:27  rurban
625 // case-sensitive for php5
626 //
627 // Revision 1.36  2004/12/06 19:49:56  rurban
628 // enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
629 // renamed delete_page to purge_page.
630 // enable action=edit&version=-1 to force creation of a new version.
631 // added BABYCART_PATH config
632 // fixed magiqc in adodb.inc.php
633 // and some more docs
634 //
635 // Revision 1.35  2004/11/21 11:59:18  rurban
636 // remove final \n to be ob_cache independent
637 //
638 // Revision 1.34  2004/10/12 13:13:19  rurban
639 // php5 compatibility (5.0.1 ok)
640 //
641 // Revision 1.33  2004/07/02 09:55:58  rurban
642 // more stability fixes: new DISABLE_GETIMAGESIZE if your php crashes when loading LinkIcons: failing getimagesize in old phps; blockparser stabilized
643 //
644 // Revision 1.32  2004/06/20 15:30:05  rurban
645 // get_class case-sensitivity issues
646 //
647 // Revision 1.31  2004/06/20 14:42:54  rurban
648 // various php5 fixes (still broken at blockparser)
649 //
650
651 // (c-file-style: "gnu")
652 // Local Variables:
653 // mode: php
654 // tab-width: 8
655 // c-basic-offset: 4
656 // c-hanging-comment-ender-p: nil
657 // indent-tabs-mode: nil
658 // End:   
659 ?>