MDL-13435 xmlize - now throwing exceptions on parsing error. Credit goes to Tim/Mahmoud
[moodle.git] / lib / xmlize.php
CommitLineData
b3afe824 1<?php
2
a93ea924 3/**
4 * xmlize.php - xmlize() is by Hans Anderson, {@link http://www.hansanderson.com/contact/}
b3afe824 5 *
6 * Ye Ole "Feel Free To Use it However" License [PHP, BSD, GPL].
7 * some code in xml_depth is based on code written by other PHPers
8 * as well as one Perl script. Poor programming practice and organization
9 * on my part is to blame for the credit these people aren't receiving.
10 * None of the code was copyrighted, though.
11 *
5423b5f5
EL
12 * @package core
13 * @subpackage lib
a93ea924 14 * @author Hans Anderson
15 * @version This is a stable release, 1.0. I don't foresee any changes, but you
16 * might check {@link http://www.hansanderson.com/php/xml/} to see
b868d3d9 17 * @copyright Hans Anderson
18 * @license Feel Free To Use it However
a93ea924 19 */
20
21/**
5423b5f5 22 * Exception thrown when there is an error parsing an XML file.
b3afe824 23 *
5423b5f5
EL
24 * @copyright 2010 The Open University
25 */
26class xml_format_exception extends moodle_exception {
27 /** @var string */
28 public $errorstring;
29 public $line;
30 public $char;
31 function __construct($errorstring, $line, $char, $link = '') {
32 $this->errorstring = $errorstring;
33 $this->line = $line;
34 $this->char = $char;
35
36 $a = new stdClass();
37 $a->errorstring = $errorstring;
38 $a->errorline = $line;
39 $a->errorchar = $char;
40 parent::__construct('errorparsingxml', 'error', $link, $a);
41 }
42}
43
44/**
45 * Create an array structure from an XML string.
46 *
47 * Usage:<br>
a93ea924 48 * <code>
49 * $xml = xmlize($array);
50 * </code>
51 * See the function {@link traverse_xmlize()} for information about the
b3afe824 52 * structure of the array, it's much easier to explain by showing you.
53 * Be aware that the array is somewhat tricky. I use xmlize all the time,
a93ea924 54 * but still need to use {@link traverse_xmlize()} quite often to show me the structure!
b3afe824 55 *
a93ea924 56 * THIS IS A PHP 5 VERSION:
57 *
58 * This modified version basically has a new optional parameter
59 * to specify an OUTPUT encoding. If not specified, it defaults to UTF-8.
60 * I recommend you to read this PHP bug. There you can see how PHP4, PHP5.0.0
61 * and PHP5.0.2 will handle this.
62 * {@link http://bugs.php.net/bug.php?id=29711}
63 * Ciao, Eloy :-)
64 *
5423b5f5
EL
65 * @param string $data The XML source to parse.
66 * @param int $whitespace If set to 1 allows the parser to skip "space" characters in xml document. Default is 1
a93ea924 67 * @param string $encoding Specify an OUTPUT encoding. If not specified, it defaults to UTF-8.
5423b5f5
EL
68 * @param bool $reporterrors if set to true, then a {@link xml_format_exception}
69 * exception will be thrown if the XML is not well-formed. Otherwise errors are ignored.
70 * @return array representation of the parsed XML.
b3afe824 71 */
5423b5f5 72function xmlize($data, $whitespace = 1, $encoding = 'UTF-8', $reporterrors = false) {
b3afe824 73
74 $data = trim($data);
5423b5f5 75 $vals = array();
b7976058 76 $parser = xml_parser_create($encoding);
b3afe824 77 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
5423b5f5
EL
78 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $whitespace);
79 xml_parse_into_struct($parser, $data, $vals);
80
81 // Error handling when the xml file is not well-formed
82 if ($reporterrors) {
83 $errorcode = xml_get_error_code($parser);
84 if ($errorcode) {
85 $exception = new xml_format_exception(xml_error_string($errorcode),
86 xml_get_current_line_number($parser),
87 xml_get_current_column_number($parser));
88 xml_parser_free($parser);
89 throw $exception;
90 }
91 }
b3afe824 92 xml_parser_free($parser);
93
94 $i = 0;
20355e5a 95 if (empty($vals)) {
96 // XML file is invalid or empty, return false
97 return false;
98 }
b3afe824 99
5423b5f5 100 $array = array();
b3afe824 101 $tagname = $vals[$i]['tag'];
5423b5f5 102 if (isset($vals[$i]['attributes'])) {
b3afe824 103 $array[$tagname]['@'] = $vals[$i]['attributes'];
104 } else {
105 $array[$tagname]['@'] = array();
106 }
107
108 $array[$tagname]["#"] = xml_depth($vals, $i);
109
b3afe824 110 return $array;
111}
112
a93ea924 113/**
114 * @internal You don't need to do anything with this function, it's called by
115 * xmlize. It's a recursive function, calling itself as it goes deeper
b3afe824 116 * into the xml levels. If you make any improvements, please let me know.
da72d76d 117 * @access private
b3afe824 118 */
b3afe824 119function xml_depth($vals, &$i) {
120 $children = array();
121
122 if ( isset($vals[$i]['value']) )
123 {
124 array_push($children, $vals[$i]['value']);
125 }
126
127 while (++$i < count($vals)) {
128
129 switch ($vals[$i]['type']) {
130
131 case 'open':
132
133 if ( isset ( $vals[$i]['tag'] ) )
134 {
135 $tagname = $vals[$i]['tag'];
136 } else {
137 $tagname = '';
138 }
139
140 if ( isset ( $children[$tagname] ) )
141 {
142 $size = sizeof($children[$tagname]);
143 } else {
144 $size = 0;
145 }
146
147 if ( isset ( $vals[$i]['attributes'] ) ) {
148 $children[$tagname][$size]['@'] = $vals[$i]["attributes"];
149
150 }
151
152 $children[$tagname][$size]['#'] = xml_depth($vals, $i);
153
154 break;
155
156
157 case 'cdata':
158 array_push($children, $vals[$i]['value']);
159 break;
160
161 case 'complete':
162 $tagname = $vals[$i]['tag'];
163
164 if( isset ($children[$tagname]) )
165 {
166 $size = sizeof($children[$tagname]);
167 } else {
168 $size = 0;
169 }
170
171 if( isset ( $vals[$i]['value'] ) )
172 {
173 $children[$tagname][$size]["#"] = $vals[$i]['value'];
174 } else {
175 $children[$tagname][$size]["#"] = '';
176 }
177
178 if ( isset ($vals[$i]['attributes']) ) {
179 $children[$tagname][$size]['@']
180 = $vals[$i]['attributes'];
181 }
182
183 break;
184
185 case 'close':
186 return $children;
187 break;
188 }
189
190 }
191
192 return $children;
193
194
195}
196
197
a93ea924 198/**
199 * This helps you understand the structure of the array {@link xmlize()} outputs
b3afe824 200 *
a93ea924 201 * Function by acebone@f2s.com, a HUGE help!<br>
202 * Usage:<br>
203 * <code>
b3afe824 204 * traverse_xmlize($xml, 'xml_');
205 * print '<pre>' . implode("", $traverse_array . '</pre>';
a93ea924 206 * </code>
207 * @author acebone@f2s.com
208 * @param array $array ?
209 * @param string $arrName ?
210 * @param int $level ?
211 * @return int
212 * @todo Finish documenting this function
b3afe824 213 */
a93ea924 214function traverse_xmlize($array, $arrName = 'array', $level = 0) {
b3afe824 215
216 foreach($array as $key=>$val)
217 {
218 if ( is_array($val) )
219 {
a93ea924 220 traverse_xmlize($val, $arrName . '[' . $key . ']', $level + 1);
b3afe824 221 } else {
222 $GLOBALS['traverse_array'][] = '$' . $arrName . '[' . $key . '] = "' . $val . "\"\n";
223 }
224 }
225
226 return 1;
227
228}