using HtmlAgilityPack; using System; using System.ComponentModel; using System.Drawing; using System.IO; using System.Net; using System.Windows.Forms; using HtmlDocument = HtmlAgilityPack.HtmlDocument; namespace bzit.bomg { public class BookmarkItem { public delegate void RetrieveCompleted(BookmarkItem sender, bool hasError, string message); public RetrieveCompleted OnRetrieveCompleted { get; set; } = null; public TreeNode TreeViewNode { get; protected set; } public byte[] IconData { get; set; } = null; public string SiteName { get; set; } = null; public string SiteAddress { get; set; } public string Description { get; set; } public string IconAddress { get; protected set; } public string Fullpath { get; set; } public string Created { get; set; } protected const char pathSeparator = '|'; //protected TreeNode treeNode = null; protected BackgroundWorker mainThread = null; protected bool hasRetrieveError = false; protected string retrieveErrorMessage = null; public BookmarkItem() { this.Clear(); } public new string ToString() { string rv = ""; rv += "Name=" + this.Fullpath + Environment.NewLine; rv += "Address=" + this.SiteAddress + Environment.NewLine; rv += "Description=" + this.Description + Environment.NewLine; rv += "Created=" + this.Created?.Trim(); return rv; } #region public properties //{ // get // { // return this.treeNode; // } // set // { // this.treeNode = value; // } //} public Bitmap Icon { get { if (this.IconData == null) { return null; } try { Image img = Image.FromStream(new MemoryStream(this.IconData)); return new Bitmap(img, 16, 16); } catch { return null; } } } #endregion #region public methods public void Clear() { this.Fullpath = string.Empty; this.SiteAddress = string.Empty; this.Description = string.Empty; this.Created = string.Empty; if (this.mainThread == null) { this.mainThread = new BackgroundWorker(); this.mainThread.WorkerReportsProgress = this.mainThread.WorkerSupportsCancellation = true; this.mainThread.DoWork += retrieveWorker_DoWork; this.mainThread.RunWorkerCompleted += retrieveWorker_RunWorkerCompleted; } } public string GetName() { if (this.Fullpath.Contains(pathSeparator.ToString())) { return decodePath(this.Fullpath.Substring(this.Fullpath.LastIndexOf(pathSeparator) + 1)).Trim(); } return decodePath(this.Fullpath).Trim(); } public void ChangeName(string newName) { string prefix = (this.Fullpath.Contains(pathSeparator.ToString()) ? this.Fullpath.Substring(0, this.Fullpath.IndexOf(pathSeparator)).Trim() : string.Empty); this.Fullpath = string.Concat(prefix, pathSeparator, encodePath(newName.Trim())); } public void GetFaviconAddress() { hasRetrieveError = false; retrieveErrorMessage = string.Empty; this.IconData = null; WebClient webClient = new WebClient(); webClient.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore); string sourceCode = string.Empty; try { sourceCode = webClient.DownloadString(this.SiteAddress); } catch (Exception exc) { hasRetrieveError = true; retrieveErrorMessage = exc.Message; return; } HtmlAgilityPack.HtmlDocument document = new HtmlAgilityPack.HtmlDocument(); document.LoadHtml(sourceCode); // favicon this.IconAddress = parseSiteIcon(document); if (!string.IsNullOrWhiteSpace(this.IconAddress)) { Uri iconAddressURI; bool rv = Uri.TryCreate(new Uri(this.SiteAddress), this.IconAddress, out iconAddressURI); if (rv) { this.IconAddress = iconAddressURI.ToString(); } } // load favicon image if (!string.IsNullOrEmpty(this.IconAddress)) { try { this.IconData = webClient.DownloadData(this.IconAddress); if (!RyzStudio.IO.FileType.IsImage(this.IconData)) { this.IconData = null; } } catch { this.IconData = null; this.IconAddress = null; } } //// parse icon //HtmlAgilityPack.HtmlNodeCollection hnc = document.DocumentNode.SelectNodes("//link"); // if (hnc != null) // { // foreach (HtmlAgilityPack.HtmlNode node in hnc) // { // if (node.Attributes["rel"] == null) // { // continue; // } // string nodeAttr = node.Attributes["rel"].Value.Trim(); // if (nodeAttr.ToLower().Contains("icon")) // { // if (!string.IsNullOrEmpty(this.IconAddress)) // { // continue; // } // if (node.Attributes["href"] != null) // { // Uri iconPath; // bool rv = Uri.TryCreate(new Uri(this.SiteAddress), WebUtility.HtmlDecode(node.Attributes["href"].Value).Trim(), out iconPath); // if (rv) // { // this.IconAddress = iconPath.ToString(); // } // break; // } // } // } // } // // default favicon // if (string.IsNullOrEmpty(this.IconAddress)) // { // Uri iconPath; // if (Uri.TryCreate(new Uri(this.SiteAddress), "/favicon.ico", out iconPath)) // { // this.IconAddress = iconPath.ToString(); // } // } // // load icon image // if (!string.IsNullOrEmpty(this.IconAddress)) // { // try // { // this.IconData = webClient.DownloadData(this.IconAddress); // if (!RyzStudio.IO.FileType.IsImage(this.IconData)) // { // this.IconData = null; // } // } // catch // { // // do nothing // } // } } public void RetrieveAsync() { if (this.mainThread.IsBusy) { return; } this.mainThread.RunWorkerAsync(); } public void RetrieveAsync(string address) { if (this.mainThread.IsBusy) { return; } this.SiteAddress = address; this.mainThread.RunWorkerAsync(); } #endregion protected void retrieveWorker_DoWork(object sender, DoWorkEventArgs e) { hasRetrieveError = false; retrieveErrorMessage = string.Empty; this.IconData = null; this.Fullpath = string.Empty; this.Description = string.Empty; this.IconAddress = string.Empty; WebClient webClient = new WebClient(); webClient.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore); string sourceCode = string.Empty; try { sourceCode = webClient.DownloadString(this.SiteAddress); } catch (Exception exc) { hasRetrieveError = true; retrieveErrorMessage = exc.Message; return; } HtmlDocument document = new HtmlDocument(); document.LoadHtml(sourceCode); // title this.SiteName = parseSiteTitle(document); // description this.Description = parseSiteDescription(document); // favicon this.IconAddress = parseSiteIcon(document); if (!string.IsNullOrWhiteSpace(this.IconAddress)) { Uri iconAddressURI; bool rv = Uri.TryCreate(new Uri(this.SiteAddress), this.IconAddress, out iconAddressURI); if (rv) { this.IconAddress = iconAddressURI.ToString(); } } // load favicon image if (!string.IsNullOrWhiteSpace(this.IconAddress)) { try { this.IconData = webClient.DownloadData(this.IconAddress); if (!RyzStudio.IO.FileType.IsImage(this.IconData)) { this.IconData = null; } } catch { this.IconData = null; this.IconAddress = null; } } } protected void retrieveWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { this.OnRetrieveCompleted?.Invoke(this, hasRetrieveError, retrieveErrorMessage); } protected string encodePath(string text) => System.Web.HttpUtility.UrlEncode(text); protected string decodePath(string text) => System.Web.HttpUtility.UrlDecode(text); protected string parseTagValue(HtmlDocument doc, string xpath, string defaultValue = "") { HtmlNodeCollection hnc = doc.DocumentNode.SelectNodes(xpath); if (hnc == null) { return defaultValue; } if (hnc.Count <= 0) { return defaultValue; } foreach (HtmlNode hn in hnc) { if (string.IsNullOrWhiteSpace(hn.InnerHtml)) { continue; } string rs = WebUtility.HtmlDecode(hn.InnerHtml)?.Replace("\r", "")?.Replace("\n", " ")?.Trim(); if (string.IsNullOrWhiteSpace(rs)) { continue; } return rs; } return defaultValue; } protected string parseTagValue_Attr(HtmlDocument doc, string xpath, string attr, string defaultValue = "") { HtmlNodeCollection hnc = doc.DocumentNode.SelectNodes(xpath); if (hnc == null) { return defaultValue; } if (hnc.Count <= 0) { return defaultValue; } foreach (HtmlNode hn in hnc) { if (hn.Attributes[attr] == null) { continue; } if (string.IsNullOrWhiteSpace(hn.Attributes[attr].Value)) { continue; } return System.Web.HttpUtility.HtmlDecode(hn.Attributes[attr].Value?.Trim()); } return defaultValue; } protected string parseSiteTitle(HtmlDocument doc) { string rs = null; if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue(doc, "//title", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@property='og:title']", "content", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@name='twitter:title']", "content", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@property='og:site_name']", "content", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@itemprop='name']", "content", string.Empty); } return rs; } protected string parseSiteDescription(HtmlDocument doc) { string rs = null; if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@name='description']", "content", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@property='og:description']", "content", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@name='twitter:description']", "content", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@property='og:description']", "content", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@itemprop='description']", "content", string.Empty); } return rs; } protected string parseSiteIcon(HtmlDocument doc) { string rs = null; if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//link[@rel='shortcut icon']", "href", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//link[@rel='icon']", "href", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//link[@rel='apple-touch-icon']", "href", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//link[@rel='apple-touch-icon-precomposed']", "href", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@property='og:image']", "content", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@name='twitter:image']", "content", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@property='og:image']", "content", string.Empty); } if (string.IsNullOrWhiteSpace(rs)) { rs = parseTagValue_Attr(doc, "//meta[@itemprop='image']", "content", string.Empty); } return rs; } } }