------ #### RaUIV4 V4.0.2311.0701 - *.[全新]整合了MyDb、ryControls、MyDb_MySQL等dll文件到RaUI一个项目。 - *.[新增]新增ApkOp类,可以轻松获取APK信息。 - *.[新增]新增JsonExt扩展类,让Json操作更简单。 - *.[新增]新增WebP类,可以支持webp格式的图片。 - *.[改进]ryQuickSQL中的AddField方法改为自动替换已存在的同名值。 - *.[修复]ryQuickSQL中的AddFieldCalc方法无法正常计算的BUG。
213 lines
8.6 KiB
C#
213 lines
8.6 KiB
C#
// "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 TheArtOfDev.HtmlRenderer.Adapters;
|
|
using TheArtOfDev.HtmlRenderer.Core.Entities;
|
|
using TheArtOfDev.HtmlRenderer.Core.Parse;
|
|
using TheArtOfDev.HtmlRenderer.Core.Utils;
|
|
|
|
namespace TheArtOfDev.HtmlRenderer.Core
|
|
{
|
|
/// <summary>
|
|
/// Holds parsed stylesheet css blocks arranged by media and classes.<br/>
|
|
/// <seealso cref="CssBlock"/>
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// To learn more about CSS blocks visit CSS spec: http://www.w3.org/TR/CSS21/syndata.html#block
|
|
/// </remarks>
|
|
public sealed class CssData
|
|
{
|
|
#region Fields and Consts
|
|
|
|
/// <summary>
|
|
/// used to return empty array
|
|
/// </summary>
|
|
private static readonly List<CssBlock> _emptyArray = new List<CssBlock>();
|
|
|
|
/// <summary>
|
|
/// dictionary of media type to dictionary of css class name to the cssBlocks collection with all the data.
|
|
/// </summary>
|
|
private readonly Dictionary<string, Dictionary<string, List<CssBlock>>> _mediaBlocks = new Dictionary<string, Dictionary<string, List<CssBlock>>>(StringComparer.InvariantCultureIgnoreCase);
|
|
|
|
#endregion
|
|
|
|
|
|
/// <summary>
|
|
/// Init.
|
|
/// </summary>
|
|
internal CssData()
|
|
{
|
|
_mediaBlocks.Add("all", new Dictionary<string, List<CssBlock>>(StringComparer.InvariantCultureIgnoreCase));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parse the given stylesheet to <see cref="CssData"/> object.<br/>
|
|
/// If <paramref name="combineWithDefault"/> is true the parsed css blocks are added to the
|
|
/// default css data (as defined by W3), merged if class name already exists. If false only the data in the given stylesheet is returned.
|
|
/// </summary>
|
|
/// <param name="adapter">Platform adapter</param>
|
|
/// <param name="stylesheet">the stylesheet source to parse</param>
|
|
/// <param name="combineWithDefault">true - combine the parsed css data with default css data, false - return only the parsed css data</param>
|
|
/// <returns>the parsed css data</returns>
|
|
public static CssData Parse(RAdapter adapter, string stylesheet, bool combineWithDefault = true)
|
|
{
|
|
CssParser parser = new CssParser(adapter);
|
|
return parser.ParseStyleSheet(stylesheet, combineWithDefault);
|
|
}
|
|
|
|
/// <summary>
|
|
/// dictionary of media type to dictionary of css class name to the cssBlocks collection with all the data
|
|
/// </summary>
|
|
internal IDictionary<string, Dictionary<string, List<CssBlock>>> MediaBlocks
|
|
{
|
|
get { return _mediaBlocks; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if there are css blocks for the given class selector.
|
|
/// </summary>
|
|
/// <param name="className">the class selector to check for css blocks by</param>
|
|
/// <param name="media">optional: the css media type (default - all)</param>
|
|
/// <returns>true - has css blocks for the class, false - otherwise</returns>
|
|
public bool ContainsCssBlock(string className, string media = "all")
|
|
{
|
|
Dictionary<string, List<CssBlock>> mid;
|
|
return _mediaBlocks.TryGetValue(media, out mid) && mid.ContainsKey(className);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get collection of css blocks for the requested class selector.<br/>
|
|
/// the <paramref name="className"/> can be: class name, html element name, html element and
|
|
/// class name (elm.class), hash tag with element id (#id).<br/>
|
|
/// returned all the blocks that word on the requested class selector, it can contain simple
|
|
/// selector or hierarchy selector.
|
|
/// </summary>
|
|
/// <param name="className">the class selector to get css blocks by</param>
|
|
/// <param name="media">optional: the css media type (default - all)</param>
|
|
/// <returns>collection of css blocks, empty collection if no blocks exists (never null)</returns>
|
|
public IEnumerable<CssBlock> GetCssBlock(string className, string media = "all")
|
|
{
|
|
List<CssBlock> block = null;
|
|
Dictionary<string, List<CssBlock>> mid;
|
|
if (_mediaBlocks.TryGetValue(media, out mid))
|
|
{
|
|
mid.TryGetValue(className, out block);
|
|
}
|
|
return block ?? _emptyArray;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add the given css block to the css data, merging to existing block if required.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If there is no css blocks for the same class it will be added to data collection.<br/>
|
|
/// If there is already css blocks for the same class it will check for each existing block
|
|
/// if the hierarchical selectors match (or not exists). if do the two css blocks will be merged into
|
|
/// one where the new block properties overwrite existing if needed. if the new block doesn't mach any
|
|
/// existing it will be added either to the beginning of the list if it has no hierarchical selectors or at the end.<br/>
|
|
/// Css block without hierarchical selectors must be added to the beginning of the list so more specific block
|
|
/// can overwrite it when the style is applied.
|
|
/// </remarks>
|
|
/// <param name="media">the media type to add the CSS to</param>
|
|
/// <param name="cssBlock">the css block to add</param>
|
|
public void AddCssBlock(string media, CssBlock cssBlock)
|
|
{
|
|
Dictionary<string, List<CssBlock>> mid;
|
|
if (!_mediaBlocks.TryGetValue(media, out mid))
|
|
{
|
|
mid = new Dictionary<string, List<CssBlock>>(StringComparer.InvariantCultureIgnoreCase);
|
|
_mediaBlocks.Add(media, mid);
|
|
}
|
|
|
|
if (!mid.ContainsKey(cssBlock.Class))
|
|
{
|
|
var list = new List<CssBlock>();
|
|
list.Add(cssBlock);
|
|
mid[cssBlock.Class] = list;
|
|
}
|
|
else
|
|
{
|
|
bool merged = false;
|
|
var list = mid[cssBlock.Class];
|
|
foreach (var block in list)
|
|
{
|
|
if (block.EqualsSelector(cssBlock))
|
|
{
|
|
merged = true;
|
|
block.Merge(cssBlock);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!merged)
|
|
{
|
|
// general block must be first
|
|
if (cssBlock.Selectors == null)
|
|
list.Insert(0, cssBlock);
|
|
else
|
|
list.Add(cssBlock);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Combine this CSS data blocks with <paramref name="other"/> CSS blocks for each media.<br/>
|
|
/// Merge blocks if exists in both.
|
|
/// </summary>
|
|
/// <param name="other">the CSS data to combine with</param>
|
|
public void Combine(CssData other)
|
|
{
|
|
ArgChecker.AssertArgNotNull(other, "other");
|
|
|
|
// for each media block
|
|
foreach (var mediaBlock in other.MediaBlocks)
|
|
{
|
|
// for each css class in the media block
|
|
foreach (var bla in mediaBlock.Value)
|
|
{
|
|
// for each css block of the css class
|
|
foreach (var cssBlock in bla.Value)
|
|
{
|
|
// combine with this
|
|
AddCssBlock(mediaBlock.Key, cssBlock);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create deep copy of the css data with cloned css blocks.
|
|
/// </summary>
|
|
/// <returns>cloned object</returns>
|
|
public CssData Clone()
|
|
{
|
|
var clone = new CssData();
|
|
foreach (var mid in _mediaBlocks)
|
|
{
|
|
var cloneMid = new Dictionary<string, List<CssBlock>>(StringComparer.InvariantCultureIgnoreCase);
|
|
foreach (var blocks in mid.Value)
|
|
{
|
|
var cloneList = new List<CssBlock>();
|
|
foreach (var cssBlock in blocks.Value)
|
|
{
|
|
cloneList.Add(cssBlock.Clone());
|
|
}
|
|
cloneMid[blocks.Key] = cloneList;
|
|
}
|
|
clone._mediaBlocks[mid.Key] = cloneMid;
|
|
}
|
|
return clone;
|
|
}
|
|
}
|
|
} |