1<?php
2/**
3 * PEAR_XMLParser
4 *
5 * PHP versions 4 and 5
6 *
7 * @category   pear
8 * @package    PEAR
9 * @author     Greg Beaver <cellog@php.net>
10 * @author     Stephan Schmidt (original XML_Unserializer code)
11 * @copyright  1997-2009 The Authors
12 * @license   http://opensource.org/licenses/bsd-license New BSD License
13 * @link       http://pear.php.net/package/PEAR
14 * @since      File available since Release 1.4.0a1
15 */
16
17/**
18 * Parser for any xml file
19 * @category  pear
20 * @package   PEAR
21 * @author    Greg Beaver <cellog@php.net>
22 * @author    Stephan Schmidt (original XML_Unserializer code)
23 * @copyright 1997-2009 The Authors
24 * @license   http://opensource.org/licenses/bsd-license New BSD License
25 * @version   Release: @package_version@
26 * @link      http://pear.php.net/package/PEAR
27 * @since     Class available since Release 1.4.0a1
28 */
29class PEAR_XMLParser
30{
31    /**
32     * unserilialized data
33     * @var string $_serializedData
34     */
35    var $_unserializedData = null;
36
37    /**
38     * name of the root tag
39     * @var string $_root
40     */
41    var $_root = null;
42
43    /**
44     * stack for all data that is found
45     * @var array    $_dataStack
46     */
47    var $_dataStack = array();
48
49    /**
50     * stack for all values that are generated
51     * @var array    $_valStack
52     */
53    var $_valStack = array();
54
55    /**
56     * current tag depth
57     * @var int    $_depth
58     */
59    var $_depth = 0;
60
61    /**
62     * The XML encoding to use
63     * @var string $encoding
64     */
65    var $encoding = 'ISO-8859-1';
66
67    /**
68     * @return array
69     */
70    function getData()
71    {
72        return $this->_unserializedData;
73    }
74
75    /**
76     * @param string xml content
77     * @return true|PEAR_Error
78     */
79    function parse($data)
80    {
81        if (!extension_loaded('xml')) {
82            include_once 'PEAR.php';
83            return PEAR::raiseError("XML Extension not found", 1);
84        }
85        $this->_dataStack =  $this->_valStack = array();
86        $this->_depth = 0;
87
88        if (
89            strpos($data, 'encoding="UTF-8"')
90            || strpos($data, 'encoding="utf-8"')
91            || strpos($data, "encoding='UTF-8'")
92            || strpos($data, "encoding='utf-8'")
93        ) {
94            $this->encoding = 'UTF-8';
95        }
96
97        $xp = xml_parser_create($this->encoding);
98        xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, 0);
99        xml_set_object($xp, $this);
100        xml_set_element_handler($xp, 'startHandler', 'endHandler');
101        xml_set_character_data_handler($xp, 'cdataHandler');
102        if (!xml_parse($xp, $data)) {
103            $msg = xml_error_string(xml_get_error_code($xp));
104            $line = xml_get_current_line_number($xp);
105            xml_parser_free($xp);
106            include_once 'PEAR.php';
107            return PEAR::raiseError("XML Error: '$msg' on line '$line'", 2);
108        }
109        xml_parser_free($xp);
110        return true;
111    }
112
113    /**
114     * Start element handler for XML parser
115     *
116     * @access private
117     * @param  object $parser  XML parser object
118     * @param  string $element XML element
119     * @param  array  $attribs attributes of XML tag
120     * @return void
121     */
122    function startHandler($parser, $element, $attribs)
123    {
124        $this->_depth++;
125        $this->_dataStack[$this->_depth] = null;
126
127        $val = array(
128            'name'         => $element,
129            'value'        => null,
130            'type'         => 'string',
131            'childrenKeys' => array(),
132            'aggregKeys'   => array()
133       );
134
135        if (count($attribs) > 0) {
136            $val['children'] = array();
137            $val['type'] = 'array';
138            $val['children']['attribs'] = $attribs;
139        }
140
141        array_push($this->_valStack, $val);
142    }
143
144    /**
145     * post-process data
146     *
147     * @param string $data
148     * @param string $element element name
149     */
150    function postProcess($data, $element)
151    {
152        return trim($data);
153    }
154
155    /**
156     * End element handler for XML parser
157     *
158     * @access private
159     * @param  object XML parser object
160     * @param  string
161     * @return void
162     */
163    function endHandler($parser, $element)
164    {
165        $value = array_pop($this->_valStack);
166        $data  = $this->postProcess($this->_dataStack[$this->_depth], $element);
167
168        // adjust type of the value
169        switch (strtolower($value['type'])) {
170            // unserialize an array
171            case 'array':
172                if ($data !== '') {
173                    $value['children']['_content'] = $data;
174                }
175
176                $value['value'] = isset($value['children']) ? $value['children'] : array();
177                break;
178
179            /*
180             * unserialize a null value
181             */
182            case 'null':
183                $data = null;
184                break;
185
186            /*
187             * unserialize any scalar value
188             */
189            default:
190                settype($data, $value['type']);
191                $value['value'] = $data;
192                break;
193        }
194
195        $parent = array_pop($this->_valStack);
196        if ($parent === null) {
197            $this->_unserializedData = &$value['value'];
198            $this->_root = &$value['name'];
199            return true;
200        }
201
202        // parent has to be an array
203        if (!isset($parent['children']) || !is_array($parent['children'])) {
204            $parent['children'] = array();
205            if ($parent['type'] != 'array') {
206                $parent['type'] = 'array';
207            }
208        }
209
210        if (!empty($value['name'])) {
211            // there already has been a tag with this name
212            if (in_array($value['name'], $parent['childrenKeys'])) {
213                // no aggregate has been created for this tag
214                if (!in_array($value['name'], $parent['aggregKeys'])) {
215                    if (isset($parent['children'][$value['name']])) {
216                        $parent['children'][$value['name']] = array($parent['children'][$value['name']]);
217                    } else {
218                        $parent['children'][$value['name']] = array();
219                    }
220                    array_push($parent['aggregKeys'], $value['name']);
221                }
222                array_push($parent['children'][$value['name']], $value['value']);
223            } else {
224                $parent['children'][$value['name']] = &$value['value'];
225                array_push($parent['childrenKeys'], $value['name']);
226            }
227        } else {
228            array_push($parent['children'],$value['value']);
229        }
230        array_push($this->_valStack, $parent);
231
232        $this->_depth--;
233    }
234
235    /**
236     * Handler for character data
237     *
238     * @access private
239     * @param  object XML parser object
240     * @param  string CDATA
241     * @return void
242     */
243    function cdataHandler($parser, $cdata)
244    {
245        $this->_dataStack[$this->_depth] .= $cdata;
246    }
247}