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