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