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