// "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 { /// /// Holds parsed stylesheet css blocks arranged by media and classes.
/// ///
/// /// To learn more about CSS blocks visit CSS spec: http://www.w3.org/TR/CSS21/syndata.html#block /// public sealed class CssData { #region Fields and Consts /// /// used to return empty array /// private static readonly List _emptyArray = new List(); /// /// dictionary of media type to dictionary of css class name to the cssBlocks collection with all the data. /// private readonly Dictionary>> _mediaBlocks = new Dictionary>>(StringComparer.InvariantCultureIgnoreCase); #endregion /// /// Init. /// internal CssData() { _mediaBlocks.Add("all", new Dictionary>(StringComparer.InvariantCultureIgnoreCase)); } /// /// Parse the given stylesheet to object.
/// If 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. ///
/// Platform adapter /// the stylesheet source to parse /// true - combine the parsed css data with default css data, false - return only the parsed css data /// the parsed css data public static CssData Parse(RAdapter adapter, string stylesheet, bool combineWithDefault = true) { CssParser parser = new CssParser(adapter); return parser.ParseStyleSheet(stylesheet, combineWithDefault); } /// /// dictionary of media type to dictionary of css class name to the cssBlocks collection with all the data /// internal IDictionary>> MediaBlocks { get { return _mediaBlocks; } } /// /// Check if there are css blocks for the given class selector. /// /// the class selector to check for css blocks by /// optional: the css media type (default - all) /// true - has css blocks for the class, false - otherwise public bool ContainsCssBlock(string className, string media = "all") { Dictionary> mid; return _mediaBlocks.TryGetValue(media, out mid) && mid.ContainsKey(className); } /// /// Get collection of css blocks for the requested class selector.
/// the can be: class name, html element name, html element and /// class name (elm.class), hash tag with element id (#id).
/// returned all the blocks that word on the requested class selector, it can contain simple /// selector or hierarchy selector. ///
/// the class selector to get css blocks by /// optional: the css media type (default - all) /// collection of css blocks, empty collection if no blocks exists (never null) public IEnumerable GetCssBlock(string className, string media = "all") { List block = null; Dictionary> mid; if (_mediaBlocks.TryGetValue(media, out mid)) { mid.TryGetValue(className, out block); } return block ?? _emptyArray; } /// /// Add the given css block to the css data, merging to existing block if required. /// /// /// If there is no css blocks for the same class it will be added to data collection.
/// 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.
/// 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. ///
/// the media type to add the CSS to /// the css block to add public void AddCssBlock(string media, CssBlock cssBlock) { Dictionary> mid; if (!_mediaBlocks.TryGetValue(media, out mid)) { mid = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); _mediaBlocks.Add(media, mid); } if (!mid.ContainsKey(cssBlock.Class)) { var list = new List(); 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); } } } /// /// Combine this CSS data blocks with CSS blocks for each media.
/// Merge blocks if exists in both. ///
/// the CSS data to combine with 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); } } } } /// /// Create deep copy of the css data with cloned css blocks. /// /// cloned object public CssData Clone() { var clone = new CssData(); foreach (var mid in _mediaBlocks) { var cloneMid = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); foreach (var blocks in mid.Value) { var cloneList = new List(); foreach (var cssBlock in blocks.Value) { cloneList.Add(cssBlock.Clone()); } cloneMid[blocks.Key] = cloneList; } clone._mediaBlocks[mid.Key] = cloneMid; } return clone; } } }