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