Overview
  • Namespace
  • Class

Namespaces

  • apemsel
    • AttributedString

Classes

  • apemsel\AttributedString\AttributedString
  • apemsel\AttributedString\MutableAttributedString
  • apemsel\AttributedString\TokenizedAttributedString
  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    * Insert string at given offset
 15    *
 16    * @param int $pos offset
 17    * @param string $string string to be inserted
 18    */
 19   public function insert($pos, $string) {
 20     $length = mb_strlen($string, "utf-8");
 21     
 22     if ($pos == $this->length) { // append instead
 23       $this->string .= $string;
 24     } else { // insert at $pos
 25       $this->string = self::mb_substr_replace($this->string, $string, $pos, 0);
 26     }
 27     
 28     $this->length += $length;
 29     $this->byte2Char = []; // invalidate cache
 30     
 31     foreach ($this->attributes as $attribute => &$map) {
 32       // Check state of surrounding map to determine state of inserted part
 33       $state = false;
 34       $maxPos = count($map) - 1;
 35       $leftState = $map[min($maxPos, $pos)];
 36       $rightState = $map[min($maxPos, $pos + 1)];
 37       
 38       if ($leftState == $rightState) {
 39         $state = $leftState;
 40       }
 41       
 42       array_splice($map, $pos, 0, array_fill(0, $length, $state));
 43     }
 44   }
 45   
 46   /**
 47    * Delete substring of given offset and length
 48    *
 49    * @param int $pos offset
 50    * @param int $length length
 51    */
 52   public function delete($pos, $length) {
 53     $leftPart = "";
 54     if ($pos >= 0) {
 55       $leftPart = mb_substr($this->string, 0, $pos, "utf-8");
 56     }
 57     
 58     $rightPart = "";
 59     if ($pos + $length < $this->length) {
 60       $rightPart = mb_substr($this->string, $pos + $length, NULL, "utf-8");
 61     }
 62     
 63     $this->string = $leftPart.$rightPart;
 64     $this->length -= $length;
 65     
 66     foreach ($this->attributes as $attribute => &$map) {
 67       array_splice($map, $pos, $length);
 68     }
 69   }
 70   
 71   /**
 72    * Missing mb_substr_replace() implementation
 73    *
 74    * @see https://gist.github.com/stemar/8287074 Original source
 75    * @param string $string string to work on
 76    * @param string $replacement replacement string
 77    * @param int $start offset
 78    * @param int $length length
 79    * @return string modified string
 80    */
 81   protected static function mb_substr_replace($string, $replacement, $start, $length = NULL) {
 82     
 83     if (is_array($string)) {
 84       $num = count($string);
 85       // $replacement
 86       $replacement = is_array($replacement) ? array_slice($replacement, 0, $num) : array_pad(array($replacement), $num, $replacement);
 87       // $start
 88       if (is_array($start)) {
 89         $start = array_slice($start, 0, $num);
 90         foreach ($start as $key => $value) {
 91           $start[$key] = is_int($value) ? $value : 0;
 92         }
 93       } else {
 94         $start = array_pad(array($start), $num, $start);
 95       }
 96       // $length
 97       if (!isset($length)) {
 98         $length = array_fill(0, $num, 0);
 99       } elseif (is_array($length)) {
100         $length = array_slice($length, 0, $num);
101         foreach ($length as $key => $value)
102           $length[$key] = isset($value) ? (is_int($value) ? $value : $num) : 0;
103       } else {
104         $length = array_pad(array($length), $num, $length);
105       }
106       // Recursive call
107       return array_map(__FUNCTION__, $string, $replacement, $start, $length);
108     }
109     preg_match_all('/./us', (string)$string, $smatches);
110     preg_match_all('/./us', (string)$replacement, $rmatches);
111     if ($length === NULL) $length = mb_strlen($string, "utf-8");
112     array_splice($smatches[0], $start, $length, $rmatches[0]);
113     
114     return join($smatches[0]);
115   }
116   
117   // Modified ArrayAccess interface
118   
119   /**
120    * Replace char at given offset
121    *
122    * @param int $offset offset
123    */
124   public function offsetSet($offset, $value) {
125     $this->string = self::mb_substr_replace($this->string, $value, $offset, mb_strlen($value, "utf-8"));
126   }
127   
128   /**
129    * Unset char at given offset
130    *
131    * @param int $offset offset
132    */
133   public function offsetUnset($offset) {
134     $this->delete($offset, 1);
135   }
136 }
137 
API documentation generated by ApiGen