Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402]
1 <?php 2 3 // This file is part of Moodle - http://moodle.org/ 4 // 5 // Moodle is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // Moodle is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU General Public License for more details. 14 // 15 // You should have received a copy of the GNU General Public License 16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 17 18 /** 19 * @package moodlecore 20 * @subpackage backup-xml 21 * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 /** 26 * Class implementing one (more or less complete) UTF-8 XML writer 27 * 28 * General purpose class used to output UTF-8 XML contents easily. Can be customized 29 * using implementations of @xml_output (to define where to send the xml) and 30 * and @xml_contenttransformer (to perform any transformation in contents before 31 * outputting the XML). 32 * 33 * Has support for attributes, basic w3c xml schemas declaration, 34 * and performs some content cleaning to avoid potential incorret UTF-8 35 * mess and has complete exception support. 36 * 37 * TODO: Provide UTF-8 safe strtoupper() function if using casefolding and non-ascii tags/attrs names 38 * TODO: Finish phpdocs 39 */ 40 class xml_writer { 41 42 protected $output; // @xml_output that defines how to output XML 43 protected $contenttransformer; // @xml_contenttransformer to modify contents before output 44 45 protected $prologue; // Complete string prologue we want to use 46 protected $xmlschema; // URI to nonamespaceschema to be added to main tag 47 48 protected $casefolding; // To define if xml tags must be uppercase (true) or not (false) 49 50 protected $level; // current number of open tags, useful for indent text 51 protected $opentags; // open tags accumulator, to check for errors 52 protected $lastwastext;// to know when we are writing after text content 53 protected $nullcontent;// to know if we are going to write one tag with null content 54 55 protected $running; // To know if writer is running 56 57 public function __construct($output, $contenttransformer = null, $casefolding = false) { 58 if (!$output instanceof xml_output) { 59 throw new xml_writer_exception('invalid_xml_output'); 60 } 61 if (!is_null($contenttransformer) && !$contenttransformer instanceof xml_contenttransformer) { 62 throw new xml_writer_exception('invalid_xml_contenttransformer'); 63 } 64 65 $this->output = $output; 66 $this->contenttransformer = $contenttransformer; 67 68 $this->prologue = null; 69 $this->xmlschema = null; 70 71 $this->casefolding = $casefolding; 72 73 $this->level = 0; 74 $this->opentags = array(); 75 $this->lastwastext = false; 76 $this->nullcontent = false; 77 78 $this->running = null; 79 } 80 81 /** 82 * Initializes the XML writer, preparing it to accept instructions, also 83 * invoking the underlying @xml_output init method to be ready for operation 84 */ 85 public function start() { 86 if ($this->running === true) { 87 throw new xml_writer_exception('xml_writer_already_started'); 88 } 89 if ($this->running === false) { 90 throw new xml_writer_exception('xml_writer_already_stopped'); 91 } 92 $this->output->start(); // Initialize whatever we need in output 93 if (!is_null($this->prologue)) { // Output prologue 94 $this->write($this->prologue); 95 } else { 96 $this->write($this->get_default_prologue()); 97 } 98 $this->running = true; 99 } 100 101 /** 102 * Finishes the XML writer, not accepting instructions any more, also 103 * invoking the underlying @xml_output finish method to close/flush everything as needed 104 */ 105 public function stop() { 106 if (is_null($this->running)) { 107 throw new xml_writer_exception('xml_writer_not_started'); 108 } 109 if ($this->running === false) { 110 throw new xml_writer_exception('xml_writer_already_stopped'); 111 } 112 if ($this->level > 0) { // Cannot stop if not at level 0, remaining open tags 113 throw new xml_writer_exception('xml_writer_open_tags_remaining'); 114 } 115 $this->output->stop(); 116 $this->running = false; 117 } 118 119 /** 120 * Set the URI location for the *nonamespace* schema to be used by the (whole) XML document 121 */ 122 public function set_nonamespace_schema($uri) { 123 if ($this->running) { 124 throw new xml_writer_exception('xml_writer_already_started'); 125 } 126 $this->xmlschema = $uri; 127 } 128 129 /** 130 * Define the complete prologue to be used, replacing the simple, default one 131 */ 132 public function set_prologue($prologue) { 133 if ($this->running) { 134 throw new xml_writer_exception('xml_writer_already_started'); 135 } 136 $this->prologue = $prologue; 137 } 138 139 /** 140 * Outputs one XML start tag with optional attributes (name => value array) 141 */ 142 public function begin_tag($tag, $attributes = null) { 143 // TODO: chek the tag name is valid 144 $pre = $this->level ? "\n" . str_repeat(' ', $this->level * 2) : ''; // Indent 145 $tag = $this->casefolding ? strtoupper($tag) : $tag; // Follow casefolding 146 $end = $this->nullcontent ? ' /' : ''; // Tag without content, close it 147 148 // Build attributes output 149 $attrstring = ''; 150 if (!empty($attributes) && is_array($attributes)) { 151 // TODO: check the attr name is valid 152 foreach ($attributes as $name => $value) { 153 $name = $this->casefolding ? strtoupper($name) : $name; // Follow casefolding 154 $attrstring .= ' ' . $name . '="'. 155 $this->xml_safe_attr_content($value) . '"'; 156 } 157 } 158 159 // Optional xml schema definition (level 0 only) 160 $schemastring = ''; 161 if ($this->level == 0 && !empty($this->xmlschema)) { 162 $schemastring .= "\n " . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . 163 "\n " . 'xsi:noNamespaceSchemaLocation="' . $this->xml_safe_attr_content($this->xmlschema) . '"'; 164 } 165 166 // Send to xml_output 167 $this->write($pre . '<' . $tag . $attrstring . $schemastring . $end . '>'); 168 169 // Acumulate the tag and inc level 170 if (!$this->nullcontent) { 171 array_push($this->opentags, $tag); 172 $this->level++; 173 } 174 $this->lastwastext = false; 175 } 176 177 /** 178 * Outputs one XML end tag 179 */ 180 public function end_tag($tag) { 181 // TODO: check the tag name is valid 182 183 if ($this->level == 0) { // Nothing to end, already at level 0 184 throw new xml_writer_exception('xml_writer_end_tag_no_match'); 185 } 186 187 $pre = $this->lastwastext ? '' : "\n" . str_repeat(' ', ($this->level - 1) * 2); // Indent 188 $tag = $this->casefolding ? strtoupper($tag) : $tag; // Follow casefolding 189 190 $lastopentag = array_pop($this->opentags); 191 192 if ($tag != $lastopentag) { 193 $a = new stdclass(); 194 $a->lastopen = $lastopentag; 195 $a->tag = $tag; 196 throw new xml_writer_exception('xml_writer_end_tag_no_match', $a); 197 } 198 199 // Send to xml_output 200 $this->write($pre . '</' . $tag . '>'); 201 202 $this->level--; 203 $this->lastwastext = false; 204 } 205 206 207 /** 208 * Outputs one tag completely (open, contents and close) 209 */ 210 public function full_tag($tag, $content = null, $attributes = null) { 211 $content = $this->text_content($content); // First of all, apply transformations 212 $this->nullcontent = is_null($content) ? true : false; // Is it null content 213 $this->begin_tag($tag, $attributes); 214 if (!$this->nullcontent) { 215 $this->write($content); 216 $this->lastwastext = true; 217 $this->end_tag($tag); 218 } 219 } 220 221 222 // Protected API starts here 223 224 /** 225 * Send some XML formatted chunk to output. 226 */ 227 protected function write($output) { 228 $this->output->write($output); 229 } 230 231 /** 232 * Get default prologue contents for this writer if there isn't a custom one 233 */ 234 protected function get_default_prologue() { 235 return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; 236 } 237 238 /** 239 * Clean attribute content and encode needed chars 240 * (&, <, >, ") - single quotes not needed in this class 241 * as far as we are enclosing with " 242 */ 243 protected function xml_safe_attr_content($content) { 244 return htmlspecialchars($this->xml_safe_utf8($content), ENT_COMPAT); 245 } 246 247 /** 248 * Clean text content and encode needed chars 249 * (&, <, >) 250 */ 251 protected function xml_safe_text_content($content) { 252 return htmlspecialchars($this->xml_safe_utf8($content), ENT_NOQUOTES); 253 } 254 255 /** 256 * Perform some UTF-8 cleaning, stripping the control chars (\x0-\x1f) 257 * but tabs (\x9), newlines (\xa) and returns (\xd). The delete control 258 * char (\x7f) is also included. All them are forbiden in XML 1.0 specs. 259 * The expression below seems to be UTF-8 safe too because it simply 260 * ignores the rest of characters. Also normalize linefeeds and return chars. 261 */ 262 protected function xml_safe_utf8($content) { 263 $content = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is', '', $content ?? ''); // clean CTRL chars. 264 $content = preg_replace("/\r\n|\r/", "\n", $content); // Normalize line&return=>line 265 return fix_utf8($content); 266 } 267 268 /** 269 * Returns text contents processed by the corresponding @xml_contenttransformer 270 */ 271 protected function text_content($content) { 272 if (!is_null($this->contenttransformer)) { // Apply content transformation 273 $content = $this->contenttransformer->process($content); 274 } 275 return is_null($content) ? null : $this->xml_safe_text_content($content); // Safe UTF-8 and encode 276 } 277 } 278 279 /* 280 * Exception class used by all the @xml_writer stuff 281 */ 282 class xml_writer_exception extends moodle_exception { 283 284 public function __construct($errorcode, $a=NULL, $debuginfo=null) { 285 parent::__construct($errorcode, 'error', '', $a, $debuginfo); 286 } 287 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body