]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/XmlElement.php
Add php-5.2.0 note
[SourceForge/phpwiki.git] / lib / XmlElement.php
1 <?php rcs_id('$Id: XmlElement.php,v 1.42 2007-01-20 11:41:30 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, 1.3.11 and 1.3.13 (php-5.2).
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     /* php-5.2 magic */
432     function __toString () {
433         return $this->_xml;
434     }
435
436     function asXML () {
437         return $this->_xml;
438     }
439
440     function isEmpty () {
441         return empty($this->_xml);
442     }
443 }
444
445 class FormattedText {
446     function FormattedText ($fs /* , ... */) {
447         if ($fs !== false) {
448             $this->_init(func_get_args());
449         }
450     }
451
452     function _init ($args) {
453         $this->_fs = array_shift($args);
454
455         // PHP's sprintf doesn't support variable width specifiers,
456         // like sprintf("%*s", 10, "x"); --- so we won't either.
457         $m = array();
458         if (! preg_match_all('/(?<!%)%(\d+)\$/x', $this->_fs, $m)) {
459             $this->_args  = $args;
460         }
461         else {
462             // Format string has '%2$s' style argument reordering.
463             // PHP doesn't support this.
464             if (preg_match('/(?<!%)%[- ]?\d*[^- \d$]/x', $this->_fs)) // $fmt
465                 // literal variable name substitution only to keep locale
466                 // strings uncluttered
467                 trigger_error(sprintf(_("Can't mix '%s' with '%s' type format strings"),
468                                       '%1\$s','%s'), E_USER_WARNING);
469         
470             $this->_fs = preg_replace('/(?<!%)%\d+\$/x', '%', $this->_fs);
471
472             $this->_args = array();
473             foreach($m[1] as $argnum) {
474                 if ($argnum < 1 || $argnum > count($args))
475                     trigger_error(sprintf("%s: argument index out of range", 
476                                           $argnum), E_USER_WARNING);
477                 $this->_args[] = $args[$argnum - 1];
478             }
479         }
480     }
481
482     function asXML () {
483         // Not all PHP's have vsprintf, so...
484         $args[] = XmlElement::_quote((string)$this->_fs);
485         foreach ($this->_args as $arg)
486             $args[] = AsXML($arg);
487         return call_user_func_array('sprintf', $args);
488     }
489
490     function printXML () {
491         // Not all PHP's have vsprintf, so...
492         $args[] = XmlElement::_quote((string)$this->_fs);
493         foreach ($this->_args as $arg)
494             $args[] = AsXML($arg);
495         call_user_func_array('printf', $args);
496     }
497
498     function asString() {
499         $args[] = $this->_fs;
500         foreach ($this->_args as $arg)
501             $args[] = AsString($arg);
502         return call_user_func_array('sprintf', $args);
503     }
504
505     /* php-5.2 magic */
506     function __toString () {
507         return $this->asString();
508     }
509 }
510
511 /**
512  * PHP5 compatibility
513  * Error[2048]: Non-static method XmlContent::_quote() should not be called statically
514  * Note: There's lot of room for performance increase if the right charset variant can 
515  * be created on load-time.
516  */
517 function XmlContent_quote ($string) {
518     if (!$string) return $string;
519     if (check_php_version(4,1) and isset($GLOBALS['charset'])
520         and (!defined('IGNORE_CHARSET_NOT_SUPPORTED_WARNING') or !IGNORE_CHARSET_NOT_SUPPORTED_WARNING))
521     {
522         return htmlspecialchars($string, ENT_COMPAT, $GLOBALS['charset']);
523     } else {
524         return htmlspecialchars($string);
525     }
526 }
527
528 function PrintXML ($val /* , ... */ ) {
529     if (func_num_args() > 1) {
530         foreach (func_get_args() as $arg)
531             PrintXML($arg);
532     }
533     elseif (is_object($val)) {
534         if (method_exists($val, 'printXML'))
535             $val->printXML();
536         elseif (method_exists($val, 'asXML')) {
537             echo $val->asXML();
538         }
539         elseif (method_exists($val, 'asString'))
540             echo XmlContent_quote($val->asString());
541         else
542             printf("==Object(%s)==", get_class($val));
543     }
544     elseif (is_array($val)) {
545         // DEPRECATED:
546         // Use XmlContent objects instead of arrays for collections of XmlElements.
547         trigger_error("Passing arrays to PrintXML() is deprecated: (" . AsXML($val, true) . ")",
548                       E_USER_NOTICE);
549         foreach ($val as $x)
550             PrintXML($x);
551     }
552     else
553         echo (string)XmlContent_quote((string)$val);
554 }
555
556 function AsXML ($val /* , ... */) {
557     static $nowarn;
558
559     if (func_num_args() > 1) {
560         $xml = '';
561         foreach (func_get_args() as $arg)
562             $xml .= AsXML($arg);
563         return $xml;
564     }
565     elseif (is_object($val)) {
566         if (method_exists($val, 'asXML'))
567             return $val->asXML();
568         elseif (method_exists($val, 'asString'))
569             return XmlContent_quote($val->asString());
570         else
571             return sprintf("==Object(%s)==", get_class($val));
572     }
573     elseif (is_array($val)) {
574         // DEPRECATED:
575         // Use XmlContent objects instead of arrays for collections of XmlElements.
576         if (empty($nowarn)) {
577             $nowarn = true;
578             trigger_error("Passing arrays to AsXML() is deprecated: (" . AsXML($val) . ")",
579                           E_USER_NOTICE);
580             unset($nowarn);
581         }
582         $xml = '';
583         foreach ($val as $x)
584             $xml .= AsXML($x);
585         return $xml;
586     }
587     else
588         return XmlContent_quote((string)$val);
589 }
590
591 function AsString ($val) {
592     if (func_num_args() > 1) {
593         $str = '';
594         foreach (func_get_args() as $arg)
595             $str .= AsString($arg);
596         return $str;
597     }
598     elseif (is_object($val)) {
599         if (method_exists($val, 'asString'))
600             return $val->asString();
601         else
602             return sprintf("==Object(%s)==", get_class($val));
603     }
604     elseif (is_array($val)) {
605         // DEPRECATED:
606         // Use XmlContent objects instead of arrays for collections of XmlElements.
607         trigger_error("Passing arrays to AsString() is deprecated", E_USER_NOTICE);
608         $str = '';
609         foreach ($val as $x)
610             $str .= AsString($x);
611         return $str;
612     }
613     
614     return (string) $val;
615 }
616
617
618 function fmt ($fs /* , ... */) {
619     $s = new FormattedText(false);
620
621     $args = func_get_args();
622     $args[0] = _($args[0]);
623     $s->_init($args);
624     return $s;
625 }
626
627 // $Log: not supported by cvs2svn $
628 // Revision 1.41  2007/01/13 23:34:49  rurban
629 // More php-5.2.0 fixes __toString
630 //
631 // Revision 1.40  2007/01/13 23:28:56  rurban
632 // php-5.2.0 fix for string fallback: Provide the magic method __toString
633 //
634 // Revision 1.39  2006/08/15 13:36:23  rurban
635 // support iso-8859-2
636 //
637 // Revision 1.38  2005/10/10 19:36:09  rurban
638 // fix comment
639 //
640 // Revision 1.37  2005/01/25 07:04:27  rurban
641 // case-sensitive for php5
642 //
643 // Revision 1.36  2004/12/06 19:49:56  rurban
644 // enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
645 // renamed delete_page to purge_page.
646 // enable action=edit&version=-1 to force creation of a new version.
647 // added BABYCART_PATH config
648 // fixed magiqc in adodb.inc.php
649 // and some more docs
650 //
651 // Revision 1.35  2004/11/21 11:59:18  rurban
652 // remove final \n to be ob_cache independent
653 //
654 // Revision 1.34  2004/10/12 13:13:19  rurban
655 // php5 compatibility (5.0.1 ok)
656 //
657 // Revision 1.33  2004/07/02 09:55:58  rurban
658 // more stability fixes: new DISABLE_GETIMAGESIZE if your php crashes when loading LinkIcons: failing getimagesize in old phps; blockparser stabilized
659 //
660 // Revision 1.32  2004/06/20 15:30:05  rurban
661 // get_class case-sensitivity issues
662 //
663 // Revision 1.31  2004/06/20 14:42:54  rurban
664 // various php5 fixes (still broken at blockparser)
665 //
666
667 // (c-file-style: "gnu")
668 // Local Variables:
669 // mode: php
670 // tab-width: 8
671 // c-basic-offset: 4
672 // c-hanging-comment-ender-p: nil
673 // indent-tabs-mode: nil
674 // End:   
675 ?>