Пример #1
0
        /// <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);
        }
Пример #2
0
        private ulong[] GetKey(string what)
        {
            var token = HtmlData.Tokenize(what.Substring(1));
            var key   = new [] { what[0], token };

            return(key);
        }
Пример #3
0
        /// <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)));
        }
Пример #4
0
        /// <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);
        }
Пример #5
0
        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)));
        }
Пример #6
0
 private string Get(string name)
 {
     name = name.CleanUp();
     if (string.IsNullOrEmpty(name))
     {
         return(null);
     }
     return(Get(HtmlData.Tokenize(name)));
 }
Пример #7
0
 /// <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);
 }
Пример #8
0
        private ushort[] GetKey(string what)
        {
            var token = HtmlData.Tokenize(what.Substring(1));

            ushort[] key = new ushort[2] {
                what[0], token
            };
            return(key);
        }
Пример #9
0
        /// <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);
        }
Пример #10
0
 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);
     }
 }
Пример #11
0
        /// <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);
            }
        }
Пример #12
0
 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);
     }
 }
Пример #13
0
        /// <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);
            }
        }
Пример #14
0
 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)));
     }
 }
Пример #15
0
 bool ICollection <KeyValuePair <string, string> > .Contains(KeyValuePair <string, string> item)
 {
     return(ContainsKey(item.Key) &&
            Attributes[HtmlData.Tokenize(item.Key)] == item.Value);
 }
Пример #16
0
        /// <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());
        }
Пример #17
0
 /// <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)));
 }
Пример #18
0
 /// <summary>
 /// Remove a single named style
 /// </summary>
 /// <param name="name"></param>
 /// <returns></returns>
 public bool Remove(string name)
 {
     return(Styles.Remove(HtmlData.Tokenize(name)));
 }
Пример #19
0
        /// <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);
        }
Пример #20
0
        /// <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)));
        }
Пример #21
0
        /// <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)));
        }
Пример #22
0
        /// <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));
        }
Пример #23
0
        /// <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;
            }
        }
Пример #24
0
        /// <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());
        }
Пример #25
0
 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);
 }
Пример #26
0
 bool IDictionary <string, string> .ContainsKey(string key)
 {
     return(HasStyleAttribute ?
            Styles.ContainsKey(HtmlData.Tokenize(key)) :
            false);
 }
Пример #27
0
        /// <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);
        }
Пример #28
0
 /// <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);
 }
Пример #29
0
 bool IDictionary <string, string> .ContainsKey(string key)
 {
     return(Styles.ContainsKey(HtmlData.Tokenize(key)));
 }