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