|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00054 class progressive_parser { 00055 00056 protected $xml_parser; // PHP's low level XML SAX parser 00057 protected $file; // full path to file being progressively parsed | => mutually exclusive 00058 protected $contents; // contents being progressively parsed | 00059 protected $procesor; // progressive_parser_procesor to be used to publish processed information 00060 00061 protected $level; // level of the current tag 00062 protected $path; // path of the current tag 00063 protected $accum; // accumulated char data of the current tag 00064 protected $attrs; // attributes of the current tag 00065 00066 protected $topush; // array containing current level information being parsed to be "pushed" 00067 protected $prevlevel; // level of the previous tag processed - to detect pushing places 00068 protected $currtag; // name/value/attributes of the tag being processed 00069 00070 public function __construct($case_folding = false) { 00071 $this->xml_parser = xml_parser_create('UTF-8'); 00072 xml_parser_set_option($this->xml_parser, XML_OPTION_CASE_FOLDING, $case_folding); 00073 xml_set_object($this->xml_parser, $this); 00074 xml_set_element_handler($this->xml_parser, array($this, 'start_tag'), array($this, 'end_tag')); 00075 xml_set_character_data_handler($this->xml_parser, array($this, 'char_data')); 00076 00077 $this->file = null; 00078 $this->contents = null; 00079 $this->procesor = null; 00080 $this->level = 0; 00081 $this->path = ''; 00082 $this->accum = ''; 00083 $this->attrs = array(); 00084 $this->topush = array(); 00085 $this->prevlevel = 0; 00086 $this->currtag = array(); 00087 } 00088 00089 /* 00090 * Sets the XML file to be processed by the parser 00091 */ 00092 public function set_file($file) { 00093 if (!file_exists($file) || (!is_readable($file))) { 00094 throw new progressive_parser_exception('invalid_file_to_parse'); 00095 } 00096 $this->file = $file; 00097 $this->contents = null; 00098 } 00099 00100 /* 00101 * Sets the XML contents to be processed by the parser 00102 */ 00103 public function set_contents($contents) { 00104 if (empty($contents)) { 00105 throw new progressive_parser_exception('invalid_contents_to_parse'); 00106 } 00107 $this->contents = $contents; 00108 $this->file = null; 00109 } 00110 00111 /* 00112 * Define the @progressive_parser_processor in charge of processing the parsed chunks 00113 */ 00114 public function set_processor($processor) { 00115 if (!$processor instanceof progressive_parser_processor) { 00116 throw new progressive_parser_exception('invalid_parser_processor'); 00117 } 00118 $this->processor = $processor; 00119 } 00120 00121 /* 00122 * Process the XML, delegating found chunks to the @progressive_parser_processor 00123 */ 00124 public function process() { 00125 if (empty($this->processor)) { 00126 throw new progressive_parser_exception('undefined_parser_processor'); 00127 } 00128 if (empty($this->file) && empty($this->contents)) { 00129 throw new progressive_parser_exception('undefined_xml_to_parse'); 00130 } 00131 if (is_null($this->xml_parser)) { 00132 throw new progressive_parser_exception('progressive_parser_already_used'); 00133 } 00134 if ($this->file) { 00135 $fh = fopen($this->file, 'r'); 00136 while ($buffer = fread($fh, 8192)) { 00137 $this->parse($buffer, feof($fh)); 00138 } 00139 fclose($fh); 00140 } else { 00141 $this->parse($this->contents, true); 00142 } 00143 xml_parser_free($this->xml_parser); 00144 $this->xml_parser = null; 00145 } 00146 00151 public static function dirname($path) { 00152 return str_replace('\\', '/', dirname($path)); 00153 } 00154 00155 // Protected API starts here 00156 00157 protected function parse($data, $eof) { 00158 if (!xml_parse($this->xml_parser, $data, $eof)) { 00159 throw new progressive_parser_exception( 00160 'xml_parsing_error', null, 00161 sprintf('XML error: %s at line %d, column %d', 00162 xml_error_string(xml_get_error_code($this->xml_parser)), 00163 xml_get_current_line_number($this->xml_parser), 00164 xml_get_current_column_number($this->xml_parser))); 00165 } 00166 } 00167 00168 protected function publish($data) { 00169 $this->processor->receive_chunk($data); 00170 } 00171 00175 protected function inform_start($path) { 00176 $this->processor->before_path($path); 00177 } 00178 00182 protected function inform_end($path) { 00183 $this->processor->after_path($path); 00184 } 00185 00186 protected function postprocess_cdata($data) { 00187 return $this->processor->process_cdata($data); 00188 } 00189 00190 protected function start_tag($parser, $tag, $attributes) { 00191 00192 // Normal update of parser internals 00193 $this->level++; 00194 $this->path .= '/' . $tag; 00195 $this->accum = ''; 00196 $this->attrs = !empty($attributes) ? $attributes : array(); 00197 00198 // Inform processor we are about to start one tag 00199 $this->inform_start($this->path); 00200 00201 // Entering a new inner level, publish all the information available 00202 if ($this->level > $this->prevlevel) { 00203 if (!empty($this->currtag) && (!empty($this->currtag['attrs']) || !empty($this->currtag['cdata']))) { 00204 // We always add the last not-empty repetition. Empty ones are ignored. 00205 if (isset($this->topush['tags'][$this->currtag['name']]) && trim($this->currtag['cdata']) === '') { 00206 // Do nothing, the tag already exists and the repetition is empty 00207 } else { 00208 $this->topush['tags'][$this->currtag['name']] = $this->currtag; 00209 } 00210 } 00211 if (!empty($this->topush['tags'])) { 00212 $this->publish($this->topush); 00213 } 00214 $this->currtag = array(); 00215 $this->topush = array(); 00216 } 00217 00218 // If not set, build to push common header 00219 if (empty($this->topush)) { 00220 $this->topush['path'] = progressive_parser::dirname($this->path); 00221 $this->topush['level'] = $this->level; 00222 $this->topush['tags'] = array(); 00223 } 00224 00225 // Handling a new tag, create it 00226 $this->currtag['name'] = $tag; 00227 // And add attributes if present 00228 if ($this->attrs) { 00229 $this->currtag['attrs'] = $this->attrs; 00230 } 00231 00232 // For the records 00233 $this->prevlevel = $this->level; 00234 } 00235 00236 protected function end_tag($parser, $tag) { 00237 00238 // Ending rencently started tag, add value to current tag 00239 if ($this->level == $this->prevlevel) { 00240 $this->currtag['cdata'] = $this->postprocess_cdata($this->accum); 00241 // We always add the last not-empty repetition. Empty ones are ignored. 00242 if (isset($this->topush['tags'][$this->currtag['name']]) && trim($this->currtag['cdata']) === '') { 00243 // Do nothing, the tag already exists and the repetition is empty 00244 } else { 00245 $this->topush['tags'][$this->currtag['name']] = $this->currtag; 00246 } 00247 $this->currtag = array(); 00248 } 00249 00250 // Leaving one level, publish all the information available 00251 if ($this->level < $this->prevlevel) { 00252 if (!empty($this->topush['tags'])) { 00253 $this->publish($this->topush); 00254 } 00255 $this->currtag = array(); 00256 $this->topush = array(); 00257 } 00258 00259 // For the records 00260 $this->prevlevel = $this->level; 00261 00262 // Inform processor we have finished one tag 00263 $this->inform_end($this->path); 00264 00265 // Normal update of parser internals 00266 $this->level--; 00267 $this->path = progressive_parser::dirname($this->path); 00268 } 00269 00270 protected function char_data($parser, $data) { 00271 $this->accum .= $data; 00272 } 00273 } 00274 00275 /* 00276 * Exception class used by all the @progressive_parser stuff 00277 */ 00278 class progressive_parser_exception extends moodle_exception { 00279 00280 public function __construct($errorcode, $a=NULL, $debuginfo=null) { 00281 parent::__construct($errorcode, 'error', '', $a, null, $debuginfo); 00282 } 00283 }