OSDN Git Service

e83e1a174b9598ee09aab90b95744048c9f006c7
[fooeditengine/FooEditEngine.git] / Common / StringBuffer.cs
1 /*\r
2  * Copyright (C) 2013 FooProject\r
3  * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by\r
4  * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.\r
5 \r
6  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of \r
7  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\r
8 \r
9 You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.\r
10  */\r
11 using System;\r
12 using System.Collections.Generic;\r
13 using System.Globalization;\r
14 using System.Linq;\r
15 using System.Text;\r
16 using System.Threading;\r
17 using Slusser.Collections.Generic;\r
18 \r
19 namespace FooEditEngine\r
20 {\r
21     /// <summary>\r
22     /// ランダムアクセス可能な列挙子を提供するインターフェイス\r
23     /// </summary>\r
24     /// <typeparam name="T"></typeparam>\r
25     public interface IRandomEnumrator<T>\r
26     {\r
27         /// <summary>\r
28         /// インデクサーを表す\r
29         /// </summary>\r
30         /// <param name="index">インデックス</param>\r
31         /// <returns>Tを返す</returns>\r
32         T this[int index]{get;}\r
33     }\r
34 \r
35     sealed class StringBuffer : IEnumerable<char>, IRandomEnumrator<char>\r
36     {\r
37         GapBuffer<char> buf = new GapBuffer<char>();\r
38 \r
39         public StringBuffer()\r
40         {\r
41             this.Update += (s, e) => { };\r
42         }\r
43 \r
44         public StringBuffer(StringBuffer buffer)\r
45             : this()\r
46         {\r
47             buf.AddRange(buffer.buf, buffer.Length);\r
48         }\r
49 \r
50         public char this[int index]\r
51         {\r
52             get\r
53             {\r
54                 char c = buf[index];\r
55                 return c;\r
56             }\r
57         }\r
58 \r
59         public string ToString(int index, int length)\r
60         {\r
61             StringBuilder temp = new StringBuilder();\r
62             temp.Clear();\r
63             for (int i = index; i < index + length; i++)\r
64                 temp.Append(buf[i]);\r
65             return temp.ToString();\r
66         }\r
67 \r
68         public IEnumerable<string> GetLines(int startIndex, int endIndex, int maxCharCount = -1)\r
69         {\r
70             foreach (Tuple<int, int> range in this.ForEachLines(startIndex, endIndex, maxCharCount))\r
71             {\r
72                 StringBuilder temp = new StringBuilder();\r
73                 temp.Clear();\r
74                 int lineEndIndex = range.Item1;\r
75                 if (range.Item2 > 0)\r
76                     lineEndIndex += range.Item2 - 1;\r
77                 for (int i = range.Item1; i <= lineEndIndex; i++)\r
78                     temp.Append(buf[i]);\r
79                 yield return temp.ToString();\r
80             }\r
81         }\r
82 \r
83         public IEnumerable<Tuple<int,int>> ForEachLines(int startIndex, int endIndex, int maxCharCount = -1)\r
84         {\r
85             int currentLineHeadIndex = startIndex;\r
86             int currentLineLength = 0;\r
87             \r
88             for (int i = startIndex; i <= endIndex; i++)\r
89             {\r
90                 currentLineLength++;\r
91                 char c = this.buf[i];\r
92                 if (c == Document.NewLine ||\r
93                     (maxCharCount != -1 && currentLineLength >= maxCharCount))\r
94                 {\r
95                     UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(c);\r
96                     if (uc != UnicodeCategory.NonSpacingMark &&\r
97                     uc != UnicodeCategory.SpacingCombiningMark &&\r
98                     uc != UnicodeCategory.EnclosingMark &&\r
99                     uc != UnicodeCategory.Surrogate)\r
100                     {\r
101                         yield return new Tuple<int,int>(currentLineHeadIndex, currentLineLength);\r
102                         currentLineHeadIndex += currentLineLength;\r
103                         currentLineLength = 0;\r
104                     }\r
105                 }\r
106             }\r
107             if (currentLineLength > 0)\r
108                 yield return new Tuple<int, int>(currentLineHeadIndex, currentLineLength);\r
109         }\r
110         \r
111         public int Length\r
112         {\r
113             get { return this.buf.Count; }\r
114         }\r
115 \r
116         internal event DocumentUpdateEventHandler Update;\r
117 \r
118         internal void Replace(StringBuffer buf)\r
119         {\r
120             this.Replace(buf.buf);\r
121         }\r
122 \r
123         internal void Replace(GapBuffer<char> buf)\r
124         {\r
125             this.Clear();\r
126             this.buf = buf;\r
127             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));\r
128         }\r
129 \r
130         internal void Replace(int index, int length, IEnumerable<char> chars,int count)\r
131         {\r
132             if (length > 0)\r
133                 this.buf.RemoveRange(index, length);\r
134             this.buf.InsertRange(index, chars,count);\r
135             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, index, length, count));\r
136         }\r
137 \r
138         internal void Replace(string target, string pattern,bool ci = false)\r
139         {\r
140             TextSearch ts = new TextSearch(target,ci);\r
141             int left = 0, right = 0;\r
142             char[] pattern_chars = pattern.ToCharArray();\r
143             while((right = ts.IndexOf(this.buf,left)) != -1)\r
144             {\r
145                 this.buf.RemoveRange(right, target.Length);\r
146                 this.buf.InsertRange(right, pattern_chars, pattern.Length);\r
147                 left = right + pattern.Length;\r
148             }\r
149             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));\r
150             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, buf.Count));\r
151         }\r
152 \r
153         internal int IndexOf(string target, int start,bool ci = false)\r
154         {\r
155             TextSearch ts = new TextSearch(target,ci);\r
156             return ts.IndexOf(this.buf, start);\r
157         }\r
158 \r
159         /// <summary>\r
160         /// 文字列を削除する\r
161         /// </summary>\r
162         internal void Clear()\r
163         {\r
164             this.buf.Clear();\r
165             this.Update(this, new DocumentUpdateEventArgs(UpdateType.Clear, 0, this.buf.Count,0));\r
166         }\r
167 \r
168         internal IEnumerable<char> GetEnumerator(int start, int length)\r
169         {\r
170             for (int i = start; i < start + length; i++)\r
171                 yield return this.buf[i];\r
172         }\r
173 \r
174         #region IEnumerable<char> メンバー\r
175 \r
176         public IEnumerator<char> GetEnumerator()\r
177         {\r
178             for (int i = 0; i < this.Length; i++)\r
179                 yield return this.buf[i];\r
180         }\r
181 \r
182         #endregion\r
183 \r
184         #region IEnumerable メンバー\r
185 \r
186         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()\r
187         {\r
188             for (int i = 0; i < this.Length; i++)\r
189                 yield return this[i];\r
190         }\r
191 \r
192         #endregion\r
193     }\r
194 \r
195     sealed class TextSearch\r
196     {\r
197         char[] pattern;\r
198         int patternLength;\r
199         Dictionary<char, int> qsTable = new Dictionary<char, int>();\r
200         bool caseInsenstive;\r
201         public TextSearch(string pattern,bool ci = false)\r
202         {\r
203             this.patternLength = pattern.Length;\r
204             this.caseInsenstive = ci;\r
205             if (ci)\r
206             {\r
207                 this.CreateQSTable(pattern.ToLower());\r
208                 this.CreateQSTable(pattern.ToUpper());\r
209                 this.pattern = new char[pattern.Length];\r
210                 for (int i = 0; i < pattern.Length; i++)\r
211                     this.pattern[i] = CharTool.ToUpperFastIf(pattern[i]);\r
212             }\r
213             else\r
214             {\r
215                 this.CreateQSTable(pattern);\r
216                 this.pattern = pattern.ToCharArray();\r
217             }\r
218         }\r
219         void CreateQSTable(string pattern)\r
220         {\r
221             int len = pattern.Length;\r
222             for (int i = 0; i < len; i++)\r
223             {\r
224                 if (!this.qsTable.ContainsKey(pattern[i]))\r
225                     this.qsTable.Add(pattern[i], len - i);\r
226                 else\r
227                     this.qsTable[pattern[i]] = len - i;\r
228             }\r
229         }\r
230         public int IndexOf(GapBuffer<char> buf, int start)\r
231         {\r
232             //QuickSearch法\r
233             int buflen = buf.Count - 1;\r
234             int plen = this.patternLength;\r
235             int i = start;\r
236             int end = buf.Count - plen;\r
237             //最適化のためわざとコピペした\r
238             if (this.caseInsenstive)\r
239             {\r
240                 while (i <= end)\r
241                 {\r
242                     int j = 0;\r
243                     while (j < plen)\r
244                     {\r
245                         if (CharTool.ToUpperFastIf(buf[i + j]) != this.pattern[j])\r
246                             break;\r
247                         j++;\r
248                     }\r
249                     if (j == plen)\r
250                     {\r
251                         return i;\r
252                     }\r
253                     else\r
254                     {\r
255                         int k = i + plen;\r
256                         if (k <= buflen)        //buffer以降にアクセスする可能性がある\r
257                         {\r
258                             int moveDelta;\r
259                             if (this.qsTable.TryGetValue(buf[k], out moveDelta))\r
260                                 i += moveDelta;\r
261                             else\r
262                                 i += plen;\r
263                         }\r
264                         else\r
265                         {\r
266                             break;\r
267                         }\r
268                     }\r
269                 }\r
270 \r
271             }\r
272             else\r
273             {\r
274                 while (i <= end)\r
275                 {\r
276                     int j = 0;\r
277                     while (j < plen)\r
278                     {\r
279                         if (buf[i + j] != this.pattern[j])\r
280                             break;\r
281                         j++;\r
282                     }\r
283                     if (j == plen)\r
284                     {\r
285                         return i;\r
286                     }\r
287                     else\r
288                     {\r
289                         int k = i + plen;\r
290                         if (k <= buflen)        //buffer以降にアクセスする可能性がある\r
291                         {\r
292                             int moveDelta;\r
293                             if (this.qsTable.TryGetValue(buf[k], out moveDelta))\r
294                                 i += moveDelta;\r
295                             else\r
296                                 i += plen;\r
297                         }\r
298                         else\r
299                         {\r
300                             break;\r
301                         }\r
302                     }\r
303                 }\r
304             }\r
305             return -1;\r
306         }\r
307     }\r
308     static class CharTool\r
309     {\r
310         /// <summary>\r
311         /// Converts characters to lowercase.\r
312         /// </summary>\r
313         const string _lookupStringL =\r
314         "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[-]^_`abcdefghijklmnopqrstuvwxyz{|}~-";\r
315 \r
316         /// <summary>\r
317         /// Converts characters to uppercase.\r
318         /// </summary>\r
319         const string _lookupStringU =\r
320         "---------------------------------!-#$%&-()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[-]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~-";\r
321 \r
322         /// <summary>\r
323         /// Get lowercase version of this ASCII character.\r
324         /// </summary>\r
325         public static char ToLower(char c)\r
326         {\r
327             return _lookupStringL[c];\r
328         }\r
329 \r
330         /// <summary>\r
331         /// Get uppercase version of this ASCII character.\r
332         /// </summary>\r
333         public static char ToUpper(char c)\r
334         {\r
335             return _lookupStringU[c];\r
336         }\r
337 \r
338         /// <summary>\r
339         /// Translate uppercase ASCII characters to lowercase.\r
340         /// </summary>\r
341         public static char ToLowerFastIf(char c)\r
342         {\r
343             if (c >= 'A' && c <= 'Z')\r
344             {\r
345                 return (char)(c + 32);\r
346             }\r
347             else\r
348             {\r
349                 return c;\r
350             }\r
351         }\r
352 \r
353         /// <summary>\r
354         /// Translate lowercase ASCII characters to uppercase.\r
355         /// </summary>\r
356         public static char ToUpperFastIf(char c)\r
357         {\r
358             if (c >= 'a' && c <= 'z')\r
359             {\r
360                 return (char)(c - 32);\r
361             }\r
362             else\r
363             {\r
364                 return c;\r
365             }\r
366         }\r
367     }\r
368 }