MDL-22699 restore parser - new grouped parser, useful for multilevel tags
[moodle.git] / backup / util / xml / parser / processors / grouped_parser_processor.class.php
CommitLineData
4c7f6ac6
EL
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
25require_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 */
41abstract 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 public function __construct(array $paths = array()) {
47 $this->groupedpaths = array();
48 $this->currentdata = null;
49 parent::__construct($paths);
50 }
51
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 }
72
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 }
86
87// Protected API starts here
88
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;
98
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
103
104 // No grouped nor child of grouped, dispatch it
105 } else {
106 $this->dispatch_chunk($data);
107 }
108 }
109
110 protected function path_is_grouped($path) {
111 return in_array($path, $this->groupedpaths);
112 }
113
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 }
129
130
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 }
146
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 }
161
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 }
176
177 /**
178 * Add non-existing subarray elements
179 */
180 protected function add_missing_sub($grouped, $path, $tags) {
181
182 // Remember tag being processed
183 $processedtag = basename($path);
184
185 $info =& $this->currentdata[$grouped]['tags'];
186 $hierarchyarr = explode('/', str_replace($grouped . '/', '', $path));
187
188 $previouselement = '';
189 $currentpath = '';
190
191 foreach ($hierarchyarr as $index => $element) {
192
193 $currentpath = $currentpath . '/' . $element;
194
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 }
203
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 }
214
215 // Create element if not defined
216 if (!isset($info[$element])) {
217 // First into last element if present
218 $info[$element] = array();
219 }
220
221 // If element is the current one, add information
222 if ($element === $processedtag) {
223 $info[$element][] = $tags;
224 }
225
226 $previouselement = $element;
227 $info =& $info[$element];
228 }
229 }
230}