📜 ⬆️ ⬇️

Why do routine work when it can be charged to a car?

Once again, revising the "Iron Man" with a friend, I was again saturated with the desire to become a superhero to create my own iron suit, or at least Jarvis. And suddenly I was visited by a brilliant idea.

Lyrical digression
A couple of days ago there was the European Cup 2015 European Cup match, which I turned on only for 70 minutes. Yes, I watched the rest of the game, but still there was an unpleasant aftermath because I could see 70 minutes of excellent football only in the record (which, as you understand, is not at all), because I somehow forgot the fact that in summer there are official matches too. Cry and that's enough.

So, recently I started actively using the system calendar on a Mac, and I thought, why not add all Barca games to this very calendar?




Terminal mac


So, you have to start somewhere. Yeah, you need to understand how to create events in the Calendar using code, i.e. we need Mac Calendar API. First I got into the terminal, and typing
Mac:~ dima$ whatis calendar 

we get several commands:
 Mac:~ dima$ whatis calendar cal(1), ncal(1) - displays a calendar and the date of easter calendar(1) - reminder service iwidgets_calendar(n), iwidgets::calendar(n) - Create and manipulate a monthly calendar widget_calendar(n) - widget::calendar Megawidget 

All these commands are relatively similar and have approximately the same functionality, displaying a calendar in various ways, including as a widget. But this is not what we need, because we want to create a separate calendar with events.
')
A little googling, I went out to the Mac script language - AppleScript , which contains some no API of all embedded applications in the format of so-called "dictionaries" (it is quite possible that Google was too clumsy, so I did not find anything better than AppleScript ).

Little appleScript


Having opened the script editor and studied the built-in “dictionary” of commands a bit, occasionally offering sacrifices to the search deities using Google, I realized that it was quite possible to create an event (I really had to “wake up” with this language, especially with dates):
 set startDate to date ", 25  2015 ., 0:00:00" set endDate to date ", 25  2015 ., 10:00:00" tell application "Calendar" tell calendar ", " make new event with properties {description:"Test", summary:"Something", start date:startDate, allday event:true} end tell end tell 

Ok, let's fix the success, have a snack and go on.

Let's parse!


Open Xamarin Studio , install the NuGet HtmlAgillityPack package using the built-in context menu, and check if it appeared in Packages.








Do not forget to connect the installed package with the using directive and begin to write code. Let's start with the class SingleMatch , in which we will keep all that we naparsim. Create auto-implementable properties, since we do not need here the execution logic, two constructors (with parameters and without) and override the standard for all classes method ToString () .

SingleMatch.cs
 using System; namespace barca_matches_to_the_calendar { /// <summary> ///   . /// </summary> public class SingleMatch { /// <summary> ///    . /// </summary> /// <value>    <see cref="DateTime"/>.</value> public DateTime StartTime { get; set; } /// <summary> ///  . /// </summary> /// <value> .</value> public string Tournament { get; set; } /// <summary> ///  -. /// </summary> /// <value>  - .</value> public string Rival { get; set; } /// <summary> ///   ( /). /// </summary> /// <value> (" "/"").</value> public string Place { get; set; } /// <summary> ///    SingleMatch /// <see cref="barca_matches_to_the_calendar.SingleMatch"/> class. /// </summary> public SingleMatch() { StartTime = new DateTime(); Tournament = null; Rival = null; Place = null; } /// <summary> ///    SingleMatch /// <see cref="barca_matches_to_the_calendar.SingleMatch"/> class. /// </summary> /// <param name="startTime">  .</param> /// <param name="tournament">.</param> /// <param name="rival">.</param> /// <param name="place"> ( /).</param> public SingleMatch(DateTime startTime, string tournament, string rival, string place) { StartTime = startTime; Tournament = tournament; Rival = rival; Place = place; } /// <summary> ///  a <see cref="System.String"/>,    ///  <see cref="barca_matches_to_the_calendar.SingleMatch"/>. /// </summary> /// <returns> ///  a <see cref="System.String"/>,    ///  <see cref="barca_matches_to_the_calendar.SingleMatch"/>. /// </returns> public override string ToString() { return string.Format("  ={0}, ={1}, ={2}, ={3}", StartTime, Tournament, Rival, Place); } } } 


Since we have not one match, but several, we will keep them in the list. But since we save the calendar of one selected FC, we will move its name to a separate property and create another class to store all matches.

Matches.cs
 using System.Collections.Generic; namespace barca_matches_to_the_calendar { public class Matches { /// <summary> ///  . /// </summary> /// <value> .</value> public string NameFC { get; set; } /// <summary> ///    /// </summary> /// <value> </value> public List<SingleMatch> ListMatches { get; set; } /// <summary> /// Initializes a new instance of the <see cref="barca_matches_to_the_calendar.Matches"/> class. /// </summary> public Matches() { ListMatches = new List<SingleMatch>(); NameFC = null; } /// <summary> /// Initializes a new instance of the <see cref="barca_matches_to_the_calendar.Matches"/> class. /// </summary> /// <param name="nameFC"> .</param> public Matches(string nameFC) { ListMatches = new List<SingleMatch>(); NameFC = nameFC; } /// <summary> ///  a <see cref="System.String"/>,    ///  <see cref="barca_matches_to_the_calendar.Matches"/>. /// </summary> /// <returns> ///  a <see cref="System.String"/>,    ///  <see cref="barca_matches_to_the_calendar.Matches"/>. /// </returns> public override string ToString() { return string.Format("  \"{0}\",  {1}", NameFC, ListMatches); } } } 


Well, actually the main class is Program.cs , in which we parse the specified site and save everything to a text file, from which we transfer all events to the calendar using AppleScript .

Program.cs
 using System; using HtmlAgilityPack; using System.Collections.Generic; using System.Linq; namespace barca_matches_to_the_calendar { class MainClass { public static void Main(string[] args) { //  ,    . string WebAddress = @"http://www.sports.ru/barcelona/calendar/"; //    -  - HtmlWeb WebGet = new HtmlWeb(); //  html-   . HtmlDocument htmlDoc = WebGet.Load(WebAddress); //     Matches MatchesFC = new Matches(); //    (   ) MatchesFC.NameFC = htmlDoc.DocumentNode. SelectSingleNode(".//*[@class='titleh2']"). FirstChild.InnerText.Replace("\r\n", ""); //           XPath-. HtmlNode Table = htmlDoc.DocumentNode.SelectSingleNode(".//*[@class='stat-table']/tbody"); //      -   "tr". IEnumerable<HtmlNode> rows = Table.Descendants().Where(x => x.Name == "tr"); foreach (var row in rows) { //      . HtmlNodeCollection cells = row.ChildNodes; //    SingleMatch,      . SingleMatch match = new SingleMatch(); //  ,       "|", //    TryParse    . DateTime time; DateTime.TryParse(cells[1].InnerText.Replace("|", " "), out time); match.StartTime = time; //    ,    . match.Tournament = cells[3].InnerText; //   ""      (" ") match.Rival = cells[5].InnerText.Replace(" ", ""); match.Place = cells[6].InnerText; //      . MatchesFC.ListMatches.Add(match); } //   . foreach (SingleMatch match in MatchesFC) { //             // (    ) string path = @"matches.txt"; //   using (StreamWriter file = new StreamWriter(path, true)) { file.WriteLine(match); } } Console.WriteLine("     !"); } } } 


Okay, the site is parsed, all matches are in a file in a convenient-readable format, back to AppleScript .

(Somewhere here I wondered if there was a button for importing the entire calendar somewhere on the site or in the application. I downloaded the site application on the phone and found there an addition of a reminder about one match, but not all of them at once. Yes even if I found the import of the entire calendar of games at once, it is unlikely to stop, because this is not the way of the Jedi! ).

New - well forgotten old


Using Finder, open the file, parse it and pass it to the event creation function.
What came out of it
 tell application "Finder" -- ,           (..    ) set Matches to paragraphs of (read (choose file with prompt " ,     ")) repeat with Match in Matches if length of Match is greater than 0 then --     '=', ','     '\n' set AppleScript's text item delimiters to {"=", ",", ASCII character 13} --      my CreateEvent(text item 2 of Match, text item 4 of Match, text item 6 of Match, text item 8 of Match, text item 10 of Match) end if end repeat end tell on CreateEvent(textDate, tournament, fc, rival, place) --       set startDate to the current date set the day of startDate to (text 1 thru 2 of textDate) set the month of startDate to (text 4 thru 5 of textDate) set the year of startDate to (text 7 thru 10 of textDate) set the hours of startDate to (text 12 thru 13 of textDate) set the minutes of startDate to (text 15 thru 16 of textDate) set the seconds of startDate to (text 18 thru 19 of textDate) --        set endDate to (startDate + 2 * hours) tell application "Calendar" create calendar with name "" tell calendar "" --     make new event with properties {description:"" & fc & " : " & rival & ". " & place, summary:"", start date:startDate, end date:endDate} end tell end tell end CreateEvent 


But somewhere in the middle of writing this script, I suddenly discovered (at this point, readers who looked at all these difficulties with the question “Why is it so difficult?” Sighed with relief) that there is such a calendar format - " .ics" , which swallows both Mac Calendar , and Google Calendar , and even Outlook , and, therefore, surely there is some C # library that allows you to save events to this very ".ics" .

Just a little bit and our crutch bike is ready


By installing and connecting the DDay.iCal library, we will slightly change our Program.cs class:
Program.cs
 using System; using HtmlAgilityPack; using System.Collections.Generic; using System.Linq; using DDay.iCal; using DDay.iCal.Serialization.iCalendar; using System.Security.Cryptography; namespace barca_matches_to_the_calendar { class MainClass { public static void Main(string[] args) { //  ,    . string WebAddress = @"http://www.sports.ru/barcelona/calendar/"; //    -  - HtmlWeb WebGet = new HtmlWeb(); //  html-   . HtmlDocument htmlDoc = WebGet.Load(WebAddress); //     Matches MatchesFC = new Matches(); //    (   ) MatchesFC.NameFC = htmlDoc.DocumentNode. SelectSingleNode(".//*[@class='titleH1']"). FirstChild.InnerText.Replace("\r\n", ""); //           XPath-. HtmlNode Table = htmlDoc.DocumentNode.SelectSingleNode(".//*[@class='stat-table']/tbody"); //      -   "tr". IEnumerable<HtmlNode> rows = Table.Descendants().Where(x => x.Name == "tr"); foreach (var row in rows) { //      . HtmlNodeCollection cells = row.ChildNodes; //    SingleMatch,      . SingleMatch match = new SingleMatch(); //  ,       "|", //    TryParse    . DateTime time; DateTime.TryParse(cells[1].InnerText.Replace("|", " "), out time); match.StartTime = time; //    ,    . match.Tournament = cells[3].InnerText; //   ""      (" ") match.Rival = cells[5].InnerText.Replace(" ", ""); match.Place = cells[6].InnerText; //      . MatchesFC.ListMatches.Add(match); } //  ,     . iCalendar CalForMatches = new iCalendar { Method = "PUBLISH", Version = "2.0" }; //      Mac,      //   (..   Mac Calendar) CalForMatches.AddProperty("CALSCALE", "GREGORIAN"); CalForMatches.AddProperty("X-WR-CALNAME", "M  " + MatchesFC.NameFC); CalForMatches.AddProperty("X-WR-TIMEZONE", "Europe/Moscow"); CalForMatches.AddLocalTimeZone(); //   . foreach (SingleMatch match in MatchesFC.ListMatches) { Event newMatch = CalForMatches.Create<Event>(); newMatch.DTStart = new iCalDateTime(match.StartTime); newMatch.Duration = new TimeSpan(2, 30, 0); newMatch.Summary = string.Format("{0} : {1}", MatchesFC.NameFC, match.Rival); newMatch.Description = string.Format("{0}. {1} : {2}, {3}", match.Tournament, MatchesFC.NameFC, match.Rival, match.Place); //    ,      Alarm alarm = new Alarm(); alarm.Trigger = new Trigger(TimeSpan.FromMinutes(-10)); alarm.Description = "  "; alarm.AddProperty("ACTION", "DISPLAY"); newMatch.Alarms.Add(alarm); } //   . iCalendarSerializer serializer = new iCalendarSerializer(); serializer.Serialize(CalForMatches, MatchesFC.NameFC + ".ics"); Console.WriteLine("   " + Environment.NewLine); return; } } } 


Here the line deserves special attention.
 alarm.AddProperty("ACTION", "DISPLAY"); 

Originally here was
 alarm.Action = AlarmAction.Display; 
because of which all the reminders did not want to be imported into the calendar just because the word “Display” was not written in capital letters, but as an ordinary word and the poppy calendar could not recognize this monstrous cryptocode .

Feet on the pedals on crutches and go!


So, it turned out, the calendar of games is saved. Create a new iCloud calendar (so that alerts come to all devices ), cross your fingers and import the calendar file into our new iCloud calendar:






Hyp-hip-hurray, it remains to choose the appropriate color (there should be no problems here), and we received a schedule of games with reminders. I hope now I will rarely miss the game.

Sources:


githab

Resources used:


AppleScript
xpath expressions
DDay.iCal usage examples
HtmlAgillityPack usage examples

PS Many of you may ask the question: "The meaning of writing your own, if you already have one ready?"
Answer: I have never once parsed anything, but here is a convenient and interesting opportunity to try something new.

Source: https://habr.com/ru/post/265393/


All Articles