Beispiel #1
0
        /// <summary>Reads a single selector matcher. E.g. :hover or .test</summary>
        public static SelectorMatcher ReadSelectorMatcher(Css.Value value)
        {
            if (value == null)
            {
                return(null);
            }

            if (value.GetType() == typeof(SquareBracketUnit))
            {
                // [attr]. Make the match object:
                return((value as SquareBracketUnit).GetMatch());
            }

            // Selector fragment.
            string      text   = value[0].Text;
            RootMatcher result = null;

            if (text == "#")
            {
                // ID selector next.
                string id = value[1].Text;

                // Create ID root:
                result      = new RootIDMatcher();
                result.Text = id;
                return(result);
            }
            else if (text == ".")
            {
                // Class selector next.
                string className = value[1].Text;

                // Create class root:
                result      = new RootClassMatcher();
                result.Text = className;
                return(result);
            }
            else if (text == "*")
            {
                // Everything:
                return(new RootUniversalMatcher());
            }
            else if (text == ":")
            {
                // Pseudo class or function next. May also be another colon.
                // after and before map to actual style properties.

                Css.Value current = value[1];

                if (current != null && !current.IsFunction)
                {
                    string keywordText = current.Text;

                    // Second colon?
                    if (keywordText == ":")
                    {
                        // Skip the double colon.
                        current     = value[2];
                        keywordText = current.Text;
                    }

                    // Keyword?
                    current = CssKeywords.Get(keywordText.ToLower());
                }

                if (current == null)
                {
                    // Selector failure - return a blank one.
                    return(null);
                }

                // Convert the value into a matcher:
                return(current.GetSelectorMatcher());
            }

            // It's just a tag:
            string tag = text.ToLower();

            // Create tag root:
            result      = new RootTagMatcher();
            result.Text = tag;
            return(result);
        }
Beispiel #2
0
        private static Selector LoadSingleSelector(StyleSheet sheet, Css.Value value, List <Selector> all)
        {
            // Create the selector:
            Selector selector = new Selector();

            selector.Value = value;

            if (sheet != null)
            {
                // Default NS:
                selector.Namespace = sheet.Namespace;
            }

            List <LocalMatcher> locals      = null;
            List <RootMatcher>  roots       = new List <RootMatcher>();
            RootMatcher         currentRoot = null;

            // Current state:
            bool            nextTarget    = false;
            bool            wasWhitespace = false;
            int             max           = value.Count;
            SelectorMatcher addMatcher    = null;
            RootMatcher     addRoot       = null;

            // For each selector fragment..
            for (int i = 0; i < max; i++)
            {
                Css.Value current = value[i];

                if (current == null)
                {
                    // Happens right at the end where the block was.
                    continue;
                }

                if (current.GetType() == typeof(SquareBracketUnit))
                {
                    // [attr]
                    SquareBracketUnit attrib = current as SquareBracketUnit;

                    // Make the match object:
                    AttributeMatch match = attrib.GetMatch();

                    addMatcher = match;
                }
                else if (current.Type == ValueType.Text)
                {
                    // Selector fragment.
                    string text = current.Text;

                    if (text == " ")
                    {
                        wasWhitespace = true;
                        continue;
                    }

                    if (text == "#")
                    {
                        // ID selector next.
                        i++;
                        string id = value[i].Text;

                        // Create ID root:
                        addRoot      = new RootIDMatcher();
                        addRoot.Text = id;
                    }
                    else if (text == ".")
                    {
                        // Class selector next.
                        i++;
                        string className = value[i].Text;

                        // Create class root:
                        addRoot      = new RootClassMatcher();
                        addRoot.Text = className;

                        // Note that if you chain multiple class selectors (.a.b)
                        // This is called twice and PreviousMatcher/NextMatcher remain null.
                        // It then simply doesn't change element (which is what we want!).
                    }
                    else if (text == "*")
                    {
                        // Apply last locals:
                        if (currentRoot != null && locals != null)
                        {
                            currentRoot.SetLocals(locals);
                            locals.Clear();
                        }

                        // Everything:
                        currentRoot = CreateUniversal(roots);
                    }
                    else if (text == ":")
                    {
                        // Pseudo class or function next. May also be another colon.
                        // after and before map to actual style properties.

                        i++;

                        current = value[i];

                        if (current != null && !current.IsFunction)
                        {
                            string keywordText = current.Text;

                            // Second colon?
                            if (keywordText == ":")
                            {
                                // Skip the double colon.
                                i++;
                                current     = value[i];
                                keywordText = current.Text;
                            }

                            // Keyword?
                            current = CssKeywords.Get(keywordText.ToLower());
                        }

                        if (current == null)
                        {
                            // Selector failure - return a blank one.
                            if (all != null)
                            {
                                all.Clear();
                            }

                            return(null);
                        }

                        // Convert the value into a matcher:
                        SelectorMatcher sm = current.GetSelectorMatcher();

                        if (sm == null)
                        {
                            // Selector failure - return a blank one.
                            if (all != null)
                            {
                                all.Clear();
                            }

                            return(null);
                        }

                        // Add it!
                        addMatcher = sm;
                    }
                    else if (text == ">")
                    {
                        // Following "thing" being a direct child.
                        addMatcher = new DirectParentMatch();
                    }
                    else if (text == ">>")
                    {
                        // Following "thing" being a descendant (same as space).
                        addMatcher = new ParentMatch();
                    }
                    else if (text == "|")
                    {
                        // Namespace declaration was before.

                        // Selector's namespace name is the latest root:
                        string nsName = currentRoot.Text;

                        // Pop the root:
                        roots.RemoveAt(roots.Count - 1);

                        if (roots.Count > 0)
                        {
                            currentRoot = roots[roots.Count - 1];
                        }
                        else
                        {
                            currentRoot = null;
                        }

                        // Note: It's null for * which we can just ignore.
                        if (nsName != null && sheet != null && sheet.Namespaces != null)
                        {
                            // Get the namespace:
                            CssNamespace ns;
                            sheet.Namespaces.TryGetValue(nsName.ToLower(), out ns);
                            selector.Namespace = ns;
                        }
                    }
                    else if (text == "!")
                    {
                        // CSS selectors level 4 selector target.
                        nextTarget = true;
                        continue;
                    }
                    else if (text == "+")
                    {
                        // Whenever this follows the following "thing" directly.

                        addMatcher = new DirectPreviousSiblingMatch();
                    }
                    else if (text == "~")
                    {
                        // Whenever this follows the following "thing".

                        addMatcher = new PreviousSiblingMatch();
                    }
                    else
                    {
                        // It's just a tag:
                        string tag = text.ToLower();

                        // Create tag root:
                        addRoot      = new RootTagMatcher();
                        addRoot.Text = tag;
                    }
                }

                if (addRoot != null)
                {
                    if (currentRoot != null && locals != null)
                    {
                        currentRoot.SetLocals(locals);
                        locals.Clear();
                    }

                    if (currentRoot != null && wasWhitespace && currentRoot.NextMatcher == null)
                    {
                        // Space before it!

                        // Following "thing" being a descendant.
                        currentRoot.NextMatcher          = new ParentMatch();
                        currentRoot.NextMatcher.Selector = selector;

                        wasWhitespace = false;
                    }

                    // Add to root set:
                    roots.Add(addRoot);
                    currentRoot = addRoot;

                    // Set selector:
                    addRoot.Selector = selector;

                    // Clear:
                    addRoot = null;
                }

                if (addMatcher != null)
                {
                    // Always clear whitespace:
                    wasWhitespace = false;

                    // Create implicit *:
                    if (roots.Count == 0)
                    {
                        if (currentRoot != null && locals != null)
                        {
                            currentRoot.SetLocals(locals);
                            locals.Clear();
                        }

                        currentRoot = CreateUniversal(roots);
                    }

                    // Set selector:
                    addMatcher.Selector = selector;

                    if (addMatcher is StructureMatcher)
                    {
                        // Structural matcher:
                        currentRoot.NextMatcher = addMatcher as StructureMatcher;
                    }
                    else if (addMatcher is PseudoSelectorMatch)
                    {
                        // Pseudo matcher:
                        selector.PseudoElement = addMatcher as PseudoSelectorMatch;
                    }
                    else if (addMatcher is LocalMatcher)
                    {
                        if (locals == null)
                        {
                            locals = new List <LocalMatcher>();
                        }

                        // Local matcher:
                        locals.Add(addMatcher as LocalMatcher);
                    }

                    addMatcher = null;
                }

                if (nextTarget && currentRoot != null)
                {
                    nextTarget = false;

                    // Update target:
                    currentRoot.IsTarget = true;
                    selector.Target      = currentRoot;
                }
            }

            // Apply last locals:
            if (currentRoot != null && locals != null)
            {
                currentRoot.SetLocals(locals);
                locals.Clear();
            }

            // Always ensure at least 1:
            if (roots.Count == 0)
            {
                // *:
                CreateUniversal(roots);
            }

            // Update selector info now:
            selector.Roots     = roots.ToArray();
            selector.RootCount = roots.Count;
            selector.LastRoot  = roots[roots.Count - 1];
            selector.FirstRoot = roots[0];

            // Set target if needed:
            if (selector.Target == null)
            {
                // Last root is the target:
                selector.Target          = selector.LastRoot;
                selector.Target.IsTarget = true;
            }

            // Go through them backwards applying PreviousMatcher too:
            // (Not including 0).
            for (int i = roots.Count - 1; i > 0; i--)
            {
                // Hook up:
                roots[i].PreviousMatcher = roots[i - 1].NextMatcher;
            }

            if (sheet != null)
            {
                // Compute specifity now:
                selector.GetSpecifity(sheet.Priority);
            }

            // Add to all set, if there is one:
            if (all != null && all.Count == 0)
            {
                all.Add(selector);
            }

            return(selector);
        }
Beispiel #3
0
        /// <summary>Reads a single value from the stream.</summary>
        public Css.Value ReadSingleValue()
        {
            Css.Value result = null;

            // Skip whitespaces.
            SkipJunk();

            char current = Peek();

            if (SelectorMode && current == '.')
            {
                Read();
                // Class selector.
                return(new TextUnit("."));
            }

            int charCode = (int)current;

            CssUnitHandlers set;

            if (CssUnits.AllStart.TryGetValue(current, out set))
            {
                // Match on the pretext:
                result = set.Handle(this, false);

                if (result != null)
                {
                    // Read the value:
                    result = result.ReadStartValue(this);

                    if (result == null)
                    {
                        return(null);
                    }

                    // Call ready:
                    result.OnValueReady(this);

                    // Ok!
                    return(result);
                }
            }

            // Number:
            int numberStart = Position;

            while (charCode == (int)'-' || charCode == '+' || (charCode >= (int)'0' && charCode <= (int)'9') || charCode == (int)'.')
            {
                if (numberStart == Position)
                {
                    // Special case if it's just +, . or -
                    // Prefixed keywords fall through here too.

                    if (charCode == (int)'.')
                    {
                        // Must be 0-9 next:
                        int next = (int)Peek(1);

                        if (next < (int)'0' || next > (int)'9')
                        {
                            // Non-numeric.
                            break;
                        }
                    }
                    else if (charCode == (int)'+' || charCode == (int)'-')
                    {
                        // Peek the next char. It must be either . or 0-9:
                        int next = (int)Peek(1);

                        if (next != (int)'.' && (next < (int)'0' || next > (int)'9'))
                        {
                            // Non-numeric.
                            break;
                        }
                    }
                }

                // Read off:
                Read();

                // Go to the next one:
                current = Peek();

                // Get the code:
                charCode = (int)current;
            }

            if (numberStart != Position)
            {
                // Read a number of some kind. Now look for the units, if there are any.

                string num = Input.Substring(numberStart, Position - numberStart);

                if (CssUnits.AllEnd.TryGetValue(current, out set) && !SelectorMode)
                {
                    // Units handler is likely!
                    result = set.Handle(this, true);

                    if (result != null)
                    {
                        // Return the result:
                        result = result.Copy();

                        // Apply the number:
                        float flt;
                        float.TryParse(num, out flt);

                        // Set:
                        result.SetRawDecimal(flt);

                        // Call ready:
                        result.OnValueReady(this);

                        return(result);
                    }
                }
                else
                {
                    // Apply the number:
                    float nFlt;
                    float.TryParse(num, out nFlt);

                    // Create:
                    result = new DecimalUnit();

                    // Set:
                    result.SetRawDecimal(nFlt);

                    // Call ready:
                    result.OnValueReady(this);

                    return(result);
                }
            }

            if (charCode == '\\')
            {
                string characters = "";

                while (true)
                {
                    // Special case here; we have one or more unicode escaped characters.
                    Read();
                    current  = Peek();
                    charCode = (int)current;
                    int res = 0;

                    for (int i = 0; i < 6; i++)
                    {
                        if (charCode >= (int)'0' && charCode <= (int)'9')
                        {
                            Read();
                            // Apply to charcode:
                            res = (res << 4) | (charCode - (int)'0');

                            // Move on:
                            current  = Peek();
                            charCode = (int)current;
                        }
                        else if (charCode >= (int)'a' && charCode <= (int)'f')
                        {
                            Read();

                            // Apply to charcode:
                            res = (res << 4) | (charCode + 10 - (int)'a');

                            // Move on:
                            current  = Peek();
                            charCode = (int)current;
                        }
                        else
                        {
                            // No longer valid unicode.
                            break;
                        }
                    }

                    characters += char.ConvertFromUtf32(res);

                    if (charCode != '\\')
                    {
                        // Go again otherwise!
                        break;
                    }
                }

                return(new TextUnit(characters));
            }

            // E.g. textual keyword or function comes down here.
            // If we spot a (, call ReadValue() to get the parameter set.
            int textStart = Position;

            while (charCode != 0 && (charCode > 128 || charCode == 45 || charCode == 40 || (95 <= charCode && charCode <= 122) ||
                                     (65 <= charCode && charCode <= 90) || (48 <= charCode && charCode <= 57))
                   )
            {
                if (current == '(')
                {
                    // Got a function name (if there is one).
                    string name = Input.Substring(textStart, Position - textStart);
                    result = ReadFunction(name);
                    return(result);
                }

                Read();
                current  = Peek();
                charCode = (int)current;
            }

            if (textStart == Position && charCode != 0)
            {
                // The only thing we've read is a delimiter.

                if (current == '\r' || current == '\n' || current == ')' || current == ' ' ||
                    current == '}' || current == '{' || current == ';' || current == ','
                    )
                {
                    // Handled elsewhere. Ignore these.
                    // They all terminate upper level value readers (or are junk!).
                }
                else
                {
                    // Add the single character into the buffer.
                    Read();

                    if (current == '$' || current == '*' || current == '^' || current == '~' || current == '|')
                    {
                        // Followed by equals?
                        if (Peek() == '=')
                        {
                            // Yep - include that in this same unit.
                            Read();

                            return(new TextUnit(current + "="));
                        }
                    }
                    else if (current == '>')
                    {
                        // Followed by another?
                        if (Peek() == '>')
                        {
                            // Yep - include that in this same unit.
                            Read();
                            return(new TextUnit(">>"));
                        }
                    }

                    return(new TextUnit(current.ToString()));
                }
            }

            // Text or keyword.
            string text = Input.Substring(textStart, Position - textStart);

            // Must not match keywords/ colours when in selector mode
            // (because keywords are always lowercase and colours act like *):
            if (SelectorMode)
            {
                // Just text:
                return(new TextUnit(text));
            }

            // Keyword tests.
            string keyword = text.ToLower();

            // Colour?
            bool wasColour;

            UnityEngine.Color32 col = Css.ColourMap.GetColourByName(keyword, out wasColour);

            if (wasColour)
            {
                // It's a colour:
                ColourUnit cResult = new ColourUnit(col);

                // Call ready:
                cResult.OnValueReady(this);

                return(cResult);
            }

            // Global keyword?
            result = CssKeywords.Get(keyword);

            if (result != null)
            {
                // Keyword! Can't share the global instance because of specifity.
                return(result.Copy());
            }

            // Just treat as some text otherwise:
            return(new TextUnit(text));
        }