weekly release 4.1dev
[moodle.git] / backup / util / xml / tests / writer_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
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.
8 //
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.
13 //
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/>.
17 /**
18  * Test xml_writer tests.
19  *
20  * @package   core_backup
21  * @category  test
22  * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 namespace core_backup;
28 use memory_xml_output;
29 use xml_contenttransformer;
30 use xml_output;
31 use xml_writer;
32 use xml_writer_exception;
34 defined('MOODLE_INTERNAL') || die();
36 // Include all the needed stuff
37 global $CFG;
38 require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php');
39 require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php');
40 require_once($CFG->dirroot . '/backup/util/xml/output/memory_xml_output.class.php');
41 require_once($CFG->dirroot . '/backup/util/xml/contenttransformer/xml_contenttransformer.class.php');
43 /**
44  * Test xml_writer tests.
45  *
46  * @package   core_backup
47  * @category  test
48  * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
49  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
50  */
51 class writer_test extends \basic_testcase {
53     /**
54      * test xml_writer public methods
55      */
56     function test_xml_writer_public_api() {
57         global $CFG;
58         // Instantiate xml_output
59         $xo = new memory_xml_output();
60         $this->assertTrue($xo instanceof xml_output);
62         // Instantiate xml_writer with null xml_output
63         try {
64             $xw = new mock_xml_writer(null);
65             $this->assertTrue(false, 'xml_writer_exception expected');
66         } catch (\Exception $e) {
67             $this->assertTrue($e instanceof xml_writer_exception);
68             $this->assertEquals($e->errorcode, 'invalid_xml_output');
69         }
71         // Instantiate xml_writer with wrong xml_output object
72         try {
73             $xw = new mock_xml_writer(new \stdClass());
74             $this->assertTrue(false, 'xml_writer_exception expected');
75         } catch (\Exception $e) {
76             $this->assertTrue($e instanceof xml_writer_exception);
77             $this->assertEquals($e->errorcode, 'invalid_xml_output');
78         }
80         // Instantiate xml_writer with wrong xml_contenttransformer object
81         try {
82             $xw = new mock_xml_writer($xo, new \stdClass());
83             $this->assertTrue(false, 'xml_writer_exception expected');
84         } catch (\Exception $e) {
85             $this->assertTrue($e instanceof xml_writer_exception);
86             $this->assertEquals($e->errorcode, 'invalid_xml_contenttransformer');
87         }
89         // Instantiate xml_writer and start it twice
90         $xw = new mock_xml_writer($xo);
91         $xw->start();
92         try {
93             $xw->start();
94             $this->assertTrue(false, 'xml_writer_exception expected');
95         } catch (\Exception $e) {
96             $this->assertTrue($e instanceof xml_writer_exception);
97             $this->assertEquals($e->errorcode, 'xml_writer_already_started');
98         }
100         // Instantiate xml_writer and stop it twice
101         $xo = new memory_xml_output();
102         $xw = new mock_xml_writer($xo);
103         $xw->start();
104         $xw->stop();
105         try {
106             $xw->stop();
107             $this->assertTrue(false, 'xml_writer_exception expected');
108         } catch (\Exception $e) {
109             $this->assertTrue($e instanceof xml_writer_exception);
110             $this->assertEquals($e->errorcode, 'xml_writer_already_stopped');
111         }
113         // Stop writer without starting it
114         $xo = new memory_xml_output();
115         $xw = new mock_xml_writer($xo);
116         try {
117             $xw->stop();
118             $this->assertTrue(false, 'xml_writer_exception expected');
119         } catch (\Exception $e) {
120             $this->assertTrue($e instanceof xml_writer_exception);
121             $this->assertEquals($e->errorcode, 'xml_writer_not_started');
122         }
124         // Start writer after stopping it
125         $xo = new memory_xml_output();
126         $xw = new mock_xml_writer($xo);
127         $xw->start();
128         $xw->stop();
129         try {
130             $xw->start();
131             $this->assertTrue(false, 'xml_writer_exception expected');
132         } catch (\Exception $e) {
133             $this->assertTrue($e instanceof xml_writer_exception);
134             $this->assertEquals($e->errorcode, 'xml_writer_already_stopped');
135         }
137         // Try to set prologue/schema after start
138         $xo = new memory_xml_output();
139         $xw = new mock_xml_writer($xo);
140         $xw->start();
141         try {
142             $xw->set_nonamespace_schema('http://moodle.org');
143             $this->assertTrue(false, 'xml_writer_exception expected');
144         } catch (\Exception $e) {
145             $this->assertTrue($e instanceof xml_writer_exception);
146             $this->assertEquals($e->errorcode, 'xml_writer_already_started');
147         }
148         try {
149             $xw->set_prologue('sweet prologue');
150             $this->assertTrue(false, 'xml_writer_exception expected');
151         } catch (\Exception $e) {
152             $this->assertTrue($e instanceof xml_writer_exception);
153             $this->assertEquals($e->errorcode, 'xml_writer_already_started');
154         }
156         // Instantiate properly with memory_xml_output, start and stop.
157         // Must get default UTF-8 prologue
158         $xo = new memory_xml_output();
159         $xw = new mock_xml_writer($xo);
160         $xw->start();
161         $xw->stop();
162         $this->assertEquals($xo->get_allcontents(), $xw->get_default_prologue());
164         // Instantiate, set prologue and schema, put 1 full tag and get results
165         $xo = new memory_xml_output();
166         $xw = new mock_xml_writer($xo);
167         $xw->set_prologue('CLEARLY WRONG PROLOGUE');
168         $xw->set_nonamespace_schema('http://moodle.org/littleschema');
169         $xw->start();
170         $xw->full_tag('TEST', 'Hello World!', array('id' => 1));
171         $xw->stop();
172         $result = $xo->get_allcontents();
173         // Perform various checks
174         $this->assertEquals(strpos($result, 'WRONG'), 8);
175         $this->assertEquals(strpos($result, '<TEST id="1"'), 22);
176         $this->assertEquals(strpos($result, 'xmlns:xsi='), 39);
177         $this->assertEquals(strpos($result, 'http://moodle.org/littleschema'), 128);
178         $this->assertEquals(strpos($result, 'Hello World'), 160);
179         $this->assertFalse(strpos($result, $xw->get_default_prologue()));
181         // Try to close one tag in wrong order
182         $xo = new memory_xml_output();
183         $xw = new mock_xml_writer($xo);
184         $xw->start();
185         $xw->begin_tag('first');
186         $xw->begin_tag('second');
187         try {
188             $xw->end_tag('first');
189             $this->assertTrue(false, 'xml_writer_exception expected');
190         } catch (\Exception $e) {
191             $this->assertTrue($e instanceof xml_writer_exception);
192             $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match');
193         }
195         // Try to close one tag before starting any tag
196         $xo = new memory_xml_output();
197         $xw = new mock_xml_writer($xo);
198         $xw->start();
199         try {
200             $xw->end_tag('first');
201             $this->assertTrue(false, 'xml_writer_exception expected');
202         } catch (\Exception $e) {
203             $this->assertTrue($e instanceof xml_writer_exception);
204             $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match');
205         }
207         // Full tag without contents (null and empty string)
208         $xo = new memory_xml_output();
209         $xw = new mock_xml_writer($xo);
210         $xw->set_prologue(''); // empty prologue for easier matching
211         $xw->start();
212         $xw->full_tag('tagname', null, array('attrname' => 'attrvalue'));
213         $xw->full_tag('tagname2', '', array('attrname' => 'attrvalue'));
214         $xw->stop();
215         $result = $xo->get_allcontents();
216         $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue"></tagname2>');
219         // Test case-folding is working
220         $xo = new memory_xml_output();
221         $xw = new mock_xml_writer($xo, null, true);
222         $xw->set_prologue(''); // empty prologue for easier matching
223         $xw->start();
224         $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attrvalue'));
225         $xw->stop();
226         $result = $xo->get_allcontents();
227         $this->assertEquals($result, '<TAGNAME ATTRNAME="attrvalue">textcontent</TAGNAME>');
229         // Test UTF-8 chars in tag and attribute names, attr values and contents
230         $xo = new memory_xml_output();
231         $xw = new mock_xml_writer($xo);
232         $xw->set_prologue(''); // empty prologue for easier matching
233         $xw->start();
234         $xw->full_tag('áéíóú', 'ÁÉÍÓÚ', array('àèìòù' => 'ÀÈÌÒÙ'));
235         $xw->stop();
236         $result = $xo->get_allcontents();
237         $this->assertEquals($result, '<áéíóú àèìòù="ÀÈÌÒÙ">ÁÉÍÓÚ</áéíóú>');
239         // Try non-safe content in attributes
240         $xo = new memory_xml_output();
241         $xw = new mock_xml_writer($xo);
242         $xw->set_prologue(''); // empty prologue for easier matching
243         $xw->start();
244         $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attr' . chr(27) . '\'"value'));
245         $xw->stop();
246         $result = $xo->get_allcontents();
247         $this->assertEquals($result, '<tagname attrname="attr\'&quot;value">textcontent</tagname>');
249         // Try non-safe content in text
250         $xo = new memory_xml_output();
251         $xw = new mock_xml_writer($xo);
252         $xw->set_prologue(''); // empty prologue for easier matching
253         $xw->start();
254         $xw->full_tag('tagname', "text\r\ncontent\rwith" . chr(27), array('attrname' => 'attrvalue'));
255         $xw->stop();
256         $result = $xo->get_allcontents();
257         $this->assertEquals($result, '<tagname attrname="attrvalue">text' . "\ncontent\n" . 'with</tagname>');
259         // Try to stop the writer without clossing all the open tags
260         $xo = new memory_xml_output();
261         $xw = new mock_xml_writer($xo);
262         $xw->start();
263         $xw->begin_tag('first');
264         try {
265             $xw->stop();
266             $this->assertTrue(false, 'xml_writer_exception expected');
267         } catch (\Exception $e) {
268             $this->assertTrue($e instanceof xml_writer_exception);
269             $this->assertEquals($e->errorcode, 'xml_writer_open_tags_remaining');
270         }
272         // Test simple transformer
273         $xo = new memory_xml_output();
274         $xt = new mock_xml_contenttransformer();
275         $xw = new mock_xml_writer($xo, $xt);
276         $xw->set_prologue(''); // empty prologue for easier matching
277         $xw->start();
278         $xw->full_tag('tagname', null, array('attrname' => 'attrvalue'));
279         $xw->full_tag('tagname2', 'somecontent', array('attrname' => 'attrvalue'));
280         $xw->stop();
281         $result = $xo->get_allcontents();
282         $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue">testsomecontent</tagname2>');
284         // Build a complex XML file and test results against stored file in fixtures
285         $xo = new memory_xml_output();
286         $xw = new mock_xml_writer($xo);
287         $xw->start();
288         $xw->begin_tag('toptag', array('name' => 'toptag', 'level' => 1, 'path' => '/toptag'));
289         $xw->full_tag('secondtag', 'secondvalue', array('name' => 'secondtag', 'level' => 2, 'path' => '/toptag/secondtag', 'value' => 'secondvalue'));
290         $xw->begin_tag('thirdtag', array('name' => 'thirdtag', 'level' => 2, 'path' => '/toptag/thirdtag'));
291         $xw->full_tag('onevalue', 'onevalue', array('name' => 'onevalue', 'level' => 3, 'path' => '/toptag/thirdtag/onevalue'));
292         $xw->full_tag('onevalue', 'anothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'anothervalue'));
293         $xw->full_tag('onevalue', 'yetanothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'yetanothervalue'));
294         $xw->full_tag('twovalue', 'twovalue', array('name' => 'twovalue', 'level' => 3, 'path' => '/toptag/thirdtag/twovalue'));
295         $xw->begin_tag('forthtag', array('name' => 'forthtag', 'level' => 3, 'path' => '/toptag/thirdtag/forthtag'));
296         $xw->full_tag('innervalue', 'innervalue');
297         $xw->begin_tag('innertag');
298         $xw->begin_tag('superinnertag', array('name' => 'superinnertag', 'level' => 5));
299         $xw->full_tag('superinnervalue', 'superinnervalue', array('name' => 'superinnervalue', 'level' => 6));
300         $xw->end_tag('superinnertag');
301         $xw->end_tag('innertag');
302         $xw->end_tag('forthtag');
303         $xw->begin_tag('fifthtag', array('level' => 3));
304         $xw->begin_tag('sixthtag', array('level' => 4));
305         $xw->full_tag('seventh', 'seventh', array('level' => 5));
306         $xw->end_tag('sixthtag');
307         $xw->end_tag('fifthtag');
308         $xw->full_tag('finalvalue', 'finalvalue', array('name' => 'finalvalue', 'level' => 3, 'path' => '/toptag/thirdtag/finalvalue'));
309         $xw->full_tag('finalvalue');
310         $xw->end_tag('thirdtag');
311         $xw->end_tag('toptag');
312         $xw->stop();
313         $result = $xo->get_allcontents();
314         $fcontents = file_get_contents($CFG->dirroot . '/backup/util/xml/tests/fixtures/test1.xml');
316         // Normalise carriage return characters.
317         $fcontents = str_replace("\r\n", "\n", $fcontents);
318         $this->assertEquals(trim($result), trim($fcontents));
319     }
322 /*
323  * helper extended xml_writer class that makes some methods public for testing
324  */
325 class mock_xml_writer extends xml_writer {
326     public function get_default_prologue() {
327         return parent::get_default_prologue();
328     }
331 /*
332  * helper extended xml_contenttransformer prepending "test" to all the notnull contents
333  */
334 class mock_xml_contenttransformer extends xml_contenttransformer {
335     public function process($content) {
336         return is_null($content) ? null : 'test' . $content;
337     }