OSDN Git Service

シフトキーを押しながらホイールで戦況を横スクロールさせる
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / JsonParser.cs
1 using System;\r
2 using System.Collections;\r
3 using System.Collections.Generic;\r
4 using System.Dynamic;\r
5 using System.Linq;\r
6 using System.Reflection;\r
7 using System.Text;\r
8 using System.Text.RegularExpressions;\r
9 \r
10 namespace KancolleSniffer\r
11 {\r
12     public class JsonParser\r
13     {\r
14         private readonly string _source;\r
15         private int _position;\r
16 \r
17         public static JsonObject Parse(string json)\r
18         {\r
19             return new JsonParser(json).Parse();\r
20         }\r
21 \r
22         private JsonParser(string json)\r
23         {\r
24             _source = json;\r
25         }\r
26 \r
27         private JsonObject Parse()\r
28         {\r
29             var ch = NextChar();\r
30             if ('0' <= ch && ch <= '9' || ch == '-')\r
31                 return ParseNumber();\r
32             switch (ch)\r
33             {\r
34                 case 'n':\r
35                     if (IsMatch("null"))\r
36                         return null;\r
37                     goto invalid;\r
38                 case 't':\r
39                     if (IsMatch("true"))\r
40                         return new JsonObject(true);\r
41                     goto invalid;\r
42                 case 'f':\r
43                     if (IsMatch("false"))\r
44                         return new JsonObject(false);\r
45                     goto invalid;\r
46                 case '"':\r
47                     return ParseString();\r
48                 case '[':\r
49                     return ParseArray();\r
50                 case '{':\r
51                     return ParseObject();\r
52             }\r
53             invalid:\r
54             throw new JsonParserException($"'{ch}'不正な文字です。 at {_position}");\r
55         }\r
56 \r
57         private bool IsMatch(string s)\r
58         {\r
59             foreach (char ch in s)\r
60             {\r
61                 var src = LookAhead();\r
62                 if (ch != src)\r
63                     return false;\r
64                 Consume();\r
65             }\r
66             return true;\r
67         }\r
68 \r
69         private JsonObject ParseNumber()\r
70         {\r
71             return new JsonObject(GetNumber());\r
72         }\r
73 \r
74         private double GetNumber()\r
75         {\r
76             var result = 0d;\r
77             var ch = LookAhead();\r
78             var sign = 1d;\r
79             if (ch == '-')\r
80             {\r
81                 sign = -1;\r
82                 Consume();\r
83                 ch = LookAhead();\r
84             }\r
85             while ('0' <= ch && ch <= '9')\r
86             {\r
87                 result = (result * 10.0) + (ch - '0');\r
88                 Consume();\r
89                 ch = LookAhead();\r
90             }\r
91             if (ch != '.')\r
92                 return sign * result;\r
93             var exp = 0.1;\r
94             Consume();\r
95             ch = LookAhead();\r
96             while ('0' <= ch && ch <= '9')\r
97             {\r
98                 result += (ch - '0') * exp;\r
99                 exp *= 0.1;\r
100                 Consume();\r
101                 ch = LookAhead();\r
102             }\r
103             return sign * result;\r
104         }\r
105 \r
106         private JsonObject ParseString()\r
107         {\r
108             return new JsonObject(GetString());\r
109         }\r
110 \r
111         private string GetString()\r
112         {\r
113             Consume();\r
114             var len = 0;\r
115             while (true)\r
116             {\r
117                 var ch = LookAhead();\r
118                 if (ch == '\\')\r
119                 {\r
120                     Consume();\r
121                     Consume();\r
122                     len += 2;\r
123                     continue;\r
124                 }\r
125                 if (ch == '"')\r
126                 {\r
127                     Consume();\r
128                     break;\r
129                 }\r
130                 len++;\r
131                 Consume();\r
132             }\r
133             return Unescape(_source.Substring(_position - len - 1, len));\r
134         }\r
135 \r
136         private static readonly Regex EscapeRegex =\r
137             new Regex(@"\\[^u]|\\u(?:[0-9A-Fa-f]{4})", RegexOptions.Compiled);\r
138 \r
139         private string Unescape(string s)\r
140         {\r
141             return EscapeRegex.Replace(s, m =>\r
142             {\r
143                 switch (m.Value[1])\r
144                 {\r
145                     case '/':\r
146                         return '/'.ToString();\r
147                     case '"':\r
148                     case '\\':\r
149                     case 'b':\r
150                     case 'f':\r
151                     case 'n':\r
152                     case 'r':\r
153                     case 't':\r
154                     case 'u':\r
155                         return Regex.Unescape(m.Value).ToString();\r
156                     default:\r
157                         throw new JsonParserException("不正なエスケープシーケンスです。 at {");\r
158                 }\r
159             });\r
160         }\r
161 \r
162         private JsonObject ParseArray()\r
163         {\r
164             Consume();\r
165             var ary = new List<JsonObject>();\r
166             while (true)\r
167             {\r
168                 var ch = NextChar();\r
169                 if (ch == ']')\r
170                 {\r
171                     Consume();\r
172                     return new JsonObject(ary);\r
173                 }\r
174                 ary.Add(Parse());\r
175                 ch = NextChar();\r
176                 if (ch != ',' && ch != ']')\r
177                     throw new JsonParserException($"','か']'が必要です。 at {_position}");\r
178                 if (ch == ']')\r
179                 {\r
180                     Consume();\r
181                     return new JsonObject(ary);\r
182                 }\r
183                 Consume();\r
184             }\r
185         }\r
186 \r
187         private JsonObject ParseObject()\r
188         {\r
189             Consume();\r
190             var dict = new Dictionary<string, JsonObject>();\r
191             while (true)\r
192             {\r
193                 var ch = NextChar();\r
194                 if (ch == '}')\r
195                 {\r
196                     Consume();\r
197                     return new JsonObject(dict);\r
198                 }\r
199                 if (ch != '"')\r
200                     throw new JsonParserException($"文字列が必要です。 at {_position}");\r
201                 var key = GetString();\r
202                 ch = NextChar();\r
203                 if (ch != ':')\r
204                     throw new JsonParserException($"':'が必要です。 at {_position}");\r
205                 Consume();\r
206                 var value = Parse();\r
207                 dict.Add(key, value);\r
208                 ch = NextChar();\r
209                 if (ch != ',' && ch != '}')\r
210                     throw new JsonParserException($"','か'}}'が必要です。 at {_position}");\r
211                 if (ch == '}')\r
212                 {\r
213                     Consume();\r
214                     return new JsonObject(dict);\r
215                 }\r
216                 Consume();\r
217             }\r
218         }\r
219 \r
220         private char LookAhead()\r
221         {\r
222             if (_source.Length == _position)\r
223                 return '\0';\r
224             return _source[_position];\r
225         }\r
226 \r
227         private void Consume()\r
228         {\r
229             if (_source.Length == _position)\r
230                 throw new JsonParserException($"入力が途切れています。 at {_position}");\r
231             _position++;\r
232         }\r
233 \r
234         private char NextChar()\r
235         {\r
236             while (true)\r
237             {\r
238                 var ch = LookAhead();\r
239                 if (!(ch == '\r' || ch == '\n' || ch == '\t' || ch == ' '))\r
240                     return ch;\r
241                 Consume();\r
242             }\r
243         }\r
244     }\r
245 \r
246     public class JsonObject : DynamicObject\r
247     {\r
248         private readonly JsonType _type;\r
249         private readonly bool _bool;\r
250         private readonly double _number;\r
251         private readonly string _string;\r
252         private readonly List<JsonObject> _array;\r
253         private readonly Dictionary<string, JsonObject> _dict;\r
254 \r
255         public bool IsArray => _type == JsonType.Array;\r
256         public bool IsObject => _type == JsonType.Object;\r
257         public bool IsDefined(string attr) => IsObject && _dict.ContainsKey(attr);\r
258 \r
259         public JsonObject(bool b)\r
260         {\r
261             _type = JsonType.Bool;\r
262             _bool = b;\r
263         }\r
264 \r
265         public JsonObject(double n)\r
266         {\r
267             _type = JsonType.Number;\r
268             _number = n;\r
269         }\r
270 \r
271         public JsonObject(string s)\r
272         {\r
273             _type = JsonType.String;\r
274             _string = s;\r
275         }\r
276 \r
277         public JsonObject(List<JsonObject> ary)\r
278         {\r
279             _type = JsonType.Array;\r
280             _array = ary;\r
281         }\r
282 \r
283         public JsonObject(Dictionary<string, JsonObject> dict)\r
284         {\r
285             _type = JsonType.Object;\r
286             _dict = dict;\r
287         }\r
288 \r
289         public override bool TryGetMember(GetMemberBinder binder, out object result)\r
290         {\r
291             result = null;\r
292             if (_type != JsonType.Object)\r
293                 return false;\r
294             if (!_dict.TryGetValue(binder.Name, out var dict))\r
295                 return false;\r
296             result = dict?.Value;\r
297             return true;\r
298         }\r
299 \r
300         public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)\r
301         {\r
302             result = _type == JsonType.Object && _dict.ContainsKey(binder.Name);\r
303             return true;\r
304         }\r
305 \r
306         public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)\r
307         {\r
308             switch (_type)\r
309             {\r
310                 case JsonType.Array:\r
311                     result = _array[(int)indexes[0]]?.Value;\r
312                     return true;\r
313                 case JsonType.Object:\r
314                     result = _dict[(string)indexes[0]]?.Value;\r
315                     return true;\r
316             }\r
317             result = null;\r
318             return false;\r
319         }\r
320 \r
321         public override bool TryConvert(ConvertBinder binder, out object result)\r
322         {\r
323             if (binder.Type == typeof(IEnumerable))\r
324             {\r
325                 switch (_type)\r
326                 {\r
327                     case JsonType.Array:\r
328                         result = _array.Select(x => x.Value);\r
329                         return true;\r
330                     case JsonType.Object:\r
331                         result = _dict.Select(x => new KeyValuePair<string, dynamic>(x.Key, x.Value));\r
332                         return true;\r
333                     default:\r
334                         result = null;\r
335                         return false;\r
336                 }\r
337             }\r
338             if (_type != JsonType.Array)\r
339                 return ConvertPrivateType(binder.Type, out result);\r
340             if (binder.Type == typeof(int[]))\r
341             {\r
342                 result = _array.Select(x => (int)x._number).ToArray();\r
343                 return true;\r
344             }\r
345             if (binder.Type.IsArray)\r
346             {\r
347                 return ConvertArray(binder.Type.GetElementType(), _array, out result);\r
348             }\r
349             result = null;\r
350             return false;\r
351         }\r
352 \r
353         private bool ConvertPrivateType(Type type, out object result)\r
354         {\r
355             if (type == typeof(bool) && _type == JsonType.Bool)\r
356             {\r
357                 result = _bool;\r
358                 return true;\r
359             }\r
360             if (type == typeof(int) && _type == JsonType.Number)\r
361             {\r
362                 result = (int)_number;\r
363                 return true;\r
364             }\r
365             if (type == typeof(double) && _type == JsonType.Number)\r
366             {\r
367                 result = _number;\r
368                 return true;\r
369             }\r
370             if (type == typeof(string) && _type == JsonType.String)\r
371             {\r
372                 result = _string;\r
373                 return true;\r
374             }\r
375             if (type == typeof(object))\r
376             {\r
377                 result = Value;\r
378                 return true;\r
379             }\r
380             result = null;\r
381             return false;\r
382         }\r
383 \r
384         private bool ConvertArray(Type type, List<JsonObject> values, out object result)\r
385         {\r
386             result = null;\r
387             var array = Array.CreateInstance(type, values.Count);\r
388             for (var i = 0; i < array.Length; i++)\r
389             {\r
390                 if (type.IsArray)\r
391                 {\r
392                     if (!values[i].IsArray || !ConvertArray(type.GetElementType(), values[i]._array, out var one))\r
393                         return false;\r
394                     array.SetValue((dynamic)one, i);\r
395                 }\r
396                 else\r
397                 {\r
398                     if (!values[i].ConvertPrivateType(type, out var one))\r
399                         return false;\r
400                     array.SetValue((dynamic)one, i);\r
401                 }\r
402             }\r
403             result = array;\r
404             return true;\r
405         }\r
406 \r
407         public static JsonObject CreateJsonObject(object value)\r
408         {\r
409             switch (value)\r
410             {\r
411                 case string s:\r
412                     return new JsonObject(s);\r
413                 case int i:\r
414                     return new JsonObject(i);\r
415                 case double d:\r
416                     return new JsonObject(d);\r
417                 case JsonObject json:\r
418                     return json;\r
419                 case IEnumerable arry:\r
420                     return new JsonObject(arry.Cast<object>().Select(CreateJsonObject).ToList());\r
421                 case object obj:\r
422                     return new JsonObject(obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)\r
423                         .ToDictionary(prop => prop.Name, prop => CreateJsonObject(prop.GetValue(obj))));\r
424             }\r
425             return null;\r
426         }\r
427 \r
428         public override bool TrySetMember(SetMemberBinder binder, object value)\r
429         {\r
430             if (_type != JsonType.Object)\r
431                 return false;\r
432             _dict[binder.Name] = CreateJsonObject(value);\r
433             return true;\r
434         }\r
435 \r
436         public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)\r
437         {\r
438             if (_type == JsonType.Array)\r
439             {\r
440                 _array[(int)indexes[0]] = CreateJsonObject(value);\r
441                 return true;\r
442             }\r
443             if (_type == JsonType.Object)\r
444             {\r
445                 _dict[(string)indexes[0]] = CreateJsonObject(value);\r
446                 return true;\r
447             }\r
448             return false;\r
449         }\r
450 \r
451         public override string ToString()\r
452         {\r
453             var sb = new StringBuilder();\r
454             ConvertToString(sb);\r
455             return sb.ToString();\r
456         }\r
457 \r
458         private void ConvertToString(StringBuilder sb)\r
459         {\r
460             switch (_type)\r
461             {\r
462                 case JsonType.Bool:\r
463                     sb.Append(_bool ? "true" : "false");\r
464                     break;\r
465                 case JsonType.Number:\r
466                     sb.Append(_number.ToString("G"));\r
467                     break;\r
468                 case JsonType.String:\r
469                     sb.Append("\"");\r
470                     sb.Append(HttpUtility.JavascriptStringEncode(_string));\r
471                     sb.Append("\"");\r
472                     break;\r
473                 case JsonType.Array:\r
474                     sb.Append("[");\r
475                     var delimiter = "";\r
476                     foreach (var val in _array)\r
477                     {\r
478                         sb.Append(delimiter);\r
479                         if (val == null)\r
480                         {\r
481                             sb.Append("null");\r
482                         }\r
483                         else\r
484                         {\r
485                             val.ConvertToString(sb);\r
486                         }\r
487                         delimiter = ",";\r
488                     }\r
489                     sb.Append("]");\r
490                     break;\r
491                 case JsonType.Object:\r
492                     sb.Append("{");\r
493                     delimiter = "";\r
494                     foreach (var entry in _dict)\r
495                     {\r
496                         sb.Append(delimiter);\r
497                         sb.Append("\"");\r
498                         sb.Append(entry.Key);\r
499                         sb.Append("\":");\r
500                         if (entry.Value == null)\r
501                         {\r
502                             sb.Append("null");\r
503                         }\r
504                         else\r
505                         {\r
506                             entry.Value.ConvertToString(sb);\r
507                         }\r
508                         delimiter = ",";\r
509                     }\r
510                     sb.Append("}");\r
511                     break;\r
512             }\r
513         }\r
514 \r
515         private object Value\r
516         {\r
517             get\r
518             {\r
519                 switch (_type)\r
520                 {\r
521                     case JsonType.Bool:\r
522                         return _bool;\r
523                     case JsonType.Number:\r
524                         return _number;\r
525                     case JsonType.String:\r
526                         return _string;\r
527                 }\r
528                 return this;\r
529             }\r
530         }\r
531 \r
532         private enum JsonType\r
533         {\r
534             Bool,\r
535             Number,\r
536             String,\r
537             Array,\r
538             Object\r
539         }\r
540     }\r
541 \r
542     public class JsonParserException : Exception\r
543     {\r
544         public JsonParserException()\r
545         {\r
546         }\r
547 \r
548         public JsonParserException(string message) : base(message)\r
549         {\r
550         }\r
551     }\r
552 }