/// <summary>
        /// Map/collect on a given property
        /// </summary>
        /// <param name="enumerableInput">The enumerable.</param>
        /// <param name="property">The property to map.</param>
        public static IEnumerable Map(IEnumerable enumerableInput, string property)
        {
            if (enumerableInput == null)
            {
                return(null);
            }

            // Enumerate to a list so we can repeatedly parse through the collection.
            List <object> listedInput = enumerableInput.Cast <object>().ToList();

            // If the list happens to be empty we are done already.
            if (!listedInput.Any())
            {
                return(listedInput);
            }

            // Note that liquid assumes that contained complex elements are all following the same schema.
            // Hence here we only check if the first element has the property requested for the map.
            if (listedInput.All(element => element is IDictionary) &&
                ((IDictionary)listedInput.First()).Contains(key: property))
            {
                return(listedInput.Select(element => ((IDictionary)element)[property]));
            }

            return(listedInput
                   .Select(selector: element =>
            {
                if (element == null)
                {
                    return null;
                }

                var indexable = element as IIndexable;
                if (indexable == null)
                {
                    var type = element.GetType();
                    var safeTypeTransformer = Template.GetSafeTypeTransformer(type);
                    if (safeTypeTransformer != null)
                    {
                        indexable = safeTypeTransformer(element) as DropBase;
                    }
                    else
                    {
                        var liquidTypeAttribute = type
                                                  .GetTypeInfo()
                                                  .GetCustomAttributes(attributeType: typeof(LiquidTypeAttribute), inherit: false)
                                                  .FirstOrDefault() as LiquidTypeAttribute;
                        if (liquidTypeAttribute != null)
                        {
                            indexable = new DropProxy(element, liquidTypeAttribute.AllowedMembers);
                        }
                        else if (TypeUtility.IsAnonymousType(type))
                        {
                            return element.RespondTo(property) ? element.Send(property) : element;
                        }
                    }
                }

                return (indexable?.ContainsKey(property) ?? false) ? indexable[property] : null;
            }));
        }
Beispiel #2
0
        /// <summary>
        /// Resolves namespaced queries gracefully.
        ///
        /// Example
        ///
        /// @context['hash'] = {"name" => 'tobi'}
        /// assert_equal 'tobi', @context['hash.name']
        /// assert_equal 'tobi', @context['hash["name"]']
        /// </summary>
        /// <param name="markup"></param>
        /// <returns></returns>
        private object Variable(string markup)
        {
            List <string> parts           = R.Scan(markup, Liquid.VariableParser);
            Regex         squareBracketed = new Regex(R.Q(@"^\[(.*)\]$"));

            string firstPart = parts.Shift();
            Match  firstPartSquareBracketedMatch = squareBracketed.Match(firstPart);

            if (firstPartSquareBracketedMatch.Success)
            {
                firstPart = Resolve(firstPartSquareBracketedMatch.Groups[1].Value).ToString();
            }

            object @object = FindVariable(firstPart);

            if (@object != null)
            {
                foreach (string forEachPart in parts)
                {
                    Match partSquareBracketedMatch = squareBracketed.Match(forEachPart);
                    bool  partResolved             = partSquareBracketedMatch.Success;

                    object part = forEachPart;
                    if (partResolved)
                    {
                        part = Resolve(partSquareBracketedMatch.Groups[1].Value);
                    }

                    var partLowcase = (part is string?((string)part).ToLower() : "");   //$ ToLower() supports non-ruby language case style

                    // If object is a KeyValuePair, we treat it a bit differently - we might be rendering
                    // an included template.
                    if (@object is KeyValuePair <string, object> && ((KeyValuePair <string, object>)@object).Key == (string)part)
                    {
                        object res = ((KeyValuePair <string, object>)@object).Value;
                        @object = Liquidize(res);
                    }
                    // If object is a hash- or array-like object we look for the
                    // presence of the key and if its available we return it
                    else if (IsHashOrArrayLikeObject(@object, part))
                    {
                        // If its a proc we will replace the entry with the proc
                        object res = LookupAndEvaluate(@object, part);
                        @object = Liquidize(res);
                    }
                    // Some special cases. If the part wasn't in square brackets and
                    // no key with the same name was found we interpret following calls
                    // as commands and call them on the current object
                    else if (!partResolved && (@object is IEnumerable) &&
                             Instrumind.Common.General.IsOneOf(partLowcase, "size", "any", "first", "last"))
                    {
                        var castCollection = ((IEnumerable)@object).Cast <object>();
                        if (partLowcase == "size")
                        {
                            @object = castCollection.Count();
                        }
                        else if (partLowcase == "any")
                        {
                            @object = castCollection.Any();
                        }
                        else if (partLowcase == "first")
                        {
                            @object = castCollection.FirstOrDefault();
                        }
                        else if (partLowcase == "last")
                        {
                            @object = castCollection.LastOrDefault();
                        }
                    }
                    else    //$ force extraction
                    {
                        @object = DropProxy.ExtractValue(@object, forEachPart);
                    }

                    // If we are dealing with a drop here we have to
                    if (@object is IContextAware)
                    {
                        ((IContextAware)@object).Context = this;
                    }
                }
            }

            return(@object);
        }