Пример #1
0
        internal static HtmlFormatter <T> CreateForAnyEnumerable(bool includeInternals)
        {
            Func <T, IEnumerable> getKeys   = null;
            Func <T, IEnumerable> getValues = instance => (IEnumerable)instance;

            var dictType =
                typeof(T).GetAllInterfaces()
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary <,>))
                ??
                typeof(T).GetAllInterfaces()
                .FirstOrDefault(i => i == typeof(IDictionary));

            if (dictType is not null)
            {
                var keysProperty = dictType.GetProperty("Keys");
                getKeys = instance => (IEnumerable)keysProperty.GetValue(instance, null);

                var valuesProperty = dictType.GetProperty("Values");
                getValues = instance => (IEnumerable)valuesProperty.GetValue(instance, null);
            }

            return(new HtmlFormatter <T>(BuildTable));

            bool BuildTable(T source, FormatContext context)
            {
                using var _ = context.IncrementTableDepth();

                if (context.TableDepth > 1)
                {
                    HtmlFormatter.FormatAndStyleAsPlainText(source, context);
                    return(true);
                }

                var(rowData, remainingCount) = getValues(source)
                                               .Cast <object>()
                                               .Select((v, i) => (v, i))
                                               .TakeAndCountRemaining(Formatter.ListExpansionLimit);

                if (rowData.Count == 0)
                {
                    context.Writer.Write(i("(empty)"));
                    return(true);
                }

                var valuesByHeader    = new Dictionary <string, Dictionary <int, object> >();
                var headerToSortIndex = new Dictionary <string, (int, int)>();
                var typesAreDifferent = false;
                var types             = new Dictionary <Type, int>();

                foreach (var(value, index) in rowData)
                {
                    IDictionary <string, object> keysAndValues;

                    if (value is { } &&
                        Formatter.GetPreferredFormatterFor(value.GetType(), HtmlFormatter.MimeType) is { } formatter&&
                        formatter.Type == typeof(object))
                    {
                        var destructurer = Destructurer.GetOrCreate(value?.GetType());

                        keysAndValues = destructurer.Destructure(value);
                    }
Пример #2
0
        private static HtmlFormatter <T> CreateForSequence(bool includeInternals)
        {
            Type valueType = null;
            Func <T, IEnumerable> getKeys   = null;
            Func <T, IEnumerable> getValues = instance => (IEnumerable)instance;

            var dictionaryGenericType = typeof(T).GetInterfaces()
                                        .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary <,>));
            var dictionaryObjectType = typeof(T).GetInterfaces()
                                       .FirstOrDefault(i => i == typeof(IDictionary));
            var enumerableGenericType = typeof(T).GetInterfaces()
                                        .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable <>));

            if (dictionaryGenericType != null || dictionaryObjectType != null)
            {
                var keysProperty = typeof(T).GetProperty("Keys");
                getKeys = instance => (IEnumerable)keysProperty.GetValue(instance, null);

                var valuesProperty = typeof(T).GetProperty("Values");
                getValues = instance => (IEnumerable)valuesProperty.GetValue(instance, null);

                if (dictionaryGenericType != null)
                {
                    valueType = typeof(T).GenericTypeArguments[1];
                }
            }
            else if (enumerableGenericType != null)
            {
                if (!enumerableGenericType.IsArray)
                {
                    var genericTypeArguments = typeof(T).GenericTypeArguments;

                    if (genericTypeArguments.Length == 1)
                    {
                        valueType = genericTypeArguments[0];
                    }
                }
            }

            var destructurer = valueType != null
                ? Destructurer.Create(valueType)
                : null;

            return(new HtmlFormatter <T>((instance, writer) =>
            {
                var index = 0;

                IHtmlContent indexHeader = null;

                Func <string> getIndex;

                if (getKeys != null)
                {
                    var keys = new List <string>();
                    foreach (var key in getKeys(instance))
                    {
                        keys.Add(key.ToString());
                    }

                    getIndex = () => keys[index];
                    indexHeader = th(i("key"));
                }
                else
                {
                    getIndex = () => index.ToString();
                    indexHeader = th(i("index"));
                }

                var rows = new List <IHtmlContent>();
                List <IHtmlContent> headers = null;

                var values = getValues(instance);

                foreach (var item in values)
                {
                    if (index < Formatter.ListExpansionLimit)
                    {
                        if (destructurer == null)
                        {
                            if (item != null)
                            {
                                destructurer = Destructurer.Create(item.GetType());
                            }
                            else
                            {
                                destructurer = NonDestructurer.Instance;
                            }
                        }

                        var dictionary = destructurer.Destructure(item);

                        if (headers == null)
                        {
                            headers = new List <IHtmlContent>();
                            headers.Add(indexHeader);
                            headers.AddRange(dictionary.Keys
                                             .Select(k => (IHtmlContent)th(k)));
                        }

                        var cells =
                            new IHtmlContent[]
                        {
                            td(getIndex().ToHtmlContent())
                        }
                        .Concat(
                            dictionary
                            .Values
                            .Select(v => (IHtmlContent)td(v)));

                        rows.Add(tr(cells));

                        index++;
                    }
                    else
                    {
                        var more = values switch
                        {
                            ICollection c => $"({c.Count - index} more)",
                            _ => "(more...)"
                        };

                        rows.Add(tr(td[colspan: $"{headers.Count}"](more)));
                        break;
                    }
                }

                var view = HtmlFormatter.Table(headers, rows);

                view.WriteTo(writer, HtmlEncoder.Default);
            }));
        }
Пример #3
0
        internal static HtmlFormatter <T> CreateForAnyEnumerable(bool includeInternals)
        {
            Func <T, IEnumerable> getKeys   = null;
            Func <T, IEnumerable> getValues = instance => (IEnumerable)instance;

            var dictionaryGenericType = typeof(T).GetAllInterfaces()
                                        .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary <,>));
            var dictionaryObjectType = typeof(T).GetAllInterfaces()
                                       .FirstOrDefault(i => i == typeof(IDictionary));

            if (dictionaryGenericType is not null || dictionaryObjectType is not null)
            {
                var dictType     = (dictionaryGenericType ?? dictionaryObjectType);
                var keysProperty = dictType.GetProperty("Keys");
                getKeys = instance => (IEnumerable)keysProperty.GetValue(instance, null);

                var valuesProperty = dictType.GetProperty("Values");
                getValues = instance => (IEnumerable)valuesProperty.GetValue(instance, null);
            }

            return(new HtmlFormatter <T>(BuildTable));

            bool BuildTable(FormatContext context, T source, TextWriter writer)
            {
                if (context.ContentThreshold < 1.0)
                {
                    // This formatter refuses to produce nested tables.
                    return(false);
                }
                var innerContext = context.ReduceContent(FormatContext.NestedInTable);

                var(rowData, remainingCount) = getValues(source)
                                               .Cast <object>()
                                               .Select((v, i) => (v, i))
                                               .TakeAndCountRemaining(Formatter.ListExpansionLimit);

                if (rowData.Count == 0)
                {
                    writer.Write(i("(empty)"));
                    return(true);
                }

                var  valuesByHeader    = new Dictionary <string, Dictionary <int, object> >();
                var  headerToSortIndex = new Dictionary <string, (int, int)>();
                bool typesAreDifferent = false;
                var  types             = new Dictionary <Type, int>();

                foreach (var(value, index) in rowData)
                {
                    var destructurer = Destructurer.GetOrCreate(value?.GetType());

                    var destructured = destructurer.Destructure(value);

                    if (value is not null)
                    {
                        var type = value.GetType();
                        if (!types.ContainsKey(type))
                        {
                            types.Add(type, types.Count);
                        }

                        typesAreDifferent = types.Count > 1;
                    }

                    var typeIndex = (value is null) ? 0 : types[value.GetType()];

                    var pairIndex = 0;
                    foreach (var pair in destructured)
                    {
                        if (!headerToSortIndex.ContainsKey(pair.Key))
                        {
                            headerToSortIndex.Add(pair.Key, (typeIndex, pairIndex));
                        }

                        valuesByHeader
                        .GetOrAdd(pair.Key, key => new Dictionary <int, object>())
                        .Add(index, pair.Value);
                        pairIndex++;
                    }
                }

                var headers = new List <IHtmlContent>();

                List <object> leftColumnValues;

                if (getKeys is not null)
                {
                    headers.Add(th(i("key")));
                    leftColumnValues = getKeys(source)
                                       .Cast <object>()
                                       .Take(rowData.Count)
                                       .ToList();
                }
                else
                {
                    headers.Add(th(i("index")));
                    leftColumnValues = Enumerable.Range(0, rowData.Count)
                                       .Select(i => str(i.ToString()))
                                       .Cast <object>()
                                       .ToList();
                }

                if (typesAreDifferent)
                {
                    headers.Insert(1, th(i("type")));
                }

                // Order the columns first by the *first* type to exhibit the
                // property, then by the destructuring order within that type.
                var valueKeys =
                    valuesByHeader.Keys
                    .OrderBy(x => headerToSortIndex[x])
                    .ToArray();

                var valueKeysLimited =
                    valueKeys
                    .Take(Math.Max(0, HtmlFormatter.MaxProperties))
                    .ToArray();

                headers.AddRange(valueKeysLimited.Select(k => (IHtmlContent)th(k)));
                if (valueKeysLimited.Length < valueKeys.Length)
                {
                    headers.Add((IHtmlContent)th(".."));
                }

                var rows = new List <IHtmlContent>();

                for (var rowIndex = 0; rowIndex < rowData.Count; rowIndex++)
                {
                    var rowValues = new List <object>
                    {
                        leftColumnValues[rowIndex]
                    };

                    if (typesAreDifferent)
                    {
                        var type = rowData[rowIndex].v?.GetType();

                        rowValues.Add(type);
                    }

                    foreach (var key in valueKeysLimited)
                    {
                        if (valuesByHeader[key].TryGetValue(rowIndex, out var cellData))
                        {
                            rowValues.Add(cellData);
                        }
                        else
                        {
                            rowValues.Add("");
                        }
                    }

                    // Note, embeds the values as arbitrary objects into the HTML content.
                    rows.Add(tr(rowValues.Select(r => td(embed(r, innerContext)))));
                }

                if (remainingCount > 0)
                {
                    var more = $"({remainingCount} more)";

                    rows.Add(tr(td[colspan: $"{headers.Count}"](more)));
                }

                var table = HtmlFormatter.Table(headers, rows);

                table.WriteTo(writer, HtmlEncoder.Default);
                return(true);
            }
        }