Aggregate Code Coverage for all tests
1 : <?php 2 : /** 3 : * PEAR2_Pyrus_XMLWriter 4 : * 5 : * PHP version 5 6 : * 7 : * @category PEAR2 8 : * @package PEAR2_Pyrus 9 : * @author Greg Beaver <cellog@php.net> 10 : * @copyright 2008 The PEAR Group 11 : * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 12 : * @version SVN: $Id$ 13 : * @link http://svn.pear.php.net/wsvn/PEARSVN/Pyrus/ 14 : */ 15 : 16 : /** 17 : * Process an array, and serialize it into XML 18 : * 19 : * @category PEAR2 20 : * @package PEAR2_Pyrus 21 : * @subpackage XML 22 : * @author Greg Beaver <cellog@php.net> 23 : * @copyright 2008 The PEAR Group 24 : * @license http://www.opensource.org/licenses/bsd-license.php New BSD License 25 : * @link http://svn.pear.php.net/wsvn/PEARSVN/Pyrus/ 26 : */ 27 : class PEAR2_Pyrus_XMLWriter 28 1 : { 29 : private $_array; 30 : private $_state; 31 : /** 32 : * @var XMLWriter 33 : */ 34 : private $_writer; 35 : private $_iter; 36 : private $_tagStack; 37 : private $_namespaces; 38 : private $_tag; 39 : private $_expectedDepth; 40 : private $_type; 41 : private $_lastkey; 42 : 43 : /** 44 : * Construct a new xml writer object. 45 : * <code> 46 : * $xmlarray = array('channel'=>array('name'=>'pear2.php.net')); 47 : * $channel = new PEAR2_Pyrus_XMLWriter($xmlarray); 48 : * </code> 49 : * 50 : * @param array $array Array representing the XML data. 51 : */ 52 : function __construct(array $array) 53 : { 54 1 : if (count($array) != 1) { 55 : throw new PEAR2_Pyrus_XMLWriter_Exception('Cannot serialize array to' . 56 : 'XML, array must have exactly 1 element'); 57 : } 58 1 : $this->_array = $array; 59 1 : $this->_writer = new XMLWriter; 60 1 : } 61 : 62 : /** 63 : * Return the raw xml string representation. 64 : * 65 : * @return string 66 : */ 67 : function __toString() 68 : { 69 1 : $this->_writer->openMemory(); 70 1 : return $this->_serialize(); 71 : } 72 : 73 : function toFile($file) 74 : { 75 : $this->_writer->openUri($file); 76 : return $this->_serialize(); 77 : } 78 : 79 : private function _pushState() 80 : { 81 1 : $this->_state[] = array($this->_type, $this->_tag, $this->_expectedDepth, 82 1 : $this->_namespaces); 83 1 : } 84 : 85 : private function _popState() 86 : { 87 1 : $save = $this->_namespaces; 88 : 89 1 : list($this->_type, $this->_tag, $this->_expectedDepth, $this->_namespaces) = 90 1 : array_pop($this->_state); 91 1 : foreach ($save as $ns) { 92 : if (!isset($this->_namespaces[$ns])) { 93 : // all namespaces must exist - only overriding is allowed 94 : $this->_namespaces = $save; 95 : return; 96 : } 97 1 : } 98 1 : } 99 : 100 : private function _finish($key, $values) 101 : { 102 1 : switch ($this->_type) { 103 1 : case 'Attribs' : 104 1 : $this->_popState(); 105 1 : return false; 106 1 : case 'Tag' : 107 1 : $this->_popState(); 108 1 : $this->_writer->endElement(); 109 1 : return false; 110 1 : case 'Sibling' : 111 1 : $this->_popState(); 112 1 : return false; 113 : } 114 : } 115 : 116 : private function _startElement($key, $values) 117 : { 118 : // new element 119 1 : if (strpos($key, ':')) { 120 : // namespaced element 121 : list($ns, $element) = explode(':', $key); 122 : } 123 1 : if (isset($element) && !isset($this->_namespaces[$ns])) { 124 : if (is_string($values)) { 125 : if (strlen($values)) { 126 : $this->_writer->writeElementNs($ns, $element, $this->_namespaces[$ns], $values); 127 : } else { 128 : $this->_writer->writeElementNs($ns, $element, $this->_namespaces[$ns]); 129 : } 130 : } else { 131 : $this->_writer->startElementNs($ns, $element, $this->_namespaces[$ns]); 132 : } 133 : } else { 134 1 : if (is_string($values) || is_int($values)) { 135 1 : if (strlen($values)) { 136 1 : $this->_writer->writeElement($key, $values); 137 1 : } else { 138 : $this->_writer->writeElement($key); 139 : } 140 1 : } else { 141 1 : $this->_writer->startElement($key); 142 : } 143 : } 144 1 : } 145 : 146 : /** 147 : * Handle an individual tag/element in the XML 148 : * 149 : * @param mixed $key The key for this element. 150 : * @param mixed $values The contents of this tag/element. 151 : */ 152 : private function _handleTag($key, $values) 153 : { 154 1 : if (is_int($key)) { 155 1 : $this->_type = 'Sibling'; 156 1 : $this->_expectedDepth = $this->_iter->getDepth(); 157 1 : $this->_pushState(); 158 : // handle sibling tags 159 1 : return '_handleSibling'; 160 : } 161 1 : $this->_startElement($key, $values); 162 1 : if (!is_string($values)) { 163 1 : $this->_expectedDepth = $this->_iter->getDepth(); 164 1 : $this->_pushState(); 165 1 : $this->_tag = $key; 166 1 : } 167 : // cycle to next key 168 1 : return false; 169 : } 170 : 171 : private function _handleSibling($key, $values) 172 : { 173 1 : if (is_int($key) && $this->_iter->getDepth() == $this->_expectedDepth) { 174 1 : if ($key) { 175 1 : $this->_startElement($this->_tag, $values); 176 1 : if (!is_string($values)) { 177 1 : $this->_pushState(); 178 1 : } 179 1 : } else { 180 1 : if (is_string($values)) { 181 : $this->_writer->text($values); 182 : $this->_writer->endElement(); 183 : } 184 : } 185 1 : } 186 1 : if (!is_string($values)) { 187 1 : $this->_type = 'Tag'; 188 1 : $this->_expectedDepth = $this->_iter->getDepth() + 1; 189 : // handle internal tags 190 1 : } 191 : // cycle to next key 192 1 : return false; 193 : } 194 : 195 : private function _handleAttribs($key, $values) 196 : { 197 : // xmlwriter converts these to and . Bad. 198 1 : $values = str_replace(array("\n","\r"), array('', ''), $values); 199 1 : if (strpos($key, ':')) { 200 : // namespaced 201 : list($ns, $attr) = explode(':', $key); 202 : if ($ns == 'xmlns' || isset($this->_namespaces[$ns])) { 203 : if ($ns == 'xmlns') { 204 : // new namespace declaration 205 : $this->_namespaces[$attr] = $values; 206 : } 207 : $this->_writer->writeAttribute($key, $values); 208 : } else { 209 : $this->_writer->writeAttributeNS($ns, $attr, $values, $values); 210 : } 211 : } else { // default namespace 212 1 : $this->_writer->writeAttribute($key, $values); 213 : } 214 : // cycle to next key 215 1 : return false; 216 : } 217 : 218 : /** 219 : * @access private 220 : * 221 : * @return bool 222 : */ 223 : public static function _filter($a) 224 : { 225 1 : if ($a === false) { 226 1 : return false; 227 : } 228 1 : return true; 229 : } 230 : 231 : /** 232 : * Utilize custom serialization for XMLWriter object, to convert object 233 : * to SQL. 234 : * 235 : * @return string 236 : */ 237 : private function _serialize() 238 : { 239 1 : $this->_writer->setIndent(true); 240 1 : $this->_writer->setIndentString(' '); 241 1 : $this->_writer->startDocument('1.0', 'UTF-8'); 242 1 : $this->_namespaces = array(); 243 1 : $this->_tagStack = array(); 244 1 : $this->_state = array(); 245 1 : $this->_type = 'Tag'; 246 1 : $this->_expectedDepth = 0; 247 1 : $this->_lastkey = array(); 248 1 : $lastdepth = 0; 249 1 : foreach ($this->_iter = new RecursiveIteratorIterator( 250 1 : new RecursiveArrayIterator($this->_array), 251 1 : RecursiveIteratorIterator::SELF_FIRST) as $key => $values) { 252 1 : $depth = $this->_iter->getDepth(); 253 1 : while ($depth < $this->_expectedDepth) { 254 : // finished with this tag 255 1 : $this->_finish($key, $values); 256 1 : $lastdepth--; 257 1 : } 258 1 : if (isset($this->_lastkey[$depth]) && $key != $this->_lastkey[$depth]) { 259 1 : while ($lastdepth > $depth) { 260 1 : $this->_finish($key, $values); 261 1 : $lastdepth--; 262 1 : } 263 1 : } 264 1 : $this->_lastkey[$depth] = $key; 265 1 : foreach ($this->_lastkey as $d => &$k) { 266 1 : if ($d > $depth) { 267 1 : $k = false; 268 1 : } 269 1 : } 270 1 : $this->_lastkey = array_filter($this->_lastkey, 271 1 : array('PEAR2_Pyrus_XMLWriter', '_filter')); 272 1 : $lastdepth = $depth; 273 1 : if ($this->_type !== 'Attribs') { 274 1 : if ($key === '_content') { 275 1 : $this->_writer->text($values); 276 1 : continue; 277 : } 278 1 : if ($key === 'attribs') { 279 : // attributes are 1 depth higher 280 1 : $this->_pushState(); 281 1 : $this->_expectedDepth = $this->_iter->getDepth() + 1; 282 1 : $this->_type = 'Attribs'; 283 : // cycle to first attribute 284 1 : continue; 285 : } 286 1 : } 287 1 : $next = '_handle' . $this->_type; 288 1 : while ($next = $this->{$next}($key, $values)); 289 1 : } 290 1 : while ($lastdepth) { 291 1 : $this->_finish($key, $values); 292 1 : $lastdepth--; 293 1 : } 294 1 : $this->_writer->endDocument(); 295 1 : return $this->_writer->flush(); 296 : } 297 1 : }