84a89c868e2b8070da51c85402b94aa1334f15ac
[moodle.git] / backup / util / xml / parser / processors / grouped_parser_processor.class.php
1 <?php
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/>.
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  */
25 require_once($CFG->dirroot.'/backup/util/xml/parser/processors/simplified_parser_processor.class.php');
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 {
43     protected $groupedpaths; // Paths we are requesting grouped
44     protected $currentdata;  // Where we'll be acummulating data
46     public function __construct(array $paths = array()) {
47         $this->groupedpaths = array();
48         $this->currentdata = null;
49         parent::__construct($paths);
50     }
52     public function add_path($path, $grouped = false) {
53         if ($grouped) {
54             // Check there is no parent in the branch being grouped
55             if ($found = $this->grouped_parent_exists($path)) {
56                 $a = new stdclass();
57                 $a->path = $path;
58                 $a->parent = $found;
59                 throw new progressive_parser_exception('xml_grouped_parent_found', $a);
60             }
61             // Check there is no child in the branch being grouped
62             if ($found = $this->grouped_child_exists($path)) {
63                 $a = new stdclass();
64                 $a->path = $path;
65                 $a->child = $found;
66                 throw new progressive_parser_exception('xml_grouped_child_found', $a);
67             }
68             $this->groupedpaths[] = $path;
69         }
70         parent::add_path($path);
71     }
73     /**
74      * Dispatch grouped chunks safely once their end tag happens
75      */
76     public function after_path($path) {
77         if ($this->path_is_grouped($path)) {
78             // Any accumulated information must be in
79             // currentdata, properly built
80             $data = $this->currentdata[$path];
81             unset($this->currentdata[$path]);
82             // TODO: If running under DEBUG_DEVELOPER notice about >1MB grouped chunks
83             $this->dispatch_chunk($data);
84         }
85     }
87 // Protected API starts here
89     /**
90      * Override this method so grouping will be happening here
91      * also deciding between accumulating/dispatching
92      */
93     protected function postprocess_chunk($data) {
94         $path = $data['path'];
95         // If the chunk is a grouped one, simply put it into currentdata
96         if ($this->path_is_grouped($path)) {
97             $this->currentdata[$path] = $data;
99         // If the chunk is child of grouped one, add it to currentdata
100         } else if ($grouped = $this->grouped_parent_exists($path)) {
101             $this->build_currentdata($grouped, $data);
102             $this->chunks--; // not counted, as it's accumulated
104         // No grouped nor child of grouped, dispatch it
105         } else {
106             $this->dispatch_chunk($data);
107         }
108     }
110     protected function path_is_grouped($path) {
111         return in_array($path, $this->groupedpaths);
112     }
114     /**
115      * Function that will look for any
116      * parent for the given path, returning it if found,
117      * false if not
118      */
119     protected function processed_parent_exists($path) {
120         $parentpath = dirname($path);
121         while ($parentpath != '/') {
122             if ($this->path_is_selected($parentpath)) {
123                 return $parentpath;
124             }
125             $parentpath = dirname($parentpath);
126         }
127         return false;
128     }
131     /**
132      * Function that will look for any grouped
133      * parent for the given path, returning it if found,
134      * false if not
135      */
136     protected function grouped_parent_exists($path) {
137         $parentpath = dirname($path);
138         while ($parentpath != '/') {
139             if ($this->path_is_grouped($parentpath)) {
140                 return $parentpath;
141             }
142             $parentpath = dirname($parentpath);
143         }
144         return false;
145     }
147     /**
148      * Function that will look for any grouped
149      * child for the given path, returning it if found,
150      * false if not
151      */
152     protected function grouped_child_exists($path) {
153         $childpath = $path . '/';
154         foreach ($this->groupedpaths as $groupedpath) {
155             if (strpos($groupedpath, $childpath) === 0) {
156                 return $groupedpath;
157             }
158         }
159         return false;
160     }
162     /**
163      * This function will accumulate the chunk into the specified
164      * grouped element for later dispatching once it is complete
165      */
166     protected function build_currentdata($grouped, $data) {
167         // Check the grouped already exists into currentdata
168         if (!array_key_exists($grouped, $this->currentdata)) {
169             $a = new stdclass();
170             $a->grouped = $grouped;
171             $a->child = $data['path'];
172             throw new progressive_parser_exception('xml_cannot_add_to_grouped', $a);
173         }
174         $this->add_missing_sub($grouped, $data['path'], $data['tags']);
175     }
177     /**
178      * Add non-existing subarray elements
179      */
180     protected function add_missing_sub($grouped, $path, $tags) {
182         // Remember tag being processed
183         $processedtag = basename($path);
185         $info =& $this->currentdata[$grouped]['tags'];
186         $hierarchyarr = explode('/', str_replace($grouped . '/', '', $path));
188         $previouselement = '';
189         $currentpath = '';
191         foreach ($hierarchyarr as $index => $element) {
193             $currentpath = $currentpath . '/' . $element;
195             // If element is already set and it's not
196             // the processed one (with tags) fast move the $info
197             // pointer and continue
198             if ($element !== $processedtag && isset($info[$element])) {
199                 $previouselement = $element;
200                 $info =& $info[$element];
201                 continue;
202             }
204             // If previous element already has occurrences
205             // we move $info pointer there (only if last is
206             // numeric occurrence)
207             if (!empty($previouselement) && is_array($info) && count($info) > 0) {
208                 end($info);
209                 $key = key($info);
210                 if ((int) $key === $key) {
211                     $info =& $info[$key];
212                 }
213             }
215             // Create element if not defined
216             if (!isset($info[$element])) {
217                 // First into last element if present
218                 $info[$element] = array();
219             }
221             // If element is the current one, add information
222             if ($element === $processedtag) {
223                 $info[$element][] = $tags;
224             }
226             $previouselement = $element;
227             $info =& $info[$element];
228         }
229     }