2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 * @package core_backup
20 * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 defined('MOODLE_INTERNAL') || die();
26 // Include all the needed stuff
28 require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php');
29 require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php');
30 require_once($CFG->dirroot . '/backup/util/xml/output/memory_xml_output.class.php');
31 require_once($CFG->dirroot . '/backup/util/xml/contenttransformer/xml_contenttransformer.class.php');
36 class xml_writer_testcase extends basic_testcase {
39 * test xml_writer public methods
41 function test_xml_writer_public_api() {
43 // Instantiate xml_output
44 $xo = new memory_xml_output();
45 $this->assertTrue($xo instanceof xml_output);
47 // Instantiate xml_writer with null xml_output
49 $xw = new mock_xml_writer(null);
50 $this->assertTrue(false, 'xml_writer_exception expected');
51 } catch (exception $e) {
52 $this->assertTrue($e instanceof xml_writer_exception);
53 $this->assertEquals($e->errorcode, 'invalid_xml_output');
56 // Instantiate xml_writer with wrong xml_output object
58 $xw = new mock_xml_writer(new stdclass());
59 $this->assertTrue(false, 'xml_writer_exception expected');
60 } catch (exception $e) {
61 $this->assertTrue($e instanceof xml_writer_exception);
62 $this->assertEquals($e->errorcode, 'invalid_xml_output');
65 // Instantiate xml_writer with wrong xml_contenttransformer object
67 $xw = new mock_xml_writer($xo, new stdclass());
68 $this->assertTrue(false, 'xml_writer_exception expected');
69 } catch (exception $e) {
70 $this->assertTrue($e instanceof xml_writer_exception);
71 $this->assertEquals($e->errorcode, 'invalid_xml_contenttransformer');
74 // Instantiate xml_writer and start it twice
75 $xw = new mock_xml_writer($xo);
79 $this->assertTrue(false, 'xml_writer_exception expected');
80 } catch (exception $e) {
81 $this->assertTrue($e instanceof xml_writer_exception);
82 $this->assertEquals($e->errorcode, 'xml_writer_already_started');
85 // Instantiate xml_writer and stop it twice
86 $xo = new memory_xml_output();
87 $xw = new mock_xml_writer($xo);
92 $this->assertTrue(false, 'xml_writer_exception expected');
93 } catch (exception $e) {
94 $this->assertTrue($e instanceof xml_writer_exception);
95 $this->assertEquals($e->errorcode, 'xml_writer_already_stopped');
98 // Stop writer without starting it
99 $xo = new memory_xml_output();
100 $xw = new mock_xml_writer($xo);
103 $this->assertTrue(false, 'xml_writer_exception expected');
104 } catch (exception $e) {
105 $this->assertTrue($e instanceof xml_writer_exception);
106 $this->assertEquals($e->errorcode, 'xml_writer_not_started');
109 // Start writer after stopping it
110 $xo = new memory_xml_output();
111 $xw = new mock_xml_writer($xo);
116 $this->assertTrue(false, 'xml_writer_exception expected');
117 } catch (exception $e) {
118 $this->assertTrue($e instanceof xml_writer_exception);
119 $this->assertEquals($e->errorcode, 'xml_writer_already_stopped');
122 // Try to set prologue/schema after start
123 $xo = new memory_xml_output();
124 $xw = new mock_xml_writer($xo);
127 $xw->set_nonamespace_schema('http://moodle.org');
128 $this->assertTrue(false, 'xml_writer_exception expected');
129 } catch (exception $e) {
130 $this->assertTrue($e instanceof xml_writer_exception);
131 $this->assertEquals($e->errorcode, 'xml_writer_already_started');
134 $xw->set_prologue('sweet prologue');
135 $this->assertTrue(false, 'xml_writer_exception expected');
136 } catch (exception $e) {
137 $this->assertTrue($e instanceof xml_writer_exception);
138 $this->assertEquals($e->errorcode, 'xml_writer_already_started');
141 // Instantiate properly with memory_xml_output, start and stop.
142 // Must get default UTF-8 prologue
143 $xo = new memory_xml_output();
144 $xw = new mock_xml_writer($xo);
147 $this->assertEquals($xo->get_allcontents(), $xw->get_default_prologue());
149 // Instantiate, set prologue and schema, put 1 full tag and get results
150 $xo = new memory_xml_output();
151 $xw = new mock_xml_writer($xo);
152 $xw->set_prologue('CLEARLY WRONG PROLOGUE');
153 $xw->set_nonamespace_schema('http://moodle.org/littleschema');
155 $xw->full_tag('TEST', 'Hello World!', array('id' => 1));
157 $result = $xo->get_allcontents();
158 // Perform various checks
159 $this->assertEquals(strpos($result, 'WRONG'), 8);
160 $this->assertEquals(strpos($result, '<TEST id="1"'), 22);
161 $this->assertEquals(strpos($result, 'xmlns:xsi='), 39);
162 $this->assertEquals(strpos($result, 'http://moodle.org/littleschema'), 128);
163 $this->assertEquals(strpos($result, 'Hello World'), 160);
164 $this->assertFalse(strpos($result, $xw->get_default_prologue()));
166 // Try to close one tag in wrong order
167 $xo = new memory_xml_output();
168 $xw = new mock_xml_writer($xo);
170 $xw->begin_tag('first');
171 $xw->begin_tag('second');
173 $xw->end_tag('first');
174 $this->assertTrue(false, 'xml_writer_exception expected');
175 } catch (exception $e) {
176 $this->assertTrue($e instanceof xml_writer_exception);
177 $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match');
180 // Try to close one tag before starting any tag
181 $xo = new memory_xml_output();
182 $xw = new mock_xml_writer($xo);
185 $xw->end_tag('first');
186 $this->assertTrue(false, 'xml_writer_exception expected');
187 } catch (exception $e) {
188 $this->assertTrue($e instanceof xml_writer_exception);
189 $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match');
192 // Full tag without contents (null and empty string)
193 $xo = new memory_xml_output();
194 $xw = new mock_xml_writer($xo);
195 $xw->set_prologue(''); // empty prologue for easier matching
197 $xw->full_tag('tagname', null, array('attrname' => 'attrvalue'));
198 $xw->full_tag('tagname2', '', array('attrname' => 'attrvalue'));
200 $result = $xo->get_allcontents();
201 $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue"></tagname2>');
204 // Test case-folding is working
205 $xo = new memory_xml_output();
206 $xw = new mock_xml_writer($xo, null, true);
207 $xw->set_prologue(''); // empty prologue for easier matching
209 $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attrvalue'));
211 $result = $xo->get_allcontents();
212 $this->assertEquals($result, '<TAGNAME ATTRNAME="attrvalue">textcontent</TAGNAME>');
214 // Test UTF-8 chars in tag and attribute names, attr values and contents
215 $xo = new memory_xml_output();
216 $xw = new mock_xml_writer($xo);
217 $xw->set_prologue(''); // empty prologue for easier matching
219 $xw->full_tag('áéíóú', 'ÁÉÍÓÚ', array('àèìòù' => 'ÀÈÌÒÙ'));
221 $result = $xo->get_allcontents();
222 $this->assertEquals($result, '<áéíóú àèìòù="ÀÈÌÒÙ">ÁÉÍÓÚ</áéíóú>');
224 // Try non-safe content in attributes
225 $xo = new memory_xml_output();
226 $xw = new mock_xml_writer($xo);
227 $xw->set_prologue(''); // empty prologue for easier matching
229 $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attr' . chr(27) . '\'"value'));
231 $result = $xo->get_allcontents();
232 $this->assertEquals($result, '<tagname attrname="attr\'"value">textcontent</tagname>');
234 // Try non-safe content in text
235 $xo = new memory_xml_output();
236 $xw = new mock_xml_writer($xo);
237 $xw->set_prologue(''); // empty prologue for easier matching
239 $xw->full_tag('tagname', "text\r\ncontent\rwith" . chr(27), array('attrname' => 'attrvalue'));
241 $result = $xo->get_allcontents();
242 $this->assertEquals($result, '<tagname attrname="attrvalue">text' . "\ncontent\n" . 'with</tagname>');
244 // Try to stop the writer without clossing all the open tags
245 $xo = new memory_xml_output();
246 $xw = new mock_xml_writer($xo);
248 $xw->begin_tag('first');
251 $this->assertTrue(false, 'xml_writer_exception expected');
252 } catch (exception $e) {
253 $this->assertTrue($e instanceof xml_writer_exception);
254 $this->assertEquals($e->errorcode, 'xml_writer_open_tags_remaining');
257 // Test simple transformer
258 $xo = new memory_xml_output();
259 $xt = new mock_xml_contenttransformer();
260 $xw = new mock_xml_writer($xo, $xt);
261 $xw->set_prologue(''); // empty prologue for easier matching
263 $xw->full_tag('tagname', null, array('attrname' => 'attrvalue'));
264 $xw->full_tag('tagname2', 'somecontent', array('attrname' => 'attrvalue'));
266 $result = $xo->get_allcontents();
267 $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue">testsomecontent</tagname2>');
269 // Build a complex XML file and test results against stored file in fixtures
270 $xo = new memory_xml_output();
271 $xw = new mock_xml_writer($xo);
273 $xw->begin_tag('toptag', array('name' => 'toptag', 'level' => 1, 'path' => '/toptag'));
274 $xw->full_tag('secondtag', 'secondvalue', array('name' => 'secondtag', 'level' => 2, 'path' => '/toptag/secondtag', 'value' => 'secondvalue'));
275 $xw->begin_tag('thirdtag', array('name' => 'thirdtag', 'level' => 2, 'path' => '/toptag/thirdtag'));
276 $xw->full_tag('onevalue', 'onevalue', array('name' => 'onevalue', 'level' => 3, 'path' => '/toptag/thirdtag/onevalue'));
277 $xw->full_tag('onevalue', 'anothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'anothervalue'));
278 $xw->full_tag('onevalue', 'yetanothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'yetanothervalue'));
279 $xw->full_tag('twovalue', 'twovalue', array('name' => 'twovalue', 'level' => 3, 'path' => '/toptag/thirdtag/twovalue'));
280 $xw->begin_tag('forthtag', array('name' => 'forthtag', 'level' => 3, 'path' => '/toptag/thirdtag/forthtag'));
281 $xw->full_tag('innervalue', 'innervalue');
282 $xw->begin_tag('innertag');
283 $xw->begin_tag('superinnertag', array('name' => 'superinnertag', 'level' => 5));
284 $xw->full_tag('superinnervalue', 'superinnervalue', array('name' => 'superinnervalue', 'level' => 6));
285 $xw->end_tag('superinnertag');
286 $xw->end_tag('innertag');
287 $xw->end_tag('forthtag');
288 $xw->begin_tag('fifthtag', array('level' => 3));
289 $xw->begin_tag('sixthtag', array('level' => 4));
290 $xw->full_tag('seventh', 'seventh', array('level' => 5));
291 $xw->end_tag('sixthtag');
292 $xw->end_tag('fifthtag');
293 $xw->full_tag('finalvalue', 'finalvalue', array('name' => 'finalvalue', 'level' => 3, 'path' => '/toptag/thirdtag/finalvalue'));
294 $xw->full_tag('finalvalue');
295 $xw->end_tag('thirdtag');
296 $xw->end_tag('toptag');
298 $result = $xo->get_allcontents();
299 $fcontents = file_get_contents($CFG->dirroot . '/backup/util/xml/tests/fixtures/test1.xml');
301 // Normalise carriage return characters.
302 $fcontents = str_replace("\r\n", "\n", $fcontents);
303 $this->assertEquals(trim($result), trim($fcontents));
308 * helper extended xml_writer class that makes some methods public for testing
310 class mock_xml_writer extends xml_writer {
311 public function get_default_prologue() {
312 return parent::get_default_prologue();
317 * helper extended xml_contenttransformer prepending "test" to all the notnull contents
319 class mock_xml_contenttransformer extends xml_contenttransformer {
320 public function process($content) {
321 return is_null($content) ? null : 'test' . $content;