1<?php 2/* 3 * This file is part of PHPUnit. 4 * 5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11/** 12 * XML helpers. 13 */ 14class PHPUnit_Util_XML 15{ 16 /** 17 * Load an $actual document into a DOMDocument. This is called 18 * from the selector assertions. 19 * 20 * If $actual is already a DOMDocument, it is returned with 21 * no changes. Otherwise, $actual is loaded into a new DOMDocument 22 * as either HTML or XML, depending on the value of $isHtml. If $isHtml is 23 * false and $xinclude is true, xinclude is performed on the loaded 24 * DOMDocument. 25 * 26 * Note: prior to PHPUnit 3.3.0, this method loaded a file and 27 * not a string as it currently does. To load a file into a 28 * DOMDocument, use loadFile() instead. 29 * 30 * @param string|DOMDocument $actual 31 * @param bool $isHtml 32 * @param string $filename 33 * @param bool $xinclude 34 * @param bool $strict 35 * 36 * @return DOMDocument 37 */ 38 public static function load($actual, $isHtml = false, $filename = '', $xinclude = false, $strict = false) 39 { 40 if ($actual instanceof DOMDocument) { 41 return $actual; 42 } 43 44 if (!is_string($actual)) { 45 throw new PHPUnit_Framework_Exception('Could not load XML from ' . gettype($actual)); 46 } 47 48 if ($actual === '') { 49 throw new PHPUnit_Framework_Exception('Could not load XML from empty string'); 50 } 51 52 // Required for XInclude on Windows. 53 if ($xinclude) { 54 $cwd = getcwd(); 55 @chdir(dirname($filename)); 56 } 57 58 $document = new DOMDocument; 59 $document->preserveWhiteSpace = false; 60 61 $internal = libxml_use_internal_errors(true); 62 $message = ''; 63 $reporting = error_reporting(0); 64 65 if ('' !== $filename) { 66 // Necessary for xinclude 67 $document->documentURI = $filename; 68 } 69 70 if ($isHtml) { 71 $loaded = $document->loadHTML($actual); 72 } else { 73 $loaded = $document->loadXML($actual); 74 } 75 76 if (!$isHtml && $xinclude) { 77 $document->xinclude(); 78 } 79 80 foreach (libxml_get_errors() as $error) { 81 $message .= "\n" . $error->message; 82 } 83 84 libxml_use_internal_errors($internal); 85 error_reporting($reporting); 86 87 if ($xinclude) { 88 @chdir($cwd); 89 } 90 91 if ($loaded === false || ($strict && $message !== '')) { 92 if ($filename !== '') { 93 throw new PHPUnit_Framework_Exception( 94 sprintf( 95 'Could not load "%s".%s', 96 $filename, 97 $message != '' ? "\n" . $message : '' 98 ) 99 ); 100 } else { 101 if ($message === '') { 102 $message = 'Could not load XML for unknown reason'; 103 } 104 throw new PHPUnit_Framework_Exception($message); 105 } 106 } 107 108 return $document; 109 } 110 111 /** 112 * Loads an XML (or HTML) file into a DOMDocument object. 113 * 114 * @param string $filename 115 * @param bool $isHtml 116 * @param bool $xinclude 117 * @param bool $strict 118 * 119 * @return DOMDocument 120 */ 121 public static function loadFile($filename, $isHtml = false, $xinclude = false, $strict = false) 122 { 123 $reporting = error_reporting(0); 124 $contents = file_get_contents($filename); 125 error_reporting($reporting); 126 127 if ($contents === false) { 128 throw new PHPUnit_Framework_Exception( 129 sprintf( 130 'Could not read "%s".', 131 $filename 132 ) 133 ); 134 } 135 136 return self::load($contents, $isHtml, $filename, $xinclude, $strict); 137 } 138 139 /** 140 * @param DOMNode $node 141 */ 142 public static function removeCharacterDataNodes(DOMNode $node) 143 { 144 if ($node->hasChildNodes()) { 145 for ($i = $node->childNodes->length - 1; $i >= 0; $i--) { 146 if (($child = $node->childNodes->item($i)) instanceof DOMCharacterData) { 147 $node->removeChild($child); 148 } 149 } 150 } 151 } 152 153 /** 154 * Escapes a string for the use in XML documents 155 * Any Unicode character is allowed, excluding the surrogate blocks, FFFE, 156 * and FFFF (not even as character reference). 157 * See http://www.w3.org/TR/xml/#charsets 158 * 159 * @param string $string 160 * 161 * @return string 162 */ 163 public static function prepareString($string) 164 { 165 return preg_replace( 166 '/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]/', 167 '', 168 htmlspecialchars( 169 PHPUnit_Util_String::convertToUtf8($string), 170 ENT_QUOTES, 171 'UTF-8' 172 ) 173 ); 174 } 175 176 /** 177 * "Convert" a DOMElement object into a PHP variable. 178 * 179 * @param DOMElement $element 180 * 181 * @return mixed 182 */ 183 public static function xmlToVariable(DOMElement $element) 184 { 185 $variable = null; 186 187 switch ($element->tagName) { 188 case 'array': 189 $variable = []; 190 191 foreach ($element->childNodes as $entry) { 192 if (!$entry instanceof DOMElement || $entry->tagName !== 'element') { 193 continue; 194 } 195 $item = $entry->childNodes->item(0); 196 197 if ($item instanceof DOMText) { 198 $item = $entry->childNodes->item(1); 199 } 200 201 $value = self::xmlToVariable($item); 202 203 if ($entry->hasAttribute('key')) { 204 $variable[(string) $entry->getAttribute('key')] = $value; 205 } else { 206 $variable[] = $value; 207 } 208 } 209 break; 210 211 case 'object': 212 $className = $element->getAttribute('class'); 213 214 if ($element->hasChildNodes()) { 215 $arguments = $element->childNodes->item(1)->childNodes; 216 $constructorArgs = []; 217 218 foreach ($arguments as $argument) { 219 if ($argument instanceof DOMElement) { 220 $constructorArgs[] = self::xmlToVariable($argument); 221 } 222 } 223 224 $class = new ReflectionClass($className); 225 $variable = $class->newInstanceArgs($constructorArgs); 226 } else { 227 $variable = new $className; 228 } 229 break; 230 231 case 'boolean': 232 $variable = $element->textContent == 'true' ? true : false; 233 break; 234 235 case 'integer': 236 case 'double': 237 case 'string': 238 $variable = $element->textContent; 239 240 settype($variable, $element->tagName); 241 break; 242 } 243 244 return $variable; 245 } 246} 247