// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using TheArtOfDev.HtmlRenderer.Core.Entities;
using TheArtOfDev.HtmlRenderer.Core.Utils;
namespace TheArtOfDev.HtmlRenderer.Core.Handlers
{
///
/// Handler for loading a stylesheet data.
///
internal static class StylesheetLoadHandler
{
///
/// Load stylesheet data from the given source.
/// The source can be local file or web URI.
/// First raise event to allow the client to overwrite the stylesheet loading.
/// If the stylesheet is downloaded from URI we will try to correct local URIs to absolute.
///
/// the container of the html to handle load stylesheet for
/// the source of the element to load the stylesheet by
/// the attributes of the link element
/// return the stylesheet string that has been loaded (null if failed or is given)
/// return stylesheet data object that was provided by overwrite (null if failed or is given)
public static void LoadStylesheet(HtmlContainerInt htmlContainer, string src, Dictionary attributes, out string stylesheet, out CssData stylesheetData)
{
ArgChecker.AssertArgNotNull(htmlContainer, "htmlContainer");
stylesheet = null;
stylesheetData = null;
try
{
var args = new HtmlStylesheetLoadEventArgs(src, attributes);
htmlContainer.RaiseHtmlStylesheetLoadEvent(args);
if (!string.IsNullOrEmpty(args.SetStyleSheet))
{
stylesheet = args.SetStyleSheet;
}
else if (args.SetStyleSheetData != null)
{
stylesheetData = args.SetStyleSheetData;
}
else if (args.SetSrc != null)
{
stylesheet = LoadStylesheet(htmlContainer, args.SetSrc);
}
else
{
stylesheet = LoadStylesheet(htmlContainer, src);
}
}
catch (Exception ex)
{
htmlContainer.ReportError(HtmlRenderErrorType.CssParsing, "Exception in handling stylesheet source", ex);
}
}
#region Private methods
///
/// Load stylesheet string from given source (file path or uri).
///
/// the container of the html to handle load stylesheet for
/// the file path or uri to load the stylesheet from
/// the stylesheet string
private static string LoadStylesheet(HtmlContainerInt htmlContainer, string src)
{
var uri = CommonUtils.TryGetUri(src);
if (uri == null || uri.Scheme == "file")
{
return LoadStylesheetFromFile(htmlContainer, uri != null ? uri.AbsolutePath : src);
}
else
{
return LoadStylesheetFromUri(htmlContainer, uri);
}
}
///
/// Load the stylesheet from local file by given path.
///
/// the container of the html to handle load stylesheet for
/// the stylesheet file to load
/// the loaded stylesheet string
private static string LoadStylesheetFromFile(HtmlContainerInt htmlContainer, string path)
{
var fileInfo = CommonUtils.TryGetFileInfo(path);
if (fileInfo != null)
{
if (fileInfo.Exists)
{
using (var sr = new StreamReader(fileInfo.FullName))
{
return sr.ReadToEnd();
}
}
else
{
htmlContainer.ReportError(HtmlRenderErrorType.CssParsing, "No stylesheet found by path: " + path);
}
}
else
{
htmlContainer.ReportError(HtmlRenderErrorType.CssParsing, "Failed load image, invalid source: " + path);
}
return string.Empty;
}
///
/// Load the stylesheet from uri by downloading the string.
///
/// the container of the html to handle load stylesheet for
/// the uri to download from
/// the loaded stylesheet string
private static string LoadStylesheetFromUri(HtmlContainerInt htmlContainer, Uri uri)
{
using (var client = new WebClient())
{
var stylesheet = client.DownloadString(uri);
try
{
stylesheet = CorrectRelativeUrls(stylesheet, uri);
}
catch (Exception ex)
{
htmlContainer.ReportError(HtmlRenderErrorType.CssParsing, "Error in correcting relative URL in loaded stylesheet", ex);
}
return stylesheet;
}
}
///
/// Make relative URLs absolute in the stylesheet using the URI of the stylesheet.
///
/// the stylesheet to correct
/// the stylesheet uri to use to create absolute URLs
/// Corrected stylesheet
private static string CorrectRelativeUrls(string stylesheet, Uri baseUri)
{
int idx = 0;
while (idx != -1 && idx < stylesheet.Length)
{
idx = stylesheet.IndexOf("url", idx, StringComparison.OrdinalIgnoreCase);
if (idx > 0)
{
int endIdx = stylesheet.IndexOf(")", idx, StringComparison.OrdinalIgnoreCase);
if (endIdx > 0)
{
var offset1 = 4 + (stylesheet[idx + 4] == '\'' ? 1 : 0);
var offset2 = (stylesheet[endIdx - 1] == '\'' ? 1 : 0);
var urlStr = stylesheet.Substring(idx + offset1, endIdx - idx - offset1 - offset2);
Uri url;
if (Uri.TryCreate(urlStr, UriKind.Relative, out url))
{
url = new Uri(baseUri, url);
stylesheet = stylesheet.Remove(idx + 4, endIdx - idx - 4);
stylesheet = stylesheet.Insert(idx + 4, url.AbsoluteUri);
idx += url.AbsoluteUri.Length + 4;
}
else
{
idx = endIdx + 1;
}
}
}
}
return stylesheet;
}
#endregion
}
}