Fix #8052 - fixing pdf
[pear] / XML / Parser.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6  * XML_Parser
7  *
8  * XML Parser package
9  *
10  * PHP versions 4 and 5
11  *
12  * LICENSE:
13  *
14  * Copyright (c) 2002-2008 The PHP Group
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_Parser
43  * @author    Stig Bakken <ssb@fast.no>
44  * @author    Tomas V.V.Cox <cox@idecnet.com>
45  * @author    Stephan Schmidt <schst@php.net>
46  * @copyright 2002-2008 The PHP Group
47  * @license   http://opensource.org/licenses/bsd-license New BSD License
48  * @version   CVS: $Id: Parser.php 302733 2010-08-24 01:09:09Z clockwerx $
49  * @link      http://pear.php.net/package/XML_Parser
50  */
51
52 /**
53  * uses PEAR's error handling
54  */
55 require_once 'PEAR.php';
56
57 /**
58  * resource could not be created
59  */
60 define('XML_PARSER_ERROR_NO_RESOURCE', 200);
61
62 /**
63  * unsupported mode
64  */
65 define('XML_PARSER_ERROR_UNSUPPORTED_MODE', 201);
66
67 /**
68  * invalid encoding was given
69  */
70 define('XML_PARSER_ERROR_INVALID_ENCODING', 202);
71
72 /**
73  * specified file could not be read
74  */
75 define('XML_PARSER_ERROR_FILE_NOT_READABLE', 203);
76
77 /**
78  * invalid input
79  */
80 define('XML_PARSER_ERROR_INVALID_INPUT', 204);
81
82 /**
83  * remote file cannot be retrieved in safe mode
84  */
85 define('XML_PARSER_ERROR_REMOTE', 205);
86
87 /**
88  * XML Parser class.
89  *
90  * This is an XML parser based on PHP's "xml" extension,
91  * based on the bundled expat library.
92  *
93  * Notes:
94  * - It requires PHP 4.0.4pl1 or greater
95  * - From revision 1.17, the function names used by the 'func' mode
96  *   are in the format "xmltag_$elem", for example: use "xmltag_name"
97  *   to handle the <name></name> tags of your xml file.
98  *          - different parsing modes
99  *
100  * @category  XML
101  * @package   XML_Parser
102  * @author    Stig Bakken <ssb@fast.no>
103  * @author    Tomas V.V.Cox <cox@idecnet.com>
104  * @author    Stephan Schmidt <schst@php.net>
105  * @copyright 2002-2008 The PHP Group
106  * @license   http://opensource.org/licenses/bsd-license New BSD License
107  * @version   Release: @package_version@
108  * @link      http://pear.php.net/package/XML_Parser
109  * @todo      create XML_Parser_Namespace to parse documents with namespaces
110  * @todo      create XML_Parser_Pull
111  * @todo      Tests that need to be made:
112  *            - mixing character encodings
113  *            - a test using all expat handlers
114  *            - options (folding, output charset)
115  */
116 class XML_Parser extends PEAR
117 {
118     // {{{ properties
119
120     /**
121      * XML parser handle
122      *
123      * @var  resource
124      * @see  xml_parser_create()
125      */
126     var $parser;
127
128     /**
129      * File handle if parsing from a file
130      *
131      * @var  resource
132      */
133     var $fp;
134
135     /**
136      * Whether to do case folding
137      *
138      * If set to true, all tag and attribute names will
139      * be converted to UPPER CASE.
140      *
141      * @var  boolean
142      */
143     var $folding = true;
144
145     /**
146      * Mode of operation, one of "event" or "func"
147      *
148      * @var  string
149      */
150     var $mode;
151
152     /**
153      * Mapping from expat handler function to class method.
154      *
155      * @var  array
156      */
157     var $handler = array(
158         'character_data_handler'            => 'cdataHandler',
159         'default_handler'                   => 'defaultHandler',
160         'processing_instruction_handler'    => 'piHandler',
161         'unparsed_entity_decl_handler'      => 'unparsedHandler',
162         'notation_decl_handler'             => 'notationHandler',
163         'external_entity_ref_handler'       => 'entityrefHandler'
164     );
165
166     /**
167      * source encoding
168      *
169      * @var string
170      */
171     var $srcenc;
172
173     /**
174      * target encoding
175      *
176      * @var string
177      */
178     var $tgtenc;
179
180     /**
181      * handler object
182      *
183      * @var object
184      */
185     var $_handlerObj;
186
187     /**
188      * valid encodings
189      *
190      * @var array
191      */
192     var $_validEncodings = array('ISO-8859-1', 'UTF-8', 'US-ASCII');
193
194     // }}}
195     // {{{ php4 constructor
196
197   
198     // }}}
199     // {{{ php5 constructor
200
201     /**
202      * PHP5 constructor
203      *
204      * @param string $srcenc source charset encoding, use NULL (default) to use
205      *                       whatever the document specifies
206      * @param string $mode   how this parser object should work, "event" for
207      *                       startelement/endelement-type events, "func"
208      *                       to have it call functions named after elements
209      * @param string $tgtenc a valid target encoding
210      */
211     function __construct($srcenc = null, $mode = 'event', $tgtenc = null)
212     {
213         parent::__construct('XML_Parser_Error');
214
215         $this->mode   = $mode;
216         $this->srcenc = $srcenc;
217         $this->tgtenc = $tgtenc;
218     }
219     // }}}
220
221     /**
222      * Sets the mode of the parser.
223      *
224      * Possible modes are:
225      * - func
226      * - event
227      *
228      * You can set the mode using the second parameter
229      * in the constructor.
230      *
231      * This method is only needed, when switching to a new
232      * mode at a later point.
233      *
234      * @param string $mode mode, either 'func' or 'event'
235      *
236      * @return boolean|object  true on success, PEAR_Error otherwise
237      * @access public
238      */
239     function setMode($mode)
240     {
241         if ($mode != 'func' && $mode != 'event') {
242             $this->raiseError('Unsupported mode given', 
243                 XML_PARSER_ERROR_UNSUPPORTED_MODE);
244         }
245
246         $this->mode = $mode;
247         return true;
248     }
249
250     /**
251      * Sets the object, that will handle the XML events
252      *
253      * This allows you to create a handler object independent of the
254      * parser object that you are using and easily switch the underlying
255      * parser.
256      *
257      * If no object will be set, XML_Parser assumes that you
258      * extend this class and handle the events in $this.
259      *
260      * @param object &$obj object to handle the events
261      *
262      * @return boolean will always return true
263      * @access public
264      * @since v1.2.0beta3
265      */
266     function setHandlerObj(&$obj)
267     {
268         $this->_handlerObj = &$obj;
269         return true;
270     }
271
272     /**
273      * Init the element handlers
274      *
275      * @return mixed
276      * @access private
277      */
278     function _initHandlers()
279     {
280         if (!is_object($this->parser)) {
281             return false;
282         }
283
284         if (!is_object($this->_handlerObj)) {
285             $this->_handlerObj = &$this;
286         }
287         switch ($this->mode) {
288
289         case 'func':
290             xml_set_object($this->parser, $this->_handlerObj);
291             xml_set_element_handler($this->parser, 
292                 array(&$this, 'funcStartHandler'), array(&$this, 'funcEndHandler'));
293             break;
294
295         case 'event':
296             xml_set_object($this->parser, $this->_handlerObj);
297             xml_set_element_handler($this->parser, 'startHandler', 'endHandler');
298             break;
299         default:
300             return $this->raiseError('Unsupported mode given', 
301                 XML_PARSER_ERROR_UNSUPPORTED_MODE);
302             break;
303         }
304
305         /**
306          * set additional handlers for character data, entities, etc.
307          */
308         foreach ($this->handler as $xml_func => $method) {
309             if (method_exists($this->_handlerObj, $method)) {
310                 $xml_func = 'xml_set_' . $xml_func;
311                 $xml_func($this->parser, $method);
312             }
313         }
314     }
315
316     // {{{ _create()
317
318     /**
319      * create the XML parser resource
320      *
321      * Has been moved from the constructor to avoid
322      * problems with object references.
323      *
324      * Furthermore it allows us returning an error
325      * if something fails.
326      *
327      * NOTE: uses '@' error suppresion in this method
328      *
329      * @return bool|PEAR_Error true on success, PEAR_Error otherwise
330      * @access private
331      * @see xml_parser_create
332      */
333     function _create()
334     {
335         if ($this->srcenc === null) {
336             $xp = xml_parser_create();
337         } else {
338             $xp = xml_parser_create($this->srcenc);
339         }
340         if (is_object($xp)) {
341             if ($this->tgtenc !== null) {
342                 if (!xml_parser_set_option($xp, XML_OPTION_TARGET_ENCODING, 
343                     $this->tgtenc)
344                 ) {
345                     return $this->raiseError('invalid target encoding', 
346                         XML_PARSER_ERROR_INVALID_ENCODING);
347                 }
348             }
349             $this->parser = $xp;
350             $result       = $this->_initHandlers($this->mode);
351             if ($this->isError($result)) {
352                 return $result;
353             }
354             xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, $this->folding);
355             return true;
356         }
357         if (!empty($this->srcenc) && !in_array(strtoupper($this->srcenc), $this->_validEncodings)) {
358             return $this->raiseError('invalid source encoding', 
359                 XML_PARSER_ERROR_INVALID_ENCODING);
360         }
361         return $this->raiseError('Unable to create XML parser resource.', 
362             XML_PARSER_ERROR_NO_RESOURCE);
363     }
364
365     // }}}
366     // {{{ reset()
367
368     /**
369      * Reset the parser.
370      *
371      * This allows you to use one parser instance
372      * to parse multiple XML documents.
373      *
374      * @access   public
375      * @return   boolean|object     true on success, PEAR_Error otherwise
376      */
377     function reset()
378     {
379         $result = $this->_create();
380         if ($this->isError($result)) {
381             return $result;
382         }
383         return true;
384     }
385
386     // }}}
387     // {{{ setInputFile()
388
389     /**
390      * Sets the input xml file to be parsed
391      *
392      * @param string $file Filename (full path)
393      *
394      * @return resource fopen handle of the given file
395      * @access public
396      * @throws XML_Parser_Error
397      * @see setInput(), setInputString(), parse()
398      */
399     function setInputFile($file)
400     {
401         /**
402          * check, if file is a remote file
403          */
404         if (preg_match('/^(http|ftp):\/\//i', substr($file, 0, 10))) {
405             if (!ini_get('allow_url_fopen')) {
406                 return $this->
407                 raiseError('Remote files cannot be parsed, as safe mode is enabled.',
408                 XML_PARSER_ERROR_REMOTE);
409             }
410         }
411
412         $fp = @fopen($file, 'rb');
413         if (is_resource($fp)) {
414             $this->fp = $fp;
415             return $fp;
416         }
417         return $this->raiseError('File could not be opened.', 
418             XML_PARSER_ERROR_FILE_NOT_READABLE);
419     }
420
421     // }}}
422     // {{{ setInputString()
423
424     /**
425      * XML_Parser::setInputString()
426      *
427      * Sets the xml input from a string
428      *
429      * @param string $data a string containing the XML document
430      *
431      * @return null
432      */
433     function setInputString($data)
434     {
435         $this->fp = $data;
436         return null;
437     }
438
439     // }}}
440     // {{{ setInput()
441
442     /**
443      * Sets the file handle to use with parse().
444      *
445      * You should use setInputFile() or setInputString() if you
446      * pass a string
447      *
448      * @param mixed $fp Can be either a resource returned from fopen(),
449      *                  a URL, a local filename or a string.
450      *
451      * @return mixed
452      * @access public
453      * @see parse()
454      * @uses setInputString(), setInputFile()
455      */
456     function setInput($fp)
457     {
458         if (is_resource($fp)) {
459             $this->fp = $fp;
460             return true;
461         } elseif (preg_match('/^[a-z]+:\/\//i', substr($fp, 0, 10))) {
462             // see if it's an absolute URL (has a scheme at the beginning)
463             return $this->setInputFile($fp);
464         } elseif (file_exists($fp)) {
465             // see if it's a local file
466             return $this->setInputFile($fp);
467         } else {
468             // it must be a string
469             $this->fp = $fp;
470             return true;
471         }
472
473         return $this->raiseError('Illegal input format', 
474             XML_PARSER_ERROR_INVALID_INPUT);
475     }
476
477     // }}}
478     // {{{ parse()
479
480     /**
481      * Central parsing function.
482      *
483      * @return bool|PEAR_Error returns true on success, or a PEAR_Error otherwise
484      * @access public
485      */
486     function parse()
487     {
488         /**
489          * reset the parser
490          */
491         $result = $this->reset();
492         if ($this->isError($result)) {
493             return $result;
494         }
495         // if $this->fp was fopened previously
496          
497         if (is_resource($this->fp)) {
498
499             while ($data = fread($this->fp, 4096)) {
500                 if (!$this->_parseString($data, feof($this->fp))) {
501                     $error =  $this->raiseError();
502                     $this->free();
503                     return $error;
504                 }
505             }
506         } else {
507             // otherwise, $this->fp must be a string
508             if (!$this->_parseString($this->fp, true)) {
509                 $error = &$this->raiseError();
510                 $this->free();
511                 return $error;
512             }
513         }
514         $this->free();
515
516         return true;
517     }
518
519     /**
520      * XML_Parser::_parseString()
521      *
522      * @param string $data data
523      * @param bool   $eof  end-of-file flag
524      *
525      * @return bool
526      * @access private
527      * @see parseString()
528      **/
529     function _parseString($data, $eof = false)
530     {
531         return xml_parse($this->parser, $data, $eof);
532     }
533
534     // }}}
535     // {{{ parseString()
536
537     /**
538      * XML_Parser::parseString()
539      *
540      * Parses a string.
541      *
542      * @param string  $data XML data
543      * @param boolean $eof  If set and TRUE, data is the last piece 
544      *                      of data sent in this parser
545      *
546      * @return bool|PEAR_Error true on success or a PEAR Error
547      * @throws XML_Parser_Error
548      * @see _parseString()
549      */
550     function parseString($data, $eof = false)
551     {
552         if (!isset($this->parser) || !is_resource($this->parser)) {
553             $this->reset();
554         }
555
556         if (!$this->_parseString($data, $eof)) {
557             $error = &$this->raiseError();
558             $this->free();
559             return $error;
560         }
561
562         if ($eof === true) {
563             $this->free();
564         }
565         return true;
566     }
567
568     /**
569      * XML_Parser::free()
570      *
571      * Free the internal resources associated with the parser
572      *
573      * @return null
574      **/
575     function free()
576     {
577         if (isset($this->parser) && is_object($this->parser)) {
578             //xml_parser_free($this->parser);
579             unset( $this->parser );
580         }
581         if (isset($this->fp) && is_resource($this->fp)) {
582             fclose($this->fp);
583         }
584         unset($this->fp);
585         return null;
586     }
587
588     /**
589      * XML_Parser::raiseError()
590      * 
591      * Throws a XML_Parser_Error
592      *
593      * @param string  $msg   the error message
594      * @param integer $ecode the error message code
595      *
596      * @return XML_Parser_Error reference to the error object
597      **/
598     function &raiseError($message= NULL, $code = NULL, $mode = NULL, $options = NULL, $userinfo = NULL, $error_class = NULL, $skipmsg = false)
599     {
600         $code = is_null($code) ? 0 : $code;
601         $msg = !is_null($message) ? $message : $this->parser;
602         $err = new XML_Parser_Error($msg, $code);
603         return parent::raiseError($err);
604     }
605
606     // }}}
607     // {{{ funcStartHandler()
608
609     /**
610      * derives and calls the Start Handler function
611      *
612      * @param mixed $xp      ??
613      * @param mixed $elem    ??
614      * @param mixed $attribs ??
615      *
616      * @return void
617      */
618     function funcStartHandler($xp, $elem, $attribs)
619     {
620         $func = 'xmltag_' . $elem;
621         $func = str_replace(array('.', '-', ':'), '_', $func);
622         if (method_exists($this->_handlerObj, $func)) {
623             call_user_func(array(&$this->_handlerObj, $func), $xp, $elem, $attribs);
624         } elseif (method_exists($this->_handlerObj, 'xmltag')) {
625             call_user_func(array(&$this->_handlerObj, 'xmltag'), 
626                 $xp, $elem, $attribs);
627         }
628     }
629
630     // }}}
631     // {{{ funcEndHandler()
632
633     /**
634      * derives and calls the End Handler function
635      *
636      * @param mixed $xp   ??
637      * @param mixed $elem ??
638      *
639      * @return void
640      */
641     function funcEndHandler($xp, $elem)
642     {
643         $func = 'xmltag_' . $elem . '_';
644         $func = str_replace(array('.', '-', ':'), '_', $func);
645         if (method_exists($this->_handlerObj, $func)) {
646             call_user_func(array(&$this->_handlerObj, $func), $xp, $elem);
647         } elseif (method_exists($this->_handlerObj, 'xmltag_')) {
648             call_user_func(array(&$this->_handlerObj, 'xmltag_'), $xp, $elem);
649         }
650     }
651
652     // }}}
653     // {{{ startHandler()
654
655     /**
656      * abstract method signature for Start Handler
657      *
658      * @param mixed $xp       ??
659      * @param mixed $elem     ??
660      * @param mixed &$attribs ??
661      *
662      * @return null
663      * @abstract
664      */
665     function startHandler($xp, $elem,  $attribs)
666     {
667         return null;
668     }
669
670     // }}}
671     // {{{ endHandler()
672
673     /**
674      * abstract method signature for End Handler
675      *
676      * @param mixed $xp   ??
677      * @param mixed $elem ??
678      *
679      * @return null
680      * @abstract
681      */
682     function endHandler( $elem)
683     {
684         return null;
685     }
686
687
688     // }}}me
689 }
690
691 /**
692  * error class, replaces PEAR_Error
693  *
694  * An instance of this class will be returned
695  * if an error occurs inside XML_Parser.
696  *
697  * There are three advantages over using the standard PEAR_Error:
698  * - All messages will be prefixed
699  * - check for XML_Parser error, using is_a( $error, 'XML_Parser_Error' )
700  * - messages can be generated from the xml_parser resource
701  *
702  * @category  XML
703  * @package   XML_Parser
704  * @author    Stig Bakken <ssb@fast.no>
705  * @author    Tomas V.V.Cox <cox@idecnet.com>
706  * @author    Stephan Schmidt <schst@php.net>
707  * @copyright 2002-2008 The PHP Group
708  * @license   http://opensource.org/licenses/bsd-license New BSD License
709  * @version   Release: @package_version@
710  * @link      http://pear.php.net/package/XML_Parser
711  * @see       PEAR_Error
712  */
713 class XML_Parser_Error extends PEAR_Error
714 {
715     // {{{ properties
716
717     /**
718     * prefix for all messages
719     *
720     * @var      string
721     */
722     var $error_message_prefix = 'XML_Parser: ';
723
724     // }}}
725     // {{{ constructor()
726     /**
727     * construct a new error instance
728     *
729     * You may either pass a message or an xml_parser resource as first
730     * parameter. If a resource has been passed, the last error that
731     * happened will be retrieved and returned.
732     *
733     * @param string|resource $msgorparser message or parser resource
734     * @param integer         $code        error code
735     * @param integer         $mode        error handling
736     * @param integer         $level       error level
737     *
738     * @access   public
739     * @todo PEAR CS - can't meet 85char line limit without arg refactoring
740     */
741     function __construct($msgorparser = 'unknown error', $code = NULL, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE)
742     {
743         $code = is_null($code) ? 0 : $code;
744         if (is_resource($msgorparser)) {
745             $code        = xml_get_error_code($msgorparser);
746             $msgorparser = sprintf('%s at XML input line %d:%d',
747                 xml_error_string($code),
748                 xml_get_current_line_number($msgorparser),
749                 xml_get_current_column_number($msgorparser));
750         }
751         parent::__construct($msgorparser, $code, $mode, $level);
752     }
753     // }}}
754 }
755 ?>