1// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*- 2/*- 3 * Copyright (c) 2011-2015 elementary LLC 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 * Authored by: Mario Guerriero <marioguerriero33@gmail.com> 19 * pantor 20 */ 21 22namespace Maya.Services { 23 24public class ParserEn : GLib.Object, EventParser { 25 26 public DateTime simulated_dt; 27 28 public string source; 29 private string remaining_source; 30 31 string[,] months = { 32 {"january", "1"}, 33 {"february", "2"}, 34 {"march", "3"}, 35 {"april", "4"}, 36 {"may", "5"}, 37 {"june", "6"}, 38 {"july", "7"}, 39 {"august", "8"}, 40 {"september", "9"}, 41 {"october", "10"}, 42 {"november", "11"}, 43 {"december", "12"} 44 }; 45 46 string[,] weekdays = { 47 {"monday", "1"}, 48 {"tuesday", "2"}, 49 {"wednesday", "3"}, 50 {"thursday", "4"}, 51 {"thu", "4"}, 52 {"friday", "5"}, 53 {"saturday", "6"}, 54 {"sunday", "7"} 55 }; 56 57 string[,] number_words = { 58 {"one", "1"}, 59 {"two", "2"}, 60 {"three", "3"}, 61 {"four", "4"}, 62 {"five", "5"}, 63 {"six", "6"}, 64 {"seven", "7"}, 65 {"eight", "8"}, 66 {"nine", "9"}, 67 {"ten", "10"}, 68 {"eleven", "11"}, 69 {"twelve", "12"} 70 }; 71 72 string months_regex; 73 string weekdays_regex; 74 string number_words_regex; 75 76 struct StringEvent { bool matched; string matched_string; int pos; int length; Array<string> p; } 77 78 delegate void transcribe_analysis (StringEvent data); 79 80 public ParserEn (DateTime _simulated_dt = new DateTime.now_local ()) { 81 this.simulated_dt = _simulated_dt; 82 83 this.source = ""; 84 this.remaining_source = ""; 85 86 this.months_regex = "("; 87 for (int i = 0; i < 12; i++) { 88 this.months_regex += months[i, 0] + "|"; 89 } 90 91 this.months_regex = this.months_regex[0:-1] + ")"; 92 93 this.weekdays_regex = "("; 94 for (int i = 0; i < 7; i++) { 95 this.weekdays_regex += weekdays[i, 0] + "|"; 96 } 97 98 this.weekdays_regex = this.weekdays_regex[0:-1] + ")"; 99 100 this.number_words_regex = "("; 101 for (int i = 0; i < 7; i++) { 102 this.number_words_regex += number_words[i, 0] + "|"; 103 } 104 105 this.number_words_regex = this.number_words_regex[0:-1] + ")"; 106 } 107 108 int get_number_of_month (string entry) { 109 for (int i = 0; i < 12; i++) { 110 if (entry.down () == months[i, 0]) { 111 return int.parse (months[i, 1]); 112 } 113 } 114 return -1; 115 } 116 117 int get_number_of_weekday (string entry) { 118 for (int i = 0; i < 12; i++) { 119 if (entry.down () == weekdays[i, 0]) { 120 return int.parse (weekdays[i, 1]); 121 } 122 } 123 return -1; 124 } 125 126 /*int get_number_of_word (string entry) { 127 for (int i = 0; i < 12; i++) { 128 if (entry.down () == number_words[i, 0]) 129 return int.parse (number_words[i, 1]); 130 } 131 return -1; 132 }*/ 133 134 // finds regex "pattern" in string source 135 StringEvent complete_string (string pattern) { 136 Regex regex; 137 MatchInfo match_info; 138 try { 139 regex = new Regex (pattern, RegexCompileFlags.CASELESS); 140 var is_matched = regex.match (this.remaining_source, 0, out match_info); 141 if (!is_matched) { 142 return StringEvent () { matched = false }; 143 } 144 } catch { 145 return StringEvent () { matched = false }; 146 } 147 148 var matched_string = match_info.fetch (0); 149 var pos = this.remaining_source.index_of (matched_string); 150 var length = matched_string.length; 151 152 var p = new Array<string> (); 153 154 while (match_info.matches ()) { 155 for (var i = 0; i < 6; i++) { 156 p.append_val (match_info.fetch_named (@"p$(i + 1)")); 157 } 158 159 try { 160 match_info.next (); 161 } catch (GLib.RegexError exc) { 162 // TODO 163 } 164 } 165 166 return StringEvent () { matched = true, pos = pos, length = length, matched_string = matched_string, p = p }; 167 } 168 169 void analyze_pattern (string pattern, transcribe_analysis del, bool delete = true) { 170 StringEvent data = complete_string ("\\b" + pattern + "\\b"); 171 if (data.matched) { 172 if (delete) { 173 this.remaining_source = this.remaining_source.splice (data.pos, data.pos + data.length); 174 } 175 176 del (data); 177 } 178 } 179 180 public ParsedEvent parse_source (string _source) { 181 this.source = _source; 182 this.remaining_source = this.source; 183 184 var event = new ParsedEvent (); 185 event.from = this.simulated_dt.add_hours (1); 186 event.from_set_minute (0); 187 event.from_set_second (0); 188 event.set_length_to_hours (1); 189 190 // --- Date --- 191 192 analyze_pattern ("two days ago", (data) => { 193 event.date_parsed = true; 194 event.from = event.from.add_days (-2); 195 event.set_one_entire_day (); 196 }); 197 198 analyze_pattern ("yesterday", (data) => { 199 event.date_parsed = true; 200 event.from = event.from.add_days (-1); 201 event.set_one_entire_day (); 202 }); 203 204 analyze_pattern ("today", (data) => { 205 event.date_parsed = true; 206 event.set_one_entire_day (); 207 }); 208 209 analyze_pattern ("tomorrow", (data) => { 210 event.date_parsed = true; 211 event.from = event.from.add_days (1); 212 event.set_one_entire_day (); 213 }); 214 215 analyze_pattern ("this weekend", (data) => { 216 int add_days = (6 - this.simulated_dt.get_day_of_week () + 7) % 7; 217 218 event.date_parsed = true; 219 event.from = event.from.add_days (add_days); 220 event.to = event.from.add_days (1); 221 event.set_all_day (); 222 }); 223 224 analyze_pattern ("all day", (data) => { 225 event.set_one_entire_day (); 226 }); 227 228 analyze_pattern ("the whole day", (data) => { 229 event.set_one_entire_day (); 230 }); 231 232 analyze_pattern ("next week", (data) => { 233 event.date_parsed = true; 234 event.from = event.from.add_days (7); 235 event.set_all_day (); 236 }); 237 238 analyze_pattern ("next month", (data) => { 239 event.date_parsed = true; 240 event.from = event.from.add_months (1); 241 event.set_all_day (); 242 }); 243 244 analyze_pattern ("(?<p1>\\d+) days ago", (data) => { 245 int days = int.parse (data.p.index (0)); 246 247 event.date_parsed = true; 248 event.from = event.from.add_days (-days); 249 event.set_one_entire_day (); 250 }); 251 252 analyze_pattern ("in (?<p1>\\d+) days", (data) => { 253 int days = int.parse (data.p.index (0)); 254 255 event.date_parsed = true; 256 event.from = event.from.add_days (days); 257 event.set_one_entire_day (); 258 }); 259 260 analyze_pattern ("in (?<p1>\\d+) weeks", (data) => { 261 int weeks = int.parse (data.p.index (0)); 262 263 event.date_parsed = true; 264 event.from = event.from.add_weeks (weeks); 265 event.set_one_entire_day (); 266 }); 267 268 analyze_pattern (@"(next|on) (?<p1>$weekdays_regex)", (data) => { 269 int weekday = get_number_of_weekday (data.p.index (0)); 270 int add_days = (weekday - this.simulated_dt.get_day_of_week () + 7) % 7; 271 272 event.date_parsed = true; 273 event.from = event.from.add_days (add_days); 274 event.set_one_entire_day (); 275 }); 276 277 analyze_pattern (@"(this )?(?<p1>$weekdays_regex)( to (?<p2>$weekdays_regex))?", (data) => { 278 int weekday_1 = get_number_of_weekday (data.p.index (0)); 279 int add_days_1 = (weekday_1 - this.simulated_dt.get_day_of_week () + 7) % 7; 280 281 event.date_parsed = true; 282 event.from = event.from.add_days (add_days_1); 283 event.set_all_day (); 284 285 if (data.p.index (1) != null) { 286 int weekday_2 = get_number_of_weekday (data.p.index (1)); 287 int add_days_2 = (weekday_2 - weekday_1 + 7) % 7; 288 289 event.to = event.from.add_days (add_days_2); 290 } 291 }); 292 293 analyze_pattern ("on ((?<p1>\\d{2,4})/)?(?<p2>\\d{1,2})/(?<p3>\\d{1,2})(st|nd|rd|th)?", (data) => { 294 int day = int.parse (data.p.index (2)); 295 int month = int.parse (data.p.index (1)); 296 297 event.date_parsed = true; 298 event.from_set_day (day); 299 event.from_set_month (month); 300 event.set_one_entire_day (); 301 302 event.if_elapsed_delay_to_next_year (this.simulated_dt); 303 304 if (data.p.index (0) != null) { 305 int year = int.parse (data.p.index (0)); 306 event.from_set_year (year); 307 } 308 }); 309 310 analyze_pattern (@"on (?<p1>\\d{1,2})(st|nd|rd|th)? (?<p2>$months_regex)( (?<p3>\\d{2,4}))?", (data) => { 311 int day = int.parse (data.p.index (0)); 312 int month = get_number_of_month (data.p.index (1)); 313 314 event.date_parsed = true; 315 event.from_set_day (day); 316 event.from_set_month (month); 317 event.set_one_entire_day (); 318 319 event.if_elapsed_delay_to_next_year (this.simulated_dt); 320 321 if (data.p.index (2) != null) { 322 int year = int.parse (data.p.index (2)); 323 event.from_set_year (year); 324 } 325 }); 326 327 analyze_pattern (@"on (?<p1>$months_regex)(,)? (?<p2>\\d{1,2})(st|nd|rd|th)?( (?<p3>\\d{2,4}))?", (data) => { 328 int day = int.parse (data.p.index (1)); 329 int month = get_number_of_month (data.p.index (0)); 330 331 event.date_parsed = true; 332 event.from_set_day (day); 333 event.from_set_month (month); 334 event.set_one_entire_day (); 335 336 if (data.p.index (2) != null) { 337 int year = int.parse (data.p.index (2)); 338 event.from_set_year (year); 339 } 340 341 event.if_elapsed_delay_to_next_year (this.simulated_dt); 342 }); 343 344 analyze_pattern (@"from (?<p1>\\d{1,2})(.)? to (?<p2>\\d{1,2}). ((?<p3>$months_regex))?", (data) => { 345 int day_1 = int.parse (data.p.index (0)); 346 int day_2 = int.parse (data.p.index (1)); 347 348 event.date_parsed = true; 349 event.from_set_day (day_1); 350 event.to_set_day (day_2); 351 event.set_all_day (); 352 353 if (data.p.index (2) != null) { 354 int month = get_number_of_month (data.p.index (2)); 355 event.from_set_month (month); 356 event.to_set_month (month); 357 358 event.if_elapsed_delay_to_next_year (this.simulated_dt); 359 } 360 361 event.if_elapsed_delay_to_next_month (this.simulated_dt); 362 }); 363 364 analyze_pattern ("from (?<p1>\\d{1,2})/(?<p2>\\d{1,2}) - ((?<p3>\\d{1,2})/)?(?<p4>\\d{1,2})", (data) => { 365 int day_1 = int.parse (data.p.index (1)); 366 int day_2 = int.parse (data.p.index (3)); 367 int month_1 = int.parse (data.p.index (0)); 368 int month_2 = int.parse (data.p.index (2)); 369 370 if (month_2 == 0) { 371 month_2 = month_1; 372 } 373 374 event.date_parsed = true; 375 event.from_set_day (day_1); 376 event.to_set_day (day_2); 377 event.from_set_month (month_1); 378 event.to_set_month (month_2); 379 event.set_all_day (); 380 381 event.if_elapsed_delay_to_next_year (this.simulated_dt); 382 }); 383 384 analyze_pattern (@"from (?<p1>$months_regex) (?<p2>\\d{1,2})(st|nd|rd|th)? - (?<p3>\\d{1,2})(st|nd|rd|th)?", (data) => { 385 int day_1 = int.parse (data.p.index (1)); 386 int day_2 = int.parse (data.p.index (2)); 387 int month_1 = get_number_of_month (data.p.index (0)); 388 389 event.date_parsed = true; 390 event.from_set_day (day_1); 391 event.to_set_day (day_2); 392 event.from_set_month (month_1); 393 event.to_set_month (month_1); 394 395 event.set_all_day (); 396 397 event.if_elapsed_delay_to_next_year (this.simulated_dt); 398 }); 399 400 analyze_pattern ("in a month", (data) => { 401 event.date_parsed = true; 402 event.from = event.from.add_months (1); 403 event.set_one_entire_day (); 404 }); 405 406 analyze_pattern ("christmas eve", (data) => { 407 event.date_parsed = true; 408 event.from_set_month (12); 409 event.from_set_day (24); 410 event.set_one_entire_day (); 411 412 event.if_elapsed_delay_to_next_year (this.simulated_dt); 413 }); 414 415 // --- Time --- 416 417 analyze_pattern ("breakfast", (data) => { 418 event.time_parsed = true; 419 event.from_set_hour (8); 420 event.set_length_to_hours (1); 421 event.all_day = false; 422 }, false); 423 424 analyze_pattern ("lunch", (data) => { 425 event.time_parsed = true; 426 event.from_set_hour (13); 427 event.set_length_to_hours (1); 428 event.all_day = false; 429 }, false); 430 431 analyze_pattern ("dinner", (data) => { 432 event.time_parsed = true; 433 event.from_set_hour (19); 434 event.set_length_to_hours (1); 435 event.all_day = false; 436 }, false); 437 438 analyze_pattern ("early", (data) => { 439 event.time_parsed = true; 440 event.from_set_hour (9); 441 event.set_length_to_hours (1); 442 event.all_day = false; 443 }); 444 445 analyze_pattern ("(this )?morning", (data) => { 446 event.time_parsed = true; 447 event.from_set_hour (11); 448 event.set_length_to_hours (1); 449 event.all_day = false; 450 }); 451 452 analyze_pattern ("(at |this )?noon", (data) => { 453 event.time_parsed = true; 454 event.from_set_hour (12); 455 event.set_length_to_hours (1); 456 event.all_day = false; 457 }); 458 459 analyze_pattern ("(this )?afternoon", (data) => { 460 event.time_parsed = true; 461 event.from_set_hour (15); 462 event.set_length_to_hours (1); 463 event.all_day = false; 464 }); 465 466 analyze_pattern ("(this )?evening", (data) => { 467 event.time_parsed = true; 468 event.from_set_hour (18); 469 event.set_length_to_hours (1); 470 event.all_day = false; 471 }); 472 473 analyze_pattern ("late", (data) => { 474 event.time_parsed = true; 475 event.from_set_hour (19); 476 event.set_length_to_hours (3); 477 event.all_day = false; 478 }); 479 480 analyze_pattern ("(at |@ ?)(?<p1>\\d{1,2})(:(?<p2>\\d{1,2}))?(?<p3>(am|pm|p))?", (data) => { 481 int hour = int.parse (data.p.index (0)); 482 483 if (data.p.index (1) != null) { 484 int minute_1 = int.parse (data.p.index (1)); 485 event.from_set_minute (minute_1); 486 } 487 488 string half = ""; 489 if (data.p.index (2) != null) { 490 half = data.p.index (2); 491 } 492 493 event.time_parsed = true; 494 event.from_set_hour (hour, half); 495 496 event.set_length_to_hours (1); 497 event.all_day = false; 498 }); 499 500 analyze_pattern ("(at |@)(?<p1>\\d{4})", (data) => { 501 int hour = int.parse (data.p.index (0)); 502 503 if (data.p.index (1) != null) { 504 int minute_1 = int.parse (data.p.index (1)); 505 event.from_set_minute (minute_1); 506 } 507 508 string half = ""; 509 if (data.p.index (2) != null) { 510 half = data.p.index (2); 511 } 512 513 event.time_parsed = true; 514 event.from_set_hour (hour, half); 515 516 event.set_length_to_hours (1); 517 event.all_day = false; 518 }); 519 520 analyze_pattern ("(from )?(?<p1>\\d{1,2})(:(?<p3>\\d{1,2}))?(?<p5>(am|pm|p)?)( to |-| - )(?<p2>\\d{1,2})(:(?<p4>\\d{1,2}))?(?<p6>(am|pm|p)?)", (data) => { 521 int hour_1 = int.parse (data.p.index (0)); 522 int hour_2 = int.parse (data.p.index (1)); 523 524 string half_1 = ""; 525 if (data.p.index (4) != null) { 526 half_1 = data.p.index (4); 527 } 528 529 event.from_set_hour (hour_1, half_1); 530 531 string half_2 = ""; 532 if (data.p.index (5) != null) { 533 half_2 = data.p.index (5); 534 } 535 536 event.to_set_hour (hour_2, half_2); 537 538 if (data.p.index (2) != null) { 539 int minute_1 = int.parse (data.p.index (2)); 540 event.from_set_minute (minute_1); 541 } 542 543 if (data.p.index (3) != null) { 544 int minute_2 = int.parse (data.p.index (3)); 545 event.to_set_minute (minute_2); 546 } 547 548 event.time_parsed = true; 549 event.all_day = false; 550 }); 551 552 analyze_pattern ("(?<p1>\\d{1,2})(:(?<p2>\\d{1,2}))? (o'clock|h)", (data) => { 553 int hour = int.parse (data.p.index (0)); 554 event.from_set_hour (hour); 555 556 if (data.p.index (1) != null) { 557 int minute_1 = int.parse (data.p.index (1)); 558 event.from_set_minute (minute_1); 559 } 560 561 event.time_parsed = true; 562 event.set_length_to_hours (1); 563 event.all_day = false; 564 }); 565 566 analyze_pattern ("for (?<p1>\\d+)(\\s?min| minutes)", (data) => { 567 int minutes = int.parse (data.p.index (0)); 568 569 event.date_parsed = true; 570 event.time_parsed = true; 571 event.all_day = false; 572 event.set_length_to_minutes (minutes); 573 }); 574 575 analyze_pattern ("for (?<p1>\\d+)(\\s?h| hours)", (data) => { 576 int hours = int.parse (data.p.index (0)); 577 578 event.date_parsed = true; 579 event.time_parsed = true; 580 event.all_day = false; 581 event.set_length_to_hours (hours); 582 }); 583 584 analyze_pattern ("for (?<p1>\\d+)(\\s?d| days)", (data) => { 585 int days = int.parse (data.p.index (0)); 586 event.date_parsed = true; 587 event.set_length_to_days (days); 588 }); 589 590 analyze_pattern ("for (?<p1>\\d+) weeks", (data) => { 591 int weeks = int.parse (data.p.index (0)); 592 event.date_parsed = true; 593 event.set_length_to_weeks (weeks); 594 }); 595 596 // --- Repetition --- 597 598 // --- Persons --- 599 analyze_pattern ("(with)( the)? (?<p1>(\\w\\s?)+)", (data) => { 600 for (int i = 0; i < data.p.length ; i++) 601 event.participants += data.p.index (i); 602 }); 603 604 // --- Location ---- 605 analyze_pattern ("(at|in)(the)? (?<p1>(\\w\\s?)+)", (data) => { 606 event.location = data.p.index (0); 607 }); 608 609 // --- Name --- 610 event.title = this.remaining_source.strip (); 611 // event.title = event.title.strip(); 612 // Strip ,.: from title 613 614 return event; 615 } 616 617 public string get_language () { 618 return EventParserHandler.FALLBACK_LANG; 619 } 620} 621 622} 623