/// <summary> /// Sets style setting with no parsing /// </summary> /// <param name="name"></param> /// <param name="value"></param> public void SetRaw(string name, string value) { bool hadStyles = HasStyles; Styles[HtmlData.Tokenize(name)] = value; DoOnHasStylesChanged(hadStyles); }
private ulong[] GetKey(string what) { var token = HtmlData.Tokenize(what.Substring(1)); var key = new [] { what[0], token }; return(key); }
/// <summary> /// Try to get a value for the specified attribute name. /// </summary> /// /// <param name="name"> /// The key. /// </param> /// <param name="value"> /// [out] The value. /// </param> /// /// <returns> /// true if the key was present, false if it fails. /// </returns> public bool TryGetValue(string name, out string value) { // do not use trygetvalue from dictionary. We need default handling in Get value = Get(name); return(value != null || Attributes.ContainsKey(HtmlData.Tokenize(name))); }
/// <summary> /// Sets style setting with no parsing /// </summary> /// <param name="name"></param> /// <param name="value"></param> public void SetRaw(string name, string value) { bool hadStyleAttribute = HasStyleAttribute; Styles[HtmlData.Tokenize(name)] = value; DoOnHasStyleAttributeChanged(hadStyleAttribute); }
public bool TryGetValue(string key, out string value) { // do not use trygetvalue from dictionary. We need default handling in Get value = Get(key); return(value != null || ContainsKey(HtmlData.Tokenize(key))); }
private string Get(string name) { name = name.CleanUp(); if (string.IsNullOrEmpty(name)) { return(null); } return(Get(HtmlData.Tokenize(name))); }
/// <summary> /// Adding an attribute implementation /// </summary> /// <param name="name"></param> /// <param name="value"></param> private void Set(string name, string value) { if (String.IsNullOrEmpty(name)) { throw new ArgumentException("Cannot set an attribute with no name."); } name = name.CleanUp(); Set(HtmlData.Tokenize(name), value); }
private ushort[] GetKey(string what) { var token = HtmlData.Tokenize(what.Substring(1)); ushort[] key = new ushort[2] { what[0], token }; return(key); }
/// <summary> /// Gets a style by name /// </summary> /// /// <param name="name"> /// The style name /// </param> /// /// <returns> /// The style, or null if it is not defined. /// </returns> public string GetStyle(string name) { string value = null; if (HasStyleAttribute) { Styles.TryGetValue(HtmlData.Tokenize(name), out value); } return(value); }
bool ICollection <KeyValuePair <string, string> > .Remove(KeyValuePair <string, string> item) { if (ContainsKey(item.Key) && Attributes[HtmlData.Tokenize(item.Key)] == item.Value) { return(Remove(item.Key)); } else { return(false); } }
/// <summary> /// Try to get the value of the named style. /// </summary> /// /// <param name="name"> /// The name of the style /// </param> /// <param name="value"> /// [out] The value. /// </param> /// /// <returns> /// true if the named style is defined, false if not. /// </returns> public bool TryGetValue(string name, out string value) { if (HasStyleAttribute) { return(Styles.TryGetValue(HtmlData.Tokenize(name), out value)); } else { value = null; return(false); } }
bool ICollection <KeyValuePair <string, string> > .Remove(KeyValuePair <string, string> item) { if (HasStyleAttribute) { var kvp = new KeyValuePair <ushort, string>(HtmlData.Tokenize(item.Key), item.Value); return(Styles.Remove(kvp)); } else { return(false); } }
/// <summary> /// Gets a style by name /// </summary> /// /// <param name="name"> /// The style name /// </param> /// /// <returns> /// The style, or null if it is not defined. /// </returns> public string GetStyle(string name) { string value; if (Styles.TryGetValue(HtmlData.Tokenize(name), out value)) { return(value); } else { return(null); } }
public bool ContainsKey(string key) { if (Count == 0) { return(false); } else if (!UseDict) { return(InnerKeys.IndexOf(HtmlData.Tokenize(key), Count) >= 0); } else { return(InnerDictionary.ContainsKey(HtmlData.Tokenize(key))); } }
bool ICollection <KeyValuePair <string, string> > .Contains(KeyValuePair <string, string> item) { return(ContainsKey(item.Key) && Attributes[HtmlData.Tokenize(item.Key)] == item.Value); }
/// <summary> /// Select from the bound Document using index. First non-class/tag/id selector will result in /// this being passed off to GetMatches. /// </summary> /// /// <exception cref="ArgumentNullException"> /// Thrown when one or more required arguments are null. /// </exception> /// /// <param name="context"> /// The context in which the selector applies. If null, the selector is run against the entire /// Document. If not, the selector is run against this sequence of elements. /// </param> /// /// <returns> /// A list of elements matching the selector. /// </returns> public List <IDomObject> Select(IEnumerable <IDomObject> context) { // this holds the final output HashSet <IDomObject> output = new HashSet <IDomObject>(); if (Selector == null) { throw new ArgumentNullException("The selector cannot be null."); } if (Selector.Count == 0) { return(EmptyEnumerable().ToList()); } ActiveSelectors = new List <SelectorClause>(Selector); // First just check if we ended up here with an HTML selector; if so, hand it off. var firstSelector = ActiveSelectors[0]; if (firstSelector.SelectorType == SelectorType.HTML) { HtmlParser.HtmlElementFactory factory = new HtmlParser.HtmlElementFactory(firstSelector.Html); // Return the factory ouptut as a list because otherwise the enumerator could end up // as the actual source of the selection, meaning it would get re-parsed each time return(factory.ParseAsFragment()); } // this holds any results that carried over from the previous loop for chaining IEnumerable <IDomObject> lastResult = null; // this is the source from which selections are made in a given iteration; it could be the DOM // root, a context, or the previous result set. IEnumerable <IDomObject> selectionSource = null; // Disable the index if there is no context (e.g. disconnected elements) // or if the first element is not indexed. bool useIndex = context.IsNullOrEmpty() || (!context.First().IsDisconnected&& context.First().IsIndexed); for (activeSelectorId = 0; activeSelectorId < ActiveSelectors.Count; activeSelectorId++) { var selector = ActiveSelectors[activeSelectorId].Clone(); if (lastResult != null) { // we will alter the selector during each iteration to remove the parts that have already been // parsed, so use a copy. This is a selector that was chained with the selector grouping // combinator "," -- we always output the results so far when beginning a new group. if (selector.CombinatorType == CombinatorType.Root && lastResult != null) { output.AddRange(lastResult); lastResult = null; } } // For "and" combinator types, we want to leave everything as it was -- the results of this // selector should compound with the prior. This is not an actual CSS combinator, this is the // equivalent of grouping parenthesis. That is, in CSS there's no way to say "(input[submit], // button):visible" - that is group the results on selector part and apply a filter to it. But // we need to do exactly this for certain selector types (for example the jQuery :button // selector). if (selector.CombinatorType != CombinatorType.Grouped) { selectionSource = GetSelectionSource(selector, context, lastResult); lastResult = null; } string key = ""; SelectorType removeSelectorType = 0; if (useIndex && !selector.NoIndex) { #if DEBUG_PATH if (selector.SelectorType.HasFlag(SelectorType.AttributeValue) && selector.AttributeSelectorType != AttributeSelectorType.NotExists && selector.AttributeSelectorType != AttributeSelectorType.NotEquals) { key = "!" + selector.AttributeName.ToLower(); // AttributeValue must still be matched manually - so remove this flag only if the // selector is conclusive without further checking if (selector.AttributeSelectorType == AttributeSelectorType.Exists) { removeSelectorType = SelectorType.AttributeValue; } } else if (selector.SelectorType.HasFlag(SelectorType.Tag)) { key = "+" + selector.Tag.ToLower(); removeSelectorType = SelectorType.Tag; } else if (selector.SelectorType.HasFlag(SelectorType.ID)) { key = "#" + selector.ID; removeSelectorType = SelectorType.ID; } else if (selector.SelectorType.HasFlag(SelectorType.Class)) { key = "." + selector.Class; removeSelectorType = SelectorType.Class; } #else // We don't want to use the index for "NotEquals" selectors because a missing attribute // is considered a valid match if (selector.SelectorType.HasFlag(SelectorType.AttributeValue) && selector.AttributeSelectorType != AttributeSelectorType.NotExists && selector.AttributeSelectorType != AttributeSelectorType.NotEquals) { key = "!" + (char)HtmlData.Tokenize(selector.AttributeName); // AttributeValue must still be matched manually - so remove this flag only if the // selector is conclusive without further checking if (selector.AttributeSelectorType == AttributeSelectorType.Exists) { removeSelectorType = SelectorType.AttributeValue; } } else if (selector.SelectorType.HasFlag(SelectorType.Tag)) { key = "+" + (char)HtmlData.Tokenize(selector.Tag); removeSelectorType = SelectorType.Tag; } else if (selector.SelectorType.HasFlag(SelectorType.ID)) { key = "#" + (char)HtmlData.TokenizeCaseSensitive(selector.ID); removeSelectorType = SelectorType.ID; } else if (selector.SelectorType.HasFlag(SelectorType.Class)) { key = "." + (char)HtmlData.TokenizeCaseSensitive(selector.Class); removeSelectorType = SelectorType.Class; } #endif } // If part of the selector was indexed, key will not be empty. Return initial set from the // index. If any selectors remain after this they will be searched the hard way. IEnumerable <IDomObject> result = null; if (key != String.Empty) { // This is the main index access point: if we have an index key, we'll get as much as we can from the index. // Anything else will be handled manually. int depth = 0; bool descendants = true; switch (selector.TraversalType) { case TraversalType.Child: depth = selector.ChildDepth; descendants = false; break; case TraversalType.Filter: case TraversalType.Adjacent: case TraversalType.Sibling: depth = 0; descendants = false; break; case TraversalType.Descendent: depth = 1; descendants = true; break; } if (selectionSource == null) { result = Document.DocumentIndex.QueryIndex(key + HtmlData.indexSeparator, depth, descendants); } else { HashSet <IDomObject> elementMatches = new HashSet <IDomObject>(); result = elementMatches; foreach (IDomObject obj in selectionSource) { elementMatches.AddRange(Document.DocumentIndex.QueryIndex(key + HtmlData.indexSeparator + obj.Path, depth, descendants)); } } selector.SelectorType &= ~removeSelectorType; // Special case for attribute selectors: when Attribute Value attribute selector is present, we // still need to filter for the correct value afterwards. But we need to change the traversal // type because any nodes with the correct attribute type have already been selected. if (selector.SelectorType.HasFlag(SelectorType.AttributeValue)) { selector.TraversalType = TraversalType.Filter; } } else if (selector.SelectorType.HasFlag(SelectorType.Elements)) { HashSet <IDomObject> elementMatches = new HashSet <IDomObject>(); result = elementMatches; foreach (IDomObject obj in GetAllChildOrDescendants(selector.TraversalType, selectionSource)) { //key = HtmlData.indexSeparator + obj.Path; HashSet <IDomObject> srcKeys = new HashSet <IDomObject>(Document.DocumentIndex.QueryIndex(HtmlData.indexSeparator + obj.Path)); foreach (IDomObject match in selector.SelectElements) { if (srcKeys.Contains(match)) { elementMatches.Add(match); } } } selector.SelectorType &= ~SelectorType.Elements; } // If any selectors were not handled via the index, match them manually if (selector.SelectorType != 0) { // if there are no temporary results (b/c there was no indexed selector) then use selection // source instead (e.g. start from the same point that the index would have) result = GetMatches(result ?? selectionSource ?? Document.ChildElements, selector); } lastResult = lastResult == null ? result : lastResult.Concat(result); } // After the loop has finished, output any results from the last iteration. output.AddRange(lastResult); // Return the results as a list so that any user will not cause the selector to be run again return(output.OrderBy(item => item.Path, StringComparer.Ordinal).ToList()); }
/// <summary> /// Returns true if the named style is defined /// </summary> /// <param name="styleName"></param> /// <returns></returns> public bool HasStyle(string styleName) { return(Styles.ContainsKey(HtmlData.Tokenize(styleName))); }
/// <summary> /// Remove a single named style /// </summary> /// <param name="name"></param> /// <returns></returns> public bool Remove(string name) { return(Styles.Remove(HtmlData.Tokenize(name))); }
/// <summary> /// Sets a boolean only attribute having no value. /// </summary> /// /// <param name="name"> /// The attribute to set /// </param> public void SetBoolean(string name) { ushort tokenId = HtmlData.Tokenize(name); SetBoolean(tokenId); }
/// <summary> /// Remove an attribute. /// </summary> /// /// <param name="name"> /// The attribute name /// </param> /// /// <returns> /// true if it succeeds, false if it fails. /// </returns> public bool Unset(string name) { return(Unset(HtmlData.Tokenize(name))); }
/// <summary> /// Test whether the named attribute exists in the collection. /// </summary> /// /// <param name="key"> /// The attribute name. /// </param> /// /// <returns> /// true if it exists, false if not. /// </returns> public bool ContainsKey(string key) { return(Attributes.ContainsKey(HtmlData.Tokenize(key))); }
/// <summary> /// Try to get the value of the named style. /// </summary> /// /// <param name="name"> /// The name of the style /// </param> /// <param name="value"> /// [out] The value. /// </param> /// /// <returns> /// true if the named style is defined, false if not. /// </returns> public bool TryGetValue(string name, out string value) { return(Styles.TryGetValue(HtmlData.Tokenize(name), out value)); }
/// <summary> /// Parse the HTML, and return it, based on options set. /// </summary> /// /// <returns> /// An enumerator of the top-level elements. /// </returns> protected IEnumerable <IDomObject> ParseImplementation() { int pos = 0; Stack <IterationData> stack = new Stack <IterationData>(); while (pos <= EndPos) { IterationData current = new IterationData(); if (WrapRootTextNodes) { current.WrapLiterals = true; } current.Reset(pos); stack.Push(current); while (stack.Count != 0) { current = stack.Pop(); while (current.TokenizerState != TokenizerState.Finished && current.Pos <= EndPos) { char c = Html[current.Pos]; switch (current.TokenizerState) { case TokenizerState.Default: if (current.FindNextTag(Html)) { // even if we fell through from ReadTextOnly (e.g. was never closed), we should proceeed to finish current.TokenizerState = TokenizerState.TagStart; } break; case TokenizerState.TagStart: IDomObject literal; if (current.TryGetLiteral(this, out literal)) { yield return(literal); } int tagStartPos = current.Pos; string newTag = current.GetTagOpener(Html); if (newTag == String.Empty) { // It's a tag closer. Make sure it's the right one. current.Pos = tagStartPos + 1; ushort closeTagId = HtmlData.Tokenize(current.GetCloseTag(Html)); // Ignore empty tags, or closing tags found when no parent is open bool isProperClose = closeTagId == current.ParentTagID(); if (closeTagId == 0) { // ignore empty tags continue; } else { // locate match for this closer up the heirarchy IterationData actualParent = null; if (!isProperClose) { actualParent = current.Parent; while (actualParent != null && actualParent.Element.NodeNameID != closeTagId) { actualParent = actualParent.Parent; } } // if no matching close tag was found up the tree, ignore it // otherwise always close this and repeat at the same position until the match is found if (!isProperClose && actualParent == null) { current.InsertionMode = InsertionMode.Invalid; continue; } } // element is closed if (current.Parent.Parent == null) { yield return(current.Parent.Element); } current.TokenizerState = TokenizerState.Finished; if (isProperClose) { current.Parent.Reset(current.Pos); } else { current.Parent.Reset(tagStartPos); } // already been returned before we added the children continue; } else if (newTag[0] == '!') { IDomSpecialElement specialElement = null; string newTagUpper = newTag.ToUpper(); if (newTagUpper.StartsWith("!DOCTYPE")) { specialElement = new DomDocumentType(); current.Element = specialElement; } else if (newTagUpper.StartsWith("![CDATA[")) { specialElement = new DomCData(); current.Element = specialElement; current.Pos = tagStartPos + 9; } else { specialElement = new DomComment(); current.Element = specialElement; if (newTag.StartsWith("!--")) { ((DomComment)specialElement).IsQuoted = true; current.Pos = tagStartPos + 4; } else { current.Pos = tagStartPos + 1; } } string endTag = (current.Element is IDomComment && ((IDomComment)current.Element).IsQuoted) ? "-->" : ">"; int tagEndPos = Html.Seek(endTag, current.Pos); if (tagEndPos < 0) { // if a tag is unclosed entirely, then just find a new line. tagEndPos = Html.Seek(System.Environment.NewLine, current.Pos); } if (tagEndPos < 0) { // Never closed, no newline - junk, treat it like such tagEndPos = EndPos; } specialElement.NonAttributeData = Html.SubstringBetween(current.Pos, tagEndPos); current.Pos = tagEndPos; } else { // seems to be a new element tag, parse it. ushort newTagId = HtmlData.Tokenize(newTag); // Before we keep going see if this is an implicit close ushort parentTagId = current.ParentTagID(); int lastPos = current.Pos; if (parentTagId == 0 && IsDocument) { if (newTagId != HtmlData.tagHTML) { current.Element = DomElement.Create(HtmlData.tagHTML); current = current.AddNewChild(); parentTagId = HtmlData.tagHTML; } } if (parentTagId != 0) { ushort action = SpecialTagActionDelegate(parentTagId, newTagId); while (action != HtmlData.tagActionNothing) { if (action == HtmlData.tagActionClose) { // track the next parent up the chain var newNode = (current.Parent != null) ? current.Parent : null; // same tag for a repeater like li occcurred - treat like a close tag if (current.Parent.Parent == null) { yield return(current.Parent.Element); } current.TokenizerState = TokenizerState.Finished; //current.Parent.Reset(tagStartPos); if (newNode != null && newNode.Parent != null && newNode.Parent.Element != null) { action = SpecialTagActionDelegate(newNode.Parent.Element.NodeNameID, newTagId); if (action != HtmlData.tagActionNothing) { current = newNode; } } else { action = HtmlData.tagActionNothing; } } else { if (GenerateOptionalElements) { stack.Push(current); current = current.AddNewParent(action, lastPos); } action = HtmlData.tagActionNothing; } } if (current.TokenizerState == TokenizerState.Finished) { current.Parent.Reset(tagStartPos); continue; } } current.Element = DomElement.Create(newTagId); if (!current.Element.InnerHtmlAllowed && current.Element.InnerTextAllowed) { current.InsertionMode = InsertionMode.Text; current.TokenizerState = TokenizerState.Default; } // Parse attribute data while (current.Pos <= EndPos) { if (!current.GetTagAttribute(Html)) { break; } } } IDomObject el; if (current.FinishTagOpener(Html, out el)) { stack.Push(current); current = current.AddNewChild(); } if (el != null) { yield return(el); } break; } } // Catchall for unclosed tags -- if there's an "unfinished" carrier here, it's because top-level tag was unclosed. // THis will wrap up any straggling text and close any open tags after it. if (current.TokenizerState != TokenizerState.Finished) { foreach (var el in current.CloseElement(this)) { yield return(el); } } } pos = current.Pos; } }
/// <summary> /// Select implementation. The public method automatically remaps a selector with the knowledge /// that the context is external (and not part of a chain) /// </summary> /// /// <exception cref="ArgumentNullException"> /// Thrown when one or more required arguments are null. /// </exception> /// /// <param name="context"> /// The context in which the selector applies. If null, the selector is run against the entire /// Document. If not, the selector is run against this sequence of elements. /// </param> /// /// <returns> /// A list of elements. This method returns a list (rather than a sequence) because the sequence /// must be enumerated to ensure that end-users don't cause the selector to be rerun repeatedly, /// and that the values are not mutable (e.g. if the underlying source changes). /// </returns> public IList <IDomObject> Select(IEnumerable <IDomObject> context) { // this holds the final output HashSet <IDomObject> output = new HashSet <IDomObject>(); if (Selector == null) { throw new ArgumentNullException("The selector cannot be null."); } if (Selector.Count == 0) { return(EmptyEnumerable().ToList()); } ActiveSelectors = new List <SelectorClause>(Selector); // First just check if we ended up here with an HTML selector; if so, hand it off. var firstSelector = ActiveSelectors[0]; if (firstSelector.SelectorType == SelectorType.HTML) { return(CsQuery.Implementation. DomDocument.Create(firstSelector.Html, HtmlParsingMode.Fragment) .ChildNodes .ToList()); } // this holds any results that carried over from the previous loop for chaining IEnumerable <IDomObject> lastResult = null; // this is the source from which selections are made in a given iteration; it could be the DOM // root, a context, or the previous result set. IEnumerable <IDomObject> selectionSource = null; // Disable the index if there is no context (e.g. disconnected elements) // or if the first element is not indexed, or the context is not from the same document as this // selector is bound. Determine which features can be used for this query by casting the index // to the known interfaces. bool useIndex; if (context.IsNullOrEmpty()) { useIndex = true; } else { IDomObject first = context.First(); useIndex = !first.IsDisconnected && first.IsIndexed && first.Document == Document; } IDomIndexRanged rangedIndex = null; IDomIndexSimple simpleIndex = null; if (useIndex) { rangedIndex = Document.DocumentIndex as IDomIndexRanged; simpleIndex = Document.DocumentIndex as IDomIndexSimple; } for (activeSelectorId = 0; activeSelectorId < ActiveSelectors.Count; activeSelectorId++) { var selector = ActiveSelectors[activeSelectorId].Clone(); if (lastResult != null && (selector.CombinatorType == CombinatorType.Root || selector.CombinatorType == CombinatorType.Context)) { // we will alter the selector during each iteration to remove the parts that have already been // parsed, so use a copy. This is a selector that was chained with the selector grouping // combinator "," -- we always output the results so far when beginning a new group. output.AddRange(lastResult); lastResult = null; } // For "and" combinator types, we want to leave everything as it was -- the results of this // selector should compound with the prior. This is not an actual CSS combinator, this is the // equivalent of grouping parenthesis. That is, in CSS there's no way to say "(input[submit], // button):visible" - that is group the results on selector part and apply a filter to it. But // we need to do exactly this for certain selector types (for example the jQuery :button // selector). if (selector.CombinatorType != CombinatorType.Grouped) { selectionSource = GetSelectionSource(selector, context, lastResult); lastResult = null; } var key = new List <ulong>(); SelectorType removeSelectorType = 0; // determine the type of traversal & depth for this selector int depth = 0; bool descendants = true; switch (selector.TraversalType) { case TraversalType.Child: depth = selector.ChildDepth; descendants = false; break; case TraversalType.Filter: case TraversalType.Adjacent: case TraversalType.Sibling: depth = 0; descendants = false; break; case TraversalType.Descendent: depth = 1; descendants = true; break; // default: fall through with default values set above. } bool canUseBasicIndex = (selectionSource == null) && descendants && depth == 0; // build index keys when possible for the active index type if (rangedIndex != null || (simpleIndex != null && canUseBasicIndex) && !selector.NoIndex) { // We don't want to use the index for "NotEquals" selectors because a missing attribute // is considered a valid match if (selector.SelectorType.HasFlag(SelectorType.AttributeValue) && selector.AttributeSelectorType != AttributeSelectorType.NotExists && selector.AttributeSelectorType != AttributeSelectorType.NotEquals) { key.Add('!'); key.Add(HtmlData.Tokenize(selector.AttributeName)); // AttributeValue must still be matched manually - so remove this flag only if the // selector is conclusive without further checking if (selector.AttributeSelectorType == AttributeSelectorType.Exists) { removeSelectorType = SelectorType.AttributeValue; } } else if (selector.SelectorType.HasFlag(SelectorType.Tag)) { key.Add('+'); key.Add(HtmlData.Tokenize(selector.Tag)); removeSelectorType = SelectorType.Tag; } else if (selector.SelectorType.HasFlag(SelectorType.ID)) { key.Add('#'); key.Add(HtmlData.TokenizeCaseSensitive(selector.ID)); removeSelectorType = SelectorType.ID; } else if (selector.SelectorType.HasFlag(SelectorType.Class)) { key.Add('.'); key.Add(HtmlData.TokenizeCaseSensitive(selector.Class)); removeSelectorType = SelectorType.Class; } } // If part of the selector was indexed, key will not be empty. Return initial set from the // index. If any selectors remain after this they will be searched the hard way. IEnumerable <IDomObject> result = null; if (key.Count > 0) { // This is the main index access point: if we have an index key, we'll get as much as we can from the index. // Anything else will be handled manually. if (selectionSource == null) { // we don't need to test for index features at this point; if canUseBasicIndex = false and we // are here, then the prior logic dictates that the ranged index is available. But always use // the simple index if that's all we need because it could be faster. result = simpleIndex.QueryIndex(key.ToArray()); } else { HashSet <IDomObject> elementMatches = new HashSet <IDomObject>(); result = elementMatches; foreach (IDomObject obj in selectionSource) { var subKey = key.Concat(HtmlData.indexSeparator).Concat(obj.NodePath).ToArray(); var matches = rangedIndex.QueryIndex(subKey, depth, descendants); elementMatches.AddRange(matches); } } selector.SelectorType &= ~removeSelectorType; // Special case for attribute selectors: when Attribute Value attribute selector is present, we // still need to filter for the correct value afterwards. But we need to change the traversal // type because any nodes with the correct attribute type have already been selected. if (selector.SelectorType.HasFlag(SelectorType.AttributeValue)) { selector.TraversalType = TraversalType.Filter; } } // If any selectors were not handled via the index, match them manually if (selector.SelectorType != 0) { // if there are no temporary results (b/c there was no indexed selector) then use selection // source instead (e.g. start from the same point that the index would have) result = GetMatches(result ?? selectionSource ?? Document.ChildElements, selector); } lastResult = lastResult == null ? result : lastResult.Concat(result); } // After the loop has finished, output any results from the last iteration. output.AddRange(lastResult); // Return the results as a list so that any user will not cause the selector to be run again return(output.OrderBy(item => item.NodePath, Implementation.PathKeyComparer.Comparer).ToList()); }
bool ICollection <KeyValuePair <string, string> > .Contains(KeyValuePair <string, string> item) { return(HasStyleAttribute ? Styles.Contains(new KeyValuePair <ushort, string>(HtmlData.Tokenize(item.Key), item.Value)) : false); }
bool IDictionary <string, string> .ContainsKey(string key) { return(HasStyleAttribute ? Styles.ContainsKey(HtmlData.Tokenize(key)) : false); }
/// <summary> /// Remove a single named style. /// </summary> /// /// <param name="name"> /// The name of the style to remove /// </param> /// /// <returns> /// true if it succeeds, false if it fails. /// </returns> public bool Remove(string name) { return(HasStyleAttribute ? Styles.Remove(HtmlData.Tokenize(name)) : false); }
/// <summary> /// Returns true if the named style is defined /// </summary> /// <param name="styleName"></param> /// <returns></returns> public bool HasStyle(string styleName) { return(HasStyleAttribute ? Styles.ContainsKey(HtmlData.Tokenize(styleName)) : false); }
bool IDictionary <string, string> .ContainsKey(string key) { return(Styles.ContainsKey(HtmlData.Tokenize(key))); }