*** empty log message ***
[m17n/m17n-lib-cs.git] / MText.cs
1 using System;
2 using System.Text;
3 using System.Collections;
4 using System.Collections.Generic;
5 using M17N.Core;
6
7 namespace M17N.Core
8 {
9 #if false
10   public enum MTextFormat
11   {
12     MTEXT_FORMAT_US_ASCII,
13     MTEXT_FORMAT_UTF_8,
14     MTEXT_FORMAT_UTF_16BE,
15     MTEXT_FORMAT_UTF_16LE,
16     MTEXT_FORMAT_UTF_32BE,
17     MTEXT_FORMAT_UTF_32LE,
18   }
19 #endif
20
21   public class MTextProperty
22   {
23     internal MProperty prop;
24     internal bool front_sticky;
25     internal bool rear_sticky;
26     internal bool merginable;
27     public MProperty Prop { get { return prop; } }
28     public bool FrontSticky { get { return front_sticky; } }
29     public bool RearSticky { get { return rear_sticky; } }
30     public bool Merginable { get { return merginable; } }
31
32     public MTextProperty (bool front_sticky, bool rear_sticky)
33     {
34       this.front_sticky = front_sticky;
35       this.rear_sticky = rear_sticky;
36     }
37     public MTextProperty (bool front_sticky, bool rear_sticky, bool merginable)
38     {
39       this.front_sticky = front_sticky;
40       this.rear_sticky = rear_sticky;
41       this.merginable = merginable;
42     }
43   }
44
45   public class MText
46   {
47 #if false
48     public enum MTextFormat format;
49 #endif
50
51     private StringBuilder sb;
52     private int nchars;
53     private int cache_pos;
54     private int cache_idx;
55     private MInterval root_interval;
56     private bool unmodifiable;
57
58     private static UTF8Encoding utf8 = new UTF8Encoding ();
59
60     private static int count_chars (String str)
61     {
62       int len = str.Length, n = 0;
63
64       for (int i = 0; i < len; i++) 
65         n += surrogate_high_p (str[i]) ? 2 : 1;
66       return n;
67     }
68
69     private static int count_chars (StringBuilder str)
70     {
71       int len = str.Length, n = 0;
72
73       for (int i = 0; i < len; i++) 
74         n += surrogate_high_p (str[i]) ? 2 : 1;
75       return n;
76     }
77
78     public MText ()
79     {
80       sb = new StringBuilder ();
81     }
82
83     public MText (byte[] str)
84     {
85       sb = new StringBuilder (utf8.GetString (str));
86       nchars = count_chars (sb);
87     }
88
89     public MText (String str)
90     {
91       sb = new StringBuilder (str);
92       nchars = count_chars (str);
93     }
94
95     public MText (StringBuilder str)
96     {
97       sb = str;
98       nchars = count_chars (str);
99     }
100
101     public static MText operator+ (MText mt1, MText mt2)
102     {
103       MText mt = new MText (mt1.sb);
104
105       mt.sb.Append (mt2.sb);
106       mt.nchars = mt1.nchars + mt2.nchars;
107       return mt;
108     }
109
110     public override string ToString ()
111     {
112       return sb.ToString ();
113     }
114
115     private static bool surrogate_high_p (char c)
116     {
117       return (c >= 0xD800 && c < 0xDC00);
118     }
119
120     private static bool surrogate_low_p (char c)
121     {
122       return (c >= 0xDC00 && c < 0xE000);
123     }
124
125     private static int inc_idx (StringBuilder sb, int i)
126     {
127       return (i + (surrogate_high_p (sb[i]) ? 2 : 1));
128     }
129
130     private static int dec_idx (StringBuilder sb, int i)
131     {
132       return (i - (surrogate_low_p (sb[i - 1]) ? 2 : 1));
133     }
134
135     private static int pos_to_idx (MText mt, int pos)
136     {
137       if (pos == mt.cache_pos)
138         return mt.cache_idx;
139
140       int p, i;
141       bool forward;
142
143       if (pos < mt.cache_pos)
144         {
145           if (mt.cache_pos == mt.cache_idx)
146             return mt.cache_idx;
147           if (pos < mt.cache_pos - pos)
148             {
149               p = i = 0;
150               forward = true;
151             }
152           else
153             {
154               p = mt.cache_pos; i = mt.cache_idx;
155               forward = false;
156             }
157         }
158       else
159         {
160           if (mt.nchars - mt.cache_pos == mt.sb.Length - mt.cache_idx)
161             return (mt.cache_idx + pos - mt.cache_pos);
162           if (pos - mt.cache_pos < mt.nchars - pos)
163             {
164               p = mt.cache_pos; i = mt.cache_idx;
165               forward = true;
166             }
167           else
168             {
169               p = mt.nchars; i = mt.sb.Length;
170               forward = false;
171             }
172         }
173       if (forward)
174         for (; p < pos; i = inc_idx (mt.sb, i), p++);
175       else
176         for (; p > pos; i = dec_idx (mt.sb, i), p--);
177       mt.cache_pos = p;
178       mt.cache_idx = i;
179       return i;
180     }
181
182     private void insert (int pos, MText mt2, int from, int to)
183     {
184       int pos_idx = pos_to_idx (this, pos);
185       int from_idx = pos_to_idx (mt2, from);
186       int to_idx = pos_to_idx (mt2, to);
187
188       sb.Insert (pos_idx, mt2.sb.ToString (from_idx, to_idx - from_idx));
189       nchars += to - from;
190     }
191
192     public int this[int i]
193     {
194       set {
195         i = pos_to_idx (this, i);
196         if (value < 0x10000)
197           {
198             if (surrogate_high_p (sb[i]))
199               sb.Remove (i, 1);
200             sb[i] = (char) value;
201           }
202         else
203           {
204             char high = (char) (0xD800 + ((value - 0x10000) >> 10));
205             char low = (char) (0xDC00 + ((value - 0x10000) & 0x3FF));
206
207             if (! surrogate_high_p (sb[i]))
208               sb.Insert (i, 0);
209             sb[i] = high;
210             sb[i + 1] = low;
211           }
212       }
213       get {
214         i = pos_to_idx (this, i);
215         return (surrogate_high_p (sb[i])
216                 ? ((sb[i] - 0xD800) << 10) + (sb[i + 1] - 0xDC00) + 0x10000
217                 : sb[i]);
218       }
219     }
220
221     public MText dup ()
222     {
223       return (new MText (sb.ToString ()));
224     }
225
226     public MText ins (int pos, MText mt)
227     {
228       insert (pos, mt, 0, mt.nchars);
229       return this;
230     }
231
232     public MText ins (int pos, MText mt, int from, int to)
233     {
234       insert (pos, mt, from, to);
235       return this;
236     }
237
238     public MText del (int from, int to)
239     {
240       sb.Remove (from, pos_to_idx (this, to) - pos_to_idx (this, from));
241       nchars -= to - from;
242       return this;
243     }
244
245     private class MInterval
246     {
247       // Start and end positions of this interval and its children.
248       // If this is the left node, the values are relative to the
249       // parent's total_start.  Otherwise, the values are relatie to
250       // the parent's total_end.
251       private int total_start, total_end;
252       // Stack of MTextProperty
253       private Stack<MTextProperty> stack;
254       private int depth;
255       private MInterval left, right, parent;
256
257       public MInterval (int start, bool front_inclusive,
258                         int end, bool rear_inclusive)
259       {
260         if (start > end)
261           throw new Exception ("Invalid Interval Range");
262         this.total_start = (start << 2) + (front_inclusive ? -1 : 1);
263         this.total_end = (end << 2) + (rear_inclusive ? 1 : -1);
264         this.stack = new Stack<MTextProperty> ();
265         this.depth = 1;
266       }
267
268       private MInterval (int start, int end, Stack<MTextProperty> stack)
269       {
270         this.total_start = start;
271         this.total_end = end;
272         this.stack = new Stack<MTextProperty> (stack);
273         this.depth = 1;
274       }
275
276       private MInterval Copy ()
277       {
278         return new MInterval (total_start, total_end, stack);
279       }
280
281       private int Start {
282         get {
283           return (left == null ? total_start : total_start + left.total_end);
284         }
285       }
286
287       private int End {
288         get {
289           return (right == null ? total_end : total_end + right.total_start);
290         }
291       }
292
293       private MInterval Left {
294         get {
295           MInterval interval;
296
297           if (left != null)
298             for (interval = left;
299                  interval.right != null;
300                  interval = interval.right);
301           else
302             for (interval = parent;
303                  interval != null && interval.total_start == 0;
304                  interval = interval.parent);
305
306           return interval;
307         }
308       }
309
310       private MInterval Right {
311         get {
312           MInterval interval;
313
314           if (right != null)
315             for (interval = right;
316                  interval.left != null;
317                  interval = interval.left);
318           else
319             for (interval = parent;
320                  interval != null && interval.total_start < 0;
321                  interval = interval.parent);
322
323           return interval;
324         }
325       }
326
327       public void Push (MTextProperty prop, int start, int end)
328       {
329         start <<= 2;
330         if (prop.FrontSticky)
331           start--;
332         else
333           start++;
334         end <<= 2;
335         if (prop.RearSticky)
336           end++;
337         else
338           end--;
339         if (start >= end)
340           throw new Exception ("Invalid Text Property Range");
341
342         push (prop, start, end);
343       }
344
345       private MInterval divide_right (int pos)
346       {
347         MInterval interval = Copy ();
348         int this_start = Start;
349         int this_end = End;
350
351         if (left == null || (right != null && left.depth < right.depth))
352           {
353             interval.left = this;
354             interval.right = right;
355             interval.parent = parent;
356             if (parent != null)
357               {
358                 if (total_start == 0)
359                   parent.left = interval;
360                 else
361                   parent.right = interval;
362               }
363             interval.depth = depth;
364             if (right == null)
365               interval.depth++;
366             total_start = 0;
367             total_end = pos - this_start;
368           }
369         else
370           {
371             interval.total_start = pos - this_end;
372             interval.total_end = 0;
373             if (right != null)
374               right.parent = interval;
375             right = interval;
376           }
377
378         return interval;
379       }
380
381       private MInterval divide_left (int pos)
382       {
383         MInterval interval = Copy ();
384         int this_start = Start;
385         int this_end = End;
386
387         if (left == null || (right != null && left.depth < right.depth))
388           {
389             interval.total_start = 0;
390             interval.total_end = pos - this_start;
391             if (left != null)
392               left.parent = interval;
393             left = interval;
394           }
395         else
396           {
397             interval.right = this;
398             interval.left = left;
399             interval.parent = parent;
400             if (parent != null)
401               {
402                 if (total_start == 0)
403                   parent.left = interval;
404                 else
405                   parent.right = interval;
406               }
407             interval.depth = depth;
408             if (left == null)
409               interval.depth++;
410             total_start = pos - this_end;
411             total_end = 0;
412           }
413
414         return interval;
415       }
416
417       private void push (MTextProperty prop, int start, int end)
418       {
419         int this_start = Start;
420         int this_end = End;
421
422         if (start < this_start)
423           {
424             if (end <= this_start)
425               Left.push (prop, start, end);
426             else
427               {
428                 Left.push (prop, start, this_start);
429                 if (end < this_end)
430                   {
431                     divide_right (end);
432                     stack.Push (prop);
433                   }
434                 else
435                   {
436                     stack.Push (prop);
437                     if (this_end < end)
438                       Right.Push (prop, this_end, end);
439                   }
440               }
441           }
442         else if (start < this_end)
443           {
444             divide_right (start).Push (prop, start, end);
445           }
446         else
447           Right.Push (prop, start, end);
448       }
449     }
450   }
451 }