1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * @package moodlecore 20 * @subpackage xml 21 * @copyright 2003 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 require_once($CFG->dirroot.'/backup/util/xml/parser/processors/progressive_parser_processor.class.php'); 26 27 /** 28 * Abstract xml parser processor to to simplify and dispatch parsed chunks 29 * 30 * This @progressive_parser_processor handles the requested paths, 31 * performing some conversions from the original "propietary array format" 32 * used by the @progressive_parser to a simplified structure to be used 33 * easily. Found attributes are converted automatically to tags and cdata 34 * to simpler values. 35 * 36 * Note: final tag attributes are discarded completely! 37 * 38 * TODO: Complete phpdocs 39 */ 40 abstract class simplified_parser_processor extends progressive_parser_processor { 41 protected $paths; // array of paths we are interested on 42 protected $parentpaths; // array of parent paths of the $paths 43 protected $parentsinfo; // array of parent attributes to be added as child tags 44 protected $startendinfo;// array (stack) of startend information 45 46 public function __construct(array $paths = array()) { 47 parent::__construct(); 48 $this->paths = array(); 49 $this->parentpaths = array(); 50 $this->parentsinfo = array(); 51 $this->startendinfo = array(); 52 // Add paths and parentpaths. We are looking for attributes there 53 foreach ($paths as $key => $path) { 54 $this->add_path($path); 55 } 56 } 57 58 public function add_path($path) { 59 $this->paths[] = $path; 60 $this->parentpaths[] = progressive_parser::dirname($path); 61 } 62 63 /** 64 * Get the already simplified chunk and dispatch it 65 */ 66 abstract protected function dispatch_chunk($data); 67 68 /** 69 * Get one selected path and notify about start 70 */ 71 abstract protected function notify_path_start($path); 72 73 /** 74 * Get one selected path and notify about end 75 */ 76 abstract protected function notify_path_end($path); 77 78 /** 79 * Get one chunk of parsed data and make it simpler 80 * adding attributes as tags and delegating to 81 * dispatch_chunk() the procesing of the resulting chunk 82 */ 83 public function process_chunk($data) { 84 // Precalculate some vars for readability 85 $path = $data['path']; 86 $parentpath = progressive_parser::dirname($path); 87 $tag = basename($path); 88 89 // If the path is a registered parent one, store all its tags 90 // so, we'll be able to find attributes later when processing 91 // (child) registered paths (to get attributes if present) 92 if ($this->path_is_selected_parent($path)) { // if path is parent 93 if (isset($data['tags'])) { // and has tags, save them 94 $this->parentsinfo[$path] = $data['tags']; 95 } 96 } 97 98 // If the path is a registered one, let's process it 99 if ($this->path_is_selected($path)) { 100 101 // Send all the pending notify_path_start/end() notifications 102 $this->process_pending_startend_notifications($path, 'start'); 103 104 // First of all, look for attributes available at parentsinfo 105 // in order to get them available as normal tags 106 if (isset($this->parentsinfo[$parentpath][$tag]['attrs'])) { 107 $data['tags'] = array_merge($this->parentsinfo[$parentpath][$tag]['attrs'], $data['tags']); 108 unset($this->parentsinfo[$parentpath][$tag]['attrs']); 109 } 110 // Now, let's simplify the tags array, ignoring tag attributtes and 111 // reconverting to simpler name => value array. At the same time, 112 // check for all the tag values being whitespace-string values, if all them 113 // are whitespace strings, we aren't going to postprocess/dispatch the chunk 114 $alltagswhitespace = true; 115 foreach ($data['tags'] as $key => $value) { 116 // If the value is already a single value, do nothing 117 // surely was added above from parentsinfo attributes, 118 // so we'll process the chunk always 119 if (!is_array($value)) { 120 $alltagswhitespace = false; 121 continue; 122 } 123 124 // If the path including the tag name matches another selected path 125 // (registered or parent) and is null or begins with linefeed, we know it's part 126 // of another chunk, delete it, another chunk will contain that info 127 if ($this->path_is_selected($path . '/' . $key) || 128 $this->path_is_selected_parent($path . '/' . $key)) { 129 if (!isset($value['cdata']) || substr($value['cdata'], 0, 1) === "\n") { 130 unset($data['tags'][$key]); 131 continue; 132 } 133 } 134 135 // Convert to simple name => value array 136 $data['tags'][$key] = isset($value['cdata']) ? $value['cdata'] : null; 137 138 // Check $alltagswhitespace continues being true 139 if ($alltagswhitespace && strlen($data['tags'][$key]) !== 0 && trim($data['tags'][$key]) !== '') { 140 $alltagswhitespace = false; // Found non-whitespace value 141 } 142 } 143 144 // Arrived here, if the chunk has tags and not all tags are whitespace, 145 // send it to postprocess filter that will decide about dispatching. Else 146 // skip the chunk completely 147 if (!empty($data['tags']) && !$alltagswhitespace) { 148 return $this->postprocess_chunk($data); 149 } else { 150 $this->chunks--; // Chunk skipped 151 } 152 } else { 153 $this->chunks--; // Chunk skipped 154 } 155 156 return true; 157 } 158 159 /** 160 * The parser fires this each time one path is going to be parsed 161 * 162 * @param string $path xml path which parsing has started 163 */ 164 public function before_path($path) { 165 if ($this->path_is_selected($path)) { 166 $this->startendinfo[] = array('path' => $path, 'action' => 'start'); 167 } 168 } 169 170 /** 171 * The parser fires this each time one path has been parsed 172 * 173 * @param string $path xml path which parsing has ended 174 */ 175 public function after_path($path) { 176 $toprocess = false; 177 // If the path being closed matches (same or parent) the first path in the stack 178 // we process pending startend notifications until one matching end is found 179 if ($element = reset($this->startendinfo)) { 180 $elepath = $element['path']; 181 $eleaction = $element['action']; 182 if (strpos($elepath, $path) === 0) { 183 $toprocess = true; 184 } 185 186 // Also, if the stack of startend notifications is empty, we can process current end 187 // path safely 188 } else { 189 $toprocess = true; 190 } 191 if ($this->path_is_selected($path)) { 192 $this->startendinfo[] = array('path' => $path, 'action' => 'end'); 193 } 194 // Send all the pending startend notifications if decided to do so 195 if ($toprocess) { 196 $this->process_pending_startend_notifications($path, 'end'); 197 } 198 } 199 200 201 // Protected API starts here 202 203 /** 204 * Adjust start/end til finding one match start/end path (included) 205 * 206 * This will trigger all the pending {@see notify_path_start} and 207 * {@see notify_path_end} calls for one given path and action 208 * 209 * @param string path the path to look for as limit 210 * @param string action the action to look for as limit 211 */ 212 protected function process_pending_startend_notifications($path, $action) { 213 214 // Iterate until one matching path and action is found (or the array is empty) 215 $elecount = count($this->startendinfo); 216 $elematch = false; 217 while ($elecount > 0 && !$elematch) { 218 $element = array_shift($this->startendinfo); 219 $elecount--; 220 $elepath = $element['path']; 221 $eleaction = $element['action']; 222 223 if ($elepath == $path && $eleaction == $action) { 224 $elematch = true; 225 } 226 227 if ($eleaction == 'start') { 228 $this->notify_path_start($elepath); 229 } else { 230 $this->notify_path_end($elepath); 231 } 232 } 233 } 234 235 protected function postprocess_chunk($data) { 236 $this->dispatch_chunk($data); 237 } 238 239 protected function path_is_selected($path) { 240 return in_array($path, $this->paths); 241 } 242 243 protected function path_is_selected_parent($path) { 244 return in_array($path, $this->parentpaths); 245 } 246 247 /** 248 * Returns the first selected parent if available or false 249 */ 250 protected function selected_parent_exists($path) { 251 $parentpath = progressive_parser::dirname($path); 252 while ($parentpath != '/') { 253 if ($this->path_is_selected($parentpath)) { 254 return $parentpath; 255 } 256 $parentpath = progressive_parser::dirname($parentpath); 257 } 258 return false; 259 } 260 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body