private static IEnumerable <ICSSFragment> Translate(ICSSFragmentBuilderComponent source, IEnumerable <ContainerFragment.SelectorSet> parentSelectors)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (parentSelectors == null)
            {
                throw new ArgumentNullException("parentSelectors");
            }

            var parentSelectorsArray = parentSelectors.ToArray();

            if (parentSelectorsArray.Any(s => s == null))
            {
                throw new ArgumentException("Null reference encountered in parentSelectors set");
            }

            var styleProperty = source as CSSFragmentBuilderStyleProperty;

            if (styleProperty != null)
            {
                var stylePropertyName = new StylePropertyName(styleProperty.Name, 0);
                return(new ICSSFragment[]
                {
                    stylePropertyName,
                    new StylePropertyValue(
                        stylePropertyName,
                        GetValueSections(styleProperty.Value),
                        0
                        )
                });
            }

            var selectorProperty = source as CSSFragmentBuilderSelector;

            if (selectorProperty != null)
            {
                var          selectors      = selectorProperty.Selectors;
                var          childFragments = selectorProperty.Children.SelectMany(c => Translate(c, parentSelectors.Concat(new[] { selectorProperty.Selectors })));
                ICSSFragment newFragment;
                if (selectors.First().Value.StartsWith("@media", StringComparison.InvariantCultureIgnoreCase))
                {
                    newFragment = new MediaQuery(selectors, parentSelectors, 0, childFragments);
                }
                else
                {
                    newFragment = new Selector(selectors, parentSelectors, 0, childFragments);
                }
                return(new[] { newFragment });
            }

            throw new ArgumentException("Unsupported type: " + source.GetType());
        }
        /// <summary>
        /// This will perform a case-insensitive match against the property name
        /// </summary>
        public static bool HasName(this StylePropertyName source, string value)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            return(source.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
        }
        /// <summary>
        /// This returns the parsed fragments and the number of lines that were processed from the source in doing so
        /// </summary>
        private static Tuple <IEnumerable <ICSSFragment>, int> ParseIntoStructuredData(
            IEnumerator <CategorisedCharacterString> segmentEnumerator,
            IEnumerable <Selector.SelectorSet> parentSelectors,
            int sourceLineIndex,
            CommentHandlingOptions commentHandling)
        {
            if (segmentEnumerator == null)
            {
                throw new ArgumentNullException("segmentEnumerator");
            }
            if (parentSelectors == null)
            {
                throw new ArgumentNullException("parentSelectors");
            }
            if (sourceLineIndex < 0)
            {
                throw new ArgumentNullException("sourceLineIndex", "must be zero or greater");
            }
            if (!Enum.IsDefined(typeof(CommentHandlingOptions), commentHandling))
            {
                throw new ArgumentOutOfRangeException("commentHandling");
            }

            var startingSourceLineIndex = sourceLineIndex;
            var fragments = new List <ICSSFragment>();
            var selectorOrStyleContentBuffer        = new StringBuilder();
            var selectorOrStyleStartSourceLineIndex = -1;
            StylePropertyName lastStylePropertyName = null;
            var stylePropertyValueBuffer            = new PropertyValueBuffer();

            while (segmentEnumerator.MoveNext())
            {
                var segment = segmentEnumerator.Current;
                if (segment == null)
                {
                    throw new ArgumentException("Null reference encountered in segments set");
                }

                switch (segment.CharacterCategorisation)
                {
                case CharacterCategorisationOptions.Comment:
                    if (commentHandling == CommentHandlingOptions.Include)
                    {
                        fragments.Add(new Comment(segment.Value, sourceLineIndex));
                    }
                    sourceLineIndex += GetNumberOfLineReturnsFromContentIfAny(segment.Value);
                    continue;

                case CharacterCategorisationOptions.Whitespace:
                    sourceLineIndex += GetNumberOfLineReturnsFromContentIfAny(segment.Value);
                    if (selectorOrStyleContentBuffer.Length > 0)
                    {
                        selectorOrStyleContentBuffer.Append(" ");
                    }
                    continue;

                case CharacterCategorisationOptions.SelectorOrStyleProperty:
                    if (selectorOrStyleContentBuffer.Length == 0)
                    {
                        selectorOrStyleStartSourceLineIndex = sourceLineIndex;
                    }
                    selectorOrStyleContentBuffer.Append(segment.Value);

                    // If we were building up content for a StylePropertyValue then encountering other content means that the value must have terminated
                    // (for valid CSS it should be only a semicolon or close brace that terminates a value but we're not concerned about invalid CSS here)
                    if (stylePropertyValueBuffer.HasContent)
                    {
                        fragments.Add(stylePropertyValueBuffer.ExtractCombinedContentAndClear());
                    }
                    continue;

                case CharacterCategorisationOptions.OpenBrace:
                    if (selectorOrStyleContentBuffer.Length == 0)
                    {
                        throw new ArgumentException("Encountered OpenBrace with no preceding selector at line " + (sourceLineIndex + 1));
                    }

                    // If we were building up content for a StylePropertyValue then encountering other content means that the value must have terminated
                    // (for valid CSS it should be only a semicolon or close brace that terminates a value but we're not concerned about invalid CSS here)
                    if (stylePropertyValueBuffer.HasContent)
                    {
                        fragments.Add(stylePropertyValueBuffer.ExtractCombinedContentAndClear());
                    }

                    var selectors = GetSelectorSet(selectorOrStyleContentBuffer.ToString());
                    var parsedNestedContentDetails = ParseIntoStructuredData(segmentEnumerator, parentSelectors.Concat(new[] { selectors }), sourceLineIndex, commentHandling);
                    if (selectors.First().Value.StartsWith("@media", StringComparison.InvariantCultureIgnoreCase))
                    {
                        fragments.Add(new MediaQuery(
                                          selectors,
                                          parentSelectors,
                                          selectorOrStyleStartSourceLineIndex,
                                          parsedNestedContentDetails.Item1                       // Item1 are the processed fragments (Item2 is the number of lines processed to extract those fragments)
                                          ));
                    }
                    else
                    {
                        fragments.Add(new Selector(
                                          selectors,
                                          parentSelectors,
                                          selectorOrStyleStartSourceLineIndex,
                                          parsedNestedContentDetails.Item1                       // Item1 are the processed fragments (Item2 is the number of lines processed to extract those fragments)
                                          ));
                    }
                    sourceLineIndex += parsedNestedContentDetails.Item2;                             // Increase sourceLineIndex by the number of lines that the recursive call processed
                    selectorOrStyleContentBuffer.Clear();
                    continue;

                case CharacterCategorisationOptions.CloseBrace:
                    // If we were building up content for a StylePropertyValue then encountering other content means that the value must have terminated
                    // (for valid CSS it should be only a semicolon or close brace that terminates a value but we're not concerned about invalid CSS here)
                    if (stylePropertyValueBuffer.HasContent)
                    {
                        fragments.Add(stylePropertyValueBuffer.ExtractCombinedContentAndClear());
                    }

                    if (selectorOrStyleContentBuffer.Length > 0)
                    {
                        fragments.Add(new StylePropertyName(
                                          selectorOrStyleContentBuffer.ToString(),
                                          selectorOrStyleStartSourceLineIndex
                                          ));
                    }
                    return(Tuple.Create <IEnumerable <ICSSFragment>, int>(fragments, sourceLineIndex - startingSourceLineIndex));

                case CharacterCategorisationOptions.StylePropertyColon:
                case CharacterCategorisationOptions.SemiColon:
                    // If we were building up content for a StylePropertyValue then encountering other content means that the value must have terminated
                    // (for valid CSS it should be only a semicolon or close brace that terminates a value but we're not concerned about invalid CSS here)
                    if (stylePropertyValueBuffer.HasContent)
                    {
                        fragments.Add(stylePropertyValueBuffer.ExtractCombinedContentAndClear());
                    }

                    if (selectorOrStyleContentBuffer.Length > 0)
                    {
                        var selectorOrStyleContent = selectorOrStyleContentBuffer.ToString();
                        if (selectorOrStyleContent.StartsWith("@import", StringComparison.InvariantCultureIgnoreCase))
                        {
                            fragments.Add(new Import(
                                              selectorOrStyleContent.Substring("@import".Length).Trim(),
                                              sourceLineIndex
                                              ));
                            selectorOrStyleContentBuffer.Clear();
                            continue;
                        }

                        // Note: The SemiColon case here probably suggests invalid content, it should only follow a Value segment (ignoring
                        // Comments and WhiteSpace), so if there is anything in the selectorOrStyleContentBuffer before the SemiColon then
                        // it's probably not correct (but we're not validating for that here, we just don't want to throw anything away!)
                        lastStylePropertyName = new StylePropertyName(
                            selectorOrStyleContentBuffer.ToString(),
                            selectorOrStyleStartSourceLineIndex
                            );
                        fragments.Add(lastStylePropertyName);
                        selectorOrStyleContentBuffer.Clear();
                    }
                    continue;

                case CharacterCategorisationOptions.Value:
                    if (selectorOrStyleContentBuffer.Length > 0)
                    {
                        var selectorOrStyleContent = selectorOrStyleContentBuffer.ToString();
                        if (selectorOrStyleContent.StartsWith("@import", StringComparison.InvariantCultureIgnoreCase))
                        {
                            selectorOrStyleContentBuffer.Append(segment.Value);
                            continue;
                        }

                        // This is presumably an error condition, there should be a colon between SelectorOrStyleProperty content and
                        // Value content, but we're not validating here so just lump it all together
                        lastStylePropertyName = new StylePropertyName(
                            selectorOrStyleContentBuffer.ToString(),
                            selectorOrStyleStartSourceLineIndex
                            );
                        fragments.Add(lastStylePropertyName);
                        selectorOrStyleContentBuffer.Clear();
                    }
                    if (lastStylePropertyName == null)
                    {
                        throw new Exception("Invalid content, orphan style property value encountered");
                    }
                    stylePropertyValueBuffer.Add(new StylePropertyValue(
                                                     lastStylePropertyName,
                                                     new[] { segment.Value },
                                                     selectorOrStyleStartSourceLineIndex
                                                     ));
                    continue;

                default:
                    throw new ArgumentException("Unsupported CharacterCategorisationOptions value: " + segment.CharacterCategorisation);
                }
            }

            // If we have any content in the selectorOrStyleContentBuffer and we're hitting a CloseBrace then it's probably invalid content,
            // but just stash it away and move on! (The purpose of this work isn't to get too nuts about invalid CSS).
            if (selectorOrStyleContentBuffer.Length > 0)
            {
                var selectors = GetSelectorSet(selectorOrStyleContentBuffer.ToString());
                if (selectors.First().Value.StartsWith("@media", StringComparison.InvariantCultureIgnoreCase))
                {
                    fragments.Add(new MediaQuery(
                                      selectors,
                                      parentSelectors,
                                      sourceLineIndex,
                                      new ICSSFragment[0]
                                      ));
                }
                else
                {
                    fragments.Add(new Selector(
                                      selectors,
                                      parentSelectors,
                                      sourceLineIndex,
                                      new ICSSFragment[0]
                                      ));
                }
            }

            // It's very feasible that there will still be some style property value content in the buffer at this point, so ensure it
            // doesn't get lost
            if (stylePropertyValueBuffer.HasContent)
            {
                fragments.Add(stylePropertyValueBuffer.ExtractCombinedContentAndClear());
            }

            return(Tuple.Create <IEnumerable <ICSSFragment>, int>(fragments, sourceLineIndex - startingSourceLineIndex));
        }
Example #4
0
 private void Set <T>(StylePropertyName styleName, T value)
 {
     this.s.Set <T>(styleName, value);
 }
        protected void VisitUrlFunction(PrimitiveTerm term)
        {
            string path = (string)term.Value;

            var response = URIHelpers.ValidateAssetURL(assetPath, path);

            if (response.hasWarningMessage)
            {
                m_Errors.AddValidationWarning(response.warningMessage, m_CurrentLine);
            }

            if (response.result != URIValidationResult.OK)
            {
                var(_, message) = ConvertErrorCode(response.result);

                m_Builder.AddValue(path, StyleValueType.MissingAssetReference);
                m_Errors.AddValidationWarning(string.Format(message, response.errorToken), m_CurrentLine);
            }
            else
            {
                var projectRelativePath = response.resolvedProjectRelativePath;
                var subAssetPath        = response.resolvedSubAssetPath;
                var asset = response.resolvedQueryAsset;

                if (asset)
                {
                    if (response.isLibraryAsset)
                    {
                        // do not add path dependencies on assets in the Library folder (e.g. built-in resources)
                        m_Builder.AddValue(asset);
                        return;
                    }

                    // explicit asset reference already loaded
                    m_Context?.DependsOnSourceAsset(projectRelativePath);
                }
                else
                {
                    asset = DeclareDependencyAndLoad(projectRelativePath, subAssetPath);
                }

                bool   isTexture   = asset is Texture2D;
                Sprite spriteAsset = asset as Sprite;

                if (isTexture && string.IsNullOrEmpty(subAssetPath))
                {
                    // Try to load a sprite sub-asset associated with this texture.
                    // Sprites have extra data, such as slices and tight-meshes that
                    // aren't stored in plain textures.
                    spriteAsset = AssetDatabase.LoadAssetAtPath <Sprite>(projectRelativePath);
                }

                if (asset != null)
                {
                    // Looking suffixed images files only
                    if (isTexture)
                    {
                        string hiResImageLocation = URIHelpers.InjectFileNameSuffix(projectRelativePath, "@2x");

                        if (File.Exists(hiResImageLocation))
                        {
                            UnityEngine.Object hiResImage = DeclareDependencyAndLoad(hiResImageLocation);

                            if (hiResImage is Texture2D)
                            {
                                m_Builder.AddValue(new ScalableImage()
                                {
                                    normalImage = asset as Texture2D, highResolutionImage = hiResImage as Texture2D
                                });
                            }
                            else
                            {
                                m_Errors.AddSemanticError(StyleSheetImportErrorCode.InvalidHighResolutionImage, string.Format(glossary.invalidHighResAssetType, asset.GetType().Name, projectRelativePath), m_CurrentLine);
                            }
                            return;
                        }
                        // If we didn't find an high res variant, tell ADB we depend on that potential file existing
                        if (spriteAsset != null)
                        {
                            DeclareDependencyAndLoad(hiResImageLocation);
                        }
                    }

                    Object assetToStore = spriteAsset != null ? spriteAsset : asset;

                    m_Builder.AddValue(assetToStore);

                    if (!disableValidation)
                    {
                        var propertyName = new StylePropertyName(m_Builder.currentProperty.name);

                        // Unknown properties (not custom) should beforehand
                        if (propertyName.id == StylePropertyId.Unknown)
                        {
                            return;
                        }

                        var allowed = StylePropertyUtil.GetAllowedAssetTypesForProperty(propertyName.id);

                        // If no types were returned, it means this property doesn't support assets.
                        // Normal syntax validation should cover this.
                        if (!allowed.Any())
                        {
                            return;
                        }

                        Type assetType = assetToStore.GetType();

                        // If none of the allowed types are compatible with the asset type, output a warning
                        if (!allowed.Any(t => t.IsAssignableFrom(assetType)))
                        {
                            string allowedTypes = string.Join(", ", allowed.Select(t => t.Name));
                            m_Errors.AddValidationWarning(
                                string.Format(glossary.invalidAssetType, assetType.Name, projectRelativePath, allowedTypes),
                                m_CurrentLine);
                        }
                    }
                }
                else
                {
                    // Asset is actually missing OR we couldn't load it for some reason; this should result in
                    // response.result != URIValidationResult.OK (above) but if assets are deleted while Unity is
                    // already open, we fall in here instead.
                    var(_, message) = ConvertErrorCode(URIValidationResult.InvalidURIProjectAssetPath);

                    // In case of error, we still want to call AddValue, with parameters to indicate the problem, in order
                    // to keep the full layout from being discarded. We also add appropriate warnings to explain to the
                    // user what is wrong.
                    m_Builder.AddValue(path, StyleValueType.MissingAssetReference);
                    m_Errors.AddValidationWarning(string.Format(message, path), m_CurrentLine);
                }
            }
        }
Example #6
0
        // https://drafts.csswg.org/css-transitions/#transition-shorthand-property
        // [ none | <single-transition-property> ] || <time> || <easing-function> || <time>
        private static void CompileTransition(StylePropertyReader reader, out List <TimeValue> outDelay, out List <TimeValue> outDuration,
                                              out List <StylePropertyName> outProperty, out List <EasingFunction> outTimingFunction)
        {
            s_TransitionDelayList.Clear();
            s_TransitionDurationList.Clear();
            s_TransitionPropertyList.Clear();
            s_TransitionTimingFunctionList.Clear();

            bool isValid         = true;
            bool noneFound       = false;
            var  valueCount      = reader.valueCount;
            int  transitionCount = 0;
            int  i = 0;

            do
            {
                // If none is present and there are more transitions the shorthand is considered invalid
                if (noneFound)
                {
                    isValid = false;
                    break;
                }

                var transitionProperty       = InitialStyle.transitionProperty[0];
                var transitionDuration       = InitialStyle.transitionDuration[0];
                var transitionDelay          = InitialStyle.transitionDelay[0];
                var transitionTimingFunction = InitialStyle.transitionTimingFunction[0];

                bool durationFound       = false;
                bool delayFound          = false;
                bool propertyFound       = false;
                bool timingFunctionFound = false;
                bool commaFound          = false;
                for (; i < valueCount && !commaFound; ++i)
                {
                    var valueType = reader.GetValueType(i);
                    switch (valueType)
                    {
                    case StyleValueType.Keyword:
                        if (reader.IsKeyword(i, StyleValueKeyword.None) && transitionCount == 0)
                        {
                            noneFound          = true;
                            propertyFound      = true;
                            transitionProperty = new StylePropertyName("none");
                        }
                        else
                        {
                            isValid = false;
                        }
                        break;

                    case StyleValueType.Dimension:
                        var time = reader.ReadTimeValue(i);
                        if (!durationFound)
                        {
                            // transition-duration
                            durationFound      = true;
                            transitionDuration = time;
                        }
                        else if (!delayFound)
                        {
                            // transition-delay
                            delayFound      = true;
                            transitionDelay = time;
                        }
                        else
                        {
                            isValid = false;
                        }
                        break;

                    case StyleValueType.Enum:
                        var str = reader.ReadAsString(i);
                        if (!timingFunctionFound && StylePropertyUtil.TryGetEnumIntValue(StyleEnumType.EasingMode, str, out var intValue))
                        {
                            // transition-timing-function
                            timingFunctionFound      = true;
                            transitionTimingFunction = (EasingMode)intValue;
                        }
                        else if (!propertyFound)
                        {
                            // transition-property
                            propertyFound      = true;
                            transitionProperty = new StylePropertyName(str);
                        }
                        else
                        {
                            isValid = false;
                        }
                        break;

                    case StyleValueType.CommaSeparator:
                        commaFound = true;
                        ++transitionCount;
                        break;

                    default:
                        isValid = false;
                        break;
                    }
                }

                s_TransitionDelayList.Add(transitionDelay);
                s_TransitionDurationList.Add(transitionDuration);
                s_TransitionPropertyList.Add(transitionProperty);
                s_TransitionTimingFunctionList.Add(transitionTimingFunction);
            }while (i < valueCount && isValid);

            if (isValid)
            {
                outProperty       = s_TransitionPropertyList;
                outDelay          = s_TransitionDelayList;
                outDuration       = s_TransitionDurationList;
                outTimingFunction = s_TransitionTimingFunctionList;
            }
            else
            {
                outProperty       = InitialStyle.transitionProperty;
                outDelay          = InitialStyle.transitionDelay;
                outDuration       = InitialStyle.transitionDuration;
                outTimingFunction = InitialStyle.transitionTimingFunction;
            }
        }