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 }