1<?php 2 3/** 4 * Hoa 5 * 6 * 7 * @license 8 * 9 * New BSD License 10 * 11 * Copyright © 2007-2017, Hoa community. All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions are met: 15 * * Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * * Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * * Neither the name of the Hoa nor the names of its contributors may be 21 * used to endorse or promote products derived from this software without 22 * specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE 28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 * POSSIBILITY OF SUCH DAMAGE. 35 */ 36 37namespace Hoa\Iterator; 38 39/** 40 * Class \Hoa\Iterator\RegularExpression. 41 * 42 * Re-implement the SPL RegexIterator class. 43 * There are too many bugs in php-src and HHVM, so we re-implement it from 44 * scratch without extending the existing class. 45 * 46 * Inspired by hhvm://hphp/system/php/spl/iterators/RegexIterator.php 47 * 48 * @copyright Copyright © 2007-2017 Hoa community 49 * @license New BSD License 50 */ 51class RegularExpression extends Filter 52{ 53 /** 54 * Flag: match the entry key instead of the entry value. 55 * 56 * @const int 57 */ 58 const USE_KEY = 1; 59 60 /** 61 * Flag: invert match. 62 * 63 * @const int 64 */ 65 const INVERT_MATCH = 2; 66 67 /** 68 * Mode and preg flag: only execute match (filter) for the current entry. 69 * 70 * @const int 71 */ 72 const MATCH = 0; 73 74 /** 75 * Mode and preg flag: first match for the current entry. 76 * 77 * @const int 78 */ 79 const GET_MATCH = 1; 80 81 /** 82 * Mode and preg flag: all matches for the current entry. 83 * 84 * @const int 85 */ 86 const ALL_MATCHES = 2; 87 88 /** 89 * Mode and preg flag: split values for the current entry. 90 * 91 * @const int 92 */ 93 const SPLIT = 3; 94 95 /** 96 * Mode and preg flag: replace the current entry. 97 * 98 * @const int 99 */ 100 const REPLACE = 4; 101 102 /** 103 * The regular expression to match. 104 * 105 * @var string 106 */ 107 protected $_regex = null; 108 109 /** 110 * Operation mode, see the \RegexIterator::setMode method for a list of 111 * modes. 112 * 113 * @var int 114 */ 115 protected $_mode = 0; 116 117 /** 118 * Special flags, see the \RegexIterator::setFlag method for a list of 119 * available flags. 120 * 121 * @var int 122 */ 123 protected $_flags = 0; 124 125 /** 126 * The regular expression flags. See constants. 127 * 128 * @var int 129 */ 130 protected $_pregFlags = 0; 131 132 /** 133 * Current key. 134 * 135 * @var int 136 */ 137 protected $_key = 0; 138 139 /** 140 * Current value. 141 * 142 * @var string 143 */ 144 protected $_current = null; 145 146 /** 147 * Replacement. 148 * 149 * @var string 150 */ 151 public $replacement = null; 152 153 154 155 /** 156 * Constructor. 157 * 158 * @param \RecursiveIterator $iterator The recursive iterator to 159 * apply this regex filter to. 160 * @param string $regex The regular expression to 161 * match. 162 * @param int $mode Operation mode, please see the 163 * \RegexIterator::setMode method. 164 * @param int $flags Special flags, please see the 165 * \RegexIterator::setFlags method. 166 * @param int $pregFlags Regular expression flags, 167 * please see 168 * \RegexIterator constants. 169 */ 170 public function __construct( 171 \Iterator $iterator, 172 $regex, 173 $mode = self::MATCH, 174 $flags = 0, 175 $pregFlags = 0 176 ) { 177 parent::__construct($iterator); 178 179 $this->_regex = $regex; 180 $this->setMode($mode); 181 $this->setFlags($flags); 182 $this->setPregFlags($pregFlags); 183 $this->replacement = null; 184 185 return; 186 } 187 188 /** 189 * Get accept status. 190 * 191 * @return bool 192 */ 193 public function accept() 194 { 195 if (is_array(parent::current())) { 196 return false; 197 } 198 199 $this->_key = parent::key(); 200 $this->_current = parent::current(); 201 202 $matches = []; 203 $useKey = $this->_flags & self::USE_KEY; 204 $subject = $useKey ? $this->_key : $this->_current; 205 $out = false; 206 207 switch ($this->_mode) { 208 209 case self::MATCH: 210 $out = 0 !== preg_match( 211 $this->_regex, 212 $subject, 213 $matches, 214 $this->_pregFlags 215 ); 216 217 break; 218 219 case self::GET_MATCH: 220 $this->_current = []; 221 $out = 0 !== preg_match( 222 $this->_regex, 223 $subject, 224 $this->_current, 225 $this->_pregFlags 226 ); 227 228 break; 229 230 case self::ALL_MATCHES: 231 $this->_current = []; 232 $out = 0 < preg_match_all( 233 $this->_regex, 234 $subject, 235 $this->_current, 236 $this->_pregFlags 237 ); 238 239 break; 240 241 case self::SPLIT: 242 $this->_current = preg_split( 243 $this->_regex, 244 $subject, 245 null, 246 $this->_pregFlags 247 ); 248 249 $out = 250 is_array($this->_current) && 251 1 < count($this->_current); 252 253 break; 254 255 case self::REPLACE: 256 $numberOfReplacement = 0; 257 $result = preg_replace( 258 $this->_regex, 259 $this->replacement, 260 $subject, 261 -1, 262 $numberOfReplacement 263 ); 264 265 if (null === $result || 0 === $numberOfReplacement) { 266 $out = false; 267 268 break; 269 } 270 271 if (0 !== $useKey) { 272 $this->_key = $result; 273 $out = true; 274 275 break; 276 } 277 278 $this->_current = $result; 279 $out = true; 280 281 break; 282 283 default: 284 $out = false; 285 286 break; 287 } 288 289 if (0 !== ($this->_flags & self::INVERT_MATCH)) { 290 return false === $out; 291 } 292 293 return $out; 294 } 295 296 /** 297 * Get current key. 298 * 299 * @return int 300 */ 301 public function key() 302 { 303 return $this->_key; 304 } 305 306 /** 307 * Get current value. 308 * 309 * @return string 310 */ 311 public function current() 312 { 313 return $this->_current; 314 } 315 316 /** 317 * Set mode. 318 * 319 * @param int $mode Mode. 320 * @return void 321 */ 322 public function setMode($mode) 323 { 324 if ($mode < self::MATCH || $mode > self::REPLACE) { 325 throw new \InvalidArgumentException( 326 'Illegal mode ' . $mode . '.' 327 ); 328 } 329 330 $this->_mode = $mode; 331 332 return; 333 } 334 335 /** 336 * Set flags. 337 * 338 * @param int $flags Flags. 339 * @return void 340 */ 341 public function setFlags($flags) 342 { 343 $this->_flags = $flags; 344 345 return; 346 } 347 348 /** 349 * Set preg flags. 350 * 351 * @param int $pregFlags Preg flags. 352 * @return void 353 */ 354 public function setPregFlags($pregFlags) 355 { 356 $this->_pregFlags = $pregFlags; 357 358 return; 359 } 360 361 /** 362 * Get regular expression. 363 * 364 * @return string 365 */ 366 public function getRegex() 367 { 368 return $this->_regex; 369 } 370 371 /** 372 * Get mode. 373 * 374 * @return int 375 */ 376 public function getMode() 377 { 378 return $this->_mode; 379 } 380 381 /** 382 * Get flags. 383 * 384 * @return int 385 */ 386 public function getFlags() 387 { 388 return $this->_flags; 389 } 390 391 /** 392 * Get preg flags. 393 * 394 * @return int 395 */ 396 public function getPregFlags() 397 { 398 return $this->_pregFlags; 399 } 400} 401