[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/backup/util/xml/parser/processors/ -> grouped_parser_processor.class.php (source)

   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  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1