Example #1
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 != null || dictionaryObjectType != 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));

            void BuildTable(T source, TextWriter writer)
            {
                var(rowData, remainingCount) = getValues(source)
                                               .Cast <object>()
                                               .Select((v, i) => (v, i))
                                               .TakeAndCountRemaining(Formatter.ListExpansionLimit);

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

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

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

                    var destructured = destructurer.Destructure(value);

                    if (!typesAreDifferent && value is {})
                    {
                        types.Add(value.GetType());

                        typesAreDifferent = types.Count > 1;
                    }

                    foreach (var pair in destructured)
                    {
                        valuesByHeader
                        .GetOrAdd(pair.Key, key => new Dictionary <int, object>())
                        .Add(index, pair.Value);
                    }
                }
Example #2
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);
                    }
        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);
            }
        }