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