Overview
  • Namespace
  • Class

Namespaces

  • apemsel
    • AttributedString

Classes

  • apemsel\AttributedString\AttributedString
  • apemsel\AttributedString\Bitmap
  • apemsel\AttributedString\BooleanArray
  • apemsel\AttributedString\MutableAttributedString
  • apemsel\AttributedString\TokenizedAttributedString

Interfaces

  • apemsel\AttributedString\Attribute
  • apemsel\AttributedString\MutableAttribute
  1 <?php
  2 namespace apemsel\AttributedString;
  3 
  4 /**
  5  * Extends AttributedString to support a mutable (changeable) string.
  6  *
  7  * During insert and delete operations the attribute layers are updated as smart as possible.
  8  *
  9  * @author Adrian Pemsel <apemsel@gmail.com>
 10  */
 11 class MutableAttributedString extends AttributedString
 12 {
 13   /**
 14    * @param string|AttributedString $string Either a simple string or another AttributedString to init the AttributedString
 15    * @param string $attributeClass Class to use for attributes
 16    */
 17   public function __construct($string, $attributeClass = "apemsel\AttributedString\BooleanArray") {
 18     if (!in_array("apemsel\AttributedString\MutableAttribute", class_implements($attributeClass))) {
 19       throw new \InvalidArgumentException("MutableAttributedString can only be used with attributes implementing MutableAttribute");
 20     }
 21     
 22     parent::__construct($string, $attributeClass);
 23   }
 24   
 25   /**
 26    * Insert string at given offset
 27    *
 28    * @param int $pos offset
 29    * @param string $string string to be inserted
 30    */
 31   public function insert($pos, $string) {
 32     $length = mb_strlen($string, "utf-8");
 33     
 34     if ($pos == $this->length) { // append instead
 35       $this->string .= $string;
 36     } else { // insert at $pos
 37       $this->string = self::mb_substr_replace($this->string, $string, $pos, 0);
 38     }
 39     
 40     $this->length += $length;
 41     $this->byte2Char = []; // invalidate cache
 42     
 43     foreach ($this->attributes as $name => $attribute) {
 44       // Check state of surrounding map to determine state of inserted part
 45       $state = false;
 46       $maxPos = count($attribute) - 1;
 47       $leftState = $attribute[min($maxPos, $pos)];
 48       $rightState = $attribute[min($maxPos, $pos + 1)];
 49       
 50       if ($leftState == $rightState) {
 51         $state = $leftState;
 52       }
 53       
 54       $attribute->insert($pos, $length, $state);
 55     }
 56   }
 57   
 58   /**
 59    * Delete substring of given offset and length
 60    *
 61    * @param int $pos offset
 62    * @param int $length length
 63    */
 64   public function delete($pos, $length) {
 65     $leftPart = "";
 66     if ($pos >= 0) {
 67       $leftPart = mb_substr($this->string, 0, $pos, "utf-8");
 68     }
 69     
 70     $rightPart = "";
 71     if ($pos + $length < $this->length) {
 72       $rightPart = mb_substr($this->string, $pos + $length, NULL, "utf-8");
 73     }
 74     
 75     $this->string = $leftPart.$rightPart;
 76     $this->length -= $length;
 77     
 78     foreach ($this->attributes as $name => $attribute) {
 79       $attribute->delete($pos, $length);
 80     }
 81   }
 82   
 83   /**
 84    * Missing mb_substr_replace() implementation
 85    *
 86    * @see https://gist.github.com/stemar/8287074 Original source
 87    * @param string $string string to work on
 88    * @param string $replacement replacement string
 89    * @param int $start offset
 90    * @param int $length length
 91    * @return string modified string
 92    */
 93   protected static function mb_substr_replace($string, $replacement, $start, $length = NULL) {
 94     
 95     if (is_array($string)) {
 96       $num = count($string);
 97       // $replacement
 98       $replacement = is_array($replacement) ? array_slice($replacement, 0, $num) : array_pad(array($replacement), $num, $replacement);
 99       // $start
100       if (is_array($start)) {
101         $start = array_slice($start, 0, $num);
102         foreach ($start as $key => $value) {
103           $start[$key] = is_int($value) ? $value : 0;
104         }
105       } else {
106         $start = array_pad(array($start), $num, $start);
107       }
108       // $length
109       if (!isset($length)) {
110         $length = array_fill(0, $num, 0);
111       } elseif (is_array($length)) {
112         $length = array_slice($length, 0, $num);
113         foreach ($length as $key => $value)
114           $length[$key] = isset($value) ? (is_int($value) ? $value : $num) : 0;
115       } else {
116         $length = array_pad(array($length), $num, $length);
117       }
118       // Recursive call
119       return array_map(__FUNCTION__, $string, $replacement, $start, $length);
120     }
121     preg_match_all('/./us', (string)$string, $smatches);
122     preg_match_all('/./us', (string)$replacement, $rmatches);
123     if ($length === NULL) $length = mb_strlen($string, "utf-8");
124     array_splice($smatches[0], $start, $length, $rmatches[0]);
125     
126     return join($smatches[0]);
127   }
128   
129   // Modified ArrayAccess interface
130   
131   /**
132    * Replace char at given offset
133    *
134    * @param int $offset offset
135    */
136   public function offsetSet($offset, $value) {
137     $this->string = self::mb_substr_replace($this->string, $value, $offset, mb_strlen($value, "utf-8"));
138   }
139   
140   /**
141    * Unset char at given offset
142    *
143    * @param int $offset offset
144    */
145   public function offsetUnset($offset) {
146     $this->delete($offset, 1);
147   }
148 }
149 
API documentation generated by ApiGen