/
SDF.cs
412 lines (355 loc) · 12.2 KB
/
SDF.cs
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
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using sdf.Matching;
namespace sdf {
/*
sdf = node|literal
node = \(name [attributes] [children]\)
name = [A-Za-z0-9_:.-]+
attributes = \{attribute*\}
attribute = name sdf
children = \[sdf*\]|sdf
literal = number|boolean|string|null
number = -?[0-9]+(.[0-9])?
boolean = true|false
string = \"[^"]*\"
*/
/// <summary>
/// <c>SDF</c> class is a representation of SDF data, which is simiar to XML or JSON, but in S-Expressions form.
/// </summary>
[SuppressMessage("ReSharper", "InconsistentNaming")]
public abstract class SDF {
/// <summary>
/// Returns all SDF elements, that match given SDF Path.
/// </summary>
/// <param name="path">SDF Path for elements to match to.</param>
/// <returns>List of all SDF elements matching given SDF Path as <c>Match</c> instances.</returns>
public List<Match> Find(string path) {
return Matcher.Match(this, path);
}
/// <summary>
/// Returns new SDF hierarchy where all elements matching to given SDF Path are replaced with a copy of given SDF.
/// If root element matches the given path, given SDF is returned (not a copy of it).
/// </summary>
/// <param name="path">SDF Path for elements to match to.</param>
/// <param name="newValue">SDF to replaced matching elements with.</param>
/// <returns>Reference to either updated <c>this</c> or <c>newValue</c>.</returns>
public SDF Replace(string path, SDF newValue) {
var topMatches = FindTopMatches(path);
foreach (var m in topMatches) {
if (m.Parent == null) {
// if root is being replaced
return newValue;
}
var oldValue = m.Value;
var parent = m.Parent.Value as Node; // parents can only be nodes
if (parent == null) {
throw new InvalidDataException("Element has parent which is not a Node (impossible).");
}
var index = parent.Children.IndexOf(oldValue);
if (index != -1) {
parent.Children[index] = newValue.DeepCopy();
} else { // not a child, then must be an attribute
var found = false;
foreach (var pair in parent.Attributes) {
if (pair.Value == oldValue) {
parent.Attributes[pair.Key] = newValue.DeepCopy();
found = true;
break;
}
}
if (!found) {
throw new InvalidDataException("Cannot replace element because element's parent doesn't have it neither as a child nor as an attribute.");
}
}
}
return this;
}
/// <summary>
/// Adds an attribute with <c>attributeName</c> name and <c>value</c> value to all elements matching given SDF Path.
/// Can only apply to nodes.
/// Throws an exception if attribute with given name already exists on a matching element.
/// </summary>
/// <param name="path">SDF Path for elements to match to.</param>
/// <param name="attributeName">Name of the added attribute.</param>
/// <param name="value">Value of the added attribute.</param>
public void AddAttribute(string path, string attributeName, SDF value) {
var matches = Find(path);
foreach (var match in matches) {
var node = match.Value as Node;
if (node == null) {
throw new InvalidDataException("Cannot add an attribute to something but a Node.");
}
if (node.Attributes.ContainsKey(attributeName)) {
throw new InvalidDataException("Cannot add an attribute, because attribute with such name already exists.");
}
node.Attributes[attributeName] = value.DeepCopy();
}
}
/// <summary>
/// Adds a clone of <c>value</c> as a child to all elements matching given SDF Path.
/// Can only apply to nodes.
/// </summary>
/// <param name="path">SDF Path for elements to match to.</param>
/// <param name="value">Value of the added child.</param>
public void AddChild(string path, SDF value) {
var matches = Find(path);
foreach (var match in matches) {
var node = match.Value as Node;
if (node == null) {
throw new InvalidDataException("Cannot add a child to something but a Node.");
}
node.Children.Add(value.DeepCopy());
}
}
/// <summary>
/// Inserts a clone of <c>value</c> as a child to all elements matching given SDF Path at position <c>index</c>.
/// </summary>
/// <param name="path">SDF Path for elements to match to.</param>
/// <param name="index">Position to insert into.</param>
/// <param name="value">Value to be inserted.</param>
public void InsertAt(string path, int index, SDF value) {
var matches = Find(path);
foreach (var match in matches) {
var node = match.Value as Node;
if (node == null) {
throw new InvalidDataException("Cannot insert a child into something but a Node.");
}
node.Children.Insert(index, value.DeepCopy());
}
}
/// <summary>
/// Inserts a clone of <c>value</c> before all elements matching given SDF Path.
/// Throws an exception if adding next to root element.
/// </summary>
/// <param name="path">SDF Path for elements to match to.</param>
/// <param name="value">Value to be inserted.</param>
public void InsertBefore(string path, SDF value) {
var matches = Find(path);
foreach (var match in matches) {
if (match.Parent == null) {
throw new InvalidDataException("Cannot add something next to root element.");
}
var node = match.Parent.Value as Node;
if (node == null) {
throw new InvalidDataException("Cannot insert a child into something but a Node.");
}
node.InsertBeforeChild(match.Value, value.DeepCopy());
}
}
/// <summary>
/// Inserts a clone of <c>value</c> after all elements matching given SDF Path.
/// Throws an exception if adding next to root element.
/// </summary>
/// <param name="path">SDF Path for elements to match to.</param>
/// <param name="value">Value to be inserted.</param>
public void InsertAfter(string path, SDF value) {
var matches = Find(path);
foreach (var match in matches) {
if (match.Parent == null) {
throw new InvalidDataException("Cannot add something next to root element.");
}
var node = match.Parent.Value as Node;
if (node == null) {
throw new InvalidDataException("Cannot insert a child into something but a Node.");
}
node.InsertAfterChild(match.Value, value.DeepCopy());
}
}
/// <summary>
/// Removes all elements matching given SDF Path.
/// If root element matches the given path, <c>null</c> is returned.
/// </summary>
/// <param name="path">SDF Path for elements to match to.</param>
/// <returns>Reference to either updated <c>this</c> or <c>null</c>.</returns>
public SDF Remove(string path) {
var topMatches = FindTopMatches(path);
foreach (var m in topMatches) {
if (m.Parent == null) {
// if root is being removed
return null;
}
var oldValue = m.Value;
var parent = m.Parent.Value as Node; // parents can only be nodes
if (parent == null) {
throw new InvalidDataException("Element has parent which is not a Node (impossible).");
}
if (parent.Children.Contains(oldValue)) {
parent.Children.Remove(oldValue);
} else { // not a child, then must be an attribute
var found = false;
foreach (var pair in parent.Attributes) {
if (pair.Value == oldValue) {
parent.Attributes.Remove(pair.Key);
found = true;
break;
}
}
if (!found) {
throw new InvalidDataException("Cannot remove element because element's parent doesn't have it neither as a child nor as an attribute.");
}
}
}
return this;
}
// private methods
private SDF DeepCopy() {
var n = this as Node;
if (n != null) {
var atrs = new Dictionary<string, SDF>();
foreach (var kv in n.Attributes) {
atrs[kv.Key] = kv.Value.DeepCopy();
}
var chld = n.Children.Select(child => child.DeepCopy()).ToList();
return new Node(n.Name, atrs, chld);
}
var nl = this as NullLiteral;
if (nl != null) {
return new NullLiteral();
}
var nml = this as NumberLiteral;
if (nml != null) {
return new NumberLiteral(nml.Integer, nml.Fraction);
}
var bl = this as BooleanLiteral;
if (bl != null) {
return new BooleanLiteral(bl.Value);
}
var sl = this as StringLiteral;
if (sl != null) {
return new StringLiteral(sl.Value);
}
return null;
}
private List<Match> FindTopMatches(string path) {
var matches = Find(path);
var topMatches = new List<Match>();
foreach (var m in matches) {
// check whether <m> has any other match as a parent (not exactly direct)
var found = false;
foreach (var o in matches) {
if (found) {
break;
}
if (m == o) {
continue;
}
var p = m;
while (p != null) {
if (p.Parent == o) {
found = true;
break;
}
p = p.Parent;
}
}
if (!found) {
topMatches.Add(m);
}
}
return topMatches;
}
}
/// <inheritdoc />
/// <summary>
/// Representation of SDF node: with a name, list of children and attributes (key-value set).
/// </summary>
public class Node : SDF {
internal Dictionary<string, SDF> Attributes;
internal List<SDF> Children;
internal string Name;
/// <summary>
/// Create new <c>Node</c> with given name, attributes and children.
/// </summary>
/// <param name="name">Name of the node.</param>
/// <param name="attributes">Attributes of the node.</param>
/// <param name="children">Children of the node.</param>
public Node(string name, Dictionary<string, SDF> attributes, List<SDF> children) {
Name = name;
Attributes = attributes;
Children = children;
}
/// <summary>
/// Insert given SDF value before given child of this node.
/// </summary>
/// <param name="child">Reference to SDF representation of child, before which <c>value</c> should be inserted before.</param>
/// <param name="value">Reference to SDF representation to be inserted before <c>child</c>.</param>
public void InsertBeforeChild(SDF child, SDF value) {
var index = Children.IndexOf(child);
if (index == -1) {
throw new ArgumentException("Argument passed as <child> is not a child of Node, thus <value> cannot be inserted before it.");
}
Children.Insert(index, value);
}
/// <summary>
/// Insert given SDF value after given child of this node.
/// </summary>
/// <param name="child">Reference to SDF representation of child, after which <c>value</c> should be inserted before.</param>
/// <param name="value">Reference to SDF representation to be inserted after <c>child</c>.</param>
public void InsertAfterChild(SDF child, SDF value) {
var index = Children.IndexOf(child);
if (index == -1) {
throw new ArgumentException("Argument passed as <child> is not a child of Node, thus <value> cannot be inserted after it.");
}
Children.Insert(index + 1, value);
}
}
// literals
/// <inheritdoc />
/// <summary>
/// SDF string literal
/// </summary>
public class StringLiteral: SDF {
internal string Value;
/// <summary>
/// Create new SDF string literal
/// </summary>
/// <param name="v">string value of the literal</param>
public StringLiteral(string v) {
Value = v;
}
}
/// <inheritdoc />
/// <summary>
/// SDF number literal
/// </summary>
public class NumberLiteral : SDF {
internal long Integer, Fraction;
/// <summary>
/// Returns double representation of the literal
/// </summary>
public double Double => double.Parse(Integer + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator + Fraction); // yeah, dumb, I know
/// <summary>
/// Create new SDF number literal
/// </summary>
/// <param name="a">integer part of the literal</param>
/// <param name="b">fraction part of the literal</param>
public NumberLiteral(long a, long b) {
Integer = a;
Fraction = b;
}
}
/// <inheritdoc />
/// <summary>
/// SDF boolean literal
/// </summary>
public class BooleanLiteral : SDF {
internal bool Value;
/// <summary>
/// Create new SDF boolean literal
/// </summary>
/// <param name="b">boolean value of the literal</param>
public BooleanLiteral(bool b) {
Value = b;
}
}
/// <inheritdoc />
/// <summary>
/// SDF null literal
/// </summary>
public class NullLiteral : SDF { }
}