]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/XmlElement.php
New methods to help manipulate elements' CSS class membership.
[SourceForge/phpwiki.git] / lib / XmlElement.php
1 <?php rcs_id('$Id: XmlElement.php,v 1.20 2003-02-18 19:00:47 dairiki Exp $');
2 /*
3  * Code for writing XML.
4  */
5
6 /**
7  * A sequence of (zero or more) XmlElements (possibly interspersed with
8  * plain strings (CDATA).
9  */
10 class XmlContent
11 {
12     function XmlContent (/* ... */) {
13         $this->_content = array();
14         $this->_pushContent_array(func_get_args());
15     }
16
17     function pushContent ($arg /*, ...*/) {
18         if (func_num_args() > 1)
19             $this->_pushContent_array(func_get_args());
20         elseif (is_array($arg))
21             $this->_pushContent_array($arg);
22         else
23             $this->_pushContent($arg);
24     }
25
26     function _pushContent_array ($array) {
27         foreach ($array as $item) {
28             if (is_array($item))
29                 $this->_pushContent_array($item);
30             else
31                 $this->_pushContent($item);
32         }
33     }
34
35     function _pushContent ($item) {
36         if (get_class($item) == 'xmlcontent')
37             array_splice($this->_content, count($this->_content), 0,
38                          $item->_content);
39         else
40             $this->_content[] = $item;
41     }
42
43     function unshiftContent ($arg /*, ...*/) {
44         if (func_num_args() > 1)
45             $this->_unshiftContent_array(func_get_args());
46         elseif (is_array($arg))
47             $this->_unshiftContent_array($arg);
48         else
49             $this->_unshiftContent($arg);
50     }
51
52     function _unshiftContent_array ($array) {
53         foreach (array_reverse($array) as $item) {
54             if (is_array($item))
55                 $this->_unshiftContent_array($item);
56             else
57                 $this->_unshiftContent($item);
58         }
59     }
60
61     function _unshiftContent ($item) {
62         if (get_class($item) == 'xmlcontent')
63             array_splice($this->_content, 0, 0, $item->_content);
64         else
65             array_unshift($this->_content, $item);
66     }
67     
68     function getContent () {
69         return $this->_content;
70     }
71
72     function setContent ($arg /* , ... */) {
73         $this->_content = array();
74         $this->_pushContent_array(func_get_args());
75     }
76
77     function printXML () {
78         foreach ($this->_content as $item) {
79             if (is_object($item)) {
80                 if (method_exists($item, 'printxml'))
81                     $item->printXML();
82                 elseif (method_exists($item, 'asxml'))
83                     echo $item->asXML();
84                 elseif (method_exists($item, 'asstring'))
85                     echo $this->_quote($item->asString());
86                 else
87                     printf("==Object(%s)==", get_class($item));
88             }
89             else
90                 echo $this->_quote((string) $item);
91         }
92     }
93
94
95     function asXML () {
96         $xml = '';
97         foreach ($this->_content as $item) {
98             if (is_object($item)) {
99                 if (method_exists($item, 'asxml'))
100                     $xml .= $item->asXML();
101                 elseif (method_exists($item, 'asstring'))
102                     $xml .= $this->_quote($item->asString());
103                 else
104                     $xml .= sprintf("==Object(%s)==", get_class($item));
105             }
106             else
107                 $xml .= $this->_quote((string) $item);
108         }
109         return $xml;
110     }
111
112     function asString () {
113         $val = '';
114         foreach ($this->_content as $item) {
115             if (is_object($item)) {
116                 if (method_exists($item, 'asstring'))
117                     $val .= $item->asString();
118                 else
119                     $val .= sprintf("==Object(%s)==", get_class($item));
120             }
121             else
122                 $val .= (string) $item;
123         }
124         return trim($val);
125     }
126
127
128     /**
129      * See if element is empty.
130      *
131      * Empty means it has no content.
132      * @return bool True if empty.
133      */
134     function isEmpty () {
135         if (empty($this->_content))
136             return true;
137         foreach ($this->_content as $x) {
138             if (!empty($x))
139                 return false;
140         }
141         return true;
142     }
143     
144     function _quote ($string) {
145         return str_replace('<', '&lt;',
146                            str_replace('>', '&gt;',
147                                        str_replace('&', '&amp;', $string)));
148     }
149 };
150
151 /**
152  * An XML element.
153  *
154  * @param $tagname string Tag of html element.
155  */
156 class XmlElement extends XmlContent
157 {
158     function XmlElement ($tagname /* , $attr_or_content , ...*/) {
159         $this->XmlContent();
160         $this->_init(func_get_args());
161     }
162
163     function _init ($args) {
164         if (!is_array($args))
165             $args = func_get_args();
166
167         assert(count($args) >= 1);
168         //assert(is_string($args[0]));
169         $this->_tag = array_shift($args);
170         
171         if ($args && is_array($args[0]))
172             $this->_attr = array_shift($args);
173         else {
174             $this->_attr = array();
175             if ($args && $args[0] === false)
176                 array_shift($args);
177         }
178
179         $this->setContent($args);
180     }
181
182     function getTag () {
183         return $this->_tag;
184     }
185     
186     function setAttr ($attr, $value = false) {
187         if (is_array($attr)) {
188             assert($value === false);
189             foreach ($attr as $a => $v)
190                 $this->set($a, $v);
191             return;
192         }
193
194         assert(is_string($attr));
195             
196         if ($value === false) {
197             unset($this->_attr[$attr]);
198         }
199         else {
200             if (is_bool($value))
201                 $value = $attr;
202             $this->_attr[$attr] = (string) $value;
203         }
204
205         if ($attr == 'class')
206             unset($this->_classes);
207     }
208
209     function getAttr ($attr) {
210         if ($attr == 'class')
211             $this->_setClasses();
212
213         if (isset($this->_attr[$attr]))
214             return $this->_attr[$attr];
215         else
216             return false;
217     }
218
219     function _getClasses() {
220         if (!isset($this->_classes)) {
221             $this->_classes = array();
222             if (isset($this->_attr['class'])) {
223                 $classes = explode(' ', (string) $this->_attr['class']);
224                 foreach ($classes as $class) {
225                     $class = trim($class);
226                     if ($class)
227                         $this->_classes[$class] = $class;
228                 }
229             }
230         }
231         return $this->_classes;
232     }
233
234     function _setClasses() {
235         if (isset($this->_classes)) {
236             if ($this->_classes)
237                 $this->_attr['class'] = join(' ', $this->_classes);
238             else
239                 unset($this->_attr['class']);
240         }
241     }
242
243     /**
244      * Manipulate the elements CSS class membership.
245      *
246      * This adds or remove an elements membership
247      * in a give CSS class.
248      *
249      * @param $class string
250      *
251      * @param $in_class bool
252      *   If true (the default) the element is added to class $class.
253      *   If false, the element is removed from the class.
254      */
255     function setInClass($class, $in_class=true) {
256         $this->_getClasses();
257         $class = trim($class);
258         if ($in_class)
259             $this->_classes[$class] = $class;
260         else 
261             unset($this->_classes[$class]);
262     }
263
264     /**
265      * Is element in a given (CSS) class?
266      *
267      * This checks for the presence of a particular class in the
268      * elements 'class' attribute.
269      *
270      * @param $class string  The class to check for.
271      * @return bool True if the element is a member of $class.
272      */
273     function inClass($class) {
274         $this->_parseClasses();
275         return isset($this->_classes[trim($class)]);
276     }
277
278     function startTag() {
279         $start = "<" . $this->_tag;
280         $this->_setClasses();
281         foreach ($this->_attr as $attr => $val) {
282             if (is_bool($val)) {
283                 if (!$val)
284                     continue;
285                 $val = $attr;
286             }
287             $qval = str_replace("\"", '&quot;', $this->_quote($val));
288             $start .= " $attr=\"$qval\"";
289         }
290         $start .= ">";
291         return $start;
292     }
293
294     function emptyTag() {
295         return substr($this->startTag(), 0, -1) . "/>";
296     }
297
298     
299     function endTag() {
300         return "</$this->_tag>";
301     }
302     
303         
304     function printXML () {
305         if ($this->isEmpty())
306             echo $this->emptyTag();
307         else {
308             echo $this->startTag();
309             // FIXME: The next two lines could be removed for efficiency
310             if (!$this->hasInlineContent())
311                 echo "\n";
312             XmlContent::printXML();
313             echo "</$this->_tag>";
314         }
315         if (!$this->isInlineElement())
316             echo "\n";
317     }
318
319     function asXML () {
320         if ($this->isEmpty()) {
321             $xml = $this->emptyTag();
322         }
323         else {
324             $xml = $this->startTag();
325             // FIXME: The next two lines could be removed for efficiency
326             if (!$this->hasInlineContent())
327                 $xml .= "\n";
328             $xml .= XmlContent::asXML();
329             $xml .= "</$this->_tag>";
330         }
331         if (!$this->isInlineElement())
332             $xml .= "\n";
333         return $xml;
334     }
335
336     /**
337      * Can this element have inline content?
338      *
339      * This is a hack, but is probably the best one can do without
340      * knowledge of the DTD...
341      */
342     function hasInlineContent () {
343         // This is a hack.
344         if (empty($this->_content))
345             return true;
346         if (is_object($this->_content[0]))
347             return false;
348         return true;
349     }
350     
351     /**
352      * Is this element part of inline content?
353      *
354      * This is a hack, but is probably the best one can do without
355      * knowledge of the DTD...
356      */
357     function isInlineElement () {
358         return false;
359     }
360     
361 };
362
363 class RawXml {
364     function RawXml ($xml_text) {
365         $this->_xml = $xml_text;
366     }
367
368     function printXML () {
369         echo $this->_xml;
370     }
371
372     function asXML () {
373         return $this->_xml;
374     }
375
376     function isEmpty () {
377         return empty($this->_xml);
378     }
379 }
380
381 class FormattedText {
382     function FormattedText ($fs /* , ... */) {
383         if ($fs !== false) {
384             $this->_init(func_get_args());
385         }
386     }
387
388     function _init ($args) {
389         $this->_fs = array_shift($args);
390
391         // PHP's sprintf doesn't support variable width specifiers,
392         // like sprintf("%*s", 10, "x"); --- so we won't either.
393
394         if (! preg_match_all('/(?<!%)%(\d+)\$/x', $this->_fs, $m)) {
395             $this->_args  = $args;
396         }
397         else {
398             // Format string has '%2$s' style argument reordering.
399             // PHP doesn't support this.
400             if (preg_match('/(?<!%)%[- ]?\d*[^- \d$]/x', $this->_fs)) // $fmt
401                 // literal variable name substitution only to keep locale
402                 // strings uncluttered
403                 trigger_error(sprintf(_("Can't mix '%s' with '%s' type format strings"),
404                                       '%1\$s','%s'), E_USER_WARNING);
405         
406             $this->_fs = preg_replace('/(?<!%)%\d+\$/x', '%', $this->_fs);
407
408             $this->_args = array();
409             foreach($m[1] as $argnum) {
410                 if ($argnum < 1 || $argnum > count($args))
411                     trigger_error(sprintf("%s: argument index out of range", 
412                                           $argnum), E_USER_WARNING);
413                 $this->_args[] = $args[$argnum - 1];
414             }
415         }
416     }
417
418     function asXML () {
419         // Not all PHP's have vsprintf, so...
420         $args[] = XmlElement::_quote($this->_fs);
421         foreach ($this->_args as $arg)
422             $args[] = AsXML($arg);
423         return call_user_func_array('sprintf', $args);
424     }
425
426     function printXML () {
427         // Not all PHP's have vsprintf, so...
428         $args[] = XmlElement::_quote($this->_fs);
429         foreach ($this->_args as $arg)
430             $args[] = AsXML($arg);
431         call_user_func_array('printf', $args);
432     }
433
434     function asString() {
435         $args[] = $this->_fs;
436         foreach ($this->_args as $arg)
437             $args[] = AsString($arg);
438         return call_user_func_array('sprintf', $args);
439     }
440 }
441
442 function PrintXML ($val /* , ... */ ) {
443     if (func_num_args() > 1) {
444         foreach (func_get_args() as $arg)
445             PrintXML($arg);
446     }
447     elseif (is_object($val)) {
448         if (method_exists($val, 'printxml'))
449             $val->printXML();
450         elseif (method_exists($val, 'asxml')) {
451             echo $val->asXML();
452         }
453         elseif (method_exists($val, 'asstring'))
454             echo XmlContent::_quote($val->asString());
455         else
456             printf("==Object(%s)==", get_class($val));
457     }
458     elseif (is_array($val)) {
459         // DEPRECATED:
460         // Use XmlContent objects instead of arrays for collections of XmlElements.
461         trigger_error("Passing arrays to PrintXML() is deprecated: (" . AsXML($val, true) . ")",
462                       E_USER_NOTICE);
463         foreach ($val as $x)
464             PrintXML($x);
465     }
466     else
467         echo (string)XmlContent::_quote($val);
468 }
469
470 function AsXML ($val /* , ... */) {
471     static $nowarn;
472
473     if (func_num_args() > 1) {
474         $xml = '';
475         foreach (func_get_args() as $arg)
476             $xml .= AsXML($arg);
477         return $xml;
478     }
479     elseif (is_object($val)) {
480         if (method_exists($val, 'asxml'))
481             return $val->asXML();
482         elseif (method_exists($val, 'asstring'))
483             return XmlContent::_quote($val->asString());
484         else
485             return sprintf("==Object(%s)==", get_class($val));
486     }
487     elseif (is_array($val)) {
488         // DEPRECATED:
489         // Use XmlContent objects instead of arrays for collections of XmlElements.
490         if (empty($nowarn)) {
491             $nowarn = true;
492             trigger_error("Passing arrays to AsXML() is deprecated: (" . AsXML($val) . ")",
493                           E_USER_NOTICE);
494             unset($nowarn);
495         }
496         $xml = '';
497         foreach ($val as $x)
498             $xml .= AsXML($x);
499         return $xml;
500     }
501     else
502         return XmlContent::_quote((string)$val);
503 }
504
505 function AsString ($val) {
506     if (func_num_args() > 1) {
507         $str = '';
508         foreach (func_get_args() as $arg)
509             $str .= AsString($arg);
510         return $str;
511     }
512     elseif (is_object($val)) {
513         if (method_exists($val, 'asstring'))
514             return $val->asString();
515         else
516             return sprintf("==Object(%s)==", get_class($val));
517     }
518     elseif (is_array($val)) {
519         // DEPRECATED:
520         // Use XmlContent objects instead of arrays for collections of XmlElements.
521         trigger_error("Passing arrays to AsString() is deprecated", E_USER_NOTICE);
522         $str = '';
523         foreach ($val as $x)
524             $str .= AsString($x);
525         return $str;
526     }
527     
528     return (string) $val;
529 }
530
531
532 function fmt ($fs /* , ... */) {
533     $s = new FormattedText(false);
534
535     $args = func_get_args();
536     $args[0] = _($args[0]);
537     $s->_init($args);
538     return $s;
539 }
540     
541 // (c-file-style: "gnu")
542 // Local Variables:
543 // mode: php
544 // tab-width: 8
545 // c-basic-offset: 4
546 // c-hanging-comment-ender-p: nil
547 // indent-tabs-mode: nil
548 // End:   
549 ?>