524 lines
14 KiB
C#
524 lines
14 KiB
C#
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.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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
}
|
|
} |