upload
[pear] / XML / Util.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * XML_Util
7  *
8  * XML Utilities package
9  * 
10  * PHP versions 4 and 5
11  *
12  * LICENSE:
13  *
14  * Copyright (c) 2003-2008 Stephan Schmidt <schst@php.net>
15  * All rights reserved.
16  *
17  * Redistribution and use in source and binary forms, with or without
18  * modification, are permitted provided that the following conditions
19  * are met:
20  *
21  *    * Redistributions of source code must retain the above copyright
22  *      notice, this list of conditions and the following disclaimer.
23  *    * Redistributions in binary form must reproduce the above copyright
24  *      notice, this list of conditions and the following disclaimer in the
25  *      documentation and/or other materials provided with the distribution.
26  *    * The name of the author may not be used to endorse or promote products
27  *      derived from this software without specific prior written permission.
28  *
29  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
30  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
31  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
32  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
33  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
34  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
35  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
36  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
37  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
38  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
39  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40  *
41  * @category  XML
42  * @package   XML_Util
43  * @author    Stephan Schmidt <schst@php.net>
44  * @copyright 2003-2008 Stephan Schmidt <schst@php.net>
45  * @license   http://opensource.org/licenses/bsd-license New BSD License
46  * @version   CVS: $Id: Util.php,v 1.38 2008/11/13 00:03:38 ashnazg Exp $
47  * @link      http://pear.php.net/package/XML_Util
48  */
49
50 /**
51  * error code for invalid chars in XML name
52  */
53 define('XML_UTIL_ERROR_INVALID_CHARS', 51);
54
55 /**
56  * error code for invalid chars in XML name
57  */
58 define('XML_UTIL_ERROR_INVALID_START', 52);
59
60 /**
61  * error code for non-scalar tag content
62  */
63 define('XML_UTIL_ERROR_NON_SCALAR_CONTENT', 60);
64
65 /**
66  * error code for missing tag name
67  */
68 define('XML_UTIL_ERROR_NO_TAG_NAME', 61);
69
70 /**
71  * replace XML entities
72  */
73 define('XML_UTIL_REPLACE_ENTITIES', 1);
74
75 /**
76  * embedd content in a CData Section
77  */
78 define('XML_UTIL_CDATA_SECTION', 5);
79
80 /**
81  * do not replace entitites
82  */
83 define('XML_UTIL_ENTITIES_NONE', 0);
84
85 /**
86  * replace all XML entitites
87  * This setting will replace <, >, ", ' and &
88  */
89 define('XML_UTIL_ENTITIES_XML', 1);
90
91 /**
92  * replace only required XML entitites
93  * This setting will replace <, " and &
94  */
95 define('XML_UTIL_ENTITIES_XML_REQUIRED', 2);
96
97 /**
98  * replace HTML entitites
99  * @link http://www.php.net/htmlentities
100  */
101 define('XML_UTIL_ENTITIES_HTML', 3);
102
103 /**
104  * Collapse all empty tags.
105  */
106 define('XML_UTIL_COLLAPSE_ALL', 1);
107
108 /**
109  * Collapse only empty XHTML tags that have no end tag.
110  */
111 define('XML_UTIL_COLLAPSE_XHTML_ONLY', 2);
112
113 /**
114  * utility class for working with XML documents
115  *
116
117  * @category  XML
118  * @package   XML_Util
119  * @author    Stephan Schmidt <schst@php.net>
120  * @copyright 2003-2008 Stephan Schmidt <schst@php.net>
121  * @license   http://opensource.org/licenses/bsd-license New BSD License
122  * @version   Release: 1.2.1
123  * @link      http://pear.php.net/package/XML_Util
124  */
125 class XML_Util
126 {
127     /**
128      * return API version
129      *
130      * @return string $version API version
131      * @access public
132      * @static
133      */
134     function apiVersion()
135     {
136         return '1.1';
137     }
138
139     /**
140      * replace XML entities
141      *
142      * With the optional second parameter, you may select, which
143      * entities should be replaced.
144      *
145      * <code>
146      * require_once 'XML/Util.php';
147      *
148      * // replace XML entites:
149      * $string = XML_Util::replaceEntities('This string contains < & >.');
150      * </code>
151      *
152      * With the optional third parameter, you may pass the character encoding
153      * <code>
154      * require_once 'XML/Util.php';
155      *
156      * // replace XML entites in UTF-8:
157      * $string = XML_Util::replaceEntities(
158      *     'This string contains < & > as well as ä, ö, ß, à and ê',
159      *     XML_UTIL_ENTITIES_HTML,
160      *     'UTF-8'
161      * );
162      * </code>
163      *
164      * @param string $string          string where XML special chars 
165      *                                should be replaced
166      * @param int    $replaceEntities setting for entities in attribute values 
167      *                                (one of XML_UTIL_ENTITIES_XML, 
168      *                                XML_UTIL_ENTITIES_XML_REQUIRED, 
169      *                                XML_UTIL_ENTITIES_HTML)
170      * @param string $encoding        encoding value (if any)...
171      *                                must be a valid encoding as determined
172      *                                by the htmlentities() function
173      *
174      * @return string string with replaced chars
175      * @access public
176      * @static
177      * @see reverseEntities()
178      */
179     function replaceEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML,
180         $encoding = 'ISO-8859-1')
181     {
182         switch ($replaceEntities) {
183         case XML_UTIL_ENTITIES_XML:
184             return strtr($string, array(
185                 '&'  => '&amp;',
186                 '>'  => '&gt;',
187                 '<'  => '&lt;',
188                 '"'  => '&quot;',
189                 '\'' => '&apos;' ));
190             break;
191         case XML_UTIL_ENTITIES_XML_REQUIRED:
192             return strtr($string, array(
193                 '&' => '&amp;',
194                 '<' => '&lt;',
195                 '"' => '&quot;' ));
196             break;
197         case XML_UTIL_ENTITIES_HTML:
198             return htmlentities($string, ENT_COMPAT, $encoding);
199             break;
200         }
201         return $string;
202     }
203
204     /**
205      * reverse XML entities
206      *
207      * With the optional second parameter, you may select, which
208      * entities should be reversed.
209      *
210      * <code>
211      * require_once 'XML/Util.php';
212      *
213      * // reverse XML entites:
214      * $string = XML_Util::reverseEntities('This string contains &lt; &amp; &gt;.');
215      * </code>
216      *
217      * With the optional third parameter, you may pass the character encoding
218      * <code>
219      * require_once 'XML/Util.php';
220      *
221      * // reverse XML entites in UTF-8:
222      * $string = XML_Util::reverseEntities(
223      *     'This string contains &lt; &amp; &gt; as well as'
224      *     . ' &auml;, &ouml;, &szlig;, &agrave; and &ecirc;',
225      *     XML_UTIL_ENTITIES_HTML,
226      *     'UTF-8'
227      * );
228      * </code>
229      *
230      * @param string $string          string where XML special chars 
231      *                                should be replaced
232      * @param int    $replaceEntities setting for entities in attribute values 
233      *                                (one of XML_UTIL_ENTITIES_XML, 
234      *                                XML_UTIL_ENTITIES_XML_REQUIRED, 
235      *                                XML_UTIL_ENTITIES_HTML)
236      * @param string $encoding        encoding value (if any)...
237      *                                must be a valid encoding as determined
238      *                                by the html_entity_decode() function
239      *
240      * @return string string with replaced chars
241      * @access public
242      * @static
243      * @see replaceEntities()
244      */
245     function reverseEntities($string, $replaceEntities = XML_UTIL_ENTITIES_XML,
246         $encoding = 'ISO-8859-1')
247     {
248         switch ($replaceEntities) {
249         case XML_UTIL_ENTITIES_XML:
250             return strtr($string, array(
251                 '&amp;'  => '&',
252                 '&gt;'   => '>',
253                 '&lt;'   => '<',
254                 '&quot;' => '"',
255                 '&apos;' => '\'' ));
256             break;
257         case XML_UTIL_ENTITIES_XML_REQUIRED:
258             return strtr($string, array(
259                 '&amp;'  => '&',
260                 '&lt;'   => '<',
261                 '&quot;' => '"' ));
262             break;
263         case XML_UTIL_ENTITIES_HTML:
264             return html_entity_decode($string, ENT_COMPAT, $encoding);
265             break;
266         }
267         return $string;
268     }
269
270     /**
271      * build an xml declaration
272      *
273      * <code>
274      * require_once 'XML/Util.php';
275      *
276      * // get an XML declaration:
277      * $xmlDecl = XML_Util::getXMLDeclaration('1.0', 'UTF-8', true);
278      * </code>
279      *
280      * @param string $version    xml version
281      * @param string $encoding   character encoding
282      * @param bool   $standalone document is standalone (or not)
283      *
284      * @return string xml declaration
285      * @access public
286      * @static
287      * @uses attributesToString() to serialize the attributes of the XML declaration
288      */
289     function getXMLDeclaration($version = '1.0', $encoding = null, 
290         $standalone = null)
291     {
292         $attributes = array(
293             'version' => $version,
294         );
295         // add encoding
296         if ($encoding !== null) {
297             $attributes['encoding'] = $encoding;
298         }
299         // add standalone, if specified
300         if ($standalone !== null) {
301             $attributes['standalone'] = $standalone ? 'yes' : 'no';
302         }
303
304         return sprintf('<?xml%s?>', 
305             XML_Util::attributesToString($attributes, false));
306     }
307
308     /**
309      * build a document type declaration
310      *
311      * <code>
312      * require_once 'XML/Util.php';
313      *
314      * // get a doctype declaration:
315      * $xmlDecl = XML_Util::getDocTypeDeclaration('rootTag','myDocType.dtd');
316      * </code>
317      *
318      * @param string $root        name of the root tag
319      * @param string $uri         uri of the doctype definition 
320      *                            (or array with uri and public id)
321      * @param string $internalDtd internal dtd entries
322      *
323      * @return string doctype declaration
324      * @access public
325      * @static
326      * @since 0.2
327      */
328     function getDocTypeDeclaration($root, $uri = null, $internalDtd = null)
329     {
330         if (is_array($uri)) {
331             $ref = sprintf(' PUBLIC "%s" "%s"', $uri['id'], $uri['uri']);
332         } elseif (!empty($uri)) {
333             $ref = sprintf(' SYSTEM "%s"', $uri);
334         } else {
335             $ref = '';
336         }
337
338         if (empty($internalDtd)) {
339             return sprintf('<!DOCTYPE %s%s>', $root, $ref);
340         } else {
341             return sprintf("<!DOCTYPE %s%s [\n%s\n]>", $root, $ref, $internalDtd);
342         }
343     }
344
345     /**
346      * create string representation of an attribute list
347      *
348      * <code>
349      * require_once 'XML/Util.php';
350      *
351      * // build an attribute string
352      * $att = array(
353      *              'foo'   =>  'bar',
354      *              'argh'  =>  'tomato'
355      *            );
356      *
357      * $attList = XML_Util::attributesToString($att);
358      * </code>
359      *
360      * @param array      $attributes attribute array
361      * @param bool|array $sort       sort attribute list alphabetically, 
362      *                               may also be an assoc array containing 
363      *                               the keys 'sort', 'multiline', 'indent', 
364      *                               'linebreak' and 'entities'
365      * @param bool       $multiline  use linebreaks, if more than 
366      *                               one attribute is given
367      * @param string     $indent     string used for indentation of 
368      *                               multiline attributes
369      * @param string     $linebreak  string used for linebreaks of 
370      *                               multiline attributes
371      * @param int        $entities   setting for entities in attribute values 
372      *                               (one of XML_UTIL_ENTITIES_NONE, 
373      *                               XML_UTIL_ENTITIES_XML, 
374      *                               XML_UTIL_ENTITIES_XML_REQUIRED, 
375      *                               XML_UTIL_ENTITIES_HTML)
376      *
377      * @return string string representation of the attributes
378      * @access public
379      * @static
380      * @uses replaceEntities() to replace XML entities in attribute values
381      * @todo allow sort also to be an options array
382      */
383     function attributesToString($attributes, $sort = true, $multiline = false, 
384         $indent = '    ', $linebreak = "\n", $entities = XML_UTIL_ENTITIES_XML)
385     {
386         /*
387          * second parameter may be an array
388          */
389         if (is_array($sort)) {
390             if (isset($sort['multiline'])) {
391                 $multiline = $sort['multiline'];
392             }
393             if (isset($sort['indent'])) {
394                 $indent = $sort['indent'];
395             }
396             if (isset($sort['linebreak'])) {
397                 $multiline = $sort['linebreak'];
398             }
399             if (isset($sort['entities'])) {
400                 $entities = $sort['entities'];
401             }
402             if (isset($sort['sort'])) {
403                 $sort = $sort['sort'];
404             } else {
405                 $sort = true;
406             }
407         }
408         $string = '';
409         if (is_array($attributes) && !empty($attributes)) {
410             if ($sort) {
411                 ksort($attributes);
412             }
413             if ( !$multiline || count($attributes) == 1) {
414                 foreach ($attributes as $key => $value) {
415                     if ($entities != XML_UTIL_ENTITIES_NONE) {
416                         if ($entities === XML_UTIL_CDATA_SECTION) {
417                             $entities = XML_UTIL_ENTITIES_XML;
418                         }
419                         $value = XML_Util::replaceEntities($value, $entities);
420                     }
421                     $string .= ' ' . $key . '="' . $value . '"';
422                 }
423             } else {
424                 $first = true;
425                 foreach ($attributes as $key => $value) {
426                     if ($entities != XML_UTIL_ENTITIES_NONE) {
427                         $value = XML_Util::replaceEntities($value, $entities);
428                     }
429                     if ($first) {
430                         $string .= ' ' . $key . '="' . $value . '"';
431                         $first   = false;
432                     } else {
433                         $string .= $linebreak . $indent . $key . '="' . $value . '"';
434                     }
435                 }
436             }
437         }
438         return $string;
439     }
440
441     /**
442      * Collapses empty tags.
443      *
444      * @param string $xml  XML
445      * @param int    $mode Whether to collapse all empty tags (XML_UTIL_COLLAPSE_ALL)
446      *                      or only XHTML (XML_UTIL_COLLAPSE_XHTML_ONLY) ones.
447      *
448      * @return string XML
449      * @access public
450      * @static
451      * @todo PEAR CS - unable to avoid "space after open parens" error
452      *       in the IF branch
453      */
454     function collapseEmptyTags($xml, $mode = XML_UTIL_COLLAPSE_ALL) 
455     {
456         if ($mode == XML_UTIL_COLLAPSE_XHTML_ONLY) {
457             return preg_replace(
458                 '/<(area|base(?:font)?|br|col|frame|hr|img|input|isindex|link|meta|'
459                 . 'param)([^>]*)><\/\\1>/s',
460                 '<\\1\\2 />',
461                 $xml);
462         } else {
463             return preg_replace('/<(\w+)([^>]*)><\/\\1>/s', '<\\1\\2 />', $xml);
464         }
465     }
466
467     /**
468      * create a tag
469      *
470      * This method will call XML_Util::createTagFromArray(), which
471      * is more flexible.
472      *
473      * <code>
474      * require_once 'XML/Util.php';
475      *
476      * // create an XML tag:
477      * $tag = XML_Util::createTag('myNs:myTag', 
478      *     array('foo' => 'bar'), 
479      *     'This is inside the tag', 
480      *     'http://www.w3c.org/myNs#');
481      * </code>
482      *
483      * @param string $qname           qualified tagname (including namespace)
484      * @param array  $attributes      array containg attributes
485      * @param mixed  $content         the content
486      * @param string $namespaceUri    URI of the namespace
487      * @param int    $replaceEntities whether to replace XML special chars in 
488      *                                content, embedd it in a CData section 
489      *                                or none of both
490      * @param bool   $multiline       whether to create a multiline tag where 
491      *                                each attribute gets written to a single line
492      * @param string $indent          string used to indent attributes 
493      *                                (_auto indents attributes so they start 
494      *                                at the same column)
495      * @param string $linebreak       string used for linebreaks
496      * @param bool   $sortAttributes  Whether to sort the attributes or not
497      *
498      * @return string XML tag
499      * @access public
500      * @static
501      * @see createTagFromArray()
502      * @uses createTagFromArray() to create the tag
503      */
504     function createTag($qname, $attributes = array(), $content = null, 
505         $namespaceUri = null, $replaceEntities = XML_UTIL_REPLACE_ENTITIES, 
506         $multiline = false, $indent = '_auto', $linebreak = "\n", 
507         $sortAttributes = true)
508     {
509         $tag = array(
510             'qname'      => $qname,
511             'attributes' => $attributes
512         );
513
514         // add tag content
515         if ($content !== null) {
516             $tag['content'] = $content;
517         }
518
519         // add namespace Uri
520         if ($namespaceUri !== null) {
521             $tag['namespaceUri'] = $namespaceUri;
522         }
523
524         return XML_Util::createTagFromArray($tag, $replaceEntities, $multiline, 
525             $indent, $linebreak, $sortAttributes);
526     }
527
528     /**
529      * create a tag from an array
530      * this method awaits an array in the following format
531      * <pre>
532      * array(
533      *     // qualified name of the tag
534      *     'qname' => $qname        
535      *
536      *     // namespace prefix (optional, if qname is specified or no namespace)
537      *     'namespace' => $namespace    
538      *
539      *     // local part of the tagname (optional, if qname is specified)
540      *     'localpart' => $localpart,   
541      *
542      *     // array containing all attributes (optional)
543      *     'attributes' => array(),      
544      *
545      *     // tag content (optional)
546      *     'content' => $content,     
547      *
548      *     // namespaceUri for the given namespace (optional)
549      *     'namespaceUri' => $namespaceUri 
550      * )
551      * </pre>
552      *
553      * <code>
554      * require_once 'XML/Util.php';
555      *
556      * $tag = array(
557      *     'qname'        => 'foo:bar',
558      *     'namespaceUri' => 'http://foo.com',
559      *     'attributes'   => array('key' => 'value', 'argh' => 'fruit&vegetable'),
560      *     'content'      => 'I\'m inside the tag',
561      * );
562      * // creating a tag with qualified name and namespaceUri
563      * $string = XML_Util::createTagFromArray($tag);
564      * </code>
565      *
566      * @param array  $tag             tag definition
567      * @param int    $replaceEntities whether to replace XML special chars in 
568      *                                content, embedd it in a CData section 
569      *                                or none of both
570      * @param bool   $multiline       whether to create a multiline tag where each 
571      *                                attribute gets written to a single line
572      * @param string $indent          string used to indent attributes 
573      *                                (_auto indents attributes so they start 
574      *                                at the same column)
575      * @param string $linebreak       string used for linebreaks
576      * @param bool   $sortAttributes  Whether to sort the attributes or not
577      *
578      * @return string XML tag
579      * @access public
580      * @static
581      * @see createTag()
582      * @uses attributesToString() to serialize the attributes of the tag
583      * @uses splitQualifiedName() to get local part and namespace of a qualified name
584      * @uses createCDataSection()
585      * @uses raiseError()
586      */
587     function createTagFromArray($tag, $replaceEntities = XML_UTIL_REPLACE_ENTITIES,
588         $multiline = false, $indent = '_auto', $linebreak = "\n", 
589         $sortAttributes = true)
590     {
591         if (isset($tag['content']) && !is_scalar($tag['content'])) {
592             return XML_Util::raiseError('Supplied non-scalar value as tag content',
593             XML_UTIL_ERROR_NON_SCALAR_CONTENT);
594         }
595
596         if (!isset($tag['qname']) && !isset($tag['localPart'])) {
597             return XML_Util::raiseError('You must either supply a qualified name '
598                 . '(qname) or local tag name (localPart).', 
599                 XML_UTIL_ERROR_NO_TAG_NAME);
600         }
601
602         // if no attributes hav been set, use empty attributes
603         if (!isset($tag['attributes']) || !is_array($tag['attributes'])) {
604             $tag['attributes'] = array();
605         }
606
607         if (isset($tag['namespaces'])) {
608             foreach ($tag['namespaces'] as $ns => $uri) {
609                 $tag['attributes']['xmlns:' . $ns] = $uri;
610             }
611         }
612
613         if (!isset($tag['qname'])) {
614             // qualified name is not given
615
616             // check for namespace
617             if (isset($tag['namespace']) && !empty($tag['namespace'])) {
618                 $tag['qname'] = $tag['namespace'] . ':' . $tag['localPart'];
619             } else {
620                 $tag['qname'] = $tag['localPart'];
621             }
622         } elseif (isset($tag['namespaceUri']) && !isset($tag['namespace'])) {
623             // namespace URI is set, but no namespace
624
625             $parts = XML_Util::splitQualifiedName($tag['qname']);
626
627             $tag['localPart'] = $parts['localPart'];
628             if (isset($parts['namespace'])) {
629                 $tag['namespace'] = $parts['namespace'];
630             }
631         }
632
633         if (isset($tag['namespaceUri']) && !empty($tag['namespaceUri'])) {
634             // is a namespace given
635             if (isset($tag['namespace']) && !empty($tag['namespace'])) {
636                 $tag['attributes']['xmlns:' . $tag['namespace']] =
637                     $tag['namespaceUri'];
638             } else {
639                 // define this Uri as the default namespace
640                 $tag['attributes']['xmlns'] = $tag['namespaceUri'];
641             }
642         }
643
644         // check for multiline attributes
645         if ($multiline === true) {
646             if ($indent === '_auto') {
647                 $indent = str_repeat(' ', (strlen($tag['qname'])+2));
648             }
649         }
650
651         // create attribute list
652         $attList = XML_Util::attributesToString($tag['attributes'], 
653             $sortAttributes, $multiline, $indent, $linebreak, $replaceEntities);
654         if (!isset($tag['content']) || (string)$tag['content'] == '') {
655             $tag = sprintf('<%s%s />', $tag['qname'], $attList);
656         } else {
657             switch ($replaceEntities) {
658             case XML_UTIL_ENTITIES_NONE:
659                 break;
660             case XML_UTIL_CDATA_SECTION:
661                 $tag['content'] = XML_Util::createCDataSection($tag['content']);
662                 break;
663             default:
664                 $tag['content'] = XML_Util::replaceEntities($tag['content'], 
665                     $replaceEntities);
666                 break;
667             }
668             $tag = sprintf('<%s%s>%s</%s>', $tag['qname'], $attList, $tag['content'],
669                 $tag['qname']);
670         }
671         return $tag;
672     }
673
674     /**
675      * create a start element
676      *
677      * <code>
678      * require_once 'XML/Util.php';
679      *
680      * // create an XML start element:
681      * $tag = XML_Util::createStartElement('myNs:myTag', 
682      *     array('foo' => 'bar') ,'http://www.w3c.org/myNs#');
683      * </code>
684      *
685      * @param string $qname          qualified tagname (including namespace)
686      * @param array  $attributes     array containg attributes
687      * @param string $namespaceUri   URI of the namespace
688      * @param bool   $multiline      whether to create a multiline tag where each 
689      *                               attribute gets written to a single line
690      * @param string $indent         string used to indent attributes (_auto indents
691      *                               attributes so they start at the same column)
692      * @param string $linebreak      string used for linebreaks
693      * @param bool   $sortAttributes Whether to sort the attributes or not
694      *
695      * @return string XML start element
696      * @access public
697      * @static
698      * @see createEndElement(), createTag()
699      */
700     function createStartElement($qname, $attributes = array(), $namespaceUri = null,
701         $multiline = false, $indent = '_auto', $linebreak = "\n", 
702         $sortAttributes = true)
703     {
704         // if no attributes hav been set, use empty attributes
705         if (!isset($attributes) || !is_array($attributes)) {
706             $attributes = array();
707         }
708
709         if ($namespaceUri != null) {
710             $parts = XML_Util::splitQualifiedName($qname);
711         }
712
713         // check for multiline attributes
714         if ($multiline === true) {
715             if ($indent === '_auto') {
716                 $indent = str_repeat(' ', (strlen($qname)+2));
717             }
718         }
719
720         if ($namespaceUri != null) {
721             // is a namespace given
722             if (isset($parts['namespace']) && !empty($parts['namespace'])) {
723                 $attributes['xmlns:' . $parts['namespace']] = $namespaceUri;
724             } else {
725                 // define this Uri as the default namespace
726                 $attributes['xmlns'] = $namespaceUri;
727             }
728         }
729
730         // create attribute list
731         $attList = XML_Util::attributesToString($attributes, $sortAttributes, 
732             $multiline, $indent, $linebreak);
733         $element = sprintf('<%s%s>', $qname, $attList);
734         return  $element;
735     }
736
737     /**
738      * create an end element
739      *
740      * <code>
741      * require_once 'XML/Util.php';
742      *
743      * // create an XML start element:
744      * $tag = XML_Util::createEndElement('myNs:myTag');
745      * </code>
746      *
747      * @param string $qname qualified tagname (including namespace)
748      *
749      * @return string XML end element
750      * @access public
751      * @static
752      * @see createStartElement(), createTag()
753      */
754     function createEndElement($qname)
755     {
756         $element = sprintf('</%s>', $qname);
757         return $element;
758     }
759
760     /**
761      * create an XML comment
762      *
763      * <code>
764      * require_once 'XML/Util.php';
765      *
766      * // create an XML start element:
767      * $tag = XML_Util::createComment('I am a comment');
768      * </code>
769      *
770      * @param string $content content of the comment
771      *
772      * @return string XML comment
773      * @access public
774      * @static
775      */
776     function createComment($content)
777     {
778         $comment = sprintf('<!-- %s -->', $content);
779         return $comment;
780     }
781
782     /**
783      * create a CData section
784      *
785      * <code>
786      * require_once 'XML/Util.php';
787      *
788      * // create a CData section
789      * $tag = XML_Util::createCDataSection('I am content.');
790      * </code>
791      *
792      * @param string $data data of the CData section
793      *
794      * @return string CData section with content
795      * @access public
796      * @static
797      */
798     function createCDataSection($data)
799     {
800         return sprintf('<![CDATA[%s]]>', 
801             preg_replace('/\]\]>/', ']]]]><![CDATA[>', strval($data)));
802
803     }
804
805     /**
806      * split qualified name and return namespace and local part
807      *
808      * <code>
809      * require_once 'XML/Util.php';
810      *
811      * // split qualified tag
812      * $parts = XML_Util::splitQualifiedName('xslt:stylesheet');
813      * </code>
814      * the returned array will contain two elements:
815      * <pre>
816      * array(
817      *     'namespace' => 'xslt',
818      *     'localPart' => 'stylesheet'
819      * );
820      * </pre>
821      *
822      * @param string $qname     qualified tag name
823      * @param string $defaultNs default namespace (optional)
824      *
825      * @return array array containing namespace and local part
826      * @access public
827      * @static
828      */
829     function splitQualifiedName($qname, $defaultNs = null)
830     {
831         if (strstr($qname, ':')) {
832             $tmp = explode(':', $qname);
833             return array(
834                 'namespace' => $tmp[0],
835                 'localPart' => $tmp[1]
836             );
837         }
838         return array(
839             'namespace' => $defaultNs,
840             'localPart' => $qname
841         );
842     }
843
844     /**
845      * check, whether string is valid XML name
846      *
847      * <p>XML names are used for tagname, attribute names and various
848      * other, lesser known entities.</p>
849      * <p>An XML name may only consist of alphanumeric characters,
850      * dashes, undescores and periods, and has to start with a letter
851      * or an underscore.</p>
852      *
853      * <code>
854      * require_once 'XML/Util.php';
855      *
856      * // verify tag name
857      * $result = XML_Util::isValidName('invalidTag?');
858      * if (is_a($result, 'PEAR_Error')) {
859      *    print 'Invalid XML name: ' . $result->getMessage();
860      * }
861      * </code>
862      *
863      * @param string $string string that should be checked
864      *
865      * @return mixed true, if string is a valid XML name, PEAR error otherwise
866      * @access public
867      * @static
868      * @todo support for other charsets
869      * @todo PEAR CS - unable to avoid 85-char limit on second preg_match
870      */
871     function isValidName($string)
872     {
873         // check for invalid chars
874         if (!preg_match('/^[[:alpha:]_]$/', $string{0})) {
875             return XML_Util::raiseError('XML names may only start with letter '
876                 . 'or underscore', XML_UTIL_ERROR_INVALID_START);
877         }
878
879         // check for invalid chars
880         if (!preg_match('/^([[:alpha:]_]([[:alnum:]\-\.]*)?:)?[[:alpha:]_]([[:alnum:]\_\-\.]+)?$/',
881             $string)
882         ) {
883             return XML_Util::raiseError('XML names may only contain alphanumeric '
884                 . 'chars, period, hyphen, colon and underscores', 
885                 XML_UTIL_ERROR_INVALID_CHARS);
886         }
887         // XML name is valid
888         return true;
889     }
890
891     /**
892      * replacement for XML_Util::raiseError
893      *
894      * Avoids the necessity to always require
895      * PEAR.php
896      *
897      * @param string $msg  error message
898      * @param int    $code error code
899      *
900      * @return PEAR_Error
901      * @access public
902      * @static
903      * @todo PEAR CS - should this use include_once instead?
904      */
905     function raiseError($msg, $code)
906     {
907         require_once 'PEAR.php';
908         return PEAR::raiseError($msg, $code);
909     }
910 }
911 ?>