RaUI/Source/ryControls/HtmlRenderer/Core/Utils/CommonUtils.cs

493 lines
19 KiB
C#
Raw Normal View History

// "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.Globalization;
using System.IO;
using System.Net;
using System.Text;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
namespace TheArtOfDev.HtmlRenderer.Core.Utils
{
internal delegate void ActionInt<in T>(T obj);
internal delegate void ActionInt<in T1, in T2>(T1 arg1, T2 arg2);
internal delegate void ActionInt<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
/// <summary>
/// Utility methods for general stuff.
/// </summary>
internal static class CommonUtils
{
#region Fields and Consts
/// <summary>
/// Table to convert numbers into roman digits
/// </summary>
private static readonly string[,] _romanDigitsTable = new[,]
{
{ "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" },
{ "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" },
{ "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" },
{
"", "M", "MM", "MMM", "M(V)", "(V)", "(V)M",
"(V)MM", "(V)MMM", "M(X)"
}
};
private static readonly string[,] _hebrewDigitsTable = new[,]
{
{ "א", "ב", "ג", "ד", "ה", "ו", "ז", "ח", "ט" },
{ "י", "כ", "ל", "מ", "נ", "ס", "ע", "פ", "צ" },
{ "ק", "ר", "ש", "ת", "תק", "תר", "תש", "תת", "תתק", }
};
private static readonly string[,] _georgianDigitsTable = new[,]
{
{ "ა", "ბ", "გ", "დ", "ე", "ვ", "ზ", "ჱ", "თ" },
{ "ი", "პ", "ლ", "მ", "ნ", "ჲ", "ო", "პ", "ჟ" },
{ "რ", "ს", "ტ", "ჳ", "ფ", "ქ", "ღ", "", "შ" }
};
private static readonly string[,] _armenianDigitsTable = new[,]
{
{ "Ա", "Բ", "Գ", "Դ", "Ե", "Զ", "Է", "Ը", "Թ" },
{ "Ժ", "Ի", "Լ", "Խ", "Ծ", "Կ", "Հ", "Ձ", "Ղ" },
{ "Ճ", "Մ", "Յ", "Ն", "Շ", "Ո", "Չ", "Պ", "Ջ" }
};
private static readonly string[] _hiraganaDigitsTable = new[]
{
"あ", "ぃ", "ぅ", "ぇ", "ぉ", "か", "き", "く", "け", "こ", "さ", "し", "す", "せ", "そ", "た", "ち", "つ", "て", "と", "な", "に", "ぬ", "ね", "の", "は", "ひ", "ふ", "へ", "ほ", "ま", "み", "む", "め", "も", "ゃ", "ゅ", "ょ", "ら", "り", "る", "れ", "ろ", "ゎ", "ゐ", "ゑ", "を", "ん"
};
private static readonly string[] _satakanaDigitsTable = new[]
{
"ア", "イ", "ウ", "エ", "オ", "カ", "キ", "ク", "ケ", "コ", "サ", "シ", "ス", "セ", "ソ", "タ", "チ", "ツ", "テ", "ト", "ナ", "ニ", "ヌ", "ネ", "", "ハ", "ヒ", "フ", "ヘ", "ホ", "マ", "ミ", "ム", "メ", "モ", "ヤ", "ユ", "ヨ", "ラ", "リ", "ル", "レ", "ロ", "ワ", "ヰ", "ヱ", "ヲ", "ン"
};
#endregion
/// <summary>
/// Check if the given char is of Asian range.
/// </summary>
/// <param name="ch">the character to check</param>
/// <returns>true - Asian char, false - otherwise</returns>
public static bool IsAsianCharecter(char ch)
{
return ch >= 0x4e00 && ch <= 0xFA2D;
}
/// <summary>
/// Check if the given char is a digit character (0-9) and (0-9, a-f for HEX)
/// </summary>
/// <param name="ch">the character to check</param>
/// <param name="hex">optional: is hex digit check</param>
/// <returns>true - is digit, false - not a digit</returns>
public static bool IsDigit(char ch, bool hex = false)
{
return (ch >= '0' && ch <= '9') || (hex && ((ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')));
}
/// <summary>
/// Convert the given char to digit.
/// </summary>
/// <param name="ch">the character to check</param>
/// <param name="hex">optional: is hex digit check</param>
/// <returns>true - is digit, false - not a digit</returns>
public static int ToDigit(char ch, bool hex = false)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
else if (hex)
{
if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
else if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
}
return 0;
}
/// <summary>
/// Get size that is max of <paramref name="size"/> and <paramref name="other"/> for width and height separately.
/// </summary>
public static RSize Max(RSize size, RSize other)
{
return new RSize(Math.Max(size.Width, other.Width), Math.Max(size.Height, other.Height));
}
/// <summary>
/// Get Uri object for the given path if it is valid uri path.
/// </summary>
/// <param name="path">the path to get uri for</param>
/// <returns>uri or null if not valid</returns>
public static Uri TryGetUri(string path)
{
try
{
if (Uri.IsWellFormedUriString(path, UriKind.RelativeOrAbsolute))
{
return new Uri(path);
}
}
catch
{ }
return null;
}
/// <summary>
/// Get the first value in the given dictionary.
/// </summary>
/// <typeparam name="TKey">the type of dictionary key</typeparam>
/// <typeparam name="TValue">the type of dictionary value</typeparam>
/// <param name="dic">the dictionary</param>
/// <param name="defaultValue">optional: the default value to return of no elements found in dictionary </param>
/// <returns>first element or default value</returns>
public static TValue GetFirstValueOrDefault<TKey, TValue>(IDictionary<TKey, TValue> dic, TValue defaultValue = default(TValue))
{
if (dic != null)
{
foreach (var value in dic)
return value.Value;
}
return defaultValue;
}
/// <summary>
/// Get file info object for the given path if it is valid file path.
/// </summary>
/// <param name="path">the path to get file info for</param>
/// <returns>file info or null if not valid</returns>
public static FileInfo TryGetFileInfo(string path)
{
try
{
return new FileInfo(path);
}
catch
{ }
return null;
}
/// <summary>
/// Get web client response content type.
/// </summary>
/// <param name="client">the web client to get the response content type from</param>
/// <returns>response content type or null</returns>
public static string GetResponseContentType(WebClient client)
{
foreach (string header in client.ResponseHeaders)
{
if (header.Equals("Content-Type", StringComparison.InvariantCultureIgnoreCase))
return client.ResponseHeaders[header];
}
return null;
}
/// <summary>
/// Gets the representation of the online uri on the local disk.
/// </summary>
/// <param name="imageUri">The online image uri.</param>
/// <returns>The path of the file on the disk.</returns>
public static FileInfo GetLocalfileName(Uri imageUri)
{
StringBuilder fileNameBuilder = new StringBuilder();
string absoluteUri = imageUri.AbsoluteUri;
int lastSlash = absoluteUri.LastIndexOf('/');
if (lastSlash == -1)
{
return null;
}
string uriUntilSlash = absoluteUri.Substring(0, lastSlash);
fileNameBuilder.Append(uriUntilSlash.GetHashCode().ToString());
fileNameBuilder.Append('_');
string restOfUri = absoluteUri.Substring(lastSlash + 1);
int indexOfParams = restOfUri.IndexOf('?');
if (indexOfParams == -1)
{
string ext = ".cache";
int indexOfDot = restOfUri.IndexOf('.');
if (indexOfDot > -1)
{
ext = restOfUri.Substring(indexOfDot);
restOfUri = restOfUri.Substring(0, indexOfDot);
}
fileNameBuilder.Append(restOfUri);
fileNameBuilder.Append(ext);
}
else
{
int indexOfDot = restOfUri.IndexOf('.');
if (indexOfDot == -1 || indexOfDot > indexOfParams)
{
//The uri is not for a filename
fileNameBuilder.Append(restOfUri);
fileNameBuilder.Append(".cache");
}
else if (indexOfParams > indexOfDot)
{
//Adds the filename without extension.
fileNameBuilder.Append(restOfUri, 0, indexOfDot);
//Adds the parameters
fileNameBuilder.Append(restOfUri, indexOfParams, restOfUri.Length - indexOfParams);
//Adds the filename extension.
fileNameBuilder.Append(restOfUri, indexOfDot, indexOfParams - indexOfDot);
}
}
var validFileName = GetValidFileName(fileNameBuilder.ToString());
if (validFileName.Length > 25)
{
validFileName = validFileName.Substring(0, 24) + validFileName.Substring(24).GetHashCode() + Path.GetExtension(validFileName);
}
return new FileInfo(Path.Combine(Path.GetTempPath(), validFileName));
}
/// <summary>
/// Get substring seperated by whitespace starting from the given idex.
/// </summary>
/// <param name="str">the string to get substring in</param>
/// <param name="idx">the index to start substring search from</param>
/// <param name="length">return the length of the found string</param>
/// <returns>the index of the substring, -1 if no valid sub-string found</returns>
public static int GetNextSubString(string str, int idx, out int length)
{
while (idx < str.Length && Char.IsWhiteSpace(str[idx]))
idx++;
if (idx < str.Length)
{
var endIdx = idx + 1;
while (endIdx < str.Length && !Char.IsWhiteSpace(str[endIdx]))
endIdx++;
length = endIdx - idx;
return idx;
}
length = 0;
return -1;
}
/// <summary>
/// Compare that the substring of <paramref name="str"/> is equal to <paramref name="str2"/>
/// Assume given substring is not empty and all indexes are valid!<br/>
/// </summary>
/// <returns>true - equals, false - not equals</returns>
public static bool SubStringEquals(string str, int idx, int length, string str2)
{
if (length == str2.Length && idx + length <= str.Length)
{
for (int i = 0; i < length; i++)
{
if (Char.ToLowerInvariant(str[idx + i]) != Char.ToLowerInvariant(str2[i]))
return false;
}
return true;
}
return false;
}
/// <summary>
/// Replaces invalid filename chars to '_'
/// </summary>
/// <param name="source">The possibly-not-valid filename</param>
/// <returns>A valid filename.</returns>
private static string GetValidFileName(string source)
{
string retVal = source;
char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
foreach (var invalidFileNameChar in invalidFileNameChars)
{
retVal = retVal.Replace(invalidFileNameChar, '_');
}
return retVal;
}
/// <summary>
/// Convert number to alpha numeric system by the requested style (UpperAlpha, LowerRoman, Hebrew, etc.).
/// </summary>
/// <param name="number">the number to convert</param>
/// <param name="style">the css style to convert by</param>
/// <returns>converted string</returns>
public static string ConvertToAlphaNumber(int number, string style = CssConstants.UpperAlpha)
{
if (number == 0)
return string.Empty;
if (style.Equals(CssConstants.LowerGreek, StringComparison.InvariantCultureIgnoreCase))
{
return ConvertToGreekNumber(number);
}
else if (style.Equals(CssConstants.LowerRoman, StringComparison.InvariantCultureIgnoreCase))
{
return ConvertToRomanNumbers(number, true);
}
else if (style.Equals(CssConstants.UpperRoman, StringComparison.InvariantCultureIgnoreCase))
{
return ConvertToRomanNumbers(number, false);
}
else if (style.Equals(CssConstants.Armenian, StringComparison.InvariantCultureIgnoreCase))
{
return ConvertToSpecificNumbers(number, _armenianDigitsTable);
}
else if (style.Equals(CssConstants.Georgian, StringComparison.InvariantCultureIgnoreCase))
{
return ConvertToSpecificNumbers(number, _georgianDigitsTable);
}
else if (style.Equals(CssConstants.Hebrew, StringComparison.InvariantCultureIgnoreCase))
{
return ConvertToSpecificNumbers(number, _hebrewDigitsTable);
}
else if (style.Equals(CssConstants.Hiragana, StringComparison.InvariantCultureIgnoreCase) || style.Equals(CssConstants.HiraganaIroha, StringComparison.InvariantCultureIgnoreCase))
{
return ConvertToSpecificNumbers2(number, _hiraganaDigitsTable);
}
else if (style.Equals(CssConstants.Katakana, StringComparison.InvariantCultureIgnoreCase) || style.Equals(CssConstants.KatakanaIroha, StringComparison.InvariantCultureIgnoreCase))
{
return ConvertToSpecificNumbers2(number, _satakanaDigitsTable);
}
else
{
var lowercase = style.Equals(CssConstants.LowerAlpha, StringComparison.InvariantCultureIgnoreCase) || style.Equals(CssConstants.LowerLatin, StringComparison.InvariantCultureIgnoreCase);
return ConvertToEnglishNumber(number, lowercase);
}
}
/// <summary>
/// Convert the given integer into alphabetic numeric format (D, AU, etc.)
/// </summary>
/// <param name="number">the number to convert</param>
/// <param name="lowercase">is to use lowercase</param>
/// <returns>the roman number string</returns>
private static string ConvertToEnglishNumber(int number, bool lowercase)
{
var sb = string.Empty;
int alphStart = lowercase ? 97 : 65;
while (number > 0)
{
var n = number % 26 - 1;
if (n >= 0)
{
sb = (Char)(alphStart + n) + sb;
number = number / 26;
}
else
{
sb = (Char)(alphStart + 25) + sb;
number = (number - 1) / 26;
}
}
return sb;
}
/// <summary>
/// Convert the given integer into alphabetic numeric format (alpha, AU, etc.)
/// </summary>
/// <param name="number">the number to convert</param>
/// <returns>the roman number string</returns>
private static string ConvertToGreekNumber(int number)
{
var sb = string.Empty;
while (number > 0)
{
var n = number % 24 - 1;
if (n > 16)
n++;
if (n >= 0)
{
sb = (Char)(945 + n) + sb;
number = number / 24;
}
else
{
sb = (Char)(945 + 24) + sb;
number = (number - 1) / 25;
}
}
return sb;
}
/// <summary>
/// Convert the given integer into roman numeric format (II, VI, IX, etc.)
/// </summary>
/// <param name="number">the number to convert</param>
/// <param name="lowercase">if to use lowercase letters for roman digits</param>
/// <returns>the roman number string</returns>
private static string ConvertToRomanNumbers(int number, bool lowercase)
{
var sb = string.Empty;
for (int i = 1000, j = 3; i > 0; i /= 10, j--)
{
int digit = number / i;
sb += string.Format(_romanDigitsTable[j, digit]);
number -= digit * i;
}
return lowercase ? sb.ToLower() : sb;
}
/// <summary>
/// Convert the given integer into given alphabet numeric system.
/// </summary>
/// <param name="number">the number to convert</param>
/// <param name="alphabet">the alphabet system to use</param>
/// <returns>the number string</returns>
private static string ConvertToSpecificNumbers(int number, string[,] alphabet)
{
int level = 0;
var sb = string.Empty;
while (number > 0 && level < alphabet.GetLength(0))
{
var n = number % 10;
if (n > 0)
sb = alphabet[level, number % 10 - 1].ToString(CultureInfo.InvariantCulture) + sb;
number /= 10;
level++;
}
return sb;
}
/// <summary>
/// Convert the given integer into given alphabet numeric system.
/// </summary>
/// <param name="number">the number to convert</param>
/// <param name="alphabet">the alphabet system to use</param>
/// <returns>the number string</returns>
private static string ConvertToSpecificNumbers2(int number, string[] alphabet)
{
for (int i = 20; i > 0; i--)
{
if (number > 49 * i - i + 1)
number++;
}
var sb = string.Empty;
while (number > 0)
{
sb = alphabet[Math.Max(0, number % 49 - 1)].ToString(CultureInfo.InvariantCulture) + sb;
number /= 49;
}
return sb;
}
}
}