���ѧۧݧ�ӧ�� �ާ֧ߧ֧էا֧� - ���֧էѧܧ�ڧ��ӧѧ�� - /home3/cpr76684/public_html/xml.tar
���ѧ٧ѧ�
output/xml_output.class.php 0000644 00000012742 15152155760 0012156 0 ustar 00 <?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 backup-xml * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * This abstract class outputs XML contents provided by @xml_writer * * Contains the common functionalities for all the xml_output_xxx classes * and the interface for them. Mainly it's in charge of: * - Initialize the corresponding stream/handler (file, DB connection...) * - Finalize the stream/handler * - Provide one common buffer for all output implementations * - Receive XML contents from the @xml_writer and output them * - Some basic throughtput stats * * TODO: Finish phpdocs */ abstract class xml_output { const DEFAULT_BUFFER_SIZE = 4096; // Use a default buffer size of 4K protected $inittime; // Initial microtime protected $sentbytes; // Bytes sent to output protected $usebuffer; // Boolean to specify if output supports buffer (true) or no (false) protected $buffersize;// Size, in bytes, of the buffer. protected $currentbuffer; // Buffer contents protected $currentbuffersize;// Current buffer size protected $running; // To know if output is running public function __construct($usebuffer = true) { $this->inittime = microtime(true); $this->finishtime = $this->inittime; $this->sentbytes = 0; $this->usebuffer = $usebuffer; $this->buffersize = $this->usebuffer ? self::DEFAULT_BUFFER_SIZE : 0; $this->running = null; } public function set_buffersize($buffersize) { if ($this->running) { throw new xml_output_exception('xml_output_already_started'); } if (!$this->usebuffer) { throw new xml_output_exception('xml_output_buffer_nosupport'); } // TODO: check it is integer > 0 $this->buffersize = $buffersize; } public function start() { if ($this->running === true) { throw new xml_output_exception('xml_output_already_started'); } if ($this->running === false) { throw new xml_output_exception('xml_output_already_stopped'); } $this->inittime = microtime(true); $this->sentbytes = 0; $this->running = true; $this->currentbuffer = ''; $this->currentbuffersize = 0; $this->init(); } public function stop() { if (!$this->running) { throw new xml_output_exception('xml_output_not_started'); } $this->finishtime = microtime(true); if ($this->usebuffer && $this->currentbuffersize > 0) { // Have pending contents in buffer $this->send($this->currentbuffer); // Send them $this->currentbuffer = ''; $this->currentbuffersize = 0; } $this->running = false; $this->finish(); } /** * Get contents from @xml_writer and buffer/output them */ public function write($content) { if (!$this->running) { throw new xml_output_exception('xml_output_not_started'); } $lenc = strlen($content ?? ''); // Get length in bytes. if ($lenc == 0) { // 0 length contents, nothing to do return; } // Buffer handling if available $tooutput = true; // By default, perform output if ($this->usebuffer) { // Buffer $this->currentbuffer .= $content; $this->currentbuffersize += $lenc; if ($this->currentbuffersize < $this->buffersize) { $tooutput = false; // Still within the buffer, don't output } else { $content = $this->currentbuffer; // Prepare for output $lenc = $this->currentbuffersize; $this->currentbuffer = ''; $this->currentbuffersize = 0; } } // Output if ($tooutput) { $this->send($content); // Efectively send the contents $this->sentbytes += $lenc; } } public function debug_info() { if ($this->running !== false) { throw new xml_output_exception('xml_output_not_stopped'); } return array('memory' => memory_get_peak_usage(true), 'time' => $this->finishtime - $this->inittime, 'sent' => $this->sentbytes); } // Implementable API starts here abstract protected function init(); abstract protected function finish(); abstract protected function send($content); } /* * Exception class used by all the @xml_output stuff */ class xml_output_exception extends moodle_exception { public function __construct($errorcode, $a=NULL, $debuginfo=null) { parent::__construct($errorcode, 'error', '', $a, null, $debuginfo); } } output/tests/output_test.php 0000644 00000025462 15152155760 0012376 0 ustar 00 <?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/>. /** * xml_output tests (base, memory and file). * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_backup; use file_xml_output; use memory_xml_output; use xml_output; use xml_output_exception; defined('MOODLE_INTERNAL') || die(); // Include all the needed stuff global $CFG; require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php'); require_once($CFG->dirroot . '/backup/util/xml/output/memory_xml_output.class.php'); require_once($CFG->dirroot . '/backup/util/xml/output/file_xml_output.class.php'); /** * xml_output tests (base, memory and file) * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class output_test extends \advanced_testcase { /* * test memory_xml_output */ function test_memory_xml_output() { // Instantiate xml_output $xo = new memory_xml_output(); $this->assertTrue($xo instanceof xml_output); // Try to write some contents before starting it $xo = new memory_xml_output(); try { $xo->write('test'); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'xml_output_not_started'); } // Try to set buffer size if unsupported $xo = new memory_xml_output(); try { $xo->set_buffersize(8192); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'xml_output_buffer_nosupport'); } // Try to set buffer after start $xo = new memory_xml_output(); $xo->start(); try { $xo->set_buffersize(8192); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'xml_output_already_started'); } // Try to stop output before starting it $xo = new memory_xml_output(); try { $xo->stop(); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'xml_output_not_started'); } // Try to debug_info() before starting $xo = new memory_xml_output(); try { $xo->debug_info(); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'xml_output_not_stopped'); } // Start output twice $xo = new memory_xml_output(); $xo->start(); try { $xo->start(); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'xml_output_already_started'); } // Try to debug_info() before stoping $xo = new memory_xml_output(); $xo->start(); try { $xo->debug_info(); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'xml_output_not_stopped'); } // Stop output twice $xo = new memory_xml_output(); $xo->start(); $xo->stop(); try { $xo->stop(); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'xml_output_not_started'); } // Try to re-start after stop $xo = new memory_xml_output(); $xo->start(); $xo->stop(); try { $xo->start(); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'xml_output_already_stopped'); } // Try to get contents before stopping $xo = new memory_xml_output(); $xo->start(); try { $xo->get_allcontents(); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'xml_output_not_stopped'); } // Write some contents and check them $xo = new memory_xml_output(); $xo->start(); $xo->write('first test'); $xo->stop(); $this->assertEquals('first test', $xo->get_allcontents()); // Write 3 times and check them $xo = new memory_xml_output(); $xo->start(); $xo->write('first test'); $xo->write(', sencond test'); $xo->write(', third test'); $xo->stop(); $this->assertEquals('first test, sencond test, third test', $xo->get_allcontents()); // Write some line feeds, tabs and friends $string = "\n\r\tcrazy test\n\r\t"; $xo = new memory_xml_output(); $xo->start(); $xo->write($string); $xo->stop(); $this->assertEquals($string, $xo->get_allcontents()); // Write some UTF-8 chars $string = 'áéíóú'; $xo = new memory_xml_output(); $xo->start(); $xo->write($string); $xo->stop(); $this->assertEquals($string, $xo->get_allcontents()); // Write some empty content $xo = new memory_xml_output(); $xo->start(); $xo->write('Hello '); $xo->write(null); $xo->write(false); $xo->write(''); $xo->write('World'); $xo->write(null); $xo->stop(); $this->assertEquals('Hello World', $xo->get_allcontents()); // Get debug info $xo = new memory_xml_output(); $xo->start(); $xo->write('01234'); $xo->write('56789'); $xo->stop(); $this->assertEquals('0123456789', $xo->get_allcontents()); $debug = $xo->debug_info(); $this->assertTrue(is_array($debug)); $this->assertTrue(array_key_exists('sent', $debug)); $this->assertEquals($debug['sent'], 10); } /* * test file_xml_output */ function test_file_xml_output() { global $CFG; $this->resetAfterTest(); $file = $CFG->tempdir . '/test/test_file_xml_output.txt'; // Remove the test dir and any content @remove_dir(dirname($file)); // Recreate test dir if (!check_dir_exists(dirname($file), true, true)) { throw new \moodle_exception('error_creating_temp_dir', 'error', dirname($file)); } // Instantiate xml_output $xo = new file_xml_output($file); $this->assertTrue($xo instanceof xml_output); // Try to init file in (near) impossible path $file = $CFG->tempdir . '/test_azby/test_file_xml_output.txt'; $xo = new file_xml_output($file); try { $xo->start(); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'directory_not_exists'); } // Try to init file already existing $file = $CFG->tempdir . '/test/test_file_xml_output.txt'; file_put_contents($file, 'createdtobedeleted'); // create file manually $xo = new file_xml_output($file); try { $xo->start(); $this->assertTrue(false, 'xml_output_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_output_exception); $this->assertEquals($e->errorcode, 'file_already_exists'); } unlink($file); // delete file // Send some output and check $file = $CFG->tempdir . '/test/test_file_xml_output.txt'; $xo = new file_xml_output($file); $xo->start(); $xo->write('first text'); $xo->stop(); $this->assertEquals('first text', file_get_contents($file)); unlink($file); // delete file // With buffer of 4 bytes, send 3 contents of 3 bytes each // so we force both buffering and last write on stop $file = $CFG->tempdir . '/test/test_file_xml_output.txt'; $xo = new file_xml_output($file); $xo->set_buffersize(5); $xo->start(); $xo->write('123'); $xo->write('456'); $xo->write('789'); $xo->stop(); $this->assertEquals('123456789', file_get_contents($file)); unlink($file); // delete file // Write some line feeds, tabs and friends $file = $CFG->tempdir . '/test/test_file_xml_output.txt'; $string = "\n\r\tcrazy test\n\r\t"; $xo = new file_xml_output($file); $xo->start(); $xo->write($string); $xo->stop(); $this->assertEquals($string, file_get_contents($file)); unlink($file); // delete file // Write some UTF-8 chars $file = $CFG->tempdir . '/test/test_file_xml_output.txt'; $string = 'áéíóú'; $xo = new file_xml_output($file); $xo->start(); $xo->write($string); $xo->stop(); $this->assertEquals($string, file_get_contents($file)); unlink($file); // delete file // Remove the test dir and any content @remove_dir(dirname($file)); } } output/file_xml_output.class.php 0000644 00000004640 15152155760 0013153 0 ustar 00 <?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 backup-xml * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * This class implements one @xml_output able to send contents to one OS file * * Buffering enabled by default (can be disabled) * * TODO: Finish phpdocs */ class file_xml_output extends xml_output { protected $fullpath; // Full path to OS file where contents will be stored protected $fhandle; // File handle where all write operations happen public function __construct($fullpath, $usebuffer = true) { $this->fullpath = $fullpath; parent::__construct($usebuffer); } // Private API starts here protected function init() { if (!file_exists(dirname($this->fullpath))) { throw new xml_output_exception('directory_not_exists', dirname($this->fullpath)); } if (file_exists($this->fullpath)) { throw new xml_output_exception('file_already_exists', $this->fullpath); } if (!is_writable(dirname($this->fullpath))) { throw new xml_output_exception('directory_not_writable', dirname($this->fullpath)); } // Open the OS file for writing if (! $this->fhandle = fopen($this->fullpath, 'w')) { throw new xml_output_exception('error_opening_file'); } } protected function finish() { if (false === fclose($this->fhandle)) { throw new xml_output_exception('error_closing_file'); } } protected function send($content) { if (false === fwrite($this->fhandle, $content)) { throw new xml_output_exception('error_writing_file'); } } } output/memory_xml_output.class.php 0000644 00000003561 15152155760 0013545 0 ustar 00 <?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 backup-xml * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * This class implements one @xml_output able to store and return output in memory * * Although possible to use, has been defined as not supporting buffering for * testing purposes. get_allcontents() will return the contents after ending. * * TODO: Finish phpdocs */ class memory_xml_output extends xml_output{ protected $allcontents; // Here we'll store all the written contents public function __construct() { $this->allcontents = ''; parent::__construct(false); // disable buffering } public function get_allcontents() { if ($this->running !== false) { throw new xml_output_exception('xml_output_not_stopped'); } return $this->allcontents; } // Private API starts here protected function init() { // Easy :-) } protected function finish() { // Trivial :-) } protected function send($content) { // Accumulate contents $this->allcontents .= $content; } } tests/fixtures/test1.xml 0000644 00000002327 15152155760 0011354 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <toptag name="toptag" level="1" path="/toptag"> <secondtag name="secondtag" level="2" path="/toptag/secondtag" value="secondvalue">secondvalue</secondtag> <thirdtag name="thirdtag" level="2" path="/toptag/thirdtag"> <onevalue name="onevalue" level="3" path="/toptag/thirdtag/onevalue">onevalue</onevalue> <onevalue name="onevalue" level="3" value="anothervalue">anothervalue</onevalue> <onevalue name="onevalue" level="3" value="yetanothervalue">yetanothervalue</onevalue> <twovalue name="twovalue" level="3" path="/toptag/thirdtag/twovalue">twovalue</twovalue> <forthtag name="forthtag" level="3" path="/toptag/thirdtag/forthtag"> <innervalue>innervalue</innervalue> <innertag> <superinnertag name="superinnertag" level="5"> <superinnervalue name="superinnervalue" level="6">superinnervalue</superinnervalue> </superinnertag> </innertag> </forthtag> <fifthtag level="3"> <sixthtag level="4"> <seventh level="5">seventh</seventh> </sixthtag> </fifthtag> <finalvalue name="finalvalue" level="3" path="/toptag/thirdtag/finalvalue">finalvalue</finalvalue> <finalvalue /> </thirdtag> </toptag> tests/writer_test.php 0000644 00000034167 15152155760 0011014 0 ustar 00 <?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/>. /** * Test xml_writer tests. * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_backup; use memory_xml_output; use xml_contenttransformer; use xml_output; use xml_writer; use xml_writer_exception; defined('MOODLE_INTERNAL') || die(); // Include all the needed stuff global $CFG; require_once($CFG->dirroot . '/backup/util/xml/xml_writer.class.php'); require_once($CFG->dirroot . '/backup/util/xml/output/xml_output.class.php'); require_once($CFG->dirroot . '/backup/util/xml/output/memory_xml_output.class.php'); require_once($CFG->dirroot . '/backup/util/xml/contenttransformer/xml_contenttransformer.class.php'); /** * Test xml_writer tests. * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class writer_test extends \basic_testcase { /** * test xml_writer public methods */ function test_xml_writer_public_api() { global $CFG; // Instantiate xml_output $xo = new memory_xml_output(); $this->assertTrue($xo instanceof xml_output); // Instantiate xml_writer with null xml_output try { $xw = new mock_xml_writer(null); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'invalid_xml_output'); } // Instantiate xml_writer with wrong xml_output object try { $xw = new mock_xml_writer(new \stdClass()); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'invalid_xml_output'); } // Instantiate xml_writer with wrong xml_contenttransformer object try { $xw = new mock_xml_writer($xo, new \stdClass()); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'invalid_xml_contenttransformer'); } // Instantiate xml_writer and start it twice $xw = new mock_xml_writer($xo); $xw->start(); try { $xw->start(); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'xml_writer_already_started'); } // Instantiate xml_writer and stop it twice $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->start(); $xw->stop(); try { $xw->stop(); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'xml_writer_already_stopped'); } // Stop writer without starting it $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); try { $xw->stop(); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'xml_writer_not_started'); } // Start writer after stopping it $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->start(); $xw->stop(); try { $xw->start(); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'xml_writer_already_stopped'); } // Try to set prologue/schema after start $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->start(); try { $xw->set_nonamespace_schema('http://moodle.org'); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'xml_writer_already_started'); } try { $xw->set_prologue('sweet prologue'); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'xml_writer_already_started'); } // Instantiate properly with memory_xml_output, start and stop. // Must get default UTF-8 prologue $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->start(); $xw->stop(); $this->assertEquals($xo->get_allcontents(), $xw->get_default_prologue()); // Instantiate, set prologue and schema, put 1 full tag and get results $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->set_prologue('CLEARLY WRONG PROLOGUE'); $xw->set_nonamespace_schema('http://moodle.org/littleschema'); $xw->start(); $xw->full_tag('TEST', 'Hello World!', array('id' => 1)); $xw->stop(); $result = $xo->get_allcontents(); // Perform various checks $this->assertEquals(strpos($result, 'WRONG'), 8); $this->assertEquals(strpos($result, '<TEST id="1"'), 22); $this->assertEquals(strpos($result, 'xmlns:xsi='), 39); $this->assertEquals(strpos($result, 'http://moodle.org/littleschema'), 128); $this->assertEquals(strpos($result, 'Hello World'), 160); $this->assertFalse(strpos($result, $xw->get_default_prologue())); // Try to close one tag in wrong order $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->start(); $xw->begin_tag('first'); $xw->begin_tag('second'); try { $xw->end_tag('first'); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match'); } // Try to close one tag before starting any tag $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->start(); try { $xw->end_tag('first'); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'xml_writer_end_tag_no_match'); } // Full tag without contents (null and empty string) $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->set_prologue(''); // empty prologue for easier matching $xw->start(); $xw->full_tag('tagname', null, array('attrname' => 'attrvalue')); $xw->full_tag('tagname2', '', array('attrname' => 'attrvalue')); $xw->stop(); $result = $xo->get_allcontents(); $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue"></tagname2>'); // Test case-folding is working $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo, null, true); $xw->set_prologue(''); // empty prologue for easier matching $xw->start(); $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attrvalue')); $xw->stop(); $result = $xo->get_allcontents(); $this->assertEquals($result, '<TAGNAME ATTRNAME="attrvalue">textcontent</TAGNAME>'); // Test UTF-8 chars in tag and attribute names, attr values and contents $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->set_prologue(''); // empty prologue for easier matching $xw->start(); $xw->full_tag('áéíóú', 'ÁÉÍÓÚ', array('àèìòù' => 'ÀÈÌÒÙ')); $xw->stop(); $result = $xo->get_allcontents(); $this->assertEquals($result, '<áéíóú àèìòù="ÀÈÌÒÙ">ÁÉÍÓÚ</áéíóú>'); // Try non-safe content in attributes $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->set_prologue(''); // empty prologue for easier matching $xw->start(); $xw->full_tag('tagname', 'textcontent', array('attrname' => 'attr' . chr(27) . '\'"value')); $xw->stop(); $result = $xo->get_allcontents(); $this->assertEquals($result, '<tagname attrname="attr\'"value">textcontent</tagname>'); // Try non-safe content in text $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->set_prologue(''); // empty prologue for easier matching $xw->start(); $xw->full_tag('tagname', "text\r\ncontent\rwith" . chr(27), array('attrname' => 'attrvalue')); $xw->stop(); $result = $xo->get_allcontents(); $this->assertEquals($result, '<tagname attrname="attrvalue">text' . "\ncontent\n" . 'with</tagname>'); // Try to stop the writer without clossing all the open tags $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->start(); $xw->begin_tag('first'); try { $xw->stop(); $this->assertTrue(false, 'xml_writer_exception expected'); } catch (\Exception $e) { $this->assertTrue($e instanceof xml_writer_exception); $this->assertEquals($e->errorcode, 'xml_writer_open_tags_remaining'); } // Test simple transformer $xo = new memory_xml_output(); $xt = new mock_xml_contenttransformer(); $xw = new mock_xml_writer($xo, $xt); $xw->set_prologue(''); // empty prologue for easier matching $xw->start(); $xw->full_tag('tagname', null, array('attrname' => 'attrvalue')); $xw->full_tag('tagname2', 'somecontent', array('attrname' => 'attrvalue')); $xw->stop(); $result = $xo->get_allcontents(); $this->assertEquals($result, '<tagname attrname="attrvalue" /><tagname2 attrname="attrvalue">testsomecontent</tagname2>'); // Build a complex XML file and test results against stored file in fixtures $xo = new memory_xml_output(); $xw = new mock_xml_writer($xo); $xw->start(); $xw->begin_tag('toptag', array('name' => 'toptag', 'level' => 1, 'path' => '/toptag')); $xw->full_tag('secondtag', 'secondvalue', array('name' => 'secondtag', 'level' => 2, 'path' => '/toptag/secondtag', 'value' => 'secondvalue')); $xw->begin_tag('thirdtag', array('name' => 'thirdtag', 'level' => 2, 'path' => '/toptag/thirdtag')); $xw->full_tag('onevalue', 'onevalue', array('name' => 'onevalue', 'level' => 3, 'path' => '/toptag/thirdtag/onevalue')); $xw->full_tag('onevalue', 'anothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'anothervalue')); $xw->full_tag('onevalue', 'yetanothervalue', array('name' => 'onevalue', 'level' => 3, 'value' => 'yetanothervalue')); $xw->full_tag('twovalue', 'twovalue', array('name' => 'twovalue', 'level' => 3, 'path' => '/toptag/thirdtag/twovalue')); $xw->begin_tag('forthtag', array('name' => 'forthtag', 'level' => 3, 'path' => '/toptag/thirdtag/forthtag')); $xw->full_tag('innervalue', 'innervalue'); $xw->begin_tag('innertag'); $xw->begin_tag('superinnertag', array('name' => 'superinnertag', 'level' => 5)); $xw->full_tag('superinnervalue', 'superinnervalue', array('name' => 'superinnervalue', 'level' => 6)); $xw->end_tag('superinnertag'); $xw->end_tag('innertag'); $xw->end_tag('forthtag'); $xw->begin_tag('fifthtag', array('level' => 3)); $xw->begin_tag('sixthtag', array('level' => 4)); $xw->full_tag('seventh', 'seventh', array('level' => 5)); $xw->end_tag('sixthtag'); $xw->end_tag('fifthtag'); $xw->full_tag('finalvalue', 'finalvalue', array('name' => 'finalvalue', 'level' => 3, 'path' => '/toptag/thirdtag/finalvalue')); $xw->full_tag('finalvalue'); $xw->end_tag('thirdtag'); $xw->end_tag('toptag'); $xw->stop(); $result = $xo->get_allcontents(); $fcontents = file_get_contents($CFG->dirroot . '/backup/util/xml/tests/fixtures/test1.xml'); // Normalise carriage return characters. $fcontents = str_replace("\r\n", "\n", $fcontents); $this->assertEquals(trim($result), trim($fcontents)); } } /* * helper extended xml_writer class that makes some methods public for testing */ class mock_xml_writer extends xml_writer { public function get_default_prologue() { return parent::get_default_prologue(); } } /* * helper extended xml_contenttransformer prepending "test" to all the notnull contents */ class mock_xml_contenttransformer extends xml_contenttransformer { public function process($content) { return is_null($content) ? null : 'test' . $content; } } parser/tests/fixtures/test6.xml 0000644 00000002760 15152155760 0012656 0 ustar 00 <test> <MOODLE_BACKUP> <COURSE> <FORMATDATA> <WEEKS> <WEEK> <SECTION>1</SECTION> <HIDENUMBER>1</HIDENUMBER> <HIDEDATE>0</HIDEDATE> <SHOWTO></SHOWTO> <OFFLINEMATERIAL>0</OFFLINEMATERIAL> </WEEK> <WEEK> <SECTION>2</SECTION> <HIDENUMBER>0</HIDENUMBER> <HIDEDATE>0</HIDEDATE> <RESETNUMBER>0</RESETNUMBER> <SHOWTO></SHOWTO> <OFFLINEMATERIAL>0</OFFLINEMATERIAL> </WEEK> </WEEKS> <IMPORTED> </IMPORTED> </FORMATDATA> <GROUPEDNOTOBSERVED> <NOBODYOBSERVERSTHIS> <NOTOBSERVED>Muhehe</NOTOBSERVED> </NOBODYOBSERVERSTHIS> </GROUPEDNOTOBSERVED> <EMPTYGROUPED> </EMPTYGROUPED> <SECONDGROUPED> <SUBS> <SUB> <PROP>Unit tests rock!</PROP> </SUB> </SUBS> </SECONDGROUPED> </COURSE> </MOODLE_BACKUP> <moodle2> <grouped id="this is not parsed at the moment because there are no final elements"> <subs> <sub id="34"> <prop>Oh yeah</prop> </sub> </subs> </grouped> <groupednonemptywithattr id="78"> <prop>Go baby go</prop> <subs> <sub id="89"> <prop>http://moodle.org</prop> </sub> </subs> </groupednonemptywithattr> <groupedemptywithattr attr="ay?"> </groupedemptywithattr> </moodle2> </test> parser/tests/fixtures/test5.xml 0000644 00000000711 15152155760 0012647 0 ustar 00 <MOODLE_BACKUP> <COURSE ID="100"> <SECTIONS> <SECTION> <ID>200</ID> <MODS> <MOD> <ID>300</ID> <ROLES_OVERRIDES> </ROLES_OVERRIDES> </MOD> <MOD /> <MOD /> <MOD ID="400" /> <MOD> <ID>500</ID> </MOD> <MOD /> <MOD /> </MODS> </SECTION> </SECTIONS> </COURSE> </MOODLE_BACKUP> parser/tests/fixtures/test1.xml 0000644 00000000364 15152155760 0012647 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <firsttag> <secondtag name="secondtag" level="2" path="/firsttag/secondtag">secondvalue</secondtag> <secondtag name="secondtag" level="2" path="/firsttag/secondtag">secondvalue</secondtag> </firsttag> parser/tests/fixtures/test3.xml 0000644 00000002351 15152155760 0012647 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <toptag name="toptag" level="1" path="/toptag"> <secondtag name="secondtag" level="2" path="/toptag/secondtag" value="secondvalue">secondvalue</secondtag> <thirdtag name="thirdtag" level="2" path="/toptag/thirdtag"> <onevalue name="onevalue" level="3" path="/toptag/thirdtag/onevalue">onevalue</onevalue> <onevalue name="onevalue" level="3" value="anothervalue">anothervalue</onevalue> <onevalue name="onevalue" level="3" value="yetanothervalue">yetanothervalue</onevalue> <twovalue name="twovalue" level="3" path="/toptag/thirdtag/twovalue">twovalue</twovalue> <forthtag name="forthtag" level="3" path="/toptag/thirdtag/forthtag"> <innervalue>innervalue</innervalue> <innertag> <superinnertag name="superinnertag" level="5"> <superinnervalue name="superinnervalue" level="6">superinnervalue</superinnervalue> </superinnertag> </innertag> </forthtag> <fifthtag level='3'> <sixthtag level='4'> <seventh level='5'>seventh</seventh> </sixthtag> </fifthtag> <finalvalue name="finalvalue" level="3" path="/toptag/thirdtag/finalvalue">finalvalue</finalvalue> <finalvalue /> <finalvalue/> </thirdtag> </toptag> parser/tests/fixtures/test4.xml 0000644 00000007555 15152155760 0012663 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <activity id="1" moduleid="5" modulename="glossary" contextid="26"> <glossary id="1"> <name>One glossary</name> <intro><p>One simple glossary to test backup &amp; restore. Here it's the standard image:</p> <p><img src="@@PLUGINFILE@@/88_31.png" alt="pwd by moodle" width="88" height="31" /></p></intro> <allowduplicatedentries>0</allowduplicatedentries> <displayformat>dictionary</displayformat> <mainglossary>0</mainglossary> <showspecial>1</showspecial> <showalphabet>1</showalphabet> <showall>1</showall> <allowcomments>0</allowcomments> <allowprintview>1</allowprintview> <usedynalink>1</usedynalink> <defaultapproval>1</defaultapproval> <globalglossary>0</globalglossary> <entbypage>10</entbypage> <editalways>0</editalways> <rsstype>0</rsstype> <rssarticles>0</rssarticles> <assessed>1</assessed> <assesstimestart>0</assesstimestart> <assesstimefinish>0</assesstimefinish> <scale>10</scale> <timecreated>1275638215</timecreated> <timemodified>1275639747</timemodified> <entries> <entry id="1"> <userid>2</userid> <concept>dog</concept> <definition><p>Traditional enemies of cats</p></definition> <definitionformat>1</definitionformat> <definitiontrust>0</definitiontrust> <attachment></attachment> <timecreated>1275638279</timecreated> <timemodified>1275638279</timemodified> <teacherentry>1</teacherentry> <sourceglossaryid>0</sourceglossaryid> <usedynalink>1</usedynalink> <casesensitive>0</casesensitive> <fullmatch>0</fullmatch> <approved>1</approved> <aliases> <alias id="1"> <alias_text>dogs</alias_text> </alias> </aliases> <ratings> <rating id="2"> <scaleid>10</scaleid> <value>6</value> <userid>5</userid> <timecreated>1275639785</timecreated> <timemodified>1275639797</timemodified> </rating> </ratings> </entry> <entry id="2"> <userid>2</userid> <concept>cat</concept> <definition><p>traditional enemies of dogs</p></definition> <definitionformat>1</definitionformat> <definitiontrust>0</definitiontrust> <attachment></attachment> <timecreated>1275638304</timecreated> <timemodified>1275638304</timemodified> <teacherentry>1</teacherentry> <sourceglossaryid>0</sourceglossaryid> <usedynalink>1</usedynalink> <casesensitive>0</casesensitive> <fullmatch>0</fullmatch> <approved>1</approved> <aliases> <alias id="2"> <alias_text>cats</alias_text> </alias> <alias id="3"> <alias_text>felines</alias_text> </alias> </aliases> <ratings> <rating id="1"> <scaleid>10</scaleid> <value>5</value> <userid>5</userid> <timecreated>1275639779</timecreated> <timemodified>1275639779</timemodified> </rating> </ratings> </entry> </entries> <categories> </categories> <onetest> <name>1</name> <value>1</value> </onetest> <onetest> <name>2</name> <value>2</value> </onetest> <othertest> <name>3</name> <value>3</value> <name>4</name><!-- Only last will be processed. We don't allow repeated final tags in our parser --> <value>4</value> <value>5</value><!-- Only last will be processed. We don't allow repeated final tags in our parser --> <value> </value><!-- If one tag already is set and the repeated is empty, the original value is kept --> </othertest> </glossary> </activity> parser/tests/fixtures/test2.xml 0000644 00000000363 15152155760 0012647 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <firsttag> <secondtag name="secondtag" level="2" path="/firsttag/secondtag">secondvalue</secondtag> <secondtag name="secondtag" level="2" path="/firsttag/secondtag">secondvalue</wrongtag> </firsttag> parser/tests/parser_test.php 0000644 00000124505 15152155760 0012264 0 ustar 00 <?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/>. /** * Test progressive_parser and progressive_parser_processor tests. * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_backup; use grouped_parser_processor; use progressive_parser; use progressive_parser_exception; use progressive_parser_processor; use simplified_parser_processor; defined('MOODLE_INTERNAL') || die(); // Include all the needed stuff global $CFG; require_once($CFG->dirroot . '/backup/util/xml/parser/progressive_parser.class.php'); require_once($CFG->dirroot . '/backup/util/xml/parser/processors/progressive_parser_processor.class.php'); require_once($CFG->dirroot . '/backup/util/xml/parser/processors/simplified_parser_processor.class.php'); require_once($CFG->dirroot . '/backup/util/xml/parser/processors/grouped_parser_processor.class.php'); /** * Test progressive_parser and progressive_parser_processor tests. * * @package core_backup * @category test * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class parser_test extends \advanced_testcase { /* * test progressive_parser public methods */ function test_parser_public_api() { global $CFG; // Instantiate progressive_parser $pp = new progressive_parser(); $this->assertTrue($pp instanceof progressive_parser); $pr = new mock_parser_processor(); $this->assertTrue($pr instanceof progressive_parser_processor); // Try to process without processor try { $pp->process(); $this->assertTrue(false); } catch (\Exception $e) { $this->assertTrue($e instanceof progressive_parser_exception); $this->assertEquals($e->errorcode, 'undefined_parser_processor'); } // Assign processor to parser $pp->set_processor($pr); // Try to process without file and contents try { $pp->process(); $this->assertTrue(false); } catch (\Exception $e) { $this->assertTrue($e instanceof progressive_parser_exception); $this->assertEquals($e->errorcode, 'undefined_xml_to_parse'); } // Assign *invalid* processor to parser try { $pp->set_processor(new \stdClass()); $this->assertTrue(false); } catch (\Exception $e) { $this->assertTrue($e instanceof progressive_parser_exception); $this->assertEquals($e->errorcode, 'invalid_parser_processor'); } // Set file from fixtures (test1.xml) and process it $pp = new progressive_parser(); $pr = new mock_parser_processor(); $pp->set_processor($pr); $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test1.xml'); $pp->process(); $serfromfile = serialize($pr->get_chunks()); // Get serialized results (to compare later) // Set *unexisting* file from fixtures try { $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test0.xml'); $this->assertTrue(false); } catch (\Exception $e) { $this->assertTrue($e instanceof progressive_parser_exception); $this->assertEquals($e->errorcode, 'invalid_file_to_parse'); } // Set contents from fixtures (test1.xml) and process it $pp = new progressive_parser(); $pr = new mock_parser_processor(); $pp->set_processor($pr); $pp->set_contents(file_get_contents($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test1.xml')); $pp->process(); $serfrommemory = serialize($pr->get_chunks()); // Get serialized results (to compare later) // Set *empty* contents try { $pp->set_contents(''); $this->assertTrue(false); } catch (\Exception $e) { $this->assertTrue($e instanceof progressive_parser_exception); $this->assertEquals($e->errorcode, 'invalid_contents_to_parse'); } // Check that both results from file processing and content processing are equal $this->assertEquals($serfromfile, $serfrommemory); // Check case_folding is working ok $pp = new progressive_parser(true); $pr = new mock_parser_processor(); $pp->set_processor($pr); $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test1.xml'); $pp->process(); $chunks = $pr->get_chunks(); $this->assertTrue($chunks[0]['path'] === '/FIRSTTAG'); $this->assertTrue($chunks[0]['tags']['SECONDTAG']['name'] === 'SECONDTAG'); $this->assertTrue($chunks[0]['tags']['SECONDTAG']['attrs']['NAME'] === 'secondtag'); // Check invalid XML exception is working ok $pp = new progressive_parser(true); $pr = new mock_parser_processor(); $pp->set_processor($pr); $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test2.xml'); try { $pp->process(); } catch (\Exception $e) { $this->assertTrue($e instanceof progressive_parser_exception); $this->assertEquals($e->errorcode, 'xml_parsing_error'); } // Check double process throws exception $pp = new progressive_parser(true); $pr = new mock_parser_processor(); $pp->set_processor($pr); $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test1.xml'); $pp->process(); try { // Second process, will throw exception $pp->process(); $this->assertTrue(false); } catch (\Exception $e) { $this->assertTrue($e instanceof progressive_parser_exception); $this->assertEquals($e->errorcode, 'progressive_parser_already_used'); } } /* * test progressive_parser parsing results using testing_parser_processor and test1.xml * auto-described file from fixtures */ function test_parser_results() { global $CFG; // Instantiate progressive_parser $pp = new progressive_parser(); // Instantiate processor, passing the unit test as param $pr = new mock_auto_parser_processor($this); $this->assertTrue($pr instanceof progressive_parser_processor); // Assign processor to parser $pp->set_processor($pr); // Set file from fixtures $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test3.xml'); // Process the file, the autotest processor will perform a bunch of automatic tests $pp->process(); // Get processor debug info $debug = $pr->debug_info(); $this->assertTrue(is_array($debug)); $this->assertTrue(array_key_exists('chunks', $debug)); // Check the number of chunks is correct for the file $this->assertEquals($debug['chunks'], 10); } /* * test progressive_parser parsing results using simplified_parser_processor and test4.xml * (one simple glossary backup file example) */ function test_simplified_parser_results() { global $CFG; // Instantiate progressive_parser $pp = new progressive_parser(); // Instantiate simplified_parser_processor declaring the interesting paths $pr = new mock_simplified_parser_processor(array( '/activity', '/activity/glossary', '/activity/glossary/entries/entry', '/activity/glossary/entries/entry/aliases/alias', '/activity/glossary/entries/entry/ratings/rating', '/activity/glossary/categories/category', '/activity/glossary/onetest', '/activity/glossary/othertest')); $this->assertTrue($pr instanceof progressive_parser_processor); // Assign processor to parser $pp->set_processor($pr); // Set file from fixtures $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test4.xml'); // Process the file $pp->process(); // Get processor debug info $debug = $pr->debug_info(); $this->assertTrue(is_array($debug)); $this->assertTrue(array_key_exists('chunks', $debug)); // Check the number of chunks is correct for the file $this->assertEquals($debug['chunks'], 12); // Get all the simplified chunks and perform various validations $chunks = $pr->get_chunks(); // Check we have received the correct number of chunks $this->assertEquals(count($chunks), 12); // chunk[0] (/activity) tests $this->assertEquals(count($chunks[0]), 3); $this->assertEquals($chunks[0]['path'], '/activity'); $this->assertEquals($chunks[0]['level'],'2'); $tags = $chunks[0]['tags']; $this->assertEquals(count($tags), 4); $this->assertEquals($tags['id'], 1); $this->assertEquals($tags['moduleid'], 5); $this->assertEquals($tags['modulename'], 'glossary'); $this->assertEquals($tags['contextid'], 26); $this->assertEquals($chunks[0]['level'],'2'); // chunk[1] (/activity/glossary) tests $this->assertEquals(count($chunks[1]), 3); $this->assertEquals($chunks[1]['path'], '/activity/glossary'); $this->assertEquals($chunks[1]['level'],'3'); $tags = $chunks[1]['tags']; $this->assertEquals(count($tags), 24); $this->assertEquals($tags['id'], 1); $this->assertEquals($tags['intro'], '<p>One simple glossary to test backup & restore. Here it\'s the standard image:</p>'. "\n". '<p><img src="@@PLUGINFILE@@/88_31.png" alt="pwd by moodle" width="88" height="31" /></p>'); $this->assertEquals($tags['timemodified'], 1275639747); $this->assertTrue(!isset($tags['categories'])); // chunk[5] (second /activity/glossary/entries/entry) tests $this->assertEquals(count($chunks[5]), 3); $this->assertEquals($chunks[5]['path'], '/activity/glossary/entries/entry'); $this->assertEquals($chunks[5]['level'],'5'); $tags = $chunks[5]['tags']; $this->assertEquals(count($tags), 15); $this->assertEquals($tags['id'], 2); $this->assertEquals($tags['concept'], 'cat'); $this->assertTrue(!isset($tags['aliases'])); $this->assertTrue(!isset($tags['entries'])); // chunk[6] (second /activity/glossary/entries/entry/aliases/alias) tests $this->assertEquals(count($chunks[6]), 3); $this->assertEquals($chunks[6]['path'], '/activity/glossary/entries/entry/aliases/alias'); $this->assertEquals($chunks[6]['level'],'7'); $tags = $chunks[6]['tags']; $this->assertEquals(count($tags), 2); $this->assertEquals($tags['id'], 2); $this->assertEquals($tags['alias_text'], 'cats'); // chunk[7] (second /activity/glossary/entries/entry/aliases/alias) tests $this->assertEquals(count($chunks[7]), 3); $this->assertEquals($chunks[7]['path'], '/activity/glossary/entries/entry/aliases/alias'); $this->assertEquals($chunks[7]['level'],'7'); $tags = $chunks[7]['tags']; $this->assertEquals(count($tags), 2); $this->assertEquals($tags['id'], 3); $this->assertEquals($tags['alias_text'], 'felines'); // chunk[8] (second /activity/glossary/entries/entry/ratings/rating) tests $this->assertEquals(count($chunks[8]), 3); $this->assertEquals($chunks[8]['path'], '/activity/glossary/entries/entry/ratings/rating'); $this->assertEquals($chunks[8]['level'],'7'); $tags = $chunks[8]['tags']; $this->assertEquals(count($tags), 6); $this->assertEquals($tags['id'], 1); $this->assertEquals($tags['timemodified'], '1275639779'); // chunk[9] (first /activity/glossary/onetest) tests $this->assertEquals(count($chunks[9]), 3); $this->assertEquals($chunks[9]['path'], '/activity/glossary/onetest'); $this->assertEquals($chunks[9]['level'],'4'); $tags = $chunks[9]['tags']; $this->assertEquals(count($tags), 2); $this->assertEquals($tags['name'], 1); $this->assertEquals($tags['value'], 1); // chunk[10] (second /activity/glossary/onetest) tests $this->assertEquals(count($chunks[10]), 3); $this->assertEquals($chunks[10]['path'], '/activity/glossary/onetest'); $this->assertEquals($chunks[10]['level'],'4'); $tags = $chunks[10]['tags']; $this->assertEquals(count($tags), 2); $this->assertEquals($tags['name'], 2); $this->assertEquals($tags['value'], 2); // chunk[11] (first /activity/glossary/othertest) tests // note we don't allow repeated "final" element, so we only return the last one $this->assertEquals(count($chunks[11]), 3); $this->assertEquals($chunks[11]['path'], '/activity/glossary/othertest'); $this->assertEquals($chunks[11]['level'],'4'); $tags = $chunks[11]['tags']; $this->assertEquals(count($tags), 2); $this->assertEquals($tags['name'], 4); $this->assertEquals($tags['value'], 5); // Now check start notifications $snotifs = $pr->get_start_notifications(); // Check we have received the correct number of notifications $this->assertEquals(count($snotifs), 12); // Check first, sixth and last notifications $this->assertEquals($snotifs[0], '/activity'); $this->assertEquals($snotifs[5], '/activity/glossary/entries/entry'); $this->assertEquals($snotifs[11], '/activity/glossary/othertest'); // Now check end notifications $enotifs = $pr->get_end_notifications(); // Check we have received the correct number of notifications $this->assertEquals(count($snotifs), 12); // Check first, sixth and last notifications $this->assertEquals($enotifs[0], '/activity/glossary/entries/entry/aliases/alias'); $this->assertEquals($enotifs[5], '/activity/glossary/entries/entry/ratings/rating'); $this->assertEquals($enotifs[11], '/activity'); // Check start and end notifications are balanced sort($snotifs); sort($enotifs); $this->assertEquals($snotifs, $enotifs); // Now verify that the start/process/end order is correct $allnotifs = $pr->get_all_notifications(); $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count // Check integrity of the notifications $errcount = $this->helper_check_notifications_order_integrity($allnotifs); $this->assertEquals($errcount, 0); // No errors found, plz } /** * test how the simplified processor and the order of start/process/end events happens * with one real fragment of one backup 1.9 file, where some problems * were found by David, hence we honor him in the name of the test ;-) */ function test_simplified_david_backup19_file_fragment() { global $CFG; // Instantiate progressive_parser $pp = new progressive_parser(); // Instantiate grouped_parser_processor $pr = new mock_simplified_parser_processor(); // Add interesting paths $pr->add_path('/MOODLE_BACKUP/COURSE'); $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION'); $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD'); $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES'); $this->assertTrue($pr instanceof progressive_parser_processor); // Assign processor to parser $pp->set_processor($pr); // Set file from fixtures $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test5.xml'); // Process the file $pp->process(); // Get all the simplified chunks and perform various validations $chunks = $pr->get_chunks(); $this->assertEquals(count($chunks), 3); // Only 3, because 7 (COURSE, ROLES_OVERRIDES and 5 MOD) are empty, aka no chunk // Now check start notifications $snotifs = $pr->get_start_notifications(); // Check we have received the correct number of notifications $this->assertEquals(count($snotifs), 10); // Start tags are dispatched for empties (ROLES_OVERRIDES) // Check first and last notifications $this->assertEquals($snotifs[0], '/MOODLE_BACKUP/COURSE'); $this->assertEquals($snotifs[1], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION'); $this->assertEquals($snotifs[2], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD'); $this->assertEquals($snotifs[3], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES'); $this->assertEquals($snotifs[7], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD'); $this->assertEquals($snotifs[8], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD'); $this->assertEquals($snotifs[9], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD'); // Now check end notifications $enotifs = $pr->get_end_notifications(); // Check we have received the correct number of notifications $this->assertEquals(count($snotifs), 10); // End tags are dispatched for empties (ROLES_OVERRIDES) // Check first, and last notifications $this->assertEquals($enotifs[0], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES'); $this->assertEquals($enotifs[1], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD'); $this->assertEquals($enotifs[2], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD'); $this->assertEquals($enotifs[3], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD'); $this->assertEquals($enotifs[7], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD'); $this->assertEquals($enotifs[8], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION'); $this->assertEquals($enotifs[9], '/MOODLE_BACKUP/COURSE'); // Check start and end notifications are balanced sort($snotifs); sort($enotifs); $this->assertEquals($snotifs, $enotifs); // Now verify that the start/process/end order is correct $allnotifs = $pr->get_all_notifications(); $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count // Check integrity of the notifications $errcount = $this->helper_check_notifications_order_integrity($allnotifs); $this->assertEquals($errcount, 0); // No errors found, plz } /* * test progressive_parser parsing results using grouped_parser_processor and test4.xml * (one simple glossary backup file example) */ function test_grouped_parser_results() { global $CFG; // Instantiate progressive_parser $pp = new progressive_parser(); // Instantiate grouped_parser_processor $pr = new mock_grouped_parser_processor(); // Add interesting paths $pr->add_path('/activity'); $pr->add_path('/activity/glossary', true); $pr->add_path('/activity/glossary/entries/entry'); $pr->add_path('/activity/glossary/entries/entry/aliases/alias'); $pr->add_path('/activity/glossary/entries/entry/ratings/rating'); $pr->add_path('/activity/glossary/categories/category'); $pr->add_path('/activity/glossary/onetest'); $pr->add_path('/activity/glossary/othertest'); $this->assertTrue($pr instanceof progressive_parser_processor); // Assign processor to parser $pp->set_processor($pr); // Set file from fixtures $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test4.xml'); // Process the file $pp->process(); // Get processor debug info $debug = $pr->debug_info(); $this->assertTrue(is_array($debug)); $this->assertTrue(array_key_exists('chunks', $debug)); // Check the number of chunks is correct for the file $this->assertEquals($debug['chunks'], 2); // Get all the simplified chunks and perform various validations $chunks = $pr->get_chunks(); // Check we have received the correct number of chunks $this->assertEquals(count($chunks), 2); // chunk[0] (/activity) tests $this->assertEquals(count($chunks[0]), 3); $this->assertEquals($chunks[0]['path'], '/activity'); $this->assertEquals($chunks[0]['level'],'2'); $tags = $chunks[0]['tags']; $this->assertEquals(count($tags), 4); $this->assertEquals($tags['id'], 1); $this->assertEquals($tags['moduleid'], 5); $this->assertEquals($tags['modulename'], 'glossary'); $this->assertEquals($tags['contextid'], 26); $this->assertEquals($chunks[0]['level'],'2'); // chunk[1] (grouped /activity/glossary tests) $this->assertEquals(count($chunks[1]), 3); $this->assertEquals($chunks[1]['path'], '/activity/glossary'); $this->assertEquals($chunks[1]['level'],'3'); $tags = $chunks[1]['tags']; $this->assertEquals(count($tags), 27); $this->assertEquals($tags['id'], 1); $this->assertEquals($tags['intro'], '<p>One simple glossary to test backup & restore. Here it\'s the standard image:</p>'. "\n". '<p><img src="@@PLUGINFILE@@/88_31.png" alt="pwd by moodle" width="88" height="31" /></p>'); $this->assertEquals($tags['timemodified'], 1275639747); $this->assertTrue(!isset($tags['categories'])); $this->assertTrue(isset($tags['entries'])); $this->assertTrue(isset($tags['onetest'])); $this->assertTrue(isset($tags['othertest'])); // Various tests under the entries $entries = $chunks[1]['tags']['entries']['entry']; $this->assertEquals(count($entries), 2); // First entry $entry1 = $entries[0]; $this->assertEquals(count($entry1), 17); $this->assertEquals($entry1['id'], 1); $this->assertEquals($entry1['userid'], 2); $this->assertEquals($entry1['concept'], 'dog'); $this->assertEquals($entry1['definition'], '<p>Traditional enemies of cats</p>'); $this->assertTrue(isset($entry1['aliases'])); $this->assertTrue(isset($entry1['ratings'])); // aliases of first entry $aliases = $entry1['aliases']['alias']; $this->assertEquals(count($aliases), 1); // first alias $alias1 = $aliases[0]; $this->assertEquals(count($alias1), 2); $this->assertEquals($alias1['id'], 1); $this->assertEquals($alias1['alias_text'], 'dogs'); // ratings of first entry $ratings = $entry1['ratings']['rating']; $this->assertEquals(count($ratings), 1); // first rating $rating1 = $ratings[0]; $this->assertEquals(count($rating1), 6); $this->assertEquals($rating1['id'], 2); $this->assertEquals($rating1['value'], 6); $this->assertEquals($rating1['timemodified'], '1275639797'); // Second entry $entry2 = $entries[1]; $this->assertEquals(count($entry2), 17); $this->assertEquals($entry2['id'], 2); $this->assertEquals($entry2['userid'], 2); $this->assertEquals($entry2['concept'], 'cat'); $this->assertEquals($entry2['definition'], '<p>traditional enemies of dogs</p>'); $this->assertTrue(isset($entry2['aliases'])); $this->assertTrue(isset($entry2['ratings'])); // aliases of first entry $aliases = $entry2['aliases']['alias']; $this->assertEquals(count($aliases), 2); // first alias $alias1 = $aliases[0]; $this->assertEquals(count($alias1), 2); $this->assertEquals($alias1['id'], 2); $this->assertEquals($alias1['alias_text'], 'cats'); // second alias $alias2 = $aliases[1]; $this->assertEquals(count($alias2), 2); $this->assertEquals($alias2['id'], 3); $this->assertEquals($alias2['alias_text'], 'felines'); // ratings of first entry $ratings = $entry2['ratings']['rating']; $this->assertEquals(count($ratings), 1); // first rating $rating1 = $ratings[0]; $this->assertEquals(count($rating1), 6); $this->assertEquals($rating1['id'], 1); $this->assertEquals($rating1['value'], 5); $this->assertEquals($rating1['scaleid'], 10); // Onetest test (only 1 level nested) $onetest = $tags['onetest']; $this->assertEquals(count($onetest), 2); $this->assertEquals(count($onetest[0]), 2); $this->assertEquals($onetest[0]['name'], 1); $this->assertEquals($onetest[0]['value'], 1); $this->assertEquals(count($onetest[1]), 2); $this->assertEquals($onetest[1]['name'], 2); $this->assertEquals($onetest[1]['value'], 2); // Other test (0 level nested, only last one is retrieved) $othertest = $tags['othertest']; $this->assertEquals(count($othertest), 1); $this->assertEquals(count($othertest[0]), 2); $this->assertEquals($othertest[0]['name'], 4); $this->assertEquals($othertest[0]['value'], 5); // Now check start notifications $snotifs = $pr->get_start_notifications(); // Check we have received the correct number of notifications $this->assertEquals(count($snotifs), 2); // Check first and last notifications $this->assertEquals($snotifs[0], '/activity'); $this->assertEquals($snotifs[1], '/activity/glossary'); // Now check end notifications $enotifs = $pr->get_end_notifications(); // Check we have received the correct number of notifications $this->assertEquals(count($snotifs), 2); // Check first, and last notifications $this->assertEquals($enotifs[0], '/activity/glossary'); $this->assertEquals($enotifs[1], '/activity'); // Check start and end notifications are balanced sort($snotifs); sort($enotifs); $this->assertEquals($snotifs, $enotifs); // Now verify that the start/process/end order is correct $allnotifs = $pr->get_all_notifications(); $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count // Check integrity of the notifications $errcount = $this->helper_check_notifications_order_integrity($allnotifs); $this->assertEquals($errcount, 0); // No errors found, plz } /** * test how the grouped processor and the order of start/process/end events happens * with one real fragment of one backup 1.9 file, where some problems * were found by David, hence we honor him in the name of the test ;-) */ function test_grouped_david_backup19_file_fragment() { global $CFG; // Instantiate progressive_parser $pp = new progressive_parser(); // Instantiate grouped_parser_processor $pr = new mock_grouped_parser_processor(); // Add interesting paths $pr->add_path('/MOODLE_BACKUP/COURSE'); $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION', true); $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD'); $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES'); $this->assertTrue($pr instanceof progressive_parser_processor); // Assign processor to parser $pp->set_processor($pr); // Set file from fixtures $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test5.xml'); // Process the file $pp->process(); // Get all the simplified chunks and perform various validations $chunks = $pr->get_chunks(); $this->assertEquals(count($chunks), 1); // Only 1, the SECTION one // Now check start notifications $snotifs = $pr->get_start_notifications(); // Check we have received the correct number of notifications $this->assertEquals(count($snotifs), 2); // Check first and last notifications $this->assertEquals($snotifs[0], '/MOODLE_BACKUP/COURSE'); $this->assertEquals($snotifs[1], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION'); // Now check end notifications $enotifs = $pr->get_end_notifications(); // Check we have received the correct number of notifications $this->assertEquals(count($snotifs), 2); // End tags are dispatched for empties (ROLES_OVERRIDES) // Check first, and last notifications $this->assertEquals($enotifs[0], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION'); $this->assertEquals($enotifs[1], '/MOODLE_BACKUP/COURSE'); // Check start and end notifications are balanced sort($snotifs); sort($enotifs); $this->assertEquals($snotifs, $enotifs); // Now verify that the start/process/end order is correct $allnotifs = $pr->get_all_notifications(); $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count // Check integrity of the notifications $errcount = $this->helper_check_notifications_order_integrity($allnotifs); $this->assertEquals($errcount, 0); // No errors found, plz } /** */ function test_grouped_at_empty_node() { global $CFG; // Instantiate progressive_parser. $pp = new progressive_parser(); // Instantiate grouped_parser_processor. $pr = new mock_grouped_parser_processor(); $this->assertTrue($pr instanceof progressive_parser_processor); // Add interesting paths - moodle1 style. $pr->add_path('/test/MOODLE_BACKUP/COURSE/FORMATDATA', true); $pr->add_path('/test/MOODLE_BACKUP/COURSE/FORMATDATA/WEEKS/WEEK'); $pr->add_path('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', true); $pr->add_path('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', true); $pr->add_path('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED/SUBS/SUB'); // Add interesting paths - moodle2 style. $pr->add_path('/test/moodle2/grouped', true); $pr->add_path('/test/moodle2/grouped/subs/sub'); $pr->add_path('/test/moodle2/groupedemptywithattr', true); $pr->add_path('/test/moodle2/groupednonemptywithattr', true); $pr->add_path('/test/moodle2/groupednonemptywithattr/subs/sub'); // Assign processor to parser. $pp->set_processor($pr); // Set file from fixtures. $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/tests/fixtures/test6.xml'); // Process the file. $pp->process(); // Get all the simplified chunks and perform various validations. $chunks = $pr->get_chunks(); $this->assertEquals(count($chunks), 6); // All grouped elements. // Check some random data. $this->assertEquals('/test/MOODLE_BACKUP/COURSE/FORMATDATA', $chunks[0]['path']); $this->assertEquals(2, $chunks[0]['tags']['WEEKS']['WEEK'][1]['SECTION']); $this->assertEquals('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', $chunks[1]['path']); $this->assertEquals(array(), $chunks[1]['tags']); $this->assertEquals('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', $chunks[2]['path']); $this->assertEquals('Unit tests rock!', $chunks[2]['tags']['SUBS']['SUB'][0]['PROP']); $this->assertEquals('/test/moodle2/grouped', $chunks[3]['path']); $this->assertFalse(isset($chunks[3]['tags']['id'])); // No final elements, this should be fixed one day. $this->assertEquals(34, $chunks[3]['tags']['subs']['sub'][0]['id']); // We have final element so this is parsed. $this->assertEquals('Oh yeah', $chunks[3]['tags']['subs']['sub'][0]['prop']); $this->assertEquals('/test/moodle2/groupednonemptywithattr', $chunks[4]['path']); $this->assertEquals(78, $chunks[4]['tags']['id']); // We have final element so this is parsed. $this->assertEquals('Go baby go', $chunks[4]['tags']['prop']); $this->assertEquals(89, $chunks[4]['tags']['subs']['sub'][0]['id']); $this->assertEquals('http://moodle.org', $chunks[4]['tags']['subs']['sub'][0]['prop']); $this->assertEquals('/test/moodle2/groupedemptywithattr', $chunks[5]['path']); $this->assertFalse(isset($chunks[5]['tags']['attr'])); // No final elements, this should be fixed one day. // Now check start notifications. $snotifs = $pr->get_start_notifications(); // Check we have received the correct number of notifications. $this->assertEquals(count($snotifs), 6); // Check the order of notifications (in order they appear in test6.xml). $this->assertEquals('/test/MOODLE_BACKUP/COURSE/FORMATDATA', $snotifs[0]); $this->assertEquals('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', $snotifs[1]); $this->assertEquals('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', $snotifs[2]); $this->assertEquals('/test/moodle2/grouped', $snotifs[3]); $this->assertEquals('/test/moodle2/groupednonemptywithattr', $snotifs[4]); $this->assertEquals('/test/moodle2/groupedemptywithattr', $snotifs[5]); // Now check end notifications. $enotifs = $pr->get_end_notifications(); // Check we have received the correct number of notifications. $this->assertEquals(count($enotifs), 6); // Check the order of notifications (in order they appear in test6.xml). $this->assertEquals('/test/MOODLE_BACKUP/COURSE/FORMATDATA', $enotifs[0]); $this->assertEquals('/test/MOODLE_BACKUP/COURSE/EMPTYGROUPED', $enotifs[1]); $this->assertEquals('/test/MOODLE_BACKUP/COURSE/SECONDGROUPED', $enotifs[2]); $this->assertEquals('/test/moodle2/grouped', $enotifs[3]); $this->assertEquals('/test/moodle2/groupednonemptywithattr', $enotifs[4]); $this->assertEquals('/test/moodle2/groupedemptywithattr', $enotifs[5]); // Now verify that the start/process/end order is correct. $allnotifs = $pr->get_all_notifications(); $this->assertEquals(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // Check integrity of the notifications. $errcount = $this->helper_check_notifications_order_integrity($allnotifs); $this->assertEquals(0, $errcount); } /** * Helper function that given one array of ordered start/process/end notifications will * check it of integrity like: * - process only happens if start is the previous notification * - end only happens if dispatch is the previous notification * - start only happen with level > than last one and if there is no already started like that * * @param array $notifications ordered array of notifications with format [start|process|end]:path * @return int number of integrity problems found (errors) */ function helper_check_notifications_order_integrity($notifications) { $numerrors = 0; $notifpile = array('pilebase' => 'start'); $lastnotif = 'start:pilebase'; foreach ($notifications as $notif) { $lastpiletype = end($notifpile); $lastpilepath = key($notifpile); $lastpilelevel = strlen(preg_replace('/[^\/]/', '', $lastpilepath)); $lastnotiftype = preg_replace('/:.*/', '', $lastnotif); $lastnotifpath = preg_replace('/.*:/', '', $lastnotif); $lastnotiflevel = strlen(preg_replace('/[^\/]/', '', $lastnotifpath)); $notiftype = preg_replace('/:.*/', '', $notif); $notifpath = preg_replace('/.*:/', '', $notif); $notiflevel = strlen(preg_replace('/[^\/]/', '', $notifpath)); switch ($notiftype) { case 'process': if ($lastnotifpath != $notifpath or $lastnotiftype != 'start') { $numerrors++; // Only start for same path from last notification is allowed before process } $notifpile[$notifpath] = 'process'; // Update the status in the pile break; case 'end': if ($lastpilepath != $notifpath or ($lastpiletype != 'process' and $lastpiletype != 'start')) { $numerrors++; // Only process and start for same path from last pile is allowed before end } unset($notifpile[$notifpath]); // Delete from the pile break; case 'start': if (array_key_exists($notifpath, $notifpile) or $notiflevel <= $lastpilelevel) { $numerrors++; // Only non existing in pile and with level > last pile is allowed on start } $notifpile[$notifpath] = 'start'; // Add to the pile break; default: $numerrors++; // Incorrect type of notification => error } // Update lastnotif $lastnotif = $notif; } return $numerrors; } } /* * helper processor able to perform various auto-cheks based on attributes while processing * the test1.xml file available in the fixtures dir. It performs these checks: * - name equal to "name" attribute of the tag (if present) * - level equal to "level" attribute of the tag (if present) * - path + tagname equal to "path" attribute of the tag (if present) * - cdata, if not empty is: * - equal to "value" attribute of the tag (if present) * - else, equal to tag name * * We pass the whole advanced_testcase object to the processor in order to be * able to perform the tests in the straight in the process */ class mock_auto_parser_processor extends progressive_parser_processor { private $utc = null; // To store the unit test case public function __construct($unit_test_case) { parent::__construct(); $this->utc = $unit_test_case; } public function process_chunk($data) { // Perform auto-checks based in the rules above if (isset($data['tags'])) { foreach ($data['tags'] as $tag) { if (isset($tag['attrs']['name'])) { // name tests $this->utc->assertEquals($tag['name'], $tag['attrs']['name']); } if (isset($tag['attrs']['level'])) { // level tests $this->utc->assertEquals($data['level'], $tag['attrs']['level']); } if (isset($tag['attrs']['path'])) { // path tests $this->utc->assertEquals(rtrim($data['path'], '/') . '/' . $tag['name'], $tag['attrs']['path']); } if (!empty($tag['cdata'])) { // cdata tests if (isset($tag['attrs']['value'])) { $this->utc->assertEquals($tag['cdata'], $tag['attrs']['value']); } else { $this->utc->assertEquals($tag['cdata'], $tag['name']); } } } } } } /* * helper processor that accumulates all the chunks, resturning them with the get_chunks() method */ class mock_parser_processor extends progressive_parser_processor { private $chunksarr = array(); // To accumulate the found chunks public function process_chunk($data) { $this->chunksarr[] = $data; } public function get_chunks() { return $this->chunksarr; } } /* * helper processor that accumulates simplified chunks, returning them with the get_chunks() method */ class mock_simplified_parser_processor extends simplified_parser_processor { private $chunksarr = array(); // To accumulate the found chunks private $startarr = array(); // To accumulate all the notified path starts private $endarr = array(); // To accumulate all the notified path ends private $allnotif = array(); // To accumulate all the notified and dispatched events in an ordered way public function dispatch_chunk($data) { $this->chunksarr[] = $data; $this->allnotif[] = 'process:' . $data['path']; } public function notify_path_start($path) { $this->startarr[] = $path; $this->allnotif[] = 'start:' . $path; } public function notify_path_end($path) { $this->endarr[] = $path; $this->allnotif[] = 'end:' . $path; } public function get_chunks() { return $this->chunksarr; } public function get_start_notifications() { return $this->startarr; } public function get_end_notifications() { return $this->endarr; } public function get_all_notifications() { return $this->allnotif; } } /* * helper processor that accumulates grouped chunks, returning them with the get_chunks() method */ class mock_grouped_parser_processor extends grouped_parser_processor { private $chunksarr = array(); // To accumulate the found chunks private $startarr = array(); // To accumulate all the notified path starts private $endarr = array(); // To accumulate all the notified path ends private $allnotif = array(); // To accumulate all the notified and dispatched events in an ordered way public function dispatch_chunk($data) { $this->chunksarr[] = $data; $this->allnotif[] = 'process:' . $data['path']; } public function notify_path_start($path) { $this->startarr[] = $path; $this->allnotif[] = 'start:' . $path; } public function notify_path_end($path) { $this->endarr[] = $path; $this->allnotif[] = 'end:' . $path; } public function get_chunks() { return $this->chunksarr; } public function get_start_notifications() { return $this->startarr; } public function get_end_notifications() { return $this->endarr; } public function get_all_notifications() { return $this->allnotif; } } parser/processors/selective_exact_parser_processor.class.php 0000644 00000003367 15152155760 0020721 0 ustar 00 <?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 2003 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/progressive_parser_processor.class.php'); /** * Selective progressive_parser_processor that will send chunks straight * to output but only for chunks matching (in an exact way) some defined paths */ class selective_exact_parser_processor extends progressive_parser_processor { protected $paths; // array of paths we are interested on public function __construct(array $paths) { parent::__construct(); $this->paths = $paths; } public function process_chunk($data) { if ($this->path_is_selected($data['path'])) { print_r($data); // Simply output chunk, for testing purposes } else { $this->chunks--; // Chunk skipped } } // Protected API starts here protected function path_is_selected($path) { return in_array($path, $this->paths); } } parser/processors/findpaths_parser_processor.class.php 0000644 00000004070 15152155760 0017522 0 ustar 00 <?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 2003 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/progressive_parser_processor.class.php'); /** * Find paths progressive_parser_processor that will search for all the paths present in * the chunks being returned. Useful to know the overal structure of the XML file. */ class findpaths_parser_processor extends progressive_parser_processor { protected $foundpaths; // array of paths foudn in the chunks received from the parser public function __construct() { parent::__construct(); $this->foundpaths = array(); } public function process_chunk($data) { if (isset($data['tags'])) { foreach ($data['tags'] as $tag) { $tagpath = $data['path'] . '/' . $tag['name']; if (!array_key_exists($tagpath, $this->foundpaths)) { $this->foundpaths[$tagpath] = 1; } else { $this->foundpaths[$tagpath]++; } } } } public function debug_info() { $debug = array(); foreach($this->foundpaths as $path => $chunks) { $debug['paths'][$path] = $chunks; } return array_merge($debug, parent::debug_info()); } } parser/processors/selective_like_parser_processor.class.php 0000644 00000003436 15152155760 0020536 0 ustar 00 <?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 2003 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/progressive_parser_processor.class.php'); /** * Selective progressive_parser_processor that will send chunks straight * to output but only for chunks matching (in a left padded way - like) some defined paths */ class selective_like_parser_processor extends progressive_parser_processor { protected $paths; // array of paths we are interested on public function __construct(array $paths) { parent::__construct(); $this->paths = '=>' . implode('=>', $paths); } public function process_chunk($data) { if ($this->path_is_selected($data['path'])) { print_r($data); // Simply output chunk, for testing purposes } else { $this->chunks--; // Chunk skipped } } // Protected API starts here protected function path_is_selected($path) { return strpos('@=>' . $path, $this->paths); } } parser/processors/progressive_parser_processor.class.php 0000644 00000005741 15152155760 0020120 0 ustar 00 <?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 backup-xml * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * This abstract class implements one progressive_parser_processor * * Processor that will receive chunks of data from the @progressive_parser * and will perform all sort of operations with them (join, split, invoke * other methods, output, whatever... * * You will need to extend this class to get the expected functionality * by implementing the @process_chunk() method to handle different * chunks of information and, optionally, the @process_cdata() to * process each cdata piece individually before being "published" to * the chunk processor. * * The "propietary array format" that the parser publishes to the @progressive_parser_procesor * is this: * array ( * 'path' => path where the tags belong to, * 'level'=> level (1-based) of the tags * 'tags => array ( * 'name' => name of the tag, * 'attrs'=> array( name of the attr => value of the attr), * 'cdata => cdata of the tag * ) * ) * * TODO: Finish phpdocs */ abstract class progressive_parser_processor { protected $inittime; // Initial microtime protected $chunks; // Number of chunks processed public function __construct() { $this->inittime= microtime(true); $this->chunks = 0; } /** * Receive one chunk of information from the parser */ abstract public function process_chunk($data); /** * The parser fires this each time one path is going to be parsed */ public function before_path($path) { } /** * The parser fires this each time one path has been parsed */ public function after_path($path) { } /** * Perform custom transformations in the processed cdata */ public function process_cdata($cdata) { return $cdata; } public function debug_info() { return array('memory' => memory_get_peak_usage(true), 'time' => microtime(true) - $this->inittime, 'chunks' => $this->chunks); } public function receive_chunk($data) { $this->chunks++; $this->process_chunk($data); } } parser/processors/simple_parser_processor.class.php 0000644 00000002463 15152155760 0017037 0 ustar 00 <?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 2003 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/progressive_parser_processor.class.php'); /** * Simple progressive_parser_processor that will send chunks straight * to output. Useful for testing, compare memory use/execution time. */ class simple_parser_processor extends progressive_parser_processor { public function process_chunk($data) { print_r($data); // Simply output chunk, for testing purposes } } parser/processors/null_parser_processor.class.php 0000644 00000002334 15152155760 0016515 0 ustar 00 <?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 2003 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/progressive_parser_processor.class.php'); /** * Null progressive_parser_processor that won't process chunks at all. * Useful for comparing memory use/execution time. */ class null_parser_processor extends progressive_parser_processor { public function process_chunk($data) { } } parser/processors/simplified_parser_processor.class.php 0000644 00000023415 15152155760 0017673 0 ustar 00 <?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 2003 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/progressive_parser_processor.class.php'); /** * Abstract xml parser processor to to simplify and dispatch parsed chunks * * This @progressive_parser_processor handles the requested paths, * performing some conversions from the original "propietary array format" * used by the @progressive_parser to a simplified structure to be used * easily. Found attributes are converted automatically to tags and cdata * to simpler values. * * Note: final tag attributes are discarded completely! * * TODO: Complete phpdocs */ abstract class simplified_parser_processor extends progressive_parser_processor { protected $paths; // array of paths we are interested on protected $parentpaths; // array of parent paths of the $paths protected $parentsinfo; // array of parent attributes to be added as child tags protected $startendinfo;// array (stack) of startend information public function __construct(array $paths = array()) { parent::__construct(); $this->paths = array(); $this->parentpaths = array(); $this->parentsinfo = array(); $this->startendinfo = array(); // Add paths and parentpaths. We are looking for attributes there foreach ($paths as $key => $path) { $this->add_path($path); } } public function add_path($path) { $this->paths[] = $path; $this->parentpaths[] = progressive_parser::dirname($path); } /** * Get the already simplified chunk and dispatch it */ abstract protected function dispatch_chunk($data); /** * Get one selected path and notify about start */ abstract protected function notify_path_start($path); /** * Get one selected path and notify about end */ abstract protected function notify_path_end($path); /** * Get one chunk of parsed data and make it simpler * adding attributes as tags and delegating to * dispatch_chunk() the procesing of the resulting chunk */ public function process_chunk($data) { // Precalculate some vars for readability $path = $data['path']; $parentpath = progressive_parser::dirname($path); $tag = basename($path); // If the path is a registered parent one, store all its tags // so, we'll be able to find attributes later when processing // (child) registered paths (to get attributes if present) if ($this->path_is_selected_parent($path)) { // if path is parent if (isset($data['tags'])) { // and has tags, save them $this->parentsinfo[$path] = $data['tags']; } } // If the path is a registered one, let's process it if ($this->path_is_selected($path)) { // Send all the pending notify_path_start/end() notifications $this->process_pending_startend_notifications($path, 'start'); // First of all, look for attributes available at parentsinfo // in order to get them available as normal tags if (isset($this->parentsinfo[$parentpath][$tag]['attrs'])) { $data['tags'] = array_merge($this->parentsinfo[$parentpath][$tag]['attrs'], $data['tags']); unset($this->parentsinfo[$parentpath][$tag]['attrs']); } // Now, let's simplify the tags array, ignoring tag attributtes and // reconverting to simpler name => value array. At the same time, // check for all the tag values being whitespace-string values, if all them // are whitespace strings, we aren't going to postprocess/dispatch the chunk $alltagswhitespace = true; foreach ($data['tags'] as $key => $value) { // If the value is already a single value, do nothing // surely was added above from parentsinfo attributes, // so we'll process the chunk always if (!is_array($value)) { $alltagswhitespace = false; continue; } // If the path including the tag name matches another selected path // (registered or parent) and is null or begins with linefeed, we know it's part // of another chunk, delete it, another chunk will contain that info if ($this->path_is_selected($path . '/' . $key) || $this->path_is_selected_parent($path . '/' . $key)) { if (!isset($value['cdata']) || substr($value['cdata'], 0, 1) === "\n") { unset($data['tags'][$key]); continue; } } // Convert to simple name => value array $data['tags'][$key] = isset($value['cdata']) ? $value['cdata'] : null; // Check $alltagswhitespace continues being true if ($alltagswhitespace && strlen($data['tags'][$key]) !== 0 && trim($data['tags'][$key]) !== '') { $alltagswhitespace = false; // Found non-whitespace value } } // Arrived here, if the chunk has tags and not all tags are whitespace, // send it to postprocess filter that will decide about dispatching. Else // skip the chunk completely if (!empty($data['tags']) && !$alltagswhitespace) { return $this->postprocess_chunk($data); } else { $this->chunks--; // Chunk skipped } } else { $this->chunks--; // Chunk skipped } return true; } /** * 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_selected($path)) { $this->startendinfo[] = array('path' => $path, 'action' => 'start'); } } /** * 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) { $toprocess = false; // If the path being closed matches (same or parent) the first path in the stack // we process pending startend notifications until one matching end is found if ($element = reset($this->startendinfo)) { $elepath = $element['path']; $eleaction = $element['action']; if (strpos($elepath, $path) === 0) { $toprocess = true; } // Also, if the stack of startend notifications is empty, we can process current end // path safely } else { $toprocess = true; } if ($this->path_is_selected($path)) { $this->startendinfo[] = array('path' => $path, 'action' => 'end'); } // Send all the pending startend notifications if decided to do so if ($toprocess) { $this->process_pending_startend_notifications($path, 'end'); } } // Protected API starts here /** * Adjust start/end til finding one match start/end path (included) * * This will trigger all the pending {@see notify_path_start} and * {@see notify_path_end} calls for one given path and action * * @param string path the path to look for as limit * @param string action the action to look for as limit */ protected function process_pending_startend_notifications($path, $action) { // Iterate until one matching path and action is found (or the array is empty) $elecount = count($this->startendinfo); $elematch = false; while ($elecount > 0 && !$elematch) { $element = array_shift($this->startendinfo); $elecount--; $elepath = $element['path']; $eleaction = $element['action']; if ($elepath == $path && $eleaction == $action) { $elematch = true; } if ($eleaction == 'start') { $this->notify_path_start($elepath); } else { $this->notify_path_end($elepath); } } } protected function postprocess_chunk($data) { $this->dispatch_chunk($data); } protected function path_is_selected($path) { return in_array($path, $this->paths); } protected function path_is_selected_parent($path) { return in_array($path, $this->parentpaths); } /** * Returns the first selected parent if available or false */ protected function selected_parent_exists($path) { $parentpath = progressive_parser::dirname($path); while ($parentpath != '/') { if ($this->path_is_selected($parentpath)) { return $parentpath; } $parentpath = progressive_parser::dirname($parentpath); } return false; } } parser/processors/grouped_parser_processor.class.php 0000644 00000030227 15152155760 0017212 0 ustar 00 <?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 // We create a array that stores each of the paths in a tree fashion // like the filesystem. Each element stores all the child elements that are // part of a full path that builds the grouped parent path we are storing. // eg Array keys are stored as follows; // root => a => b // => b // => c => d // => e => f. // Grouped paths here are; /a/b, /b, /c/d, /c/e/f. // There are no nested parent paths, that is an enforced rule so // we store an empty array to designate that the particular XML path element // is in fact a grouped path. // eg; $this->groupedparentprefixtree['a']['b'] = array(); /** @var array Search tree storing the grouped paths. */ protected $groupedparentprefixtree; /** * Keep cache of parent directory paths for XML parsing. * @var array */ protected $parentcache = array(); /** * Remaining space for parent directory paths. * @var integer */ protected $parentcacheavailablesize = 2048; 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] = true; // We check earlier in the function if there is a parent that is above the path // to be added so we can be sure no parent exists in the tree. $patharray = explode('/', $path); $currentpos = &$this->groupedparentprefixtree; foreach ($patharray as $item) { if (!isset($currentpos[$item])) { $currentpos[$item] = array(); } // Update the current array position using a reference to allow in-place updates to the array. $currentpos = &$currentpos[$item]; } } 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 isset($this->groupedpaths[$path]); } /** * 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) { // Search the tree structure to find out if one of the paths // above the $path is a grouped path. $patharray = explode('/', $this->get_parent_path($path)); $groupedpath = ''; $currentpos = &$this->groupedparentprefixtree; foreach ($patharray as $item) { // When the item isn't set in the array we know // there is no parent grouped path. if (!isset($currentpos[$item])) { return false; } // When we aren't at the start of the path, continue to build // a string representation of the path that is traversed. We will // return the grouped path to the caller if we find one. if ($item != '') { $groupedpath .= '/'.$item; } if ($currentpos[$item] == array()) { return $groupedpath; } $currentpos = &$currentpos[$item]; } return false; } /** * Get the parent path using a local cache for performance. * * @param $path string The pathname you wish to obtain the parent name for. * @return string The parent pathname. */ protected function get_parent_path($path) { if (!isset($this->parentcache[$path])) { $this->parentcache[$path] = progressive_parser::dirname($path); $this->parentcacheavailablesize--; if ($this->parentcacheavailablesize < 0) { // Older first is cheaper than LRU. We use 10% as items are grouped together and the large quiz // restore from MDL-40585 used only 600 parent paths. This is an XML heirarchy, so common paths // are grouped near each other. eg; /question_bank/question_category/question/element. After keeping // question_bank paths in the cache when we move to another area and the question_bank cache is not // useful any longer. $this->parentcache = array_slice($this->parentcache, 200, null, true); $this->parentcacheavailablesize += 200; } } return $this->parentcache[$path]; } /** * 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 => $set) { 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]; } } } parser/progressive_parser.class.php 0000644 00000026044 15152155760 0013616 0 ustar 00 <?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 backup-xml * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class implementing one SAX progressive push parser. * * SAX parser able to process XML content from files/variables. It supports * attributes and case folding and works only with UTF-8 content. It's one * progressive push parser because, intead of loading big crunchs of information * in memory, it "publishes" (pushes) small information in a "propietary array format" througt * the corresponding @progressive_parser_processor, that will be the responsibe for * returning information into handy formats to higher levels. * * Note that, while this progressive parser is able to process any XML file, it is * 100% progressive so it publishes the information in the original order it's parsed (that's * the expected behaviour) so information belonging to the same path can be returned in * different chunks if there are inner levels/paths in the middle. Be warned! * * The "propietary array format" that the parser publishes to the @progressive_parser_processor * is this: * array ( * 'path' => path where the tags belong to, * 'level'=> level (1-based) of the tags * 'tags => array ( * 'name' => name of the tag, * 'attrs'=> array( name of the attr => value of the attr), * 'cdata => cdata of the tag * ) * ) * * TODO: Finish phpdocs */ class progressive_parser { protected $xml_parser; // PHP's low level XML SAX parser protected $file; // full path to file being progressively parsed | => mutually exclusive protected $contents; // contents being progressively parsed | /** * @var progressive_parser_processor to be used to publish processed information */ protected $processor; protected $level; // level of the current tag protected $path; // path of the current tag protected $accum; // accumulated char data of the current tag protected $attrs; // attributes of the current tag protected $topush; // array containing current level information being parsed to be "pushed" protected $prevlevel; // level of the previous tag processed - to detect pushing places protected $currtag; // name/value/attributes of the tag being processed /** * @var \core\progress\base Progress tracker called for each action */ protected $progress; public function __construct($case_folding = false) { $this->xml_parser = xml_parser_create('UTF-8'); xml_parser_set_option($this->xml_parser, XML_OPTION_CASE_FOLDING, $case_folding); xml_set_object($this->xml_parser, $this); xml_set_element_handler($this->xml_parser, array($this, 'start_tag'), array($this, 'end_tag')); xml_set_character_data_handler($this->xml_parser, array($this, 'char_data')); $this->file = null; $this->contents = null; $this->procesor = null; $this->level = 0; $this->path = ''; $this->accum = ''; $this->attrs = array(); $this->topush = array(); $this->prevlevel = 0; $this->currtag = array(); } /* * Sets the XML file to be processed by the parser */ public function set_file($file) { if (!file_exists($file) || (!is_readable($file))) { throw new progressive_parser_exception('invalid_file_to_parse'); } $this->file = $file; $this->contents = null; } /* * Sets the XML contents to be processed by the parser */ public function set_contents($contents) { if (empty($contents)) { throw new progressive_parser_exception('invalid_contents_to_parse'); } $this->contents = $contents; $this->file = null; } /* * Define the @progressive_parser_processor in charge of processing the parsed chunks */ public function set_processor($processor) { if (!$processor instanceof progressive_parser_processor) { throw new progressive_parser_exception('invalid_parser_processor'); } $this->processor = $processor; } /** * Sets the progress tracker for the parser. If set, the tracker will be * called to report indeterminate progress for each chunk of XML. * * The caller should have already called start_progress on the progress tracker. * * @param \core\progress\base $progress Progress tracker */ public function set_progress(\core\progress\base $progress) { $this->progress = $progress; } /* * Process the XML, delegating found chunks to the @progressive_parser_processor */ public function process() { if (empty($this->processor)) { throw new progressive_parser_exception('undefined_parser_processor'); } if (empty($this->file) && empty($this->contents)) { throw new progressive_parser_exception('undefined_xml_to_parse'); } if (is_null($this->xml_parser)) { throw new progressive_parser_exception('progressive_parser_already_used'); } if ($this->file) { $fh = fopen($this->file, 'r'); while ($buffer = fread($fh, 8192)) { $this->parse($buffer, feof($fh)); } fclose($fh); } else { $this->parse($this->contents, true); } xml_parser_free($this->xml_parser); $this->xml_parser = null; } /** * Provides one cross-platform dirname function for * handling parser paths, see MDL-24381 */ public static function dirname($path) { return str_replace('\\', '/', dirname($path)); } // Protected API starts here protected function parse($data, $eof) { if (!xml_parse($this->xml_parser, $data, $eof)) { throw new progressive_parser_exception( 'xml_parsing_error', null, sprintf('XML error: %s at line %d, column %d', xml_error_string(xml_get_error_code($this->xml_parser)), xml_get_current_line_number($this->xml_parser), xml_get_current_column_number($this->xml_parser))); } } protected function publish($data) { $this->processor->receive_chunk($data); if (!empty($this->progress)) { // Report indeterminate progress. $this->progress->progress(); } } /** * Inform to the processor that we have started parsing one path */ protected function inform_start($path) { $this->processor->before_path($path); } /** * Inform to the processor that we have finished parsing one path */ protected function inform_end($path) { $this->processor->after_path($path); } protected function postprocess_cdata($data) { return $this->processor->process_cdata($data); } protected function start_tag($parser, $tag, $attributes) { // Normal update of parser internals $this->level++; $this->path .= '/' . $tag; $this->accum = ''; $this->attrs = !empty($attributes) ? $attributes : array(); // Inform processor we are about to start one tag $this->inform_start($this->path); // Entering a new inner level, publish all the information available if ($this->level > $this->prevlevel) { if (!empty($this->currtag) && (!empty($this->currtag['attrs']) || !empty($this->currtag['cdata']))) { // We always add the last not-empty repetition. Empty ones are ignored. if (isset($this->topush['tags'][$this->currtag['name']]) && trim($this->currtag['cdata']) === '') { // Do nothing, the tag already exists and the repetition is empty } else { $this->topush['tags'][$this->currtag['name']] = $this->currtag; } } if (!empty($this->topush['tags'])) { $this->publish($this->topush); } $this->currtag = array(); $this->topush = array(); } // If not set, build to push common header if (empty($this->topush)) { $this->topush['path'] = progressive_parser::dirname($this->path); $this->topush['level'] = $this->level; $this->topush['tags'] = array(); } // Handling a new tag, create it $this->currtag['name'] = $tag; // And add attributes if present if ($this->attrs) { $this->currtag['attrs'] = $this->attrs; } // For the records $this->prevlevel = $this->level; } protected function end_tag($parser, $tag) { // Ending rencently started tag, add value to current tag if ($this->level == $this->prevlevel) { $this->currtag['cdata'] = $this->postprocess_cdata($this->accum); // We always add the last not-empty repetition. Empty ones are ignored. if (isset($this->topush['tags'][$this->currtag['name']]) && trim($this->currtag['cdata']) === '') { // Do nothing, the tag already exists and the repetition is empty } else { $this->topush['tags'][$this->currtag['name']] = $this->currtag; } $this->currtag = array(); } // Leaving one level, publish all the information available if ($this->level < $this->prevlevel) { if (!empty($this->topush['tags'])) { $this->publish($this->topush); } $this->currtag = array(); $this->topush = array(); } // For the records $this->prevlevel = $this->level; // Inform processor we have finished one tag $this->inform_end($this->path); // Normal update of parser internals $this->level--; $this->path = progressive_parser::dirname($this->path); } protected function char_data($parser, $data) { $this->accum .= $data; } } /* * Exception class used by all the @progressive_parser stuff */ class progressive_parser_exception extends moodle_exception { public function __construct($errorcode, $a=NULL, $debuginfo=null) { parent::__construct($errorcode, 'error', '', $a, $debuginfo); } } xml_writer.class.php 0000644 00000024514 15152155760 0010572 0 ustar 00 <?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 backup-xml * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class implementing one (more or less complete) UTF-8 XML writer * * General purpose class used to output UTF-8 XML contents easily. Can be customized * using implementations of @xml_output (to define where to send the xml) and * and @xml_contenttransformer (to perform any transformation in contents before * outputting the XML). * * Has support for attributes, basic w3c xml schemas declaration, * and performs some content cleaning to avoid potential incorret UTF-8 * mess and has complete exception support. * * TODO: Provide UTF-8 safe strtoupper() function if using casefolding and non-ascii tags/attrs names * TODO: Finish phpdocs */ class xml_writer { protected $output; // @xml_output that defines how to output XML protected $contenttransformer; // @xml_contenttransformer to modify contents before output protected $prologue; // Complete string prologue we want to use protected $xmlschema; // URI to nonamespaceschema to be added to main tag protected $casefolding; // To define if xml tags must be uppercase (true) or not (false) protected $level; // current number of open tags, useful for indent text protected $opentags; // open tags accumulator, to check for errors protected $lastwastext;// to know when we are writing after text content protected $nullcontent;// to know if we are going to write one tag with null content protected $running; // To know if writer is running public function __construct($output, $contenttransformer = null, $casefolding = false) { if (!$output instanceof xml_output) { throw new xml_writer_exception('invalid_xml_output'); } if (!is_null($contenttransformer) && !$contenttransformer instanceof xml_contenttransformer) { throw new xml_writer_exception('invalid_xml_contenttransformer'); } $this->output = $output; $this->contenttransformer = $contenttransformer; $this->prologue = null; $this->xmlschema = null; $this->casefolding = $casefolding; $this->level = 0; $this->opentags = array(); $this->lastwastext = false; $this->nullcontent = false; $this->running = null; } /** * Initializes the XML writer, preparing it to accept instructions, also * invoking the underlying @xml_output init method to be ready for operation */ public function start() { if ($this->running === true) { throw new xml_writer_exception('xml_writer_already_started'); } if ($this->running === false) { throw new xml_writer_exception('xml_writer_already_stopped'); } $this->output->start(); // Initialize whatever we need in output if (!is_null($this->prologue)) { // Output prologue $this->write($this->prologue); } else { $this->write($this->get_default_prologue()); } $this->running = true; } /** * Finishes the XML writer, not accepting instructions any more, also * invoking the underlying @xml_output finish method to close/flush everything as needed */ public function stop() { if (is_null($this->running)) { throw new xml_writer_exception('xml_writer_not_started'); } if ($this->running === false) { throw new xml_writer_exception('xml_writer_already_stopped'); } if ($this->level > 0) { // Cannot stop if not at level 0, remaining open tags throw new xml_writer_exception('xml_writer_open_tags_remaining'); } $this->output->stop(); $this->running = false; } /** * Set the URI location for the *nonamespace* schema to be used by the (whole) XML document */ public function set_nonamespace_schema($uri) { if ($this->running) { throw new xml_writer_exception('xml_writer_already_started'); } $this->xmlschema = $uri; } /** * Define the complete prologue to be used, replacing the simple, default one */ public function set_prologue($prologue) { if ($this->running) { throw new xml_writer_exception('xml_writer_already_started'); } $this->prologue = $prologue; } /** * Outputs one XML start tag with optional attributes (name => value array) */ public function begin_tag($tag, $attributes = null) { // TODO: chek the tag name is valid $pre = $this->level ? "\n" . str_repeat(' ', $this->level * 2) : ''; // Indent $tag = $this->casefolding ? strtoupper($tag) : $tag; // Follow casefolding $end = $this->nullcontent ? ' /' : ''; // Tag without content, close it // Build attributes output $attrstring = ''; if (!empty($attributes) && is_array($attributes)) { // TODO: check the attr name is valid foreach ($attributes as $name => $value) { $name = $this->casefolding ? strtoupper($name) : $name; // Follow casefolding $attrstring .= ' ' . $name . '="'. $this->xml_safe_attr_content($value) . '"'; } } // Optional xml schema definition (level 0 only) $schemastring = ''; if ($this->level == 0 && !empty($this->xmlschema)) { $schemastring .= "\n " . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . "\n " . 'xsi:noNamespaceSchemaLocation="' . $this->xml_safe_attr_content($this->xmlschema) . '"'; } // Send to xml_output $this->write($pre . '<' . $tag . $attrstring . $schemastring . $end . '>'); // Acumulate the tag and inc level if (!$this->nullcontent) { array_push($this->opentags, $tag); $this->level++; } $this->lastwastext = false; } /** * Outputs one XML end tag */ public function end_tag($tag) { // TODO: check the tag name is valid if ($this->level == 0) { // Nothing to end, already at level 0 throw new xml_writer_exception('xml_writer_end_tag_no_match'); } $pre = $this->lastwastext ? '' : "\n" . str_repeat(' ', ($this->level - 1) * 2); // Indent $tag = $this->casefolding ? strtoupper($tag) : $tag; // Follow casefolding $lastopentag = array_pop($this->opentags); if ($tag != $lastopentag) { $a = new stdclass(); $a->lastopen = $lastopentag; $a->tag = $tag; throw new xml_writer_exception('xml_writer_end_tag_no_match', $a); } // Send to xml_output $this->write($pre . '</' . $tag . '>'); $this->level--; $this->lastwastext = false; } /** * Outputs one tag completely (open, contents and close) */ public function full_tag($tag, $content = null, $attributes = null) { $content = $this->text_content($content); // First of all, apply transformations $this->nullcontent = is_null($content) ? true : false; // Is it null content $this->begin_tag($tag, $attributes); if (!$this->nullcontent) { $this->write($content); $this->lastwastext = true; $this->end_tag($tag); } } // Protected API starts here /** * Send some XML formatted chunk to output. */ protected function write($output) { $this->output->write($output); } /** * Get default prologue contents for this writer if there isn't a custom one */ protected function get_default_prologue() { return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; } /** * Clean attribute content and encode needed chars * (&, <, >, ") - single quotes not needed in this class * as far as we are enclosing with " */ protected function xml_safe_attr_content($content) { return htmlspecialchars($this->xml_safe_utf8($content), ENT_COMPAT); } /** * Clean text content and encode needed chars * (&, <, >) */ protected function xml_safe_text_content($content) { return htmlspecialchars($this->xml_safe_utf8($content), ENT_NOQUOTES); } /** * Perform some UTF-8 cleaning, stripping the control chars (\x0-\x1f) * but tabs (\x9), newlines (\xa) and returns (\xd). The delete control * char (\x7f) is also included. All them are forbiden in XML 1.0 specs. * The expression below seems to be UTF-8 safe too because it simply * ignores the rest of characters. Also normalize linefeeds and return chars. */ protected function xml_safe_utf8($content) { $content = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is', '', $content ?? ''); // clean CTRL chars. $content = preg_replace("/\r\n|\r/", "\n", $content); // Normalize line&return=>line return $content; } /** * Returns text contents processed by the corresponding @xml_contenttransformer */ protected function text_content($content) { if (!is_null($this->contenttransformer)) { // Apply content transformation $content = $this->contenttransformer->process($content); } return is_null($content) ? null : $this->xml_safe_text_content($content); // Safe UTF-8 and encode } } /* * Exception class used by all the @xml_writer stuff */ class xml_writer_exception extends moodle_exception { public function __construct($errorcode, $a=NULL, $debuginfo=null) { parent::__construct($errorcode, 'error', '', $a, $debuginfo); } } contenttransformer/xml_contenttransformer.class.php 0000644 00000002615 15152155760 0017146 0 ustar 00 <?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 backup-xml * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Abstract class to extend in order to transform @xml_writer text contents * * Implementations of this class will provide @xml_writer with the ability of * transform xml text contents before being sent to output. Useful for various * things like link transformations in the backup process and others. * * Just define the process() method, program the desired transformations and done! * * TODO: Finish phpdocs */ abstract class xml_contenttransformer { abstract public function process($content); }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | ���֧ߧ֧�ѧ�ڧ� ����ѧߧڧ��: 0 |
proxy
|
phpinfo
|
���ѧ����ۧܧ�