-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJsonPath.cs
More file actions
455 lines (371 loc) · 15.1 KB
/
Copy pathJsonPath.cs
File metadata and controls
455 lines (371 loc) · 15.1 KB
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
namespace FB_API_Plugin
{
#region Imports
using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
#endregion
public delegate object JsonPathScriptEvaluator(string script, object value, string context);
public delegate void JsonPathResultAccumulator(object value, string[] indicies);
public interface IJsonPathValueSystem
{
bool HasMember(object value, string member);
object GetMemberValue(object value, string member);
IEnumerable GetMembers(object value);
bool IsObject(object value);
bool IsArray(object value);
bool IsPrimitive(object value);
}
[Serializable]
public sealed class JsonPathNode
{
private readonly object value;
private readonly string path;
public JsonPathNode(object value, string path)
{
if (path == null)
throw new ArgumentNullException("path");
if (path.Length == 0)
throw new ArgumentException("path");
this.value = value;
this.path = path;
}
public object Value
{
get { return value; }
}
public string Path
{
get { return path; }
}
public override string ToString()
{
return Path + " = " + Value;
}
public static object[] ValuesFrom(ICollection nodes)
{
object[] values = new object[nodes != null ? nodes.Count : 0];
if (values.Length > 0)
{
Debug.Assert(nodes != null);
int i = 0;
foreach (JsonPathNode node in nodes)
values[i++] = node.Value;
}
return values;
}
public static string[] PathsFrom(ICollection nodes)
{
string[] paths = new string[nodes != null ? nodes.Count : 0];
if (paths.Length > 0)
{
Debug.Assert(nodes != null);
int i = 0;
foreach (JsonPathNode node in nodes)
paths[i++] = node.Path;
}
return paths;
}
}
public sealed class JsonPathContext
{
public static readonly JsonPathContext Default = new JsonPathContext();
private JsonPathScriptEvaluator eval;
private IJsonPathValueSystem system;
public JsonPathScriptEvaluator ScriptEvaluator
{
get { return eval; }
set { eval = value; }
}
public IJsonPathValueSystem ValueSystem
{
get { return system; }
set { system = value; }
}
public void SelectTo(object obj, string expr, JsonPathResultAccumulator output)
{
if (obj == null)
throw new ArgumentNullException("obj");
if (output == null)
throw new ArgumentNullException("output");
Interpreter i = new Interpreter(output, ValueSystem, ScriptEvaluator);
expr = Normalize(expr);
if (expr.Length >= 1 && expr[0] == '$') // ^\$:?
expr = expr.Substring(expr.Length >= 2 && expr[1] == ';' ? 2 : 1);
i.Trace(expr, obj, "$");
}
public JsonPathNode[] SelectNodes(object obj, string expr)
{
ArrayList list = new ArrayList();
SelectNodesTo(obj, expr, list);
return (JsonPathNode[])list.ToArray(typeof(JsonPathNode));
}
public IList SelectNodesTo(object obj, string expr, IList output)
{
ListAccumulator accumulator = new ListAccumulator(output != null ? output : new ArrayList());
SelectTo(obj, expr, new JsonPathResultAccumulator(accumulator.Put));
return output;
}
private static Regex RegExp(string pattern)
{
return new Regex(pattern, RegexOptions.ECMAScript);
}
private static string Normalize(string expr)
{
NormalizationSwap swap = new NormalizationSwap();
expr = RegExp(@"[\['](\??\(.*?\))[\]']").Replace(expr, new MatchEvaluator(swap.Capture));
expr = RegExp(@"'?\.'?|\['?").Replace(expr, ";");
expr = RegExp(@";;;|;;").Replace(expr, ";..;");
expr = RegExp(@";$|'?\]|'$").Replace(expr, string.Empty);
expr = RegExp(@"#([0-9]+)").Replace(expr, new MatchEvaluator(swap.Yield));
return expr;
}
private sealed class NormalizationSwap
{
private readonly ArrayList subx = new ArrayList(4);
public string Capture(Match match)
{
Debug.Assert(match != null);
int index = subx.Add(match.Groups[1].Value);
return "[#" + index.ToString(CultureInfo.InvariantCulture) + "]";
}
public string Yield(Match match)
{
Debug.Assert(match != null);
int index = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
return (string)subx[index];
}
}
public static string AsBracketNotation(string[] indicies)
{
if (indicies == null)
throw new ArgumentNullException("indicies");
StringBuilder sb = new StringBuilder();
foreach (string index in indicies)
{
if (sb.Length == 0)
{
sb.Append('$');
}
else
{
sb.Append('[');
if (RegExp(@"^[0-9*]+$").IsMatch(index))
sb.Append(index);
else
sb.Append('\'').Append(index).Append('\'');
sb.Append(']');
}
}
return sb.ToString();
}
private static int ParseInt(string s)
{
return ParseInt(s, 0);
}
private static int ParseInt(string str, int defaultValue)
{
if (str == null || str.Length == 0)
return defaultValue;
try
{
return int.Parse(str, NumberStyles.None, CultureInfo.InvariantCulture);
}
catch (FormatException)
{
return defaultValue;
}
}
private sealed class Interpreter
{
private readonly JsonPathResultAccumulator output;
private readonly JsonPathScriptEvaluator eval;
private readonly IJsonPathValueSystem system;
private static readonly IJsonPathValueSystem defaultValueSystem = new BasicValueSystem();
private static readonly char[] colon = new char[] { ':' };
private static readonly char[] semicolon = new char[] { ';' };
private delegate void WalkCallback(object member, string loc, string expr, object value, string path);
public Interpreter(JsonPathResultAccumulator output, IJsonPathValueSystem valueSystem, JsonPathScriptEvaluator eval)
{
Debug.Assert(output != null);
this.output = output;
this.eval = eval != null ? eval : new JsonPathScriptEvaluator(NullEval);
this.system = valueSystem != null ? valueSystem : defaultValueSystem;
}
public void Trace(string expr, object value, string path)
{
if (expr == null || expr.Length == 0)
{
Store(path, value);
return;
}
int i = expr.IndexOf(';');
string atom = i >= 0 ? expr.Substring(0, i) : expr;
string tail = i >= 0 ? expr.Substring(i + 1) : string.Empty;
if (value != null && system.HasMember(value, atom))
{
Trace(tail, Index(value, atom), path + ";" + atom);
}
else if (atom == "*")
{
Walk(atom, tail, value, path, new WalkCallback(WalkWild));
}
else if (atom == "..")
{
Trace(tail, value, path);
Walk(atom, tail, value, path, new WalkCallback(WalkTree));
}
else if (atom.Length > 2 && atom[0] == '(' && atom[atom.Length - 1] == ')') // [(exp)]
{
Trace(eval(atom, value, path.Substring(path.LastIndexOf(';') + 1)) + ";" + tail, value, path);
}
else if (atom.Length > 3 && atom[0] == '?' && atom[1] == '(' && atom[atom.Length - 1] == ')') // [?(exp)]
{
Walk(atom, tail, value, path, new WalkCallback(WalkFiltered));
}
else if (RegExp(@"^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$").IsMatch(atom)) // [start:end:step] Phyton slice syntax
{
Slice(atom, tail, value, path);
}
else if (atom.IndexOf(',') >= 0) // [name1,name2,...]
{
foreach (string part in RegExp(@"'?,'?").Split(atom))
Trace(part + ";" + tail, value, path);
}
}
private void Store(string path, object value)
{
if (path != null)
output(value, path.Split(semicolon));
}
private void Walk(string loc, string expr, object value, string path, WalkCallback callback)
{
if (system.IsPrimitive(value))
return;
if (system.IsArray(value))
{
IList list = (IList)value;
for (int i = 0; i < list.Count; i++)
callback(i, loc, expr, value, path);
}
else if (system.IsObject(value))
{
foreach (string key in system.GetMembers(value))
callback(key, loc, expr, value, path);
}
}
private void WalkWild(object member, string loc, string expr, object value, string path)
{
Trace(member + ";" + expr, value, path);
}
private void WalkTree(object member, string loc, string expr, object value, string path)
{
object result = Index(value, member.ToString());
if (result != null && !system.IsPrimitive(result))
Trace("..;" + expr, result, path + ";" + member);
}
private void WalkFiltered(object member, string loc, string expr, object value, string path)
{
object result = eval(RegExp(@"^\?\((.*?)\)$").Replace(loc, "$1"),
Index(value, member.ToString()), member.ToString());
if (Convert.ToBoolean(result, CultureInfo.InvariantCulture))
Trace(member + ";" + expr, value, path);
}
private void Slice(string loc, string expr, object value, string path)
{
IList list = value as IList;
if (list == null)
return;
int length = list.Count;
string[] parts = loc.Split(colon);
int start = ParseInt(parts[0]);
int end = ParseInt(parts[1], list.Count);
int step = parts.Length > 2 ? ParseInt(parts[2], 1) : 1;
start = (start < 0) ? Math.Max(0, start + length) : Math.Min(length, start);
end = (end < 0) ? Math.Max(0, end + length) : Math.Min(length, end);
for (int i = start; i < end; i += step)
Trace(i + ";" + expr, value, path);
}
private object Index(object obj, string member)
{
return system.GetMemberValue(obj, member);
}
private static object NullEval(string expr, object value, string context)
{
//
// @ symbol in expr must be interpreted specially to resolve
// to value. In JavaScript, the implementation would look
// like:
//
// return obj && value && eval(expr.replace(/@/g, "value"));
//
return null;
}
}
private sealed class BasicValueSystem : IJsonPathValueSystem
{
public bool HasMember(object value, string member)
{
if (IsPrimitive(value))
return false;
IDictionary dict = value as IDictionary;
if (dict != null)
return dict.Contains(member);
IList list = value as IList;
if (list != null)
{
int index = ParseInt(member, -1);
return index >= 0 && index < list.Count;
}
return false;
}
public object GetMemberValue(object value, string member)
{
if (IsPrimitive(value))
throw new ArgumentException("value");
IDictionary dict = value as IDictionary;
if (dict != null)
return dict[member];
IList list = (IList)value;
int index = ParseInt(member, -1);
if (index >= 0 && index < list.Count)
return list[index];
return null;
}
public IEnumerable GetMembers(object value)
{
return ((IDictionary)value).Keys;
}
public bool IsObject(object value)
{
return value is IDictionary;
}
public bool IsArray(object value)
{
return value is IList;
}
public bool IsPrimitive(object value)
{
if (value == null)
throw new ArgumentNullException("value");
return Type.GetTypeCode(value.GetType()) != TypeCode.Object;
}
}
private sealed class ListAccumulator
{
private readonly IList list;
public ListAccumulator(IList list)
{
Debug.Assert(list != null);
this.list = list;
}
public void Put(object value, string[] indicies)
{
list.Add(new JsonPathNode(value, JsonPathContext.AsBracketNotation(indicies)));
}
}
}
}