]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/XmlElement.php
rcs_id no longer makes sense with Subversion global version number
[SourceForge/phpwiki.git] / lib / XmlElement.php
1 <?php // rcs_id('$Id$');
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 (is_object($item) && 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             elseif (is_array($item)) {
97                 // DEPRECATED:
98                 // Use XmlContent objects instead of arrays for collections of XmlElements.
99                 trigger_error("Passing arrays to printXML() is deprecated: (" . AsXML($item, true) . ")",
100                       E_USER_NOTICE);
101                 foreach ($item as $x)
102                     $this->printXML($x);
103             } else {
104                 echo $this->_quote((string) $item);
105             }
106         }
107     }
108
109     function asXML () {
110         $xml = '';
111         foreach ($this->_content as $item) {
112             if (is_object($item)) {
113                 if (method_exists($item, 'asXML'))
114                     $xml .= $item->asXML();
115                 elseif (method_exists($item, 'asString'))
116                     $xml .= $this->_quote($item->asString());
117                 else
118                     $xml .= sprintf("==Object(%s)==", get_class($item));
119             }
120             elseif (is_array($item)) {
121                 trigger_error("Passing arrays to ->asXML() is deprecated: (" . AsXML($item, true) . ")",
122                       E_USER_NOTICE);
123                 foreach ($item as $x)
124                     $xml .= $this->asXML($x);
125             }
126             else
127                 $xml .= $this->_quote((string) $item);
128         }
129         return $xml;
130     }
131
132     function asPDF () {
133         $pdf = '';
134         foreach ($this->_content as $item) {
135             if (is_object($item)) {
136                 if (method_exists($item, 'asPDF'))
137                     $pdf .= $item->asPDF();
138                 elseif (method_exists($item, 'asString'))
139                     $pdf .= $this->_quote($item->asString());
140                 else
141                     $pdf .= sprintf("==Object(%s)==", get_class($item));
142             }
143             else
144                 $pdf .= $this->_quote((string) $item);
145         }
146         return $pdf;
147     }
148
149     /* php-5.2 magic */
150     function __toString () {
151         return $this->asString();
152     }
153
154     function asString () {
155         $val = '';
156         foreach ($this->_content as $item) {
157             if (is_object($item)) {
158                 if (method_exists($item, 'asString')) {
159                     $string = $item->asString();
160                     if (is_object($string)) {
161                         ; // ignore error so far: ImageLink labels
162                     } else {
163                         $val .= $this->_quote($item->asString());
164                     }
165                 } else {
166                     $val .= sprintf("==Object(%s)==", get_class($item));
167                 }
168             }
169             else
170                 $val .= (string) $item;
171         }
172         return trim($val);
173     }
174
175
176     /**
177      * See if element is empty.
178      *
179      * Empty means it has no content.
180      * @return bool True if empty.
181      */
182     function isEmpty () {
183         if (empty($this->_content))
184             return true;
185         foreach ($this->_content as $x) {
186             if (is_string($x) ? strlen($x) : !empty($x))
187                 return false;
188         }
189         return true;
190     }
191     
192     function _quote ($string) {
193         if (!$string) return $string;
194         if (check_php_version(4,1) and isset($GLOBALS['charset'])) {
195             return htmlspecialchars($string, ENT_COMPAT, $GLOBALS['charset']);
196         } else {
197             return htmlspecialchars($string);
198         }
199     }
200 };
201
202 /**
203  * An XML element.
204  *
205  * @param $tagname string Tag of html element.
206  */
207 class XmlElement extends XmlContent
208 {
209     function XmlElement ($tagname /* , $attr_or_content , ...*/) {
210         //FIXME: php5 incompatible
211         $this->XmlContent();
212         $this->_init(func_get_args());
213     }
214
215     function _init ($args) {
216         if (!is_array($args))
217             $args = func_get_args();
218
219         assert(count($args) >= 1);
220         //assert(is_string($args[0]));
221         $this->_tag = array_shift($args);
222         
223         if ($args && is_array($args[0]))
224             $this->_attr = array_shift($args);
225         else {
226             $this->_attr = array();
227             if ($args && $args[0] === false)
228                 array_shift($args);
229         }
230
231         $this->setContent($args);
232     }
233     
234     /** Methods only needed for XmlParser,
235      *  to be fully compatible to perl Html::Element
236      */
237     // doesn't yet work with php5 as __destruct()
238     function _destruct () {
239         if ($this->hasChildren()) {
240             foreach ($this->getChildren() as $node) {
241                 $node->_destruct();
242             }
243         }
244         unset($this->_tag);
245         unset($this->_attr);
246         unset($this->_content);
247     }
248     
249     function getChildren () {
250         return $this->_children;
251     }
252
253     function hasChildren () {
254         return !empty($this->_children);
255     }
256     /* End XmlParser Methods
257      */
258
259     function getTag () {
260         return $this->_tag;
261     }
262     
263     function setAttr ($attr, $value = false) {
264         if (is_array($attr)) {
265             assert($value === false);
266             foreach ($attr as $a => $v) {
267                 $this->_attr[strtolower($a)] = $v;
268                 //$this->set($a, $v);
269             }
270             return;
271         }
272
273         assert(is_string($attr));
274             
275         if ($value === false) {
276             unset($this->_attr[$attr]);
277         }
278         else {
279             if (is_bool($value))
280                 $value = $attr;
281             $this->_attr[$attr] = (string) $value;
282         }
283
284         if ($attr == 'class')
285             unset($this->_classes);
286     }
287
288     function getAttr ($attr) {
289         if ($attr == 'class')
290             $this->_setClasses();
291
292         if (isset($this->_attr[strtolower($attr)]))
293             return $this->_attr[strtolower($attr)];
294         else
295             return false;
296     }
297
298     function _getClasses() {
299         if (!isset($this->_classes)) {
300             $this->_classes = array();
301             if (isset($this->_attr['class'])) {
302                 $classes = explode(' ', (string) $this->_attr['class']);
303                 foreach ($classes as $class) {
304                     $class = trim($class);
305                     if ($class)
306                         $this->_classes[$class] = $class;
307                 }
308             }
309         }
310         return $this->_classes;
311     }
312
313     function _setClasses() {
314         if (isset($this->_classes)) {
315             if ($this->_classes)
316                 $this->_attr['class'] = join(' ', $this->_classes);
317             else
318                 unset($this->_attr['class']);
319         }
320     }
321
322     /**
323      * Manipulate the elements CSS class membership.
324      *
325      * This adds or remove an elements membership
326      * in a give CSS class.
327      *
328      * @param $class string
329      *
330      * @param $in_class bool
331      *   If true (the default) the element is added to class $class.
332      *   If false, the element is removed from the class.
333      */
334     function setInClass($class, $in_class=true) {
335         $this->_getClasses();
336         $class = trim($class);
337         if ($in_class)
338             $this->_classes[$class] = $class;
339         else 
340             unset($this->_classes[$class]);
341     }
342
343     /**
344      * Is element in a given (CSS) class?
345      *
346      * This checks for the presence of a particular class in the
347      * elements 'class' attribute.
348      *
349      * @param $class string  The class to check for.
350      * @return bool True if the element is a member of $class.
351      */
352     function inClass($class) {
353         $this->_parseClasses();
354         return isset($this->_classes[trim($class)]);
355     }
356
357     function startTag() {
358         $start = "<" . $this->_tag;
359         $this->_setClasses();
360         foreach ($this->_attr as $attr => $val) {
361             if (is_bool($val)) {
362                 if (!$val)
363                     continue;
364                 $val = $attr;
365             }
366             $qval = str_replace("\"", '&quot;', $this->_quote((string)$val));
367             $start .= " $attr=\"$qval\"";
368         }
369         $start .= ">";
370         return $start;
371     }
372
373     function emptyTag() {
374         return substr($this->startTag(), 0, -1) . "/>";
375     }
376
377     
378     function endTag() {
379         return "</$this->_tag>";
380     }
381     
382         
383     function printXML () {
384         if ($this->isEmpty())
385             echo $this->emptyTag();
386         else {
387             echo $this->startTag();
388             // FIXME: The next two lines could be removed for efficiency
389             if (!$this->hasInlineContent())
390                 echo "\n";
391             XmlContent::printXML();
392             echo "</$this->_tag>";
393         }
394         if (!$this->isInlineElement())
395             echo "\n";
396     }
397
398     function asXML () {
399         if ($this->isEmpty()) {
400             $xml = $this->emptyTag();
401         }
402         else {
403             $xml = $this->startTag();
404             // FIXME: The next two lines could be removed for efficiency
405             if (!$this->hasInlineContent())
406                 $xml .= "\n";
407             $xml .= XmlContent::asXML();
408             $xml .= "</$this->_tag>";
409         }
410         if (!$this->isInlineElement())
411             $xml .= "\n";
412         return $xml;
413     }
414
415     /**
416      * Can this element have inline content?
417      *
418      * This is a hack, but is probably the best one can do without
419      * knowledge of the DTD...
420      */
421     function hasInlineContent () {
422         // This is a hack.
423         if (empty($this->_content))
424             return true;
425         if (is_object($this->_content[0]))
426             return false;
427         return true;
428     }
429     
430     /**
431      * Is this element part of inline content?
432      *
433      * This is a hack, but is probably the best one can do without
434      * knowledge of the DTD...
435      */
436     function isInlineElement () {
437         return false;
438     }
439     
440 };
441
442 class RawXml {
443     function RawXml ($xml_text) {
444         $this->_xml = $xml_text;
445     }
446
447     function printXML () {
448         echo $this->_xml;
449     }
450
451     /* php-5.2 magic */
452     function __toString () {
453         return $this->_xml;
454     }
455
456     function asXML () {
457         return $this->_xml;
458     }
459     
460     function asString () {
461         return $this->_xml;
462     }
463
464     function isEmpty () {
465         return empty($this->_xml);
466     }
467 }
468
469 class FormattedText {
470     function FormattedText ($fs /* , ... */) {
471         if ($fs !== false) {
472             $this->_init(func_get_args());
473         }
474     }
475
476     function _init ($args) {
477         $this->_fs = array_shift($args);
478
479         // PHP's sprintf doesn't support variable width specifiers,
480         // like sprintf("%*s", 10, "x"); --- so we won't either.
481         $m = array();
482         if (! preg_match_all('/(?<!%)%(\d+)\$/x', $this->_fs, $m)) {
483             $this->_args  = $args;
484         }
485         else {
486             // Format string has '%2$s' style argument reordering.
487             // PHP doesn't support this.
488             if (preg_match('/(?<!%)%[- ]?\d*[^- \d$]/x', $this->_fs)) // $fmt
489                 // literal variable name substitution only to keep locale
490                 // strings uncluttered
491                 trigger_error(sprintf(_("Can't mix '%s' with '%s' type format strings"),
492                                       '%1\$s','%s'), E_USER_WARNING);
493         
494             $this->_fs = preg_replace('/(?<!%)%\d+\$/x', '%', $this->_fs);
495
496             $this->_args = array();
497             foreach($m[1] as $argnum) {
498                 if ($argnum < 1 || $argnum > count($args))
499                     trigger_error(sprintf("%s: argument index out of range", 
500                                           $argnum), E_USER_WARNING);
501                 $this->_args[] = $args[$argnum - 1];
502             }
503         }
504     }
505
506     function asXML () {
507         // Not all PHP's have vsprintf, so...
508         $args[] = XmlElement::_quote((string)$this->_fs);
509         foreach ($this->_args as $arg)
510             $args[] = AsXML($arg);
511         return call_user_func_array('sprintf', $args);
512     }
513
514     function printXML () {
515         // Not all PHP's have vsprintf, so...
516         $args[] = XmlElement::_quote((string)$this->_fs);
517         foreach ($this->_args as $arg)
518             $args[] = AsXML($arg);
519         call_user_func_array('printf', $args);
520     }
521
522     function asString() {
523         $args[] = $this->_fs;
524         foreach ($this->_args as $arg)
525             $args[] = AsString($arg);
526         return call_user_func_array('sprintf', $args);
527     }
528
529     /* php-5.2 magic */
530     function __toString () {
531         return $this->asString();
532     }
533 }
534
535 /**
536  * PHP5 compatibility
537  * Error[2048]: Non-static method XmlContent::_quote() should not be called statically
538  * Note: There's lot of room for performance increase if the right charset variant can 
539  * be created on load-time.
540  */
541 function XmlContent_quote ($string) {
542     if (!$string) return $string;
543     if (check_php_version(4,1) and isset($GLOBALS['charset'])
544         and (!defined('IGNORE_CHARSET_NOT_SUPPORTED_WARNING') or !IGNORE_CHARSET_NOT_SUPPORTED_WARNING))
545     {
546         return htmlspecialchars($string, ENT_COMPAT, $GLOBALS['charset']);
547     } else {
548         return htmlspecialchars($string);
549     }
550 }
551
552 function PrintXML ($val /* , ... */ ) {
553     if (func_num_args() > 1) {
554         foreach (func_get_args() as $arg)
555             PrintXML($arg);
556     }
557     elseif (is_object($val)) {
558         if (method_exists($val, 'printXML'))
559             $val->printXML();
560         elseif (method_exists($val, 'asXML')) {
561             echo $val->asXML();
562         }
563         elseif (method_exists($val, 'asString'))
564             echo XmlContent_quote($val->asString());
565         else
566             printf("==Object(%s)==", get_class($val));
567     }
568     elseif (is_array($val)) {
569         // DEPRECATED:
570         // Use XmlContent objects instead of arrays for collections of XmlElements.
571         trigger_error("Passing arrays to PrintXML() is deprecated: (" . AsXML($val, true) . ")",
572                       E_USER_NOTICE);
573         foreach ($val as $x)
574             PrintXML($x);
575     }
576     else
577         echo (string)XmlContent_quote((string)$val);
578 }
579
580 function AsXML ($val /* , ... */) {
581     static $nowarn;
582
583     if (func_num_args() > 1) {
584         $xml = '';
585         foreach (func_get_args() as $arg)
586             $xml .= AsXML($arg);
587         return $xml;
588     }
589     elseif (is_object($val)) {
590         if (method_exists($val, 'asXML'))
591             return $val->asXML();
592         elseif (method_exists($val, 'asString'))
593             return XmlContent_quote($val->asString());
594         else
595             return sprintf("==Object(%s)==", get_class($val));
596     }
597     elseif (is_array($val)) {
598         // DEPRECATED:
599         // Use XmlContent objects instead of arrays for collections of XmlElements.
600         if (empty($nowarn)) {
601             $nowarn = true;
602             trigger_error("Passing arrays to AsXML() is deprecated: (" . AsXML($val) . ")",
603                           E_USER_NOTICE);
604             unset($nowarn);
605         }
606         $xml = '';
607         foreach ($val as $x)
608             $xml .= AsXML($x);
609         return $xml;
610     }
611     else
612         return XmlContent_quote((string)$val);
613 }
614
615 function AsString ($val) {
616     if (func_num_args() > 1) {
617         $str = '';
618         foreach (func_get_args() as $arg)
619             $str .= AsString($arg);
620         return $str;
621     }
622     elseif (is_object($val)) {
623         if (method_exists($val, 'asString'))
624             return $val->asString();
625         else
626             return sprintf("==Object(%s)==", get_class($val));
627     }
628     elseif (is_array($val)) {
629         // DEPRECATED:
630         // Use XmlContent objects instead of arrays for collections of XmlElements.
631         trigger_error("Passing arrays to AsString() is deprecated", E_USER_NOTICE);
632         $str = '';
633         foreach ($val as $x)
634             $str .= AsString($x);
635         return $str;
636     }
637     
638     return (string) $val;
639 }
640
641 function fmt ($fs /* , ... */) {
642     $s = new FormattedText(false);
643
644     $args = func_get_args();
645     $args[0] = _($args[0]);
646     $s->_init($args);
647     return $s;
648 }
649
650 // (c-file-style: "gnu")
651 // Local Variables:
652 // mode: php
653 // tab-width: 8
654 // c-basic-offset: 4
655 // c-hanging-comment-ender-p: nil
656 // indent-tabs-mode: nil
657 // End:   
658 ?>