/// <summary>
        /// Select from DOM using index. First non-class/tag/id selector will result in this being passed off to GetMatches
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        public IEnumerable <IDomObject> Select(IDomDocument document, IEnumerable <IDomObject> context)
        {
            if (Selectors == null)
            {
                throw new ArgumentException("No selectors provided.");
            }
            if (Selectors.Count == 0)
            {
                yield break;
            }
            Document = document;
            IEnumerable <IDomObject> lastResult      = null;
            HashSet <IDomObject>     output          = new HashSet <IDomObject>();
            IEnumerable <IDomObject> selectionSource = context;

            // Disable the index if there is no context (e.g. disconnected elements)
            bool useIndex = context.IsNullOrEmpty() || !context.First().IsDisconnected;

            // Copy the list because it may change during the process
            ActiveSelectors = new List <Selector>(Selectors);

            for (activeSelectorId = 0; activeSelectorId < ActiveSelectors.Count; activeSelectorId++)
            {
                var            selector       = ActiveSelectors[activeSelectorId];
                CombinatorType combinatorType = selector.CombinatorType;
                SelectorType   selectorType   = selector.SelectorType;
                TraversalType  traversalType  = selector.TraversalType;

                // Determine what kind of combining method we will use with previous selection results

                if (activeSelectorId != 0)
                {
                    switch (combinatorType)
                    {
                    case CombinatorType.Cumulative:
                        // do nothing
                        break;

                    case CombinatorType.Root:
                        selectionSource = context;
                        if (lastResult != null)
                        {
                            output.AddRange(lastResult);
                            lastResult = null;
                        }
                        break;

                    case CombinatorType.Chained:
                        selectionSource = lastResult;
                        lastResult      = null;
                        break;
                        // default (chained): leave lastresult alone
                    }
                }

                HashSet <IDomObject>     tempResult    = null;
                IEnumerable <IDomObject> interimResult = null;

                string key = "";
                if (useIndex && !selector.NoIndex)
                {
#if DEBUG_PATH
                    if (type.HasFlag(SelectorType.Attribute))
                    {
                        key   = "!" + selector.AttributeName;
                        type &= ~SelectorType.Attribute;
                        if (selector.AttributeValue != null)
                        {
                            InsertAttributeValueSelector(selector);
                        }
                    }
                    else if (type.HasFlag(SelectorType.Tag))
                    {
                        key   = "+" + selector.Tag;
                        type &= ~SelectorType.Tag;
                    }
                    else if (type.HasFlag(SelectorType.ID))
                    {
                        key   = "#" + selector.ID;
                        type &= ~SelectorType.ID;
                    }
                    else if (type.HasFlag(SelectorType.Class))
                    {
                        key   = "." + selector.Class;
                        type &= ~SelectorType.Class;
                    }
#else
                    if (selectorType.HasFlag(SelectorType.Attribute))
                    {
                        key           = "!" + (char)DomData.TokenID(selector.AttributeName);
                        selectorType &= ~SelectorType.Attribute;
                        if (selector.AttributeValue != null)
                        {
                            InsertAttributeValueSelector(selector);
                        }
                    }
                    else if (selectorType.HasFlag(SelectorType.Tag))
                    {
                        key           = "+" + (char)DomData.TokenID(selector.Tag, true);
                        selectorType &= ~SelectorType.Tag;
                    }
                    else if (selectorType.HasFlag(SelectorType.ID))
                    {
                        key           = "#" + (char)DomData.TokenID(selector.ID);
                        selectorType &= ~SelectorType.ID;
                    }
                    else if (selectorType.HasFlag(SelectorType.Class))
                    {
                        key           = "." + (char)DomData.TokenID(selector.Class);
                        selectorType &= ~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.

                if (key != String.Empty)
                {
                    int  depth       = 0;
                    bool descendants = true;

                    switch (traversalType)
                    {
                    case TraversalType.Child:
                        depth       = selector.ChildDepth;;
                        descendants = false;
                        break;

                    case TraversalType.Filter:
                        depth       = 0;
                        descendants = false;
                        break;

                    case TraversalType.Descendent:
                        depth       = 1;
                        descendants = true;
                        break;
                    }

                    if (selectionSource == null)
                    {
                        interimResult = document.QueryIndex(key + DomData.indexSeparator, depth, descendants);
                    }
                    else
                    {
                        interimResult = new HashSet <IDomObject>();
                        foreach (IDomObject obj in selectionSource)
                        {
                            ((HashSet <IDomObject>)interimResult)
                            .AddRange(document.QueryIndex(key + DomData.indexSeparator + obj.Path,
                                                          depth, descendants));
                        }
                    }
                }
                else if (selectorType.HasFlag(SelectorType.Elements))
                {
                    selectorType &= ~SelectorType.Elements;
                    HashSet <IDomObject> source = new HashSet <IDomObject>(selectionSource);
                    interimResult = new HashSet <IDomObject>();

                    foreach (IDomObject obj in selectionSource)
                    {
                        key = DomData.indexSeparator + obj.Path;
                        HashSet <IDomObject> srcKeys = new HashSet <IDomObject>(document.QueryIndex(key));
                        foreach (IDomObject match in selector.SelectElements)
                        {
                            if (srcKeys.Contains(match))
                            {
                                ((HashSet <IDomObject>)interimResult).Add(match);
                            }
                        }
                    }
                }
                // TODO - GetMatch should work if passed with no selectors (returning nothing), now it returns everything
                // 12/10/11 - this todo is not verified, much has changed since it was written. TODO confirm this and
                // fix if needed. If having the conversation with self again, remove comments and forget it. This is
                // an example of why comments can do more harm than good.

                if ((selectorType & ~(SelectorType.SubSelectorNot | SelectorType.SubSelectorHas)) != 0)
                {
                    IEnumerable <IDomObject> finalSelectWithin =
                        interimResult
                        ?? (combinatorType == CombinatorType.Chained ? lastResult : null)
                        ?? selectionSource
                        ?? document.ChildElements;

                    // if there are no temporary results (b/c there was no indexed selector) then use the whole set
                    interimResult = GetMatches(finalSelectWithin, selector);
                }

                // Deal with subselectors: has() and not() test for the presence of a selector within the children of
                // an element. This is essentially similar to the manual selection above.

                if (selectorType.HasFlag(SelectorType.SubSelectorHas) ||
                    selectorType.HasFlag(SelectorType.SubSelectorNot))
                {
                    bool isHasSelector = selectorType.HasFlag(SelectorType.SubSelectorHas);

                    IEnumerable <IDomObject> subSelectWithin = interimResult
                                                               ?? (combinatorType == CombinatorType.Chained ? lastResult : null)
                                                               ?? selectionSource;

                    // subselects are a filter. start a new interim result.

                    HashSet <IDomObject> filteredResults = new HashSet <IDomObject>();

                    foreach (IDomObject obj in subSelectWithin)
                    {
                        bool match = true;
                        foreach (var sub in selector.SubSelectors)
                        {
                            List <IDomObject> listOfOne = new List <IDomObject>();
                            listOfOne.Add(obj);

                            bool has = !sub.Select(document, listOfOne).IsNullOrEmpty();

                            match &= isHasSelector == has;
                        }
                        if (match)
                        {
                            filteredResults.Add(obj);
                        }
                    }
                    interimResult = filteredResults;
                }
                tempResult = new HashSet <IDomObject>();
                if (lastResult != null)
                {
                    tempResult.AddRange(lastResult);
                }
                if (interimResult != null)
                {
                    tempResult.AddRange(interimResult);
                }
                lastResult = tempResult;
            }


            if (lastResult != null)
            {
                output.AddRange(lastResult);
            }

            if (output.IsNullOrEmpty())
            {
                yield break;
            }
            else
            {
                // Selectors always return in DOM order. Selections may end up in a different order but
                // we always sort here.

                foreach (IDomObject item in output.OrderBy(item => item.Path, StringComparer.Ordinal))
                {
                    yield return(item);
                }
            }
            ActiveSelectors.Clear();
        }
Beispiel #2
0
        /// <summary>
        /// Returns a string representation of the parsed selector. This may not exactly match the input
        /// selector as it is regenerated.
        /// </summary>
        ///
        /// <returns>
        /// A CSS selector string.
        /// </returns>

        public override string ToString()
        {
            string output = "";

            switch (TraversalType)
            {
            case TraversalType.Child:
                output += " > ";
                break;

            case TraversalType.Descendent:
                output += " ";
                break;

            case TraversalType.Adjacent:
                output += " + ";
                break;

            case TraversalType.Sibling:
                output += " ~ ";
                break;
            }

            if (SelectorType.HasFlag(SelectorType.Elements))
            {
                output += "<ElementList[" + SelectElements.Count() + "]> ";
            }
            if (SelectorType.HasFlag(SelectorType.HTML))
            {
                output += "<HTML[" + Html.Length + "]> ";
            }
            if (SelectorType.HasFlag(SelectorType.Tag))
            {
                output += Tag;
            }
            if (SelectorType.HasFlag(SelectorType.ID))
            {
                output += "#" + ID;
            }

            if (SelectorType.HasFlag(SelectorType.AttributeValue)
                //|| SelectorType.HasFlag(SelectorType.AttributeExists)
                )
            {
                output += "[" + AttributeName;
                if (!String.IsNullOrEmpty(AttributeValue))
                {
                    output += "." + AttributeSelectorType.ToString() + ".'" + AttributeValue + "'";
                }
                output += "]";
            }
            if (SelectorType.HasFlag(SelectorType.Class))
            {
                output += "." + Class;
            }
            if (SelectorType.HasFlag(SelectorType.All))
            {
                output += "*";
            }
            if (SelectorType.HasFlag(SelectorType.PseudoClass))
            {
                output += ":" + PseudoSelector.Name;
                if (PseudoSelector.Arguments != null && PseudoSelector.Arguments.Length > 0)
                {
                    output += "(" + String.Join(",", PseudoSelector.Arguments) + ")";
                }
            }

            return(output);
        }
Beispiel #3
0
        public override string ToString()
        {
            string output = "";

            switch (TraversalType)
            {
            case TraversalType.All:
                output = "";
                break;

            case TraversalType.Child:
                output += " > ";
                break;

            case TraversalType.Descendent:
                output += " ";
                break;
            }
            if (SelectorType.HasFlag(SelectorType.Elements))
            {
                output += "<ElementList[" + SelectElements.Count() + "]> ";
            }
            if (SelectorType.HasFlag(SelectorType.HTML))
            {
                output += "<HTML[" + Html.Length + "]> ";
            }
            if (SelectorType.HasFlag(SelectorType.Tag))
            {
                output += Tag;
            }
            if (SelectorType.HasFlag(SelectorType.ID))
            {
                output += "#" + ID;
            }
            if (SelectorType.HasFlag(SelectorType.Attribute))
            {
                output += "[" + AttributeName;
                if (!String.IsNullOrEmpty(AttributeValue))
                {
                    output += "." + AttributeSelectorType.ToString() + ".'" + AttributeValue + "'";
                }
                output += "]";
            }
            if (SelectorType.HasFlag(SelectorType.Class))
            {
                output += "." + Class;
            }
            if (SelectorType.HasFlag(SelectorType.All))
            {
                output += "*";
            }
            if (SelectorType.HasFlag(SelectorType.Position))
            {
                output += ":" + PositionType.ToString();
                if (IsFunction)
                {
                    output += "(" + PositionIndex + ")";
                }
                else if (SubSelectors.Count > 0)
                {
                    output += SubSelectors.ToString();
                }
            }
            if (SelectorType.HasFlag(SelectorType.Contains))
            {
                output += ":contains(" + Criteria + ")";
            }


            return(output);
        }