1 <?php 2 /* Copyright (c) 2012, Geert Bergman (geert@scrivo.nl) 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. Neither the name of "Scrivo" nor the names of its contributors may be 14 * used to endorse or promote products derived from this software without 15 * specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 * 29 * $Id: ByteArray.php 708 2013-07-02 11:59:37Z geert $ 30 */ 31 32 /** 33 * Implementation of the \Scrivo\ByteArray class. 34 */ 35 36 namespace Scrivo; 37 38 /** 39 * Wrapper class for 8 byte character strings. 40 * 41 * ByteArray is a primitive wrapper class for 8 byte character strings. So 42 * this is a wrapper class for PHP native strings. It's purpose is to create 43 * a clear distinction in the code between byte arrays and UTF-8 Strings. 44 * 45 * Using only these two classes to handle strings will force you to make a 46 * consious decicion each time you work with string data, and thus hopefully 47 * prevent error by preventing mixups. A secondary objective is to create a 48 * more consistent interface for string handling as PHP itself provides. 49 */ 50 class ByteArray implements \Iterator, \ArrayAccess, \Countable { 51 52 /** 53 * The primitive string/byte array. 54 * @var string 55 */ 56 private $str; 57 58 /** 59 * The current position when iterating. 60 * @var string 61 */ 62 private $pos; 63 64 /** 65 * The length of the string (characters not bytes). 66 * @var int 67 */ 68 private $len = -1; 69 70 /** 71 * Get a substring from a string without first checking the boundaries. 72 * 73 * @param int $start Start offset for the substring, use a negative number 74 * to use an offset from the end of the string. 75 * @param int $length The length of the substring. 76 * 77 * @return ByteArray The requested portion of this string. 78 */ 79 private function unsafeSubstr($start, $length) { 80 return new ByteArray(substr($this->str, $start, $length)); 81 } 82 83 /** 84 * Construct an ByteArray. 85 * 86 * @param string $str The source string. 87 */ 88 public function __construct($str="") { 89 \Scrivo\ArgumentCheck::assert($str, \Scrivo\ArgumentCheck::TYPE_STRING); 90 $this->str = $str; 91 $this->pos = 0; 92 } 93 94 /** 95 * Factory method to construct an ByteArray. 96 * 97 * @see ByteArray::__construct() 98 * 99 * @param string|array $str The source strings. 100 * 101 * @return ByteArray|ByteArray[] An ByteArray wrapper object. 102 */ 103 public static function create($str="") { 104 if (is_array($str)) { 105 foreach($str as $k=>$v) { 106 $str[$k] = self::create($v); 107 } 108 return $str; 109 } 110 return new ByteArray($str); 111 } 112 113 /** 114 * Return the primitive string for this instance. 115 * 116 * @return string The primitive string for this instance. 117 */ 118 public function __toString() { 119 return $this->str; 120 } 121 122 /** 123 * Implementation of the readable properties using the PHP magic 124 * method __get(). 125 * 126 * @param string $name The name of the property to get. 127 * 128 * @return mixed The value of the requested property. 129 */ 130 public function __get($name) { 131 switch($name) { 132 case "length": return $this->getLength(); 133 } 134 throw new \Scrivo\SystemException("No such property '$name'."); 135 } 136 137 /** 138 * Test if this string equals another ByteArray object. 139 * 140 * When you want test ByteArray object for equality, use this method 141 * and never the equality operator (==) because then you'll compare 142 * objects and therefore all data members of ByteArray and this can 143 * give you other results (or cast the ByteArray strings to PHP strings 144 * before comparing). 145 * 146 * @param ByteArray $str The string to compare this string to. 147 * 148 * @return boolean True if the given string equals this string. 149 */ 150 public function equals(ByteArray $str) { 151 return (string)$this->str == (string)$str; 152 } 153 154 /** 155 * Get the length of the string. 156 * 157 * @return int The length of the string in characters (not bytes). 158 */ 159 public function getLength() { 160 if ($this->len == -1) { 161 $this->len = strlen($this->str); 162 } 163 return $this->len; 164 } 165 166 /** 167 * Return the character count of the string. 168 * 169 * This is an alias for getLength() and part of the implementation of 170 * Countable. 171 * 172 * @return int The length of the string in characters. 173 */ 174 public function count() { 175 return $this->getLength(); 176 } 177 178 /** 179 * Return the current character when iterating. 180 * 181 * Note that this method is part of the implementation of Iterator and 182 * should not be called from an other context. 183 * 184 * @return string The current character in this string when 185 * iterating. 186 */ 187 public function current() { 188 // note: iterator will call valid() before current(). 189 return $this->unsafeSubstr($this->pos, 1); 190 } 191 192 /** 193 * Return the index of the current character when iterating. 194 * 195 * Note that this method is part of the implementation of Iterator and 196 * should not be called from an other context. 197 * 198 * @return int The index of the current character in this string 199 * when iterating. 200 */ 201 public function key() { 202 return $this->pos; 203 } 204 205 /** 206 * Move forward in this string to the next character when iterating. 207 * 208 * Note that this method is part of the implementation of Iterator and 209 * should not be called from an other context. 210 */ 211 public function next() { 212 $this->pos++; 213 } 214 215 /** 216 * Reset the current character index so iterating will (re)start at the 217 * beginning of this string. 218 * 219 * Note that this method is part of the implementation of Iterator and 220 * should not be called from an other context. 221 */ 222 public function rewind() { 223 $this->pos = 0; 224 } 225 226 /** 227 * Check if the current character index for iterating is valid. 228 * 229 * Note that this method is part of the implementation of Iterator and 230 * should not be called from an other context. 231 * 232 * @return boolean True if the current character index is valid else false. 233 */ 234 public function valid() { 235 return ($this->pos >= 0 && $this->pos < $this->getLength()); 236 } 237 238 /** 239 * Illegal method: set a character at a specified index location. 240 * 241 * Note that this method is part of the implementation of ArrayAccess. 242 * ByteArrays are immutable and therefore it is prohibited to set 243 * elements (characters) in a string, so this method implementation is 244 * not relevant and throws an exception if called. 245 * 246 * @param int $offset 247 * @param string $value 248 * 249 * @throws \Scrivo\SystemException If this method is called. 250 */ 251 public function offsetSet($offset, $value) { 252 throw new \Scrivo\SystemException( 253 "offsetSet can't be called on ByteArray objects"); 254 } 255 256 /** 257 * Get an UTF-8 character from a string using array brackets. 258 * 259 * Note that this method is part of the implementation of ArrayAccess and 260 * should not be called from an other context. 261 * 262 * @param int $offset A character offet in the string. 263 * 264 * @throws \Scrivo\SystemException If the requested offset was out of range. 265 */ 266 public function offsetGet($offset) { 267 \Scrivo\ArgumentCheck::assert( 268 $offset, \Scrivo\ArgumentCheck::TYPE_INTEGER); 269 if (!$this->offsetExists($offset)) { 270 throw new \Scrivo\SystemException( 271 "String index [$offset] out of bounds"); 272 } 273 return $this->unsafeSubstr($offset, 1); 274 } 275 276 /** 277 * Check if the specified index location in this string is valid. 278 * 279 * Note that this method is part of the implementation of ArrayAccess and 280 * should not be called from an other context. 281 * 282 * @param int $offset A character offet in the string. 283 * 284 * @return boolean True if the specified in index is within the valid range. 285 */ 286 public function offsetExists($offset) { 287 \Scrivo\ArgumentCheck::assert( 288 $offset, \Scrivo\ArgumentCheck::TYPE_INTEGER); 289 return ($offset >= 0 && $offset < $this->getLength()); 290 } 291 292 /** 293 * Illegal method: unset a character at a specified index location. 294 * 295 * Note that this method is part of the implementation of ArrayAccess. 296 * ByteArrays are immutable and therefore it is prohibited to unset 297 * elements (characters) in a string, so this method implementation is 298 * not relevant and throws an exception if called. 299 * 300 * @param int $offset 301 * 302 * @throws \Scrivo\SystemException If this method is called. 303 */ 304 public function offsetUnset($offset) { 305 \Scrivo\ArgumentCheck::assert( 306 $offset, \Scrivo\ArgumentCheck::TYPE_INTEGER); 307 throw new \Scrivo\SystemException( 308 "offsetUnset can't be called on ByteArray objects"); 309 } 310 311 /** 312 * Get a substring from a string using an offset and a length. 313 * 314 * Just like PHP's native substr function this method returns a substring 315 * from this string using an offset and a length. But note that this 316 * method will throw an exception if the offset is invalid. 317 * 318 * @param int $start Start offset for the substring, use a negative number 319 * to use an offset from the end of the string. 320 * @param int $length The length of the substring. 321 * 322 * @return ByteArray The portion of this string specified by the $start 323 * and $length parameter. 324 * 325 * @throws \Scrivo\SystemException if the requested offset was out of range. 326 */ 327 public function substr($start, $length=0xFFFF) { 328 \Scrivo\ArgumentCheck::assert( 329 $start, \Scrivo\ArgumentCheck::TYPE_INTEGER); 330 \Scrivo\ArgumentCheck::assert( 331 $length, \Scrivo\ArgumentCheck::TYPE_INTEGER); 332 $tmp = $start < 1 ? -$start : $start; 333 if (!$this->offsetExists($tmp)) { 334 throw new \Scrivo\SystemException( 335 "String index [$start] out of bounds"); 336 } 337 return $this->unsafeSubstr($start, $length); 338 } 339 340 /** 341 * Get a substring from a string using a start and end index. 342 * 343 * This method is inspired by it's JAVA counterpart and returns a 344 * substring of this string using an start and end index. 345 * 346 * @param int $start Start offset for the substring. 347 * @param int $end The end offset for the substring. 348 * 349 * @return ByteArray The portion of this string specified by the $start 350 * and $end parameter. 351 * 352 * @throws \Scrivo\SystemException if the requested offset was out of range. 353 */ 354 public function substring($start, $end) { 355 \Scrivo\ArgumentCheck::assert( 356 $start, \Scrivo\ArgumentCheck::TYPE_INTEGER); 357 \Scrivo\ArgumentCheck::assert( 358 $end, \Scrivo\ArgumentCheck::TYPE_INTEGER); 359 if (!$this->offsetExists($start) || !$this->offsetExists($end) 360 || $start > $end) { 361 throw new \Scrivo\SystemException( 362 "String index [$start, $end] out of bounds"); 363 } 364 return $this->unsafeSubstr($start, $end-$start); 365 } 366 367 /** 368 * Get a trimmed copy of this string. 369 * 370 * Returns a copy of the string, with leading and trailing whitespace 371 * removed. Whitespace characters are: ' ', \t, \r, \n. 372 * 373 * @return ByteArray A copy of this string with leading and trailing 374 * white space removed. 375 */ 376 public function trim() { 377 return new ByteArray( 378 preg_replace("/(^[\s]+)|([\s]+$)/s", "", $this->str)); 379 } 380 381 /** 382 * Check if the string contains the given substring. 383 * 384 * This is the test you normally use strpos(...) !== false for. 385 * 386 * @param ByteArray $str The string to search for. 387 * @param int $offset An offset from where to start the search. 388 * @param boolean $ignoreCase Set to perform an case insensitive lookup. 389 * 390 * @return boolean True if the given string is contained by this string. 391 * 392 * @throws \Scrivo\SystemException If the $offset is out of range. 393 */ 394 public function contains(ByteArray $str, $offset=0, $ignoreCase=false) { 395 \Scrivo\ArgumentCheck::assert( 396 $offset, \Scrivo\ArgumentCheck::TYPE_INTEGER); 397 \Scrivo\ArgumentCheck::assert( 398 $ignoreCase, \Scrivo\ArgumentCheck::TYPE_BOOLEAN); 399 if ($offset && !$this->offsetExists($offset)) { 400 throw new \Scrivo\SystemException( 401 "String index [$offset] out of bounds"); 402 } 403 if ($ignoreCase) { 404 return stripos( 405 $this->str, (string)$str, $offset) !== false; 406 } else { 407 return strpos($this->str, (string)$str, $offset) !== false; 408 } 409 } 410 411 /** 412 * Returns the index of the given substring in this string. 413 * 414 * Just like the PHP's native strpos and stripos functions this method 415 * returns the index of a substring in this string. But there are two 416 * important differences: this method returns -1 if the substring was 417 * not found, and this method will raise an exception if the given 418 * offset was out of range. 419 * 420 * @param ByteArray $str The string to search for. 421 * @param int $offset An offset from where to start the search. 422 * @param boolean $ignoreCase Set to perform an case insensitive lookup. 423 * 424 * @return int The index of the first occurance of the substring after 425 * $offset and -1 if the substring was not found. 426 * 427 * @throws \Scrivo\SystemException If the $offset is out of range. 428 */ 429 public function indexOf(ByteArray $str, $offset=0, $ignoreCase=false) { 430 \Scrivo\ArgumentCheck::assert( 431 $offset, \Scrivo\ArgumentCheck::TYPE_INTEGER); 432 \Scrivo\ArgumentCheck::assert( 433 $ignoreCase, \Scrivo\ArgumentCheck::TYPE_BOOLEAN); 434 if ($offset && !$this->offsetExists($offset)) { 435 throw new \Scrivo\SystemException( 436 "String index [$offset] out of bounds"); 437 } 438 $res = -1; 439 if ($ignoreCase) { 440 $res = stripos($this->str, (string)$str, $offset); 441 } else { 442 $res = strpos($this->str, (string)$str, $offset); 443 } 444 return $res !== false ? $res : -1; 445 } 446 447 /** 448 * Returns the index of the last occurance of the given substring in this 449 * string. 450 * 451 * Just like the PHP's native strrpos and strripos functions this method 452 * returns the substring of this string that start with the first occurance 453 * of the given a substring in this string. But note that this 454 * method will throw an exception if the offset is invalid. 455 * Also an negative offset to indicate an offset measured from the end 456 * of the string is allowed. But there are two important differences: 457 * this method returns -1 if the substring was not found, and this method 458 * will raise an exception if the given offset was out of range. 459 * 460 * @param ByteArray $str The string to search for. 461 * @param int $offset An offset from where to start the search. A positive 462 * value indicates an offset measured from the start of the string, a 463 * negative value from the end of the string. 464 * @param boolean $ignoreCase Perform an case insensitive lookup. 465 * 466 * @return int The index of the last occurance of the substring after 467 * $offset. 468 * @throws \Scrivo\SystemException If the $offset is out of range. 469 */ 470 public function lastIndexOf(ByteArray $str, $offset=0, $ignoreCase=false) { 471 \Scrivo\ArgumentCheck::assert( 472 $offset, \Scrivo\ArgumentCheck::TYPE_INTEGER); 473 \Scrivo\ArgumentCheck::assert( 474 $ignoreCase, \Scrivo\ArgumentCheck::TYPE_BOOLEAN); 475 if ($offset) { 476 $tmp = $offset < 1 ? -$offset : $offset; 477 if (!$this->offsetExists($tmp)) { 478 throw new \Scrivo\SystemException( 479 "String index [$offset] out of bounds"); 480 } 481 } 482 $res = -1; 483 if ($ignoreCase) { 484 $res = strripos($this->str, (string)$str, $offset); 485 } else { 486 $res = strrpos($this->str, (string)$str, $offset); 487 } 488 return $res !== false ? $res : -1; 489 } 490 491 /** 492 * Returns the first occurance of a given substring in this string. 493 * 494 * Just like the PHP's native strstr and stristr functions this method 495 * returns the substring of this string that start with the first occurance 496 * of the given a substring in this string. Note that this method throws 497 * an exception if an empty string was given as search string and not 498 * a warning as strstr does. 499 * 500 * @param ByteArray $str The string to search for. 501 * @param boolean $part Flag to indicate to return the part of the string 502 * before the first occurance of the given substring i.o. the part 503 * after the substring. 504 * @param boolean $ignoreCase Perform an case insensitive lookup. 505 * 506 * @return ByteArray The substring plus the part of the string after the 507 * the first occurance of the substring, or the part of the string before 508 * the first occurance of the substring (excluding the substring) or NULL 509 * if not found. 510 * 511 * @throws \Scrivo\SystemException If an empty search string was given. 512 */ 513 public function firstOccurranceOf(ByteArray $str, $part=false, 514 $ignoreCase=false) { 515 \Scrivo\ArgumentCheck::assert( 516 $part, \Scrivo\ArgumentCheck::TYPE_BOOLEAN); 517 \Scrivo\ArgumentCheck::assert( 518 $ignoreCase, \Scrivo\ArgumentCheck::TYPE_BOOLEAN); 519 if (!$str->getLength()) { 520 throw new \Scrivo\SystemException( 521 "firstOccurranceOf requires a search string"); 522 } 523 $res = NULL; 524 if ($ignoreCase) { 525 $res = stristr($this->str, (string)$str, $part); 526 } else { 527 $res = strstr($this->str, (string)$str, $part); 528 } 529 return $res !== false ? new ByteArray($res) : NULL; 530 } 531 532 /** 533 * Returns the last occurance of a given character in this string. 534 * 535 * Just like the PHP's native strrchr and strrichr functions this method 536 * returns the substring of this string that start with the first occurance 537 * of the given a substring in this string. Note that this method throws 538 * an exception if an empty string was given as search string and not 539 * a warning as strstr does. 540 * 541 * @param ByteArray $str The character to search for. 542 * @param boolean $part Flag to indicate to return part of the string before 543 * the last occurance of the given character i.o. the part after the 544 * character. 545 * @param boolean $ignoreCase Perform an case insensitive lookup. 546 * 547 * @return ByteArray The substring plus the part of the string after the 548 * the last occurance of the character, or the part of the string before 549 * the last occurance of the character (excluding the character) or NULL 550 * if not found. 551 * 552 * @throws \Scrivo\SystemException If a search string of not exactly one 553 * character in length was given. 554 */ 555 public function lastOccurranceOf(ByteArray $str, $part=false, 556 $ignoreCase=false) { 557 \Scrivo\ArgumentCheck::assert( 558 $part, \Scrivo\ArgumentCheck::TYPE_BOOLEAN); 559 \Scrivo\ArgumentCheck::assert( 560 $ignoreCase, \Scrivo\ArgumentCheck::TYPE_BOOLEAN); 561 if ($str->getLength() != 1) { 562 throw new \Scrivo\SystemException( 563 "lastOccurranceOf accepts single charaters only"); 564 } 565 $pos = $this->lastIndexOf($str, 0, $ignoreCase); 566 if ($pos == -1) { 567 return null; 568 } 569 if ($part) { 570 return $this->unsafeSubstr(0, $pos); 571 } 572 return $this->unsafeSubstr($pos, $this->getLength()-$pos); 573 } 574 575 /** 576 * Replace a substring or set of substrings in this string. 577 * 578 * You can use this method in favour of PHP's native str_replace and strtr 579 * functions. This method will do proper type checking for you. Note 580 * that you can safely use str_replace: if all input parameter are 581 * correct UTF-8 this method is UTF-8 safe too. 582 * 583 * @param ByteArray|ByteArray[] $from A (set of) string(s) to replace 584 * in this string. 585 * @param ByteArray|ByteArray[] $to A (set of) replacement string(s) to 586 * replace the found string(s). 587 * 588 * @return ByteArray A string with the replaced values. 589 * 590 * @throws \Scrivo\SystemException If the input data is not of type 591 * ByteArray or ByteArray[], of if the $to parameter is an array 592 * and $from isn't or hasn't the same number of elements. 593 */ 594 public function replace($from, $to) { 595 if ($from instanceof ByteArray && $to instanceof ByteArray) { 596 return new ByteArray(str_replace($from, $to, $this->str)); 597 } else if (is_array($from) && $to instanceof ByteArray) { 598 foreach ($from as $k=>$v) { 599 if (!($v instanceof ByteArray)) { 600 throw new \Scrivo\SystemException("From element is" 601 . " not an ByteArray as array position [$k]"); 602 } 603 } 604 return new ByteArray(str_replace($from, $to, $this->str)); 605 } else if (is_array($from) && is_array($to)) { 606 if (count($from) != count($to)) { 607 throw new \Scrivo\SystemException( 608 "Input arrays are not the same size"); 609 } 610 foreach ($from as $k=>$v) { 611 if (!($v instanceof ByteArray) 612 || !($to[$k] instanceof ByteArray)) { 613 throw new \Scrivo\SystemException("To or from element is" 614 . " not an ByteArray as array position [$k]"); 615 } 616 } 617 return new ByteArray(str_replace($from, $to, $this->str)); 618 } 619 throw new \Scrivo\SystemException("Invalid argument types"); 620 } 621 622 /** 623 * Split this string using a delimiter. 624 * 625 * Just like PHP's native explode this method splits a string on 626 * boundaries formed by the string delimiter. Note that the behavoir 627 * of the limit parameter is a little bit different and that this method 628 * will throw an exception if an empty string is passed as a delimiter. 629 * 630 * @param ByteArray $delimiter The boundary string. 631 * @param int $limit If limit is set and positive, the returned array 632 * will contain a maximum of limit elements with the last element 633 * containing the rest of string. If the limit parameter is negative, 634 * all components except the last -limit are returned. If the limit is 635 * not set or 0 no limit wil be used. 636 * 637 * @return ByteArray[] An array of strings created by splitting the 638 * string parameter on boundaries formed by the delimiter. If the 639 * delimiter was not found and array containing a copy of this string 640 * will be returned except if limit was negative, in that case an 641 * empty array will be returned. 642 * 643 * @throws \Scrivo\SystemException If an empty search string was given. 644 */ 645 public function split(ByteArray $delimiter, $limit=0) { 646 \Scrivo\ArgumentCheck::assert( 647 $limit, \Scrivo\ArgumentCheck::TYPE_INTEGER); 648 if ($delimiter == "") { 649 throw new \Scrivo\SystemException( 650 "split cannot use an empty \"\" delimiter."); 651 } 652 $r = $limit ? explode($delimiter, $this->str, $limit) 653 : explode($delimiter, $this->str); 654 foreach ($r as $k=>$v) { 655 $r[$k] = new ByteArray($v); 656 } 657 return $r; 658 } 659 660 /** 661 * Get a copy of this string with all of its characters converted to lower 662 * case. 663 * 664 * @return ByteArray A string containing only lower case characters. 665 */ 666 public function toLowerCase() { 667 return new ByteArray(strtolower($this->str)); 668 } 669 670 /** 671 * Get a copy of this string with all of its characters converted to upper 672 * case. 673 * 674 * @return ByteArray A string containing only upper case characters. 675 */ 676 public function toUpperCase() { 677 return new ByteArray(strtoupper($this->str)); 678 } 679 680 /** 681 * Compare this string to another ByteArray object. 682 * 683 * @param ByteArray $str The string to compare this string to. 684 * 685 * @return int Less than 0 if this string is less than the given 686 * string $str; more than 0 if this string is greater than $str, and 687 * 0 if they are equal. 688 */ 689 public function compareTo(ByteArray $str) { 690 return strcmp($this->str, $str); 691 } 692 693 } 694 695 ?>
Documentation generated by phpDocumentor 2.0.0a12 and ScrivoDocumentor on August 29, 2013