1 <?php
2 namespace apemsel\AttributedString;
3
4 5 6 7 8 9 10
11 class MutableAttributedString extends AttributedString
12 {
13 14 15 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 27 28 29 30
31 public function insert($pos, $string) {
32 $length = mb_strlen($string, "utf-8");
33
34 if ($pos == $this->length) {
35 $this->string .= $string;
36 } else {
37 $this->string = self::mb_substr_replace($this->string, $string, $pos, 0);
38 }
39
40 $this->length += $length;
41 $this->byte2Char = [];
42
43 foreach ($this->attributes as $name => $attribute) {
44
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 60 61 62 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 85 86 87 88 89 90 91 92
93 protected static function mb_substr_replace($string, $replacement, $start, $length = NULL) {
94
95 if (is_array($string)) {
96 $num = count($string);
97
98 $replacement = is_array($replacement) ? array_slice($replacement, 0, $num) : array_pad(array($replacement), $num, $replacement);
99
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
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
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
130
131 132 133 134 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 142 143 144
145 public function offsetUnset($offset) {
146 $this->delete($offset, 1);
147 }
148 }
149