/// <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; })); }
/// <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); }