[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
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 2010 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/simplified_parser_processor.class.php'); 26 27 /** 28 * Abstract xml parser processor able to group chunks as configured 29 * and dispatch them to other arbitrary methods 30 * 31 * This @progressive_parser_processor handles the requested paths, 32 * allowing to group information under any of them, dispatching them 33 * to the methods specified 34 * 35 * Note memory increases as you group more and more paths, so use it for 36 * well-known structures being smaller enough (never to group MBs into one 37 * in-memory structure) 38 * 39 * TODO: Complete phpdocs 40 */ 41 abstract class grouped_parser_processor extends simplified_parser_processor { 42 43 protected $groupedpaths; // Paths we are requesting grouped 44 protected $currentdata; // Where we'll be acummulating data 45 46 /** 47 * Keep cache of parent directory paths for XML parsing. 48 * @var array 49 */ 50 protected $parentcache = array(); 51 52 /** 53 * Remaining space for parent directory paths. 54 * @var integer 55 */ 56 protected $parentcacheavailablesize = 2048; 57 58 public function __construct(array $paths = array()) { 59 $this->groupedpaths = array(); 60 $this->currentdata = null; 61 parent::__construct($paths); 62 } 63 64 public function add_path($path, $grouped = false) { 65 if ($grouped) { 66 // Check there is no parent in the branch being grouped 67 if ($found = $this->grouped_parent_exists($path)) { 68 $a = new stdclass(); 69 $a->path = $path; 70 $a->parent = $found; 71 throw new progressive_parser_exception('xml_grouped_parent_found', $a); 72 } 73 // Check there is no child in the branch being grouped 74 if ($found = $this->grouped_child_exists($path)) { 75 $a = new stdclass(); 76 $a->path = $path; 77 $a->child = $found; 78 throw new progressive_parser_exception('xml_grouped_child_found', $a); 79 } 80 $this->groupedpaths[$path] = true; 81 } 82 parent::add_path($path); 83 } 84 85 /** 86 * The parser fires this each time one path is going to be parsed 87 * 88 * @param string $path xml path which parsing has started 89 */ 90 public function before_path($path) { 91 if ($this->path_is_grouped($path) and !isset($this->currentdata[$path])) { 92 // If the grouped element itself does not contain any final tags, 93 // we would not get any chunk data for it. So we add an artificial 94 // empty data chunk here that will be eventually replaced with 95 // real data later in {@link self::postprocess_chunk()}. 96 $this->currentdata[$path] = array( 97 'path' => $path, 98 'level' => substr_count($path, '/') + 1, 99 'tags' => array(), 100 ); 101 } 102 if (!$this->grouped_parent_exists($path)) { 103 parent::before_path($path); 104 } 105 } 106 107 /** 108 * The parser fires this each time one path has been parsed 109 * 110 * @param string $path xml path which parsing has ended 111 */ 112 public function after_path($path) { 113 // Have finished one grouped path, dispatch it 114 if ($this->path_is_grouped($path)) { 115 // Any accumulated information must be in 116 // currentdata, properly built 117 $data = $this->currentdata[$path]; 118 unset($this->currentdata[$path]); 119 // Always, before dispatching any chunk, send all pending start notifications. 120 $this->process_pending_startend_notifications($path, 'start'); 121 // TODO: If running under DEBUG_DEVELOPER notice about >1MB grouped chunks 122 // And, finally, dispatch it. 123 $this->dispatch_chunk($data); 124 } 125 // Normal notification of path end 126 // Only if path is selected and not child of grouped 127 if (!$this->grouped_parent_exists($path)) { 128 parent::after_path($path); 129 } 130 } 131 132 // Protected API starts here 133 134 /** 135 * Override this method so grouping will be happening here 136 * also deciding between accumulating/dispatching 137 */ 138 protected function postprocess_chunk($data) { 139 $path = $data['path']; 140 // If the chunk is a grouped one, simply put it into currentdata 141 if ($this->path_is_grouped($path)) { 142 $this->currentdata[$path] = $data; 143 144 // If the chunk is child of grouped one, add it to currentdata 145 } else if ($grouped = $this->grouped_parent_exists($path)) { 146 $this->build_currentdata($grouped, $data); 147 $this->chunks--; // not counted, as it's accumulated 148 149 // No grouped nor child of grouped, dispatch it 150 } else { 151 $this->dispatch_chunk($data); 152 } 153 } 154 155 protected function path_is_grouped($path) { 156 return isset($this->groupedpaths[$path]); 157 } 158 159 /** 160 * Function that will look for any grouped 161 * parent for the given path, returning it if found, 162 * false if not 163 */ 164 protected function grouped_parent_exists($path) { 165 $parentpath = $this->get_parent_path($path); 166 167 while ($parentpath != '/') { 168 if ($this->path_is_grouped($parentpath)) { 169 return $parentpath; 170 } 171 $parentpath = $this->get_parent_path($parentpath); 172 } 173 return false; 174 } 175 176 /** 177 * Get the parent path using a local cache for performance. 178 * 179 * @param $path string The pathname you wish to obtain the parent name for. 180 * @return string The parent pathname. 181 */ 182 protected function get_parent_path($path) { 183 if (!isset($this->parentcache[$path])) { 184 $this->parentcache[$path] = progressive_parser::dirname($path); 185 $this->parentcacheavailablesize--; 186 if ($this->parentcacheavailablesize < 0) { 187 // Older first is cheaper than LRU. We use 10% as items are grouped together and the large quiz 188 // restore from MDL-40585 used only 600 parent paths. This is an XML heirarchy, so common paths 189 // are grouped near each other. eg; /question_bank/question_category/question/element. After keeping 190 // question_bank paths in the cache when we move to another area and the question_bank cache is not 191 // useful any longer. 192 $this->parentcache = array_slice($this->parentcache, 200, null, true); 193 $this->parentcacheavailablesize += 200; 194 } 195 } 196 return $this->parentcache[$path]; 197 } 198 199 200 /** 201 * Function that will look for any grouped 202 * child for the given path, returning it if found, 203 * false if not 204 */ 205 protected function grouped_child_exists($path) { 206 $childpath = $path . '/'; 207 foreach ($this->groupedpaths as $groupedpath => $set) { 208 if (strpos($groupedpath, $childpath) === 0) { 209 return $groupedpath; 210 } 211 } 212 return false; 213 } 214 215 /** 216 * This function will accumulate the chunk into the specified 217 * grouped element for later dispatching once it is complete 218 */ 219 protected function build_currentdata($grouped, $data) { 220 // Check the grouped already exists into currentdata 221 if (!is_array($this->currentdata) or !array_key_exists($grouped, $this->currentdata)) { 222 $a = new stdclass(); 223 $a->grouped = $grouped; 224 $a->child = $data['path']; 225 throw new progressive_parser_exception('xml_cannot_add_to_grouped', $a); 226 } 227 $this->add_missing_sub($grouped, $data['path'], $data['tags']); 228 } 229 230 /** 231 * Add non-existing subarray elements 232 */ 233 protected function add_missing_sub($grouped, $path, $tags) { 234 235 // Remember tag being processed 236 $processedtag = basename($path); 237 238 $info =& $this->currentdata[$grouped]['tags']; 239 $hierarchyarr = explode('/', str_replace($grouped . '/', '', $path)); 240 241 $previouselement = ''; 242 $currentpath = ''; 243 244 foreach ($hierarchyarr as $index => $element) { 245 246 $currentpath = $currentpath . '/' . $element; 247 248 // If element is already set and it's not 249 // the processed one (with tags) fast move the $info 250 // pointer and continue 251 if ($element !== $processedtag && isset($info[$element])) { 252 $previouselement = $element; 253 $info =& $info[$element]; 254 continue; 255 } 256 257 // If previous element already has occurrences 258 // we move $info pointer there (only if last is 259 // numeric occurrence) 260 if (!empty($previouselement) && is_array($info) && count($info) > 0) { 261 end($info); 262 $key = key($info); 263 if ((int) $key === $key) { 264 $info =& $info[$key]; 265 } 266 } 267 268 // Create element if not defined 269 if (!isset($info[$element])) { 270 // First into last element if present 271 $info[$element] = array(); 272 } 273 274 // If element is the current one, add information 275 if ($element === $processedtag) { 276 $info[$element][] = $tags; 277 } 278 279 $previouselement = $element; 280 $info =& $info[$element]; 281 } 282 } 283 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |