aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Utils/DataHelper.cs
blob: 6f9b4b22c4f185b41d1fa98bcda2005eb92a2b21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
using Newtonsoft.Json.Linq;
using System.Collections;
using System.Data;
using System.Net;
using System.Text;
using System.Text.Json.Nodes;

namespace Tango.Portal.Chat.Web.Utils
{
    public static class DataHelper
    {
        /// <summary>
        /// Converts a DataTable into a robust Markdown table string.
        /// - HTML-encodes (&, <, >) to avoid HTML injection.
        /// - Replaces '|' with &#124; so it doesn't break cell boundaries.
        /// - Normalizes CR/LF and turns newlines into <br/> so multi-line cells stay in-row.
        /// - Replaces tabs with spaces.
        /// - Optionally truncates very long cells.
        /// </summary>
        public static string ToMarkdownTable(DataTable table, int maxCellChars = 100)
        {
            if (table == null || table.Columns.Count == 0) return string.Empty;

            string Esc(string? s)
            {
                if (string.IsNullOrEmpty(s)) return string.Empty;

                // HTML-encode first (safer in Markdown renderers that allow HTML)
                string t = WebUtility.HtmlEncode(s);

                // Normalize newlines and tabs
                t = t.Replace("\r\n", "\n").Replace("\r", "\n");
                t = t.Replace("\t", "    ");
                t = t.Replace("\n", "<br/>");

                // Escape Markdown table pipes via HTML entity (more reliable than backslash)
                t = t.Replace("|", "&#124;");

                if (maxCellChars > 0 && t.Length > maxCellChars)
                    t = t.Substring(0, maxCellChars) + "…";

                return t;
            }

            var sb = new StringBuilder();

            // Header row
            for (int c = 0; c < table.Columns.Count; c++)
                sb.Append("| ").Append(Esc(table.Columns[c].ColumnName)).Append(' ');
            sb.AppendLine("|");

            // Separator row
            for (int c = 0; c < table.Columns.Count; c++)
                sb.Append("|---");
            sb.AppendLine("|");

            // Data rows
            foreach (DataRow row in table.Rows)
            {
                for (int c = 0; c < table.Columns.Count; c++)
                    sb.Append("| ").Append(Esc(row[c]?.ToString())).Append(' ');
                sb.AppendLine("|");
            }

            return sb.ToString();
        }

        public static object ToPreview(DataTable dt, int maxRows)
        {
            var cols = dt.Columns.Cast<DataColumn>().Select(c => c.ColumnName).ToArray();
            var rows = new List<Dictionary<string, object?>>();
            int count = 0;

            foreach (DataRow r in dt.Rows)
            {
                if (count++ >= maxRows) break;

                var d = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
                foreach (var c in cols)
                {
                    var cell = r[c];
                    d[c] = NormalizeCell(cell);
                }
                rows.Add(d);
            }

            // (Optional but helpful) include the full row count
            return new { columns = cols, rows, totalRows = dt.Rows.Count };
        }

        private static object? NormalizeCell(object? v)
        {
            if (v is null || v is DBNull) return null;

            // Newtonsoft JToken -> System.Text.Json node
            if (v is JToken jt)
            {
                // Preserves arrays/objects rather than stringifying
                return JsonNode.Parse(jt.ToString(Newtonsoft.Json.Formatting.None));
            }

            // Parse JSON strings (dynamic often arrives as text)
            if (v is string s)
            {
                s = s.Trim();
                if ((s.StartsWith("[") && s.EndsWith("]")) || (s.StartsWith("{") && s.EndsWith("}")))
                {
                    try { return JsonNode.Parse(s); } catch { /* fall through */ }
                }
                return s; // plain string
            }

            // Flatten enumerables (but not strings)
            if (v is IEnumerable en && v is not string)
            {
                var list = new List<object?>();
                foreach (var item in en) list.Add(NormalizeCell(item));
                return list;
            }

            // Optional: normalize DateTime -> ISO 8601 for consistency
            if (v is DateTime dt) return dt.ToUniversalTime().ToString("o");

            // Numbers, bools, etc. pass through
            return v;
        }
    }
}