]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/XmlElement.php
Activated Id substitution for Subversion
[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 (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  * An XML element.
203  *
204  * @param $tagname string Tag of html element.
205  */
206 class XmlElement extends XmlContent
207 {
208     function XmlElement ($tagname /* , $attr_or_content , ...*/) {
209         //FIXME: php5 incompatible
210         $this->XmlContent();
211         $this->_init(func_get_args());
212     }
213
214     function _init ($args) {
215         if (!is_array($args))
216             $args = func_get_args();
217
218         assert(count($args) >= 1);
219         //assert(is_string($args[0]));
220         $this->_tag = array_shift($args);
221         
222         if ($args && is_array($args[0]))
223             $this->_attr = array_shift($args);
224         else {
225             $this->_attr = array();
226             if ($args && $args[0] === false)
227                 array_shift($args);
228         }
229
230         $this->setContent($args);
231     }
232     
233     /** Methods only needed for XmlParser,
234      *  to be fully compatible to perl Html::Element
235      */
236     // doesn't yet work with php5 as __destruct()
237     function _destruct () {
238         if ($this->hasChildren()) {
239             foreach ($this->getChildren() as $node) {
240                 $node->_destruct();
241             }
242         }
243         unset($this->_tag);
244         unset($this->_attr);
245         unset($this->_content);
246     }
247     
248     function getChildren () {
249         return $this->_children;
250     }
251
252     function hasChildren () {
253         return !empty($this->_children);
254     }
255     /* End XmlParser Methods
256      */
257
258     function getTag () {
259         return $this->_tag;
260     }
261     
262     function setAttr ($attr, $value = false) {
263         if (is_array($attr)) {
264             assert($value === false);
265             foreach ($attr as $a => $v) {
266                 $this->_attr[strtolower($a)] = $v;
267                 //$this->set($a, $v);
268             }
269             return;
270         }
271
272         assert(is_string($attr));
273             
274         if ($value === false) {
275             unset($this->_attr[$attr]);
276         }
277         else {
278             if (is_bool($value))
279                 $value = $attr;
280             $this->_attr[$attr] = (string) $value;
281         }
282
283         if ($attr == 'class')
284             unset($this->_classes);
285     }
286
287     function getAttr ($attr) {
288         if ($attr == 'class')
289             $this->_setClasses();
290
291         if (isset($this->_attr[strtolower($attr)]))
292             return $this->_attr[strtolower($attr)];
293         else
294             return false;
295     }
296
297     function _getClasses() {
298         if (!isset($this->_classes)) {
299             $this->_classes = array();
300             if (isset($this->_attr['class'])) {
301                 $classes = explode(' ', (string) $this->_attr['class']);
302                 foreach ($classes as $class) {
303                     $class = trim($class);
304                     if ($class)
305                         $this->_classes[$class] = $class;
306                 }
307             }
308         }
309         return $this->_classes;
310     }
311
312     function _setClasses() {
313         if (isset($this->_classes)) {
314             if ($this->_classes)
315                 $this->_attr['class'] = join(' ', $this->_classes);
316             else
317                 unset($this->_attr['class']);
318         }
319     }
320
321     /**
322      * Manipulate the elements CSS class membership.
323      *
324      * This adds or remove an elements membership
325      * in a give CSS class.
326      *
327      * @param $class string
328      *
329      * @param $in_class bool
330      *   If true (the default) the element is added to class $class.
331      *   If false, the element is removed from the class.
332      */
333     function setInClass($class, $in_class=true) {
334         $this->_getClasses();
335         $class = trim($class);
336         if ($in_class)
337             $this->_classes[$class] = $class;
338         else 
339             unset($this->_classes[$class]);
340     }
341
342     /**
343      * Is element in a given (CSS) class?
344      *
345      * This checks for the presence of a particular class in the
346      * elements 'class' attribute.
347      *
348      * @param $class string  The class to check for.
349      * @return bool True if the element is a member of $class.
350      */
351     function inClass($class) {
352         $this->_parseClasses();
353         return isset($this->_classes[trim($class)]);
354     }
355
356     function startTag() {
357         $start = "<" . $this->_tag;
358         $this->_setClasses();
359         foreach ($this->_attr as $attr => $val) {
360             if (is_bool($val)) {
361                 if (!$val)
362                     continue;
363                 $val = $attr;
364             }
365             $qval = str_replace("\"", '&quot;', $this->_quote((string)$val));
366             $start .= " $attr=\"$qval\"";
367         }
368         $start .= ">";
369         return $start;
370     }
371
372     function emptyTag() {
373         return substr($this->startTag(), 0, -1) . "/>";
374     }
375
376     
377     function endTag() {
378         return "</$this->_tag>";
379     }
380     
381         
382     function printXML () {
383         if ($this->isEmpty())
384             echo $this->emptyTag();
385         else {
386             echo $this->startTag();
387             // FIXME: The next two lines could be removed for efficiency
388             if (!$this->hasInlineContent())
389                 echo "\n";
390             XmlContent::printXML();
391             echo "</$this->_tag>";
392         }
393         if (!$this->isInlineElement())
394             echo "\n";
395     }
396
397     function asXML () {
398         if ($this->isEmpty()) {
399             $xml = $this->emptyTag();
400         }
401         else {
402             $xml = $this->startTag();
403             // FIXME: The next two lines could be removed for efficiency
404             if (!$this->hasInlineContent())
405                 $xml .= "\n";
406             $xml .= XmlContent::asXML();
407             $xml .= "</$this->_tag>";
408         }
409         if (!$this->isInlineElement())
410             $xml .= "\n";
411         return $xml;
412     }
413
414     /**
415      * Can this element have inline content?
416      *
417      * This is a hack, but is probably the best one can do without
418      * knowledge of the DTD...
419      */
420     function hasInlineContent () {
421         // This is a hack.
422         if (empty($this->_content))
423             return true;
424         if (is_object($this->_content[0]))
425             return false;
426         return true;
427     }
428     
429     /**
430      * Is this element part of inline content?
431      *
432      * This is a hack, but is probably the best one can do without
433      * knowledge of the DTD...
434      */
435     function isInlineElement () {
436         return false;
437     }
438     
439 };
440
441 class RawXml {
442     function RawXml ($xml_text) {
443         $this->_xml = $xml_text;
444     }
445
446     function printXML () {
447         echo $this->_xml;
448     }
449
450     /* php-5.2 magic */
451     function __toString () {
452         return $this->_xml;
453     }
454
455     function asXML () {
456         return $this->_xml;
457     }
458
459     function isEmpty () {
460         return empty($this->_xml);
461     }
462 }
463
464 class FormattedText {
465     function FormattedText ($fs /* , ... */) {
466         if ($fs !== false) {
467             $this->_init(func_get_args());
468         }
469     }
470
471     function _init ($args) {
472         $this->_fs = array_shift($args);
473
474         // PHP's sprintf doesn't support variable width specifiers,
475         // like sprintf("%*s", 10, "x"); --- so we won't either.
476         $m = array();
477         if (! preg_match_all('/(?<!%)%(\d+)\$/x', $this->_fs, $m)) {
478             $this->_args  = $args;
479         }
480         else {
481             // Format string has '%2$s' style argument reordering.
482             // PHP doesn't support this.
483             if (preg_match('/(?<!%)%[- ]?\d*[^- \d$]/x', $this->_fs)) // $fmt
484                 // literal variable name substitution only to keep locale
485                 // strings uncluttered
486                 trigger_error(sprintf(_("Can't mix '%s' with '%s' type format strings"),
487                                       '%1\$s','%s'), E_USER_WARNING);
488         
489             $this->_fs = preg_replace('/(?<!%)%\d+\$/x', '%', $this->_fs);
490
491             $this->_args = array();
492             foreach($m[1] as $argnum) {
493                 if ($argnum < 1 || $argnum > count($args))
494                     trigger_error(sprintf("%s: argument index out of range", 
495                                           $argnum), E_USER_WARNING);
496                 $this->_args[] = $args[$argnum - 1];
497             }
498         }
499     }
500
501     function asXML () {
502         // Not all PHP's have vsprintf, so...
503         $args[] = XmlElement::_quote((string)$this->_fs);
504         foreach ($this->_args as $arg)
505             $args[] = AsXML($arg);
506         return call_user_func_array('sprintf', $args);
507     }
508
509     function printXML () {
510         // Not all PHP's have vsprintf, so...
511         $args[] = XmlElement::_quote((string)$this->_fs);
512         foreach ($this->_args as $arg)
513             $args[] = AsXML($arg);
514         call_user_func_array('printf', $args);
515     }
516
517     function asString() {
518         $args[] = $this->_fs;
519         foreach ($this->_args as $arg)
520             $args[] = AsString($arg);
521         return call_user_func_array('sprintf', $args);
522     }
523
524     /* php-5.2 magic */
525     function __toString () {
526         return $this->asString();
527     }
528 }
529
530 /**
531  * PHP5 compatibility
532  * Error[2048]: Non-static method XmlContent::_quote() should not be called statically
533  * Note: There's lot of room for performance increase if the right charset variant can 
534  * be created on load-time.
535  */
536 function XmlContent_quote ($string) {
537     if (!$string) return $string;
538     if (check_php_version(4,1) and isset($GLOBALS['charset'])
539         and (!defined('IGNORE_CHARSET_NOT_SUPPORTED_WARNING') or !IGNORE_CHARSET_NOT_SUPPORTED_WARNING))
540     {
541         return htmlspecialchars($string, ENT_COMPAT, $GLOBALS['charset']);
542     } else {
543         return htmlspecialchars($string);
544     }
545 }
546
547 function PrintXML ($val /* , ... */ ) {
548     if (func_num_args() > 1) {
549         foreach (func_get_args() as $arg)
550             PrintXML($arg);
551     }
552     elseif (is_object($val)) {
553         if (method_exists($val, 'printXML'))
554             $val->printXML();
555         elseif (method_exists($val, 'asXML')) {
556             echo $val->asXML();
557         }
558         elseif (method_exists($val, 'asString'))
559             echo XmlContent_quote($val->asString());
560         else
561             printf("==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         trigger_error("Passing arrays to PrintXML() is deprecated: (" . AsXML($val, true) . ")",
567                       E_USER_NOTICE);
568         foreach ($val as $x)
569             PrintXML($x);
570     }
571     else
572         echo (string)XmlContent_quote((string)$val);
573 }
574
575 function AsXML ($val /* , ... */) {
576     static $nowarn;
577
578     if (func_num_args() > 1) {
579         $xml = '';
580         foreach (func_get_args() as $arg)
581             $xml .= AsXML($arg);
582         return $xml;
583     }
584     elseif (is_object($val)) {
585         if (method_exists($val, 'asXML'))
586             return $val->asXML();
587         elseif (method_exists($val, 'asString'))
588             return XmlContent_quote($val->asString());
589         else
590             return sprintf("==Object(%s)==", get_class($val));
591     }
592     elseif (is_array($val)) {
593         // DEPRECATED:
594         // Use XmlContent objects instead of arrays for collections of XmlElements.
595         if (empty($nowarn)) {
596             $nowarn = true;
597             trigger_error("Passing arrays to AsXML() is deprecated: (" . AsXML($val) . ")",
598                           E_USER_NOTICE);
599             unset($nowarn);
600         }
601         $xml = '';
602         foreach ($val as $x)
603             $xml .= AsXML($x);
604         return $xml;
605     }
606     else
607         return XmlContent_quote((string)$val);
608 }
609
610 function AsString ($val) {
611     if (func_num_args() > 1) {
612         $str = '';
613         foreach (func_get_args() as $arg)
614             $str .= AsString($arg);
615         return $str;
616     }
617     elseif (is_object($val)) {
618         if (method_exists($val, 'asString'))
619             return $val->asString();
620         else
621             return sprintf("==Object(%s)==", get_class($val));
622     }
623     elseif (is_array($val)) {
624         // DEPRECATED:
625         // Use XmlContent objects instead of arrays for collections of XmlElements.
626         trigger_error("Passing arrays to AsString() is deprecated", E_USER_NOTICE);
627         $str = '';
628         foreach ($val as $x)
629             $str .= AsString($x);
630         return $str;
631     }
632     
633     return (string) $val;
634 }
635
636
637 function fmt ($fs /* , ... */) {
638     $s = new FormattedText(false);
639
640     $args = func_get_args();
641     $args[0] = _($args[0]);
642     $s->_init($args);
643     return $s;
644 }
645
646 // $Log: not supported by cvs2svn $
647 // Revision 1.44  2008/03/17 19:40:18  rurban
648 // print printXML(array) deprecation warnings
649 //
650 // Revision 1.43  2007/07/14 17:55:30  rurban
651 // SemanticWeb.php
652 //
653 // Revision 1.42  2007/01/20 11:41:30  rurban
654 // Add php-5.2.0 note
655 //
656 // Revision 1.41  2007/01/13 23:34:49  rurban
657 // More php-5.2.0 fixes __toString
658 //
659 // Revision 1.40  2007/01/13 23:28:56  rurban
660 // php-5.2.0 fix for string fallback: Provide the magic method __toString
661 //
662 // Revision 1.39  2006/08/15 13:36:23  rurban
663 // support iso-8859-2
664 //
665 // Revision 1.38  2005/10/10 19:36:09  rurban
666 // fix comment
667 //
668 // Revision 1.37  2005/01/25 07:04:27  rurban
669 // case-sensitive for php5
670 //
671 // Revision 1.36  2004/12/06 19:49:56  rurban
672 // enable action=remove which is undoable and seeable in RecentChanges: ADODB ony for now.
673 // renamed delete_page to purge_page.
674 // enable action=edit&version=-1 to force creation of a new version.
675 // added BABYCART_PATH config
676 // fixed magiqc in adodb.inc.php
677 // and some more docs
678 //
679 // Revision 1.35  2004/11/21 11:59:18  rurban
680 // remove final \n to be ob_cache independent
681 //
682 // Revision 1.34  2004/10/12 13:13:19  rurban
683 // php5 compatibility (5.0.1 ok)
684 //
685 // Revision 1.33  2004/07/02 09:55:58  rurban
686 // more stability fixes: new DISABLE_GETIMAGESIZE if your php crashes when loading LinkIcons: failing getimagesize in old phps; blockparser stabilized
687 //
688 // Revision 1.32  2004/06/20 15:30:05  rurban
689 // get_class case-sensitivity issues
690 //
691 // Revision 1.31  2004/06/20 14:42:54  rurban
692 // various php5 fixes (still broken at blockparser)
693 //
694
695 // (c-file-style: "gnu")
696 // Local Variables:
697 // mode: php
698 // tab-width: 8
699 // c-basic-offset: 4
700 // c-hanging-comment-ender-p: nil
701 // indent-tabs-mode: nil
702 // End:   
703 ?>