| 1 | using System;
 | 
|---|
| 2 | using System.Collections.Generic;
 | 
|---|
| 3 | using System.Linq;
 | 
|---|
| 4 | using System.Text;
 | 
|---|
| 5 | using System.Text.RegularExpressions;
 | 
|---|
| 6 | using AllocsFixes.PersistentData;
 | 
|---|
| 7 | using JetBrains.Annotations;
 | 
|---|
| 8 | using Webserver;
 | 
|---|
| 9 | using Webserver.Permissions;
 | 
|---|
| 10 | using Webserver.WebAPI;
 | 
|---|
| 11 | 
 | 
|---|
| 12 | namespace AllocsFixes.WebAPIs {
 | 
|---|
| 13 |         [UsedImplicitly]
 | 
|---|
| 14 |         public class GetPlayerList : AbsWebAPI {
 | 
|---|
| 15 |                 private static readonly Regex numberFilterMatcher =
 | 
|---|
| 16 |                         new Regex (@"^(>=|=>|>|<=|=<|<|==|=)?\s*([0-9]+(\.[0-9]*)?)$");
 | 
|---|
| 17 | 
 | 
|---|
| 18 | #if ENABLE_PROFILER
 | 
|---|
| 19 |                 private static readonly UnityEngine.Profiling.CustomSampler jsonSerializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Build");
 | 
|---|
| 20 | #endif
 | 
|---|
| 21 | 
 | 
|---|
| 22 |                 public override void HandleRequest (RequestContext _context) {
 | 
|---|
| 23 |                         AdminTools admTools = GameManager.Instance.adminTools;
 | 
|---|
| 24 |                         PlatformUserIdentifierAbs userId = _context.Connection?.UserId;
 | 
|---|
| 25 | 
 | 
|---|
| 26 |                         bool bViewAll = PermissionUtils.CanViewAllPlayers (_context.PermissionLevel);
 | 
|---|
| 27 | 
 | 
|---|
| 28 |                         // TODO: Sort (and filter?) prior to converting to JSON ... hard as how to get the correct column's data? (i.e. column name matches JSON object field names, not source data)
 | 
|---|
| 29 | 
 | 
|---|
| 30 |                         int rowsPerPage = 25;
 | 
|---|
| 31 |                         if (_context.Request.QueryString ["rowsperpage"] != null) {
 | 
|---|
| 32 |                                 int.TryParse (_context.Request.QueryString ["rowsperpage"], out rowsPerPage);
 | 
|---|
| 33 |                         }
 | 
|---|
| 34 | 
 | 
|---|
| 35 |                         int page = 0;
 | 
|---|
| 36 |                         if (_context.Request.QueryString ["page"] != null) {
 | 
|---|
| 37 |                                 int.TryParse (_context.Request.QueryString ["page"], out page);
 | 
|---|
| 38 |                         }
 | 
|---|
| 39 | 
 | 
|---|
| 40 |                         int firstEntry = page * rowsPerPage;
 | 
|---|
| 41 | 
 | 
|---|
| 42 |                         Players playersList = PersistentContainer.Instance.Players;
 | 
|---|
| 43 | 
 | 
|---|
| 44 |                         
 | 
|---|
| 45 |                         List<JSONObject> playerList = new List<JSONObject> ();
 | 
|---|
| 46 | 
 | 
|---|
| 47 | #if ENABLE_PROFILER
 | 
|---|
| 48 |                         jsonSerializeSampler.Begin ();
 | 
|---|
| 49 | #endif
 | 
|---|
| 50 | 
 | 
|---|
| 51 |                         foreach (KeyValuePair<PlatformUserIdentifierAbs, Player> kvp in playersList.Dict) {
 | 
|---|
| 52 |                                 Player p = kvp.Value;
 | 
|---|
| 53 | 
 | 
|---|
| 54 |                                 if (bViewAll || p.InternalId.Equals (userId)) {
 | 
|---|
| 55 |                                         JSONObject pos = new JSONObject ();
 | 
|---|
| 56 |                                         pos.Add ("x", new JSONNumber (p.LastPosition.x));
 | 
|---|
| 57 |                                         pos.Add ("y", new JSONNumber (p.LastPosition.y));
 | 
|---|
| 58 |                                         pos.Add ("z", new JSONNumber (p.LastPosition.z));
 | 
|---|
| 59 | 
 | 
|---|
| 60 |                                         JSONObject pJson = new JSONObject ();
 | 
|---|
| 61 |                                         pJson.Add ("steamid", new JSONString (kvp.Value.PlatformId.CombinedString));
 | 
|---|
| 62 |                                         pJson.Add ("crossplatformid", new JSONString (kvp.Value.CrossPlatformId?.CombinedString ?? ""));
 | 
|---|
| 63 |                                         pJson.Add ("entityid", new JSONNumber (p.EntityID));
 | 
|---|
| 64 |                                         pJson.Add ("ip", new JSONString (p.IP));
 | 
|---|
| 65 |                                         pJson.Add ("name", new JSONString (p.Name));
 | 
|---|
| 66 |                                         pJson.Add ("online", new JSONBoolean (p.IsOnline));
 | 
|---|
| 67 |                                         pJson.Add ("position", pos);
 | 
|---|
| 68 | 
 | 
|---|
| 69 |                                         pJson.Add ("totalplaytime", new JSONNumber (p.TotalPlayTime));
 | 
|---|
| 70 |                                         pJson.Add ("lastonline",
 | 
|---|
| 71 |                                                 new JSONString (p.LastOnline.ToUniversalTime ().ToString ("yyyy-MM-ddTHH:mm:ssZ")));
 | 
|---|
| 72 |                                         pJson.Add ("ping", new JSONNumber (p.IsOnline ? p.ClientInfo.ping : -1));
 | 
|---|
| 73 | 
 | 
|---|
| 74 |                                         JSONBoolean banned = admTools != null ? new JSONBoolean (admTools.Blacklist.IsBanned (kvp.Key, out _, out _)) : new JSONBoolean (false);
 | 
|---|
| 75 | 
 | 
|---|
| 76 |                                         pJson.Add ("banned", banned);
 | 
|---|
| 77 | 
 | 
|---|
| 78 |                                         playerList.Add (pJson);
 | 
|---|
| 79 |                                 }
 | 
|---|
| 80 |                         }
 | 
|---|
| 81 | 
 | 
|---|
| 82 | #if ENABLE_PROFILER
 | 
|---|
| 83 |                         jsonSerializeSampler.End ();
 | 
|---|
| 84 | #endif
 | 
|---|
| 85 | 
 | 
|---|
| 86 |                         IEnumerable<JSONObject> list = playerList;
 | 
|---|
| 87 | 
 | 
|---|
| 88 |                         foreach (string key in _context.Request.QueryString.AllKeys) {
 | 
|---|
| 89 |                                 if (!string.IsNullOrEmpty (key) && key.StartsWith ("filter[")) {
 | 
|---|
| 90 |                                         string filterCol = key.Substring (key.IndexOf ('[') + 1);
 | 
|---|
| 91 |                                         filterCol = filterCol.Substring (0, filterCol.Length - 1);
 | 
|---|
| 92 |                                         string filterVal = _context.Request.QueryString.Get (key).Trim ();
 | 
|---|
| 93 | 
 | 
|---|
| 94 |                                         list = ExecuteFilter (list, filterCol, filterVal);
 | 
|---|
| 95 |                                 }
 | 
|---|
| 96 |                         }
 | 
|---|
| 97 | 
 | 
|---|
| 98 |                         int totalAfterFilter = list.Count ();
 | 
|---|
| 99 | 
 | 
|---|
| 100 |                         foreach (string key in _context.Request.QueryString.AllKeys) {
 | 
|---|
| 101 |                                 if (!string.IsNullOrEmpty (key) && key.StartsWith ("sort[")) {
 | 
|---|
| 102 |                                         string sortCol = key.Substring (key.IndexOf ('[') + 1);
 | 
|---|
| 103 |                                         sortCol = sortCol.Substring (0, sortCol.Length - 1);
 | 
|---|
| 104 |                                         string sortVal = _context.Request.QueryString.Get (key);
 | 
|---|
| 105 | 
 | 
|---|
| 106 |                                         list = ExecuteSort (list, sortCol, sortVal == "0");
 | 
|---|
| 107 |                                 }
 | 
|---|
| 108 |                         }
 | 
|---|
| 109 | 
 | 
|---|
| 110 |                         list = list.Skip (firstEntry);
 | 
|---|
| 111 |                         list = list.Take (rowsPerPage);
 | 
|---|
| 112 | 
 | 
|---|
| 113 | 
 | 
|---|
| 114 |                         JSONArray playersJsResult = new JSONArray ();
 | 
|---|
| 115 |                         foreach (JSONObject jsO in list) {
 | 
|---|
| 116 |                                 playersJsResult.Add (jsO);
 | 
|---|
| 117 |                         }
 | 
|---|
| 118 | 
 | 
|---|
| 119 |                         JSONObject result = new JSONObject ();
 | 
|---|
| 120 |                         result.Add ("total", new JSONNumber (totalAfterFilter));
 | 
|---|
| 121 |                         result.Add ("totalUnfiltered", new JSONNumber (playerList.Count));
 | 
|---|
| 122 |                         result.Add ("firstResult", new JSONNumber (firstEntry));
 | 
|---|
| 123 |                         result.Add ("players", playersJsResult);
 | 
|---|
| 124 | 
 | 
|---|
| 125 |                         StringBuilder sb = new StringBuilder ();
 | 
|---|
| 126 |                         result.ToString (sb);
 | 
|---|
| 127 |                         WebUtils.WriteText (_context.Response, sb.ToString(), _mimeType: WebUtils.MimeJson);
 | 
|---|
| 128 |                 }
 | 
|---|
| 129 | 
 | 
|---|
| 130 |                 private static IEnumerable<JSONObject> ExecuteFilter (IEnumerable<JSONObject> _list, string _filterCol,
 | 
|---|
| 131 |                         string _filterVal) {
 | 
|---|
| 132 |                         if (!_list.Any()) {
 | 
|---|
| 133 |                                 return _list;
 | 
|---|
| 134 |                         }
 | 
|---|
| 135 | 
 | 
|---|
| 136 |                         if (_list.First ().ContainsKey (_filterCol)) {
 | 
|---|
| 137 |                                 Type colType = _list.First () [_filterCol].GetType ();
 | 
|---|
| 138 |                                 if (colType == typeof (JSONNumber)) {
 | 
|---|
| 139 |                                         return ExecuteNumberFilter (_list, _filterCol, _filterVal);
 | 
|---|
| 140 |                                 }
 | 
|---|
| 141 | 
 | 
|---|
| 142 |                                 if (colType == typeof (JSONBoolean)) {
 | 
|---|
| 143 |                                         bool value = StringParsers.ParseBool (_filterVal);
 | 
|---|
| 144 |                                         return _list.Where (_line => ((JSONBoolean) _line [_filterCol]).GetBool () == value);
 | 
|---|
| 145 |                                 }
 | 
|---|
| 146 | 
 | 
|---|
| 147 |                                 if (colType == typeof (JSONString)) {
 | 
|---|
| 148 |                                         // regex-match whole ^string$, replace * by .*, ? by .?, + by .+
 | 
|---|
| 149 |                                         _filterVal = _filterVal.Replace ("*", ".*").Replace ("?", ".?").Replace ("+", ".+");
 | 
|---|
| 150 |                                         _filterVal = $"^{_filterVal}$";
 | 
|---|
| 151 | 
 | 
|---|
| 152 |                                         //Log.Out ("GetPlayerList: Filter on String with Regex '" + _filterVal + "'");
 | 
|---|
| 153 |                                         Regex matcher = new Regex (_filterVal, RegexOptions.IgnoreCase);
 | 
|---|
| 154 |                                         return _list.Where (_line => matcher.IsMatch (((JSONString) _line [_filterCol]).GetString ()));
 | 
|---|
| 155 |                                 }
 | 
|---|
| 156 |                         }
 | 
|---|
| 157 | 
 | 
|---|
| 158 |                         return _list;
 | 
|---|
| 159 |                 }
 | 
|---|
| 160 | 
 | 
|---|
| 161 | 
 | 
|---|
| 162 |                 private static IEnumerable<JSONObject> ExecuteNumberFilter (IEnumerable<JSONObject> _list, string _filterCol,
 | 
|---|
| 163 |                         string _filterVal) {
 | 
|---|
| 164 |                         // allow value (exact match), =, ==, >=, >, <=, <
 | 
|---|
| 165 |                         Match filterMatch = numberFilterMatcher.Match (_filterVal);
 | 
|---|
| 166 |                         if (filterMatch.Success) {
 | 
|---|
| 167 |                                 double value = StringParsers.ParseDouble (filterMatch.Groups [2].Value);
 | 
|---|
| 168 |                                 NumberMatchType matchType;
 | 
|---|
| 169 |                                 double epsilon = value / 100000;
 | 
|---|
| 170 |                                 switch (filterMatch.Groups [1].Value) {
 | 
|---|
| 171 |                                         case "":
 | 
|---|
| 172 |                                         case "=":
 | 
|---|
| 173 |                                         case "==":
 | 
|---|
| 174 |                                                 matchType = NumberMatchType.Equal;
 | 
|---|
| 175 |                                                 break;
 | 
|---|
| 176 |                                         case ">":
 | 
|---|
| 177 |                                                 matchType = NumberMatchType.Greater;
 | 
|---|
| 178 |                                                 break;
 | 
|---|
| 179 |                                         case ">=":
 | 
|---|
| 180 |                                         case "=>":
 | 
|---|
| 181 |                                                 matchType = NumberMatchType.GreaterEqual;
 | 
|---|
| 182 |                                                 break;
 | 
|---|
| 183 |                                         case "<":
 | 
|---|
| 184 |                                                 matchType = NumberMatchType.Lesser;
 | 
|---|
| 185 |                                                 break;
 | 
|---|
| 186 |                                         case "<=":
 | 
|---|
| 187 |                                         case "=<":
 | 
|---|
| 188 |                                                 matchType = NumberMatchType.LesserEqual;
 | 
|---|
| 189 |                                                 break;
 | 
|---|
| 190 |                                         default:
 | 
|---|
| 191 |                                                 matchType = NumberMatchType.Equal;
 | 
|---|
| 192 |                                                 break;
 | 
|---|
| 193 |                                 }
 | 
|---|
| 194 | 
 | 
|---|
| 195 |                                 return _list.Where (delegate (JSONObject _line) {
 | 
|---|
| 196 |                                         double objVal = ((JSONNumber) _line [_filterCol]).GetDouble ();
 | 
|---|
| 197 |                                         switch (matchType) {
 | 
|---|
| 198 |                                                 case NumberMatchType.Greater:
 | 
|---|
| 199 |                                                         return objVal > value;
 | 
|---|
| 200 |                                                 case NumberMatchType.GreaterEqual:
 | 
|---|
| 201 |                                                         return objVal >= value;
 | 
|---|
| 202 |                                                 case NumberMatchType.Lesser:
 | 
|---|
| 203 |                                                         return objVal < value;
 | 
|---|
| 204 |                                                 case NumberMatchType.LesserEqual:
 | 
|---|
| 205 |                                                         return objVal <= value;
 | 
|---|
| 206 |                                                 case NumberMatchType.Equal:
 | 
|---|
| 207 |                                                 default:
 | 
|---|
| 208 |                                                         return NearlyEqual (objVal, value, epsilon);
 | 
|---|
| 209 |                                         }
 | 
|---|
| 210 |                                 });
 | 
|---|
| 211 |                         }
 | 
|---|
| 212 | 
 | 
|---|
| 213 |                         Log.Out ($"GetPlayerList: ignoring invalid filter for number-column '{_filterCol}': '{_filterVal}'");
 | 
|---|
| 214 |                         return _list;
 | 
|---|
| 215 |                 }
 | 
|---|
| 216 | 
 | 
|---|
| 217 | 
 | 
|---|
| 218 |                 private static IEnumerable<JSONObject> ExecuteSort (IEnumerable<JSONObject> _list, string _sortCol, bool _ascending) {
 | 
|---|
| 219 |                         if (_list.Count () == 0) {
 | 
|---|
| 220 |                                 return _list;
 | 
|---|
| 221 |                         }
 | 
|---|
| 222 | 
 | 
|---|
| 223 |                         if (_list.First ().ContainsKey (_sortCol)) {
 | 
|---|
| 224 |                                 Type colType = _list.First () [_sortCol].GetType ();
 | 
|---|
| 225 |                                 if (colType == typeof (JSONNumber)) {
 | 
|---|
| 226 |                                         if (_ascending) {
 | 
|---|
| 227 |                                                 return _list.OrderBy (_line => ((JSONNumber) _line [_sortCol]).GetDouble ());
 | 
|---|
| 228 |                                         }
 | 
|---|
| 229 | 
 | 
|---|
| 230 |                                         return _list.OrderByDescending (_line => ((JSONNumber) _line [_sortCol]).GetDouble ());
 | 
|---|
| 231 |                                 }
 | 
|---|
| 232 | 
 | 
|---|
| 233 |                                 if (colType == typeof (JSONBoolean)) {
 | 
|---|
| 234 |                                         if (_ascending) {
 | 
|---|
| 235 |                                                 return _list.OrderBy (_line => ((JSONBoolean) _line [_sortCol]).GetBool ());
 | 
|---|
| 236 |                                         }
 | 
|---|
| 237 | 
 | 
|---|
| 238 |                                         return _list.OrderByDescending (_line => ((JSONBoolean) _line [_sortCol]).GetBool ());
 | 
|---|
| 239 |                                 }
 | 
|---|
| 240 | 
 | 
|---|
| 241 |                                 if (_ascending) {
 | 
|---|
| 242 |                                         return _list.OrderBy (_line => _line [_sortCol].ToString ());
 | 
|---|
| 243 |                                 }
 | 
|---|
| 244 | 
 | 
|---|
| 245 |                                 return _list.OrderByDescending (_line => _line [_sortCol].ToString ());
 | 
|---|
| 246 |                         }
 | 
|---|
| 247 | 
 | 
|---|
| 248 |                         return _list;
 | 
|---|
| 249 |                 }
 | 
|---|
| 250 | 
 | 
|---|
| 251 | 
 | 
|---|
| 252 |                 private static bool NearlyEqual (double _a, double _b, double _epsilon) {
 | 
|---|
| 253 |                         double absA = Math.Abs (_a);
 | 
|---|
| 254 |                         double absB = Math.Abs (_b);
 | 
|---|
| 255 |                         double diff = Math.Abs (_a - _b);
 | 
|---|
| 256 | 
 | 
|---|
| 257 |                         if (_a == _b) {
 | 
|---|
| 258 |                                 return true;
 | 
|---|
| 259 |                         }
 | 
|---|
| 260 | 
 | 
|---|
| 261 |                         if (_a == 0 || _b == 0 || diff < double.Epsilon) {
 | 
|---|
| 262 |                                 return diff < _epsilon;
 | 
|---|
| 263 |                         }
 | 
|---|
| 264 | 
 | 
|---|
| 265 |                         return diff / (absA + absB) < _epsilon;
 | 
|---|
| 266 |                 }
 | 
|---|
| 267 | 
 | 
|---|
| 268 |                 private enum NumberMatchType {
 | 
|---|
| 269 |                         Equal,
 | 
|---|
| 270 |                         Greater,
 | 
|---|
| 271 |                         GreaterEqual,
 | 
|---|
| 272 |                         Lesser,
 | 
|---|
| 273 |                         LesserEqual,
 | 
|---|
| 274 |                 }
 | 
|---|
| 275 | 
 | 
|---|
| 276 | 
 | 
|---|
| 277 | #region JSON encoder
 | 
|---|
| 278 | 
 | 
|---|
| 279 |                 private abstract class JSONNode {
 | 
|---|
| 280 |                         public abstract void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0);
 | 
|---|
| 281 | 
 | 
|---|
| 282 |                         public override string ToString () {
 | 
|---|
| 283 |                                 StringBuilder sb = new StringBuilder ();
 | 
|---|
| 284 |                                 ToString (sb);
 | 
|---|
| 285 |                                 return sb.ToString ();
 | 
|---|
| 286 |                         }
 | 
|---|
| 287 |                 }
 | 
|---|
| 288 | 
 | 
|---|
| 289 |                 private abstract class JSONValue : JSONNode {
 | 
|---|
| 290 |                 }
 | 
|---|
| 291 | 
 | 
|---|
| 292 |                 private class JSONNull : JSONValue {
 | 
|---|
| 293 |                         public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
 | 
|---|
| 294 |                                 _stringBuilder.Append ("null");
 | 
|---|
| 295 |                         }
 | 
|---|
| 296 |                 }
 | 
|---|
| 297 | 
 | 
|---|
| 298 |                 private class JSONBoolean : JSONValue {
 | 
|---|
| 299 |                         private readonly bool value;
 | 
|---|
| 300 | 
 | 
|---|
| 301 |                         public JSONBoolean (bool _value) {
 | 
|---|
| 302 |                                 value = _value;
 | 
|---|
| 303 |                         }
 | 
|---|
| 304 | 
 | 
|---|
| 305 |                         public bool GetBool () {
 | 
|---|
| 306 |                                 return value;
 | 
|---|
| 307 |                         }
 | 
|---|
| 308 | 
 | 
|---|
| 309 |                         public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
 | 
|---|
| 310 |                                 _stringBuilder.Append (value ? "true" : "false");
 | 
|---|
| 311 |                         }
 | 
|---|
| 312 |                 }
 | 
|---|
| 313 | 
 | 
|---|
| 314 |                 private class JSONNumber : JSONValue {
 | 
|---|
| 315 |                         private readonly double value;
 | 
|---|
| 316 | 
 | 
|---|
| 317 |                         public JSONNumber (double _value) {
 | 
|---|
| 318 |                                 value = _value;
 | 
|---|
| 319 |                         }
 | 
|---|
| 320 | 
 | 
|---|
| 321 |                         public double GetDouble () {
 | 
|---|
| 322 |                                 return value;
 | 
|---|
| 323 |                         }
 | 
|---|
| 324 | 
 | 
|---|
| 325 |                         public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
 | 
|---|
| 326 |                                 _stringBuilder.Append (value.ToCultureInvariantString ());
 | 
|---|
| 327 |                         }
 | 
|---|
| 328 |                 }
 | 
|---|
| 329 | 
 | 
|---|
| 330 |                 private class JSONString : JSONValue {
 | 
|---|
| 331 |                         private readonly string value;
 | 
|---|
| 332 | 
 | 
|---|
| 333 |                         public JSONString (string _value) {
 | 
|---|
| 334 |                                 value = _value;
 | 
|---|
| 335 |                         }
 | 
|---|
| 336 | 
 | 
|---|
| 337 |                         public string GetString () {
 | 
|---|
| 338 |                                 return value;
 | 
|---|
| 339 |                         }
 | 
|---|
| 340 | 
 | 
|---|
| 341 |                         public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
 | 
|---|
| 342 |                                 if (string.IsNullOrEmpty (value)) {
 | 
|---|
| 343 |                                         _stringBuilder.Append ("\"\"");
 | 
|---|
| 344 |                                         return;
 | 
|---|
| 345 |                                 }
 | 
|---|
| 346 | 
 | 
|---|
| 347 |                                 int len = value.Length;
 | 
|---|
| 348 | 
 | 
|---|
| 349 |                                 _stringBuilder.EnsureCapacity (_stringBuilder.Length + 2 * len);
 | 
|---|
| 350 | 
 | 
|---|
| 351 |                                 _stringBuilder.Append ('"');
 | 
|---|
| 352 | 
 | 
|---|
| 353 |                                 foreach (char c in value) {
 | 
|---|
| 354 |                                         switch (c) {
 | 
|---|
| 355 |                                                 case '\\':
 | 
|---|
| 356 |                                                 case '"':
 | 
|---|
| 357 | 
 | 
|---|
| 358 | //                                      case '/':
 | 
|---|
| 359 |                                                         _stringBuilder.Append ('\\');
 | 
|---|
| 360 |                                                         _stringBuilder.Append (c);
 | 
|---|
| 361 |                                                         break;
 | 
|---|
| 362 |                                                 case '\b':
 | 
|---|
| 363 |                                                         _stringBuilder.Append ("\\b");
 | 
|---|
| 364 |                                                         break;
 | 
|---|
| 365 |                                                 case '\t':
 | 
|---|
| 366 |                                                         _stringBuilder.Append ("\\t");
 | 
|---|
| 367 |                                                         break;
 | 
|---|
| 368 |                                                 case '\n':
 | 
|---|
| 369 |                                                         _stringBuilder.Append ("\\n");
 | 
|---|
| 370 |                                                         break;
 | 
|---|
| 371 |                                                 case '\f':
 | 
|---|
| 372 |                                                         _stringBuilder.Append ("\\f");
 | 
|---|
| 373 |                                                         break;
 | 
|---|
| 374 |                                                 case '\r':
 | 
|---|
| 375 |                                                         _stringBuilder.Append ("\\r");
 | 
|---|
| 376 |                                                         break;
 | 
|---|
| 377 |                                                 default:
 | 
|---|
| 378 |                                                         if (c < ' ') {
 | 
|---|
| 379 |                                                                 _stringBuilder.Append ("\\u");
 | 
|---|
| 380 |                                                                 _stringBuilder.Append (((int) c).ToString ("X4"));
 | 
|---|
| 381 |                                                         } else {
 | 
|---|
| 382 |                                                                 _stringBuilder.Append (c);
 | 
|---|
| 383 |                                                         }
 | 
|---|
| 384 | 
 | 
|---|
| 385 |                                                         break;
 | 
|---|
| 386 |                                         }
 | 
|---|
| 387 |                                 }
 | 
|---|
| 388 | 
 | 
|---|
| 389 |                                 _stringBuilder.Append ('"');
 | 
|---|
| 390 |                         }
 | 
|---|
| 391 | 
 | 
|---|
| 392 |                 }
 | 
|---|
| 393 | 
 | 
|---|
| 394 |                 private class JSONArray : JSONNode {
 | 
|---|
| 395 |                         private readonly List<JSONNode> nodes = new List<JSONNode> ();
 | 
|---|
| 396 | 
 | 
|---|
| 397 |                         public void Add (JSONNode _node) {
 | 
|---|
| 398 |                                 nodes.Add (_node);
 | 
|---|
| 399 |                         }
 | 
|---|
| 400 | 
 | 
|---|
| 401 |                         public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
 | 
|---|
| 402 |                                 _stringBuilder.Append ("[");
 | 
|---|
| 403 |                                 if (_prettyPrint) {
 | 
|---|
| 404 |                                         _stringBuilder.Append ('\n');
 | 
|---|
| 405 |                                 }
 | 
|---|
| 406 | 
 | 
|---|
| 407 |                                 foreach (JSONNode n in nodes) {
 | 
|---|
| 408 |                                         if (_prettyPrint) {
 | 
|---|
| 409 |                                                 _stringBuilder.Append (new string ('\t', _currentLevel + 1));
 | 
|---|
| 410 |                                         }
 | 
|---|
| 411 | 
 | 
|---|
| 412 |                                         n.ToString (_stringBuilder, _prettyPrint, _currentLevel + 1);
 | 
|---|
| 413 |                                         _stringBuilder.Append (",");
 | 
|---|
| 414 |                                         if (_prettyPrint) {
 | 
|---|
| 415 |                                                 _stringBuilder.Append ('\n');
 | 
|---|
| 416 |                                         }
 | 
|---|
| 417 |                                 }
 | 
|---|
| 418 | 
 | 
|---|
| 419 |                                 if (nodes.Count > 0) {
 | 
|---|
| 420 |                                         _stringBuilder.Remove (_stringBuilder.Length - (_prettyPrint ? 2 : 1), 1);
 | 
|---|
| 421 |                                 }
 | 
|---|
| 422 | 
 | 
|---|
| 423 |                                 if (_prettyPrint) {
 | 
|---|
| 424 |                                         _stringBuilder.Append (new string ('\t', _currentLevel));
 | 
|---|
| 425 |                                 }
 | 
|---|
| 426 | 
 | 
|---|
| 427 |                                 _stringBuilder.Append ("]");
 | 
|---|
| 428 |                         }
 | 
|---|
| 429 | 
 | 
|---|
| 430 |                 }
 | 
|---|
| 431 | 
 | 
|---|
| 432 |                 private class JSONObject : JSONNode {
 | 
|---|
| 433 |                         private readonly Dictionary<string, JSONNode> nodes = new Dictionary<string, JSONNode> ();
 | 
|---|
| 434 | 
 | 
|---|
| 435 |                         public JSONNode this [string _name] => nodes [_name];
 | 
|---|
| 436 | 
 | 
|---|
| 437 |                         public bool ContainsKey (string _name) {
 | 
|---|
| 438 |                                 return nodes.ContainsKey (_name);
 | 
|---|
| 439 |                         }
 | 
|---|
| 440 | 
 | 
|---|
| 441 |                         public void Add (string _name, JSONNode _node) {
 | 
|---|
| 442 |                                 nodes.Add (_name, _node);
 | 
|---|
| 443 |                         }
 | 
|---|
| 444 | 
 | 
|---|
| 445 |                         public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
 | 
|---|
| 446 |                                 _stringBuilder.Append ("{");
 | 
|---|
| 447 |                                 if (_prettyPrint) {
 | 
|---|
| 448 |                                         _stringBuilder.Append ('\n');
 | 
|---|
| 449 |                                 }
 | 
|---|
| 450 | 
 | 
|---|
| 451 |                                 foreach (KeyValuePair<string, JSONNode> kvp in nodes) {
 | 
|---|
| 452 |                                         if (_prettyPrint) {
 | 
|---|
| 453 |                                                 _stringBuilder.Append (new string ('\t', _currentLevel + 1));
 | 
|---|
| 454 |                                         }
 | 
|---|
| 455 | 
 | 
|---|
| 456 |                                         _stringBuilder.Append ($"\"{kvp.Key}\":");
 | 
|---|
| 457 |                                         if (_prettyPrint) {
 | 
|---|
| 458 |                                                 _stringBuilder.Append (" ");
 | 
|---|
| 459 |                                         }
 | 
|---|
| 460 | 
 | 
|---|
| 461 |                                         kvp.Value.ToString (_stringBuilder, _prettyPrint, _currentLevel + 1);
 | 
|---|
| 462 |                                         _stringBuilder.Append (",");
 | 
|---|
| 463 |                                         if (_prettyPrint) {
 | 
|---|
| 464 |                                                 _stringBuilder.Append ('\n');
 | 
|---|
| 465 |                                         }
 | 
|---|
| 466 |                                 }
 | 
|---|
| 467 | 
 | 
|---|
| 468 |                                 if (nodes.Count > 0) {
 | 
|---|
| 469 |                                         _stringBuilder.Remove (_stringBuilder.Length - (_prettyPrint ? 2 : 1), 1);
 | 
|---|
| 470 |                                 }
 | 
|---|
| 471 | 
 | 
|---|
| 472 |                                 if (_prettyPrint) {
 | 
|---|
| 473 |                                         _stringBuilder.Append (new string ('\t', _currentLevel));
 | 
|---|
| 474 |                                 }
 | 
|---|
| 475 | 
 | 
|---|
| 476 |                                 _stringBuilder.Append ("}");
 | 
|---|
| 477 |                         }
 | 
|---|
| 478 | 
 | 
|---|
| 479 |                 }
 | 
|---|
| 480 |                 
 | 
|---|
| 481 | #endregion
 | 
|---|
| 482 | 
 | 
|---|
| 483 |         }
 | 
|---|
| 484 | }
 | 
|---|