|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 00003 // This file is part of Moodle - http://moodle.org/ 00004 // 00005 // Moodle is free software: you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation, either version 3 of the License, or 00008 // (at your option) any later version. 00009 // 00010 // Moodle is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 // GNU General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00017 00040 class xml_writer { 00041 00042 protected $output; // @xml_output that defines how to output XML 00043 protected $contenttransformer; // @xml_contenttransformer to modify contents before output 00044 00045 protected $prologue; // Complete string prologue we want to use 00046 protected $xmlschema; // URI to nonamespaceschema to be added to main tag 00047 00048 protected $casefolding; // To define if xml tags must be uppercase (true) or not (false) 00049 00050 protected $level; // current number of open tags, useful for indent text 00051 protected $opentags; // open tags accumulator, to check for errors 00052 protected $lastwastext;// to know when we are writing after text content 00053 protected $nullcontent;// to know if we are going to write one tag with null content 00054 00055 protected $running; // To know if writer is running 00056 00057 public function __construct($output, $contenttransformer = null, $casefolding = false) { 00058 if (!$output instanceof xml_output) { 00059 throw new xml_writer_exception('invalid_xml_output'); 00060 } 00061 if (!is_null($contenttransformer) && !$contenttransformer instanceof xml_contenttransformer) { 00062 throw new xml_writer_exception('invalid_xml_contenttransformer'); 00063 } 00064 00065 $this->output = $output; 00066 $this->contenttransformer = $contenttransformer; 00067 00068 $this->prologue = null; 00069 $this->xmlschema = null; 00070 00071 $this->casefolding = $casefolding; 00072 00073 $this->level = 0; 00074 $this->opentags = array(); 00075 $this->lastwastext = false; 00076 $this->nullcontent = false; 00077 00078 $this->running = null; 00079 } 00080 00085 public function start() { 00086 if ($this->running === true) { 00087 throw new xml_writer_exception('xml_writer_already_started'); 00088 } 00089 if ($this->running === false) { 00090 throw new xml_writer_exception('xml_writer_already_stopped'); 00091 } 00092 $this->output->start(); // Initialize whatever we need in output 00093 if (!is_null($this->prologue)) { // Output prologue 00094 $this->write($this->prologue); 00095 } else { 00096 $this->write($this->get_default_prologue()); 00097 } 00098 $this->running = true; 00099 } 00100 00105 public function stop() { 00106 if (is_null($this->running)) { 00107 throw new xml_writer_exception('xml_writer_not_started'); 00108 } 00109 if ($this->running === false) { 00110 throw new xml_writer_exception('xml_writer_already_stopped'); 00111 } 00112 if ($this->level > 0) { // Cannot stop if not at level 0, remaining open tags 00113 throw new xml_writer_exception('xml_writer_open_tags_remaining'); 00114 } 00115 $this->output->stop(); 00116 $this->running = false; 00117 } 00118 00122 public function set_nonamespace_schema($uri) { 00123 if ($this->running) { 00124 throw new xml_writer_exception('xml_writer_already_started'); 00125 } 00126 $this->xmlschema = $uri; 00127 } 00128 00132 public function set_prologue($prologue) { 00133 if ($this->running) { 00134 throw new xml_writer_exception('xml_writer_already_started'); 00135 } 00136 $this->prologue = $prologue; 00137 } 00138 00142 public function begin_tag($tag, $attributes = null) { 00143 // TODO: chek the tag name is valid 00144 $pre = $this->level ? "\n" . str_repeat(' ', $this->level * 2) : ''; // Indent 00145 $tag = $this->casefolding ? strtoupper($tag) : $tag; // Follow casefolding 00146 $end = $this->nullcontent ? ' /' : ''; // Tag without content, close it 00147 00148 // Build attributes output 00149 $attrstring = ''; 00150 if (!empty($attributes) && is_array($attributes)) { 00151 // TODO: check the attr name is valid 00152 foreach ($attributes as $name => $value) { 00153 $name = $this->casefolding ? strtoupper($name) : $name; // Follow casefolding 00154 $attrstring .= ' ' . $name . '="'. 00155 $this->xml_safe_attr_content($value) . '"'; 00156 } 00157 } 00158 00159 // Optional xml schema definition (level 0 only) 00160 $schemastring = ''; 00161 if ($this->level == 0 && !empty($this->xmlschema)) { 00162 $schemastring .= "\n " . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . 00163 "\n " . 'xsi:noNamespaceSchemaLocation="' . $this->xml_safe_attr_content($this->xmlschema) . '"'; 00164 } 00165 00166 // Send to xml_output 00167 $this->write($pre . '<' . $tag . $attrstring . $schemastring . $end . '>'); 00168 00169 // Acumulate the tag and inc level 00170 if (!$this->nullcontent) { 00171 array_push($this->opentags, $tag); 00172 $this->level++; 00173 } 00174 $this->lastwastext = false; 00175 } 00176 00180 public function end_tag($tag) { 00181 // TODO: check the tag name is valid 00182 00183 if ($this->level == 0) { // Nothing to end, already at level 0 00184 throw new xml_writer_exception('xml_writer_end_tag_no_match'); 00185 } 00186 00187 $pre = $this->lastwastext ? '' : "\n" . str_repeat(' ', ($this->level - 1) * 2); // Indent 00188 $tag = $this->casefolding ? strtoupper($tag) : $tag; // Follow casefolding 00189 00190 $lastopentag = array_pop($this->opentags); 00191 00192 if ($tag != $lastopentag) { 00193 $a = new stdclass(); 00194 $a->lastopen = $lastopentag; 00195 $a->tag = $tag; 00196 throw new xml_writer_exception('xml_writer_end_tag_no_match', $a); 00197 } 00198 00199 // Send to xml_output 00200 $this->write($pre . '</' . $tag . '>'); 00201 00202 $this->level--; 00203 $this->lastwastext = false; 00204 } 00205 00206 00210 public function full_tag($tag, $content = null, $attributes = null) { 00211 $content = $this->text_content($content); // First of all, apply transformations 00212 $this->nullcontent = is_null($content) ? true : false; // Is it null content 00213 $this->begin_tag($tag, $attributes); 00214 if (!$this->nullcontent) { 00215 $this->write($content); 00216 $this->lastwastext = true; 00217 $this->end_tag($tag); 00218 } 00219 } 00220 00221 00222 // Protected API starts here 00223 00227 protected function write($output) { 00228 $this->output->write($output); 00229 } 00230 00234 protected function get_default_prologue() { 00235 return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; 00236 } 00237 00243 protected function xml_safe_attr_content($content) { 00244 return htmlspecialchars($this->xml_safe_utf8($content), ENT_COMPAT); 00245 } 00246 00251 protected function xml_safe_text_content($content) { 00252 return htmlspecialchars($this->xml_safe_utf8($content), ENT_NOQUOTES); 00253 } 00254 00262 protected function xml_safe_utf8($content) { 00263 $content = preg_replace('/[\x-\x8\xb-\xc\xe-\x1f\x7f]/is','', $content); // clean CTRL chars 00264 $content = preg_replace("/\r\n|\r/", "\n", $content); // Normalize line&return=>line 00265 return $content; 00266 } 00267 00271 protected function text_content($content) { 00272 if (!is_null($this->contenttransformer)) { // Apply content transformation 00273 $content = $this->contenttransformer->process($content); 00274 } 00275 return is_null($content) ? null : $this->xml_safe_text_content($content); // Safe UTF-8 and encode 00276 } 00277 } 00278 00279 /* 00280 * Exception class used by all the @xml_writer stuff 00281 */ 00282 class xml_writer_exception extends moodle_exception { 00283 00284 public function __construct($errorcode, $a=NULL, $debuginfo=null) { 00285 parent::__construct($errorcode, 'error', '', $a, $debuginfo); 00286 } 00287 }