1 module hunt.time.format.PrefixTree;
2 
3 import hunt.time.format.DateTimeParseContext;
4 import hunt.time.text.ParsePosition;
5 
6 import hunt.collection.Set;
7 import hunt.text.Common;
8 import hunt.util.StringBuilder;
9 
10 import std.string;
11 
12 //-----------------------------------------------------------------------
13 /**
14 * A string based prefix tree for parsing time-zone names.
15 */
16 static class PrefixTree
17 {
18     protected string key;
19     protected string value;
20     protected dchar c0; // performance optimization to avoid the
21     // boundary check cost of key.charat(0)
22     protected PrefixTree child;
23     protected PrefixTree sibling;
24 
25     private this(string k, string v, PrefixTree child)
26     {
27         this.key = k;
28         this.value = v;
29         this.child = child;
30         if (k.length == 0)
31         {
32             c0 = 0xffff;
33         }
34         else
35         {
36             c0 = key[0];
37         }
38     }
39 
40     /**
41  * Creates a new prefix parsing tree based on parse context.
42  *
43  * @param context  the parse context
44  * @return the tree, not null
45  */
46     public static PrefixTree newTree(DateTimeParseContext context)
47     {
48         //if (!context.isStrict()) {
49         //    return new LENIENT("", null, null);
50         //}
51         if (context.isCaseSensitive())
52         {
53             return new PrefixTree("", null, null);
54         }
55         return new CI("", null, null);
56     }
57 
58     /**
59  * Creates a new prefix parsing tree.
60  *
61  * @param keys  a set of strings to build the prefix parsing tree, not null
62  * @param context  the parse context
63  * @return the tree, not null
64  */
65     public static PrefixTree newTree(Set!(string) keys, DateTimeParseContext context)
66     {
67         PrefixTree tree = newTree(context);
68         foreach (string k; keys)
69         {
70             tree.add0(k, k);
71         }
72         return tree;
73     }
74 
75     /**
76  * Clone a copy of this tree
77  */
78     public PrefixTree copyTree()
79     {
80         PrefixTree copy = new PrefixTree(key, value, null);
81         if (child !is null)
82         {
83             copy.child = child.copyTree();
84         }
85         if (sibling !is null)
86         {
87             copy.sibling = sibling.copyTree();
88         }
89         return copy;
90     }
91 
92     /**
93  * Adds a pair of {key, value} into the prefix tree.
94  *
95  * @param k  the key, not null
96  * @param v  the value, not null
97  * @return  true if the pair is added successfully
98  */
99     public bool add(string k, string v)
100     {
101         return add0(k, v);
102     }
103 
104     private bool add0(string k, string v)
105     {
106         k = toKey(k);
107         int prefixLen = prefixLength(k);
108         if (prefixLen == key.length)
109         {
110             if (prefixLen < k.length)
111             { // down the tree
112                 string subKey = k.substring(prefixLen);
113                 PrefixTree c = child;
114                 while (c !is null)
115                 {
116                     if (isEqual(c.c0, subKey[0]))
117                     {
118                         return c.add0(subKey, v);
119                     }
120                     c = c.sibling;
121                 }
122                 // add the node as the child of the current node
123                 c = newNode(subKey, v, null);
124                 c.sibling = child;
125                 child = c;
126                 return true;
127             }
128             // have an existing !(key, value) already, overwrite it
129             // if (value !is null) {
130             //    return false;
131             //}
132             value = v;
133             return true;
134         }
135         // split the existing node
136         PrefixTree n1 = newNode(key.substring(prefixLen), value, child);
137         key = k.substring(0, prefixLen);
138         child = n1;
139         if (prefixLen < k.length)
140         {
141             PrefixTree n2 = newNode(k.substring(prefixLen), v, null);
142             child.sibling = n2;
143             value = null;
144         }
145         else
146         {
147             value = v;
148         }
149         return true;
150     }
151 
152     /**
153  * Match text with the prefix tree.
154  *
155  * @param text  the input text to parse, not null
156  * @param off  the offset position to start parsing at
157  * @param end  the end position to stop parsing
158  * @return the resulting string, or null if no match found.
159  */
160     public string match(string text, int off, int end)
161     {
162         if (!prefixOf(text, off, end))
163         {
164             return null;
165         }
166         if (child !is null && (off += key.length) != end)
167         {
168             PrefixTree c = child;
169             do
170             {
171                 if (isEqual(c.c0, text[off]))
172                 {
173                     string found = c.match(text, off, end);
174                     if (found !is null)
175                     {
176                         return found;
177                     }
178                     return value;
179                 }
180                 c = c.sibling;
181             }
182             while (c !is null);
183         }
184         return value;
185     }
186 
187     /**
188  * Match text with the prefix tree.
189  *
190  * @param text  the input text to parse, not null
191  * @param pos  the position to start parsing at, from 0 to the text
192  *  length. Upon return, position will be updated to the new parse
193  *  position, or unchanged, if no match found.
194  * @return the resulting string, or null if no match found.
195  */
196     public string match(string text, ParsePosition pos)
197     {
198         int off = pos.getIndex();
199         int end = cast(int)(text.length);
200         if (!prefixOf(text, off, end))
201         {
202             return null;
203         }
204         off += key.length;
205         if (child !is null && off != end)
206         {
207             PrefixTree c = child;
208             do
209             {
210                 if (isEqual(c.c0, text[off]))
211                 {
212                     pos.setIndex(off);
213                     string found = c.match(text, pos);
214                     if (found !is null)
215                     {
216                         return found;
217                     }
218                     break;
219                 }
220                 c = c.sibling;
221             }while (c !is null);
222         }
223         pos.setIndex(off);
224         return value;
225     }
226 
227     protected string toKey(string k)
228     {
229         return k;
230     }
231 
232     protected PrefixTree newNode(string k, string v, PrefixTree child)
233     {
234         return new PrefixTree(k, v, child);
235     }
236 
237     protected bool isEqual(dchar c1, char c2)
238     {
239         return cast(char) c1 == c2;
240     }
241 
242     protected bool isEqual(char c1, char c2)
243     {
244         return c1 == c2;
245     }
246 
247     protected bool prefixOf(string text, int off, int end)
248     {
249         if (cast(string)(text) !is null)
250         {
251             return (cast(string) text).startsWith(key, off) > 0 ? true : false;
252         }
253         int len = cast(int)(key.length);
254         if (len > end - off)
255         {
256             return false;
257         }
258         int off0 = 0;
259         while (len-- > 0)
260         {
261             if (!isEqual(key[off0++], text[off++]))
262             {
263                 return false;
264             }
265         }
266         return true;
267     }
268 
269     private int prefixLength(string k)
270     {
271         int off = 0;
272         while (off < k.length && off < key.length)
273         {
274             if (!isEqual(k[off], key[off]))
275             {
276                 return off;
277             }
278             off++;
279         }
280         return off;
281     }
282 
283     /**
284  * Case Insensitive prefix tree.
285  */
286     private static class CI : PrefixTree
287     {
288 
289         private this(string k, string v, PrefixTree child)
290         {
291             super(k, v, child);
292         }
293 
294         override protected CI newNode(string k, string v, PrefixTree child)
295         {
296             return new CI(k, v, child);
297         }
298 
299         override protected bool isEqual(char c1, char c2)
300         {
301             return DateTimeParseContext.charEqualsIgnoreCase(c1, c2);
302         }
303 
304         override protected bool isEqual(dchar c1, char c2)
305         {
306             return DateTimeParseContext.charEqualsIgnoreCase(cast(char) c1, c2);
307         }
308 
309         override protected bool prefixOf(string text, int off, int end)
310         {
311             int len = cast(int)(key.length);
312             if (len > end - off)
313             {
314                 return false;
315             }
316             int off0 = 0;
317             while (len-- > 0)
318             {
319                 if (!isEqual(key[off0++], text[off++]))
320                 {
321                     return false;
322                 }
323             }
324             return true;
325         }
326     }
327 
328     /**
329  * Lenient prefix tree. Case insensitive and ignores characters
330  * like space, underscore and slash.
331  */
332     private static class LENIENT : CI
333     {
334 
335         private this(string k, string v, PrefixTree child)
336         {
337             super(k, v, child);
338         }
339 
340         override protected CI newNode(string k, string v, PrefixTree child)
341         {
342             return new LENIENT(k, v, child);
343         }
344 
345         private bool isLenientChar(char c)
346         {
347             return c == ' ' || c == '_' || c == '/';
348         }
349 
350         override protected string toKey(string k)
351         {
352             for (int i = 0; i < k.length; i++)
353             {
354                 if (isLenientChar(k[i]))
355                 {
356                     StringBuilder sb = new StringBuilder(k.length);
357                     sb.append(k, 0, i);
358                     i++;
359                     while (i < k.length)
360                     {
361                         if (!isLenientChar(k[i]))
362                         {
363                             sb.append(k[i]);
364                         }
365                         i++;
366                     }
367                     return sb.toString();
368                 }
369             }
370             return k;
371         }
372 
373         override public string match(string text, ParsePosition pos)
374         {
375             int off = pos.getIndex();
376             int end = cast(int)(text.length);
377             int len = cast(int)(key.length);
378             int koff = 0;
379             while (koff < len && off < end)
380             {
381                 if (isLenientChar(text[off]))
382                 {
383                     off++;
384                     continue;
385                 }
386                 if (!isEqual(key[koff++], text[off++]))
387                 {
388                     return null;
389                 }
390             }
391             if (koff != len)
392             {
393                 return null;
394             }
395             if (child !is null && off != end)
396             {
397                 int off0 = off;
398                 while (off0 < end && isLenientChar(text[off0]))
399                 {
400                     off0++;
401                 }
402                 if (off0 < end)
403                 {
404                     PrefixTree c = child;
405                     do
406                     {
407                         if (isEqual(c.c0, text[off0]))
408                         {
409                             pos.setIndex(off0);
410                             string found = c.match(text, pos);
411                             if (found !is null)
412                             {
413                                 return found;
414                             }
415                             break;
416                         }
417                         c = c.sibling;
418                     }
419                     while (c !is null);
420                 }
421             }
422             pos.setIndex(off);
423             return value;
424         }
425     }
426 }