mirrored from git://git.moodle.org/moodle.git
-
Notifications
You must be signed in to change notification settings - Fork 6.6k
/
grouped_parser_processor.class.php
246 lines (218 loc) · 8.68 KB
/
grouped_parser_processor.class.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @package moodlecore
* @subpackage xml
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once($CFG->dirroot.'/backup/util/xml/parser/processors/simplified_parser_processor.class.php');
/**
* Abstract xml parser processor able to group chunks as configured
* and dispatch them to other arbitrary methods
*
* This @progressive_parser_processor handles the requested paths,
* allowing to group information under any of them, dispatching them
* to the methods specified
*
* Note memory increases as you group more and more paths, so use it for
* well-known structures being smaller enough (never to group MBs into one
* in-memory structure)
*
* TODO: Complete phpdocs
*/
abstract class grouped_parser_processor extends simplified_parser_processor {
protected $groupedpaths; // Paths we are requesting grouped
protected $currentdata; // Where we'll be acummulating data
public function __construct(array $paths = array()) {
$this->groupedpaths = array();
$this->currentdata = null;
parent::__construct($paths);
}
public function add_path($path, $grouped = false) {
if ($grouped) {
// Check there is no parent in the branch being grouped
if ($found = $this->grouped_parent_exists($path)) {
$a = new stdclass();
$a->path = $path;
$a->parent = $found;
throw new progressive_parser_exception('xml_grouped_parent_found', $a);
}
// Check there is no child in the branch being grouped
if ($found = $this->grouped_child_exists($path)) {
$a = new stdclass();
$a->path = $path;
$a->child = $found;
throw new progressive_parser_exception('xml_grouped_child_found', $a);
}
$this->groupedpaths[] = $path;
}
parent::add_path($path);
}
/**
* The parser fires this each time one path is going to be parsed
*
* @param string $path xml path which parsing has started
*/
public function before_path($path) {
if ($this->path_is_grouped($path) and !isset($this->currentdata[$path])) {
// If the grouped element itself does not contain any final tags,
// we would not get any chunk data for it. So we add an artificial
// empty data chunk here that will be eventually replaced with
// real data later in {@link self::postprocess_chunk()}.
$this->currentdata[$path] = array(
'path' => $path,
'level' => substr_count($path, '/') + 1,
'tags' => array(),
);
}
if (!$this->grouped_parent_exists($path)) {
parent::before_path($path);
}
}
/**
* The parser fires this each time one path has been parsed
*
* @param string $path xml path which parsing has ended
*/
public function after_path($path) {
// Have finished one grouped path, dispatch it
if ($this->path_is_grouped($path)) {
// Any accumulated information must be in
// currentdata, properly built
$data = $this->currentdata[$path];
unset($this->currentdata[$path]);
// Always, before dispatching any chunk, send all pending start notifications.
$this->process_pending_startend_notifications($path, 'start');
// TODO: If running under DEBUG_DEVELOPER notice about >1MB grouped chunks
// And, finally, dispatch it.
$this->dispatch_chunk($data);
}
// Normal notification of path end
// Only if path is selected and not child of grouped
if (!$this->grouped_parent_exists($path)) {
parent::after_path($path);
}
}
// Protected API starts here
/**
* Override this method so grouping will be happening here
* also deciding between accumulating/dispatching
*/
protected function postprocess_chunk($data) {
$path = $data['path'];
// If the chunk is a grouped one, simply put it into currentdata
if ($this->path_is_grouped($path)) {
$this->currentdata[$path] = $data;
// If the chunk is child of grouped one, add it to currentdata
} else if ($grouped = $this->grouped_parent_exists($path)) {
$this->build_currentdata($grouped, $data);
$this->chunks--; // not counted, as it's accumulated
// No grouped nor child of grouped, dispatch it
} else {
$this->dispatch_chunk($data);
}
}
protected function path_is_grouped($path) {
return in_array($path, $this->groupedpaths);
}
/**
* Function that will look for any grouped
* parent for the given path, returning it if found,
* false if not
*/
protected function grouped_parent_exists($path) {
$parentpath = progressive_parser::dirname($path);
while ($parentpath != '/') {
if ($this->path_is_grouped($parentpath)) {
return $parentpath;
}
$parentpath = progressive_parser::dirname($parentpath);
}
return false;
}
/**
* Function that will look for any grouped
* child for the given path, returning it if found,
* false if not
*/
protected function grouped_child_exists($path) {
$childpath = $path . '/';
foreach ($this->groupedpaths as $groupedpath) {
if (strpos($groupedpath, $childpath) === 0) {
return $groupedpath;
}
}
return false;
}
/**
* This function will accumulate the chunk into the specified
* grouped element for later dispatching once it is complete
*/
protected function build_currentdata($grouped, $data) {
// Check the grouped already exists into currentdata
if (!is_array($this->currentdata) or !array_key_exists($grouped, $this->currentdata)) {
$a = new stdclass();
$a->grouped = $grouped;
$a->child = $data['path'];
throw new progressive_parser_exception('xml_cannot_add_to_grouped', $a);
}
$this->add_missing_sub($grouped, $data['path'], $data['tags']);
}
/**
* Add non-existing subarray elements
*/
protected function add_missing_sub($grouped, $path, $tags) {
// Remember tag being processed
$processedtag = basename($path);
$info =& $this->currentdata[$grouped]['tags'];
$hierarchyarr = explode('/', str_replace($grouped . '/', '', $path));
$previouselement = '';
$currentpath = '';
foreach ($hierarchyarr as $index => $element) {
$currentpath = $currentpath . '/' . $element;
// If element is already set and it's not
// the processed one (with tags) fast move the $info
// pointer and continue
if ($element !== $processedtag && isset($info[$element])) {
$previouselement = $element;
$info =& $info[$element];
continue;
}
// If previous element already has occurrences
// we move $info pointer there (only if last is
// numeric occurrence)
if (!empty($previouselement) && is_array($info) && count($info) > 0) {
end($info);
$key = key($info);
if ((int) $key === $key) {
$info =& $info[$key];
}
}
// Create element if not defined
if (!isset($info[$element])) {
// First into last element if present
$info[$element] = array();
}
// If element is the current one, add information
if ($element === $processedtag) {
$info[$element][] = $tags;
}
$previouselement = $element;
$info =& $info[$element];
}
}
}