• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

zorbathut / dec / 11674610626

04 Nov 2024 11:37PM UTC coverage: 90.088% (-0.5%) from 90.556%
11674610626

push

github

zorbathut
Rig up the first attempt at usable path data.

4617 of 5125 relevant lines covered (90.09%)

191115.08 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.08
/src/ReaderXml.cs
1
using System;
2
using System.Collections;
3
using System.Collections.Generic;
4
using System.Linq;
5
using System.Reflection;
6
using System.Xml.Linq;
7

8
namespace Dec
9
{
10
    internal class ReaderNodeXml : ReaderNodeParseable
11
    {
12
        public override Recorder.IUserSettings UserSettings { get; }
1,302,524✔
13

14
        private XElement xml;
15
        private string fileIdentifier;
16
        private Path path;
17

18
        public ReaderNodeXml(XElement xml, string fileIdentifier, Path path, Recorder.IUserSettings userSettings)
1,558,429✔
19
        {
1,558,429✔
20
            this.UserSettings = userSettings;
1,558,429✔
21
            this.xml = xml;
1,558,429✔
22
            this.fileIdentifier = fileIdentifier;
1,558,429✔
23
            this.path = path;
1,558,429✔
24
        }
1,558,429✔
25

26
        public override InputContext GetInputContext()
27
        {
3,676,145✔
28
            return new InputContext(fileIdentifier, xml, path);
3,676,145✔
29
        }
3,676,145✔
30

31
        public override ReaderNode GetChildNamed(string name)
32
        {
772,510✔
33
            var child = xml.ElementNamed(name);
772,510✔
34
            return child == null ? null : new ReaderNodeXml(child, fileIdentifier, new PathMember(path, name), UserSettings);
772,510✔
35
        }
772,510✔
36
        public override string[] GetAllChildren()
37
        {
388,760✔
38
            return xml.Elements().Select(e => e.Name.LocalName).ToArray();
1,160,915✔
39
        }
388,760✔
40

41
        public override string GetText()
42
        {
1,954,950✔
43
            return xml.GetText();
1,954,950✔
44
        }
1,954,950✔
45

46
        public override string GetMetadata(Metadata metadata)
47
        {
14,015,351✔
48
            return xml.Attribute(metadata.ToLowerString())?.Value;
14,015,351✔
49
        }
14,015,351✔
50

51
        private readonly HashSet<string> metadataNames = UtilMisc.GetEnumValues<Metadata>().Select(metadata => metadata.ToLowerString()).ToHashSet();
7,792,145✔
52
        public override string GetMetadataUnrecognized()
53
        {
1,557,264✔
54
            if (!xml.HasAttributes)
1,557,264✔
55
            {
739,536✔
56
                return null;
739,536✔
57
            }
58

59
            var unrecognized = string.Join(", ", xml.Attributes().Select(attr => attr.Name.LocalName).Where(name => !metadataNames.Contains(name)));
2,454,054✔
60
            return unrecognized == string.Empty ? null : unrecognized;
817,728✔
61
        }
1,557,264✔
62

63
        public override bool HasChildren()
64
        {
1,555,824✔
65
            return xml.Elements().Any();
1,555,824✔
66
        }
1,555,824✔
67

68
        public override int[] GetArrayDimensions(int rank)
69
        {
2,515✔
70
            // The actual processing will be handled by ParseArray, so we're not doing much validation here right now
71
            int[] results = new int[rank];
2,515✔
72
            var tier = xml;
2,515✔
73
            for (int i = 0; i < rank; ++i)
10,320✔
74
            {
2,715✔
75
                results[i] = tier.Elements().Count();
2,715✔
76

77
                tier = tier.Elements().FirstOrDefault();
2,715✔
78
                if (tier == null)
2,715✔
79
                {
70✔
80
                    // ran out of elements; stop now, we'll leave them full of 0's
81
                    break;
70✔
82
                }
83
            }
2,645✔
84

85
            return results;
2,515✔
86
        }
2,515✔
87

88
        public override void ParseList(IList list, Type referencedType, ReaderContext readerContext, Recorder.Context recorderContext)
89
        {
46,120✔
90
            var recorderChildContext = recorderContext.CreateChild();
46,120✔
91

92
            int index = 0;
46,120✔
93
            foreach (var fieldElement in xml.Elements())
354,530✔
94
            {
108,085✔
95
                if (fieldElement.Name.LocalName != "li")
108,085✔
96
                {
40✔
97
                    var elementContext = new InputContext(fileIdentifier, fieldElement, path);
40✔
98
                    Dbg.Err($"{elementContext}: Tag should be <li>, is <{fieldElement.Name.LocalName}>");
40✔
99
                }
40✔
100

101
                list.Add(Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, new PathIndex(path, index++), UserSettings) }, referencedType, null, readerContext, recorderChildContext));
108,085✔
102
            }
108,085✔
103

104
            list.GetType().GetField("_version", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(list, Util.CollectionDeserializationVersion);
46,120✔
105
        }
46,120✔
106

107
        private void ParseArrayRank(ReaderNodeXml node, ReaderContext readerContext, Recorder.Context recorderContext, Array value, Type referencedType, int rank, int[] indices, int startAt)
108
        {
1,520✔
109
            if (rank == indices.Length)
1,520✔
110
            {
885✔
111
                value.SetValue(Serialization.ParseElement(new List<ReaderNodeParseable>() { node }, referencedType, null, readerContext, recorderContext), indices);
885✔
112
            }
885✔
113
            else
114
            {
635✔
115
                // this is kind of unnecessary but it's also an irrelevant perf hit
116
                var recorderChildContext = recorderContext.CreateChild();
635✔
117

118
                int elementCount = node.xml.Elements().Count();
635✔
119
                int rankLength = value.GetLength(rank);
635✔
120
                if (elementCount > rankLength)
635✔
121
                {
15✔
122
                    Dbg.Err($"{node.GetInputContext()}: Array dimension {rank} expects {rankLength} elements but got {elementCount}; truncating");
15✔
123
                }
15✔
124
                else if (elementCount < rankLength)
620✔
125
                {
30✔
126
                    Dbg.Err($"{node.GetInputContext()}: Array dimension {rank} expects {rankLength} elements but got {elementCount}; padding with default values");
30✔
127
                }
30✔
128

129
                int i = 0;
635✔
130
                foreach (var fieldElement in node.xml.Elements())
4,770✔
131
                {
1,440✔
132
                    if (i >= rankLength)
1,440✔
133
                    {
15✔
134
                        // truncate, we're done here
135
                        break;
15✔
136
                    }
137

138
                    var newPath = new PathIndexMultidim(path, indices.ToArray());
1,425✔
139

140
                    if (fieldElement.Name.LocalName != "li")
1,425✔
141
                    {
×
142
                        var elementContext = new InputContext(fileIdentifier, fieldElement, newPath);
×
143
                        Dbg.Err($"{elementContext}: Tag should be <li>, is <{fieldElement.Name.LocalName}>");
×
144
                    }
×
145

146
                    indices[rank] = startAt + i++;
1,425✔
147
                    // the pathIndexMultidim is kind of slow and I should solve this at some point
148
                    ParseArrayRank(new ReaderNodeXml(fieldElement, fileIdentifier, newPath, UserSettings), readerContext, recorderChildContext, value, referencedType, rank + 1, indices, 0);
1,425✔
149
                }
1,425✔
150
            }
635✔
151
        }
1,520✔
152

153
        public override void ParseArray(Array array, Type referencedType, ReaderContext readerContext, Recorder.Context recorderContext, int startOffset)
154
        {
2,485✔
155
            var recorderChildContext = recorderContext.CreateChild();
2,485✔
156

157
            if (array.Rank == 1)
2,485✔
158
            {
2,390✔
159
                // fast path
160
                int index = 0;
2,390✔
161
                foreach (var fieldElement in xml.Elements())
20,000✔
162
                {
6,415✔
163
                    var newPath = new PathIndex(path, index);
6,415✔
164

165
                    if (fieldElement.Name.LocalName != "li")
6,415✔
166
                    {
40✔
167
                        var elementContext = new InputContext(fileIdentifier, fieldElement, newPath);
40✔
168
                        Dbg.Err($"{elementContext}: Tag should be <li>, is <{fieldElement.Name.LocalName}>");
40✔
169
                    }
40✔
170

171
                    array.SetValue(Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, newPath, UserSettings) }, referencedType, null, readerContext, recorderChildContext), startOffset + index);
6,415✔
172
                    ++index;
6,415✔
173
                }
6,415✔
174
            }
2,390✔
175
            else
176
            {
95✔
177
                // slow path
178
                var indices = new int[array.Rank];
95✔
179
                ParseArrayRank(this, readerContext, recorderChildContext, array, referencedType, 0, indices, startOffset);
95✔
180
            }
95✔
181
        }
2,485✔
182

183
        public override void ParseDictionary(IDictionary dict, Type referencedKeyType, Type referencedValueType, ReaderContext readerContext, Recorder.Context recorderContext, bool permitPatch)
184
        {
23,380✔
185
            var recorderChildContext = recorderContext.CreateChild();
23,380✔
186

187
            // avoid the heap allocation if we can
188
            var writtenFields = permitPatch ? new HashSet<object>() : null;
23,380✔
189

190
            foreach (var fieldElement in xml.Elements())
165,710✔
191
            {
47,785✔
192
                var elementContext = new InputContext(fileIdentifier, fieldElement);
47,785✔
193

194
                if (fieldElement.Name.LocalName == "li")
47,785✔
195
                {
46,060✔
196
                    // Treat this like a key/value pair
197
                    var keyNode = fieldElement.ElementNamedWithFallback("key", elementContext, "Dictionary includes li tag without a `key`");
46,060✔
198
                    var valueNode = fieldElement.ElementNamedWithFallback("value", elementContext, "Dictionary includes li tag without a `value`");
46,060✔
199

200
                    if (keyNode == null)
46,060✔
201
                    {
40✔
202
                        // error has already been generated
203
                        continue;
40✔
204
                    }
205

206
                    if (valueNode == null)
46,020✔
207
                    {
20✔
208
                        // error has already been generated
209
                        continue;
20✔
210
                    }
211

212
                    var keyPath = new PathDictionaryKey(path);
46,000✔
213
                    var key = Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(keyNode, fileIdentifier, null, UserSettings) }, referencedKeyType, null, readerContext, recorderChildContext);
46,000✔
214

215
                    if (key == null)
46,000✔
216
                    {
40✔
217
                        Dbg.Err($"{new InputContext(fileIdentifier, keyNode, keyPath)}: Dictionary includes null key, skipping pair");
40✔
218
                        continue;
40✔
219
                    }
220

221
                    object originalValue = null;
45,960✔
222
                    if (dict.Contains(key))
45,960✔
223
                    {
60✔
224
                        // Annoyingly the IDictionary interface does not allow for simultaneous retrieval and existence check a la .TryGetValue(), so we get to do a second lookup here
225
                        // This is definitely not the common path, though, so, fine
226
                        originalValue = dict[key];
60✔
227

228
                        if (writtenFields == null || writtenFields.Contains(key))
60✔
229
                        {
20✔
230
                            Dbg.Err($"{elementContext}: Dictionary includes duplicate key `{key.ToString()}`");
20✔
231
                        }
20✔
232
                    }
60✔
233

234
                    writtenFields?.Add(key);
45,960✔
235

236
                    var valuePath = new PathDictionaryValue(path);
45,960✔
237
                    dict[key] = Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(valueNode, fileIdentifier,valuePath, UserSettings) }, referencedValueType, originalValue, readerContext, recorderChildContext);
45,960✔
238
                }
45,960✔
239
                else
240
                {
1,725✔
241
                    var key = Serialization.ParseString(fieldElement.Name.LocalName, referencedKeyType, null, elementContext);
1,725✔
242

243
                    if (key == null)
1,725✔
244
                    {
×
245
                        // it's really rare for this to happen, I think you could do it with a converter but that's it
246
                        Dbg.Err($"{elementContext}: Dictionary includes null key, skipping pair");
×
247

248
                        // just in case . . .
249
                        if (string.Compare(fieldElement.Name.LocalName, "li", true) == 0)
×
250
                        {
×
251
                            Dbg.Err($"{elementContext}: Did you mean to write `li`? This field is case-sensitive.");
×
252
                        }
×
253

254
                        continue;
×
255
                    }
256

257
                    object originalValue = null;
1,725✔
258
                    if (dict.Contains(key))
1,725✔
259
                    {
240✔
260
                        // Annoyingly the IDictionary interface does not allow for simultaneous retrieval and existence check a la .TryGetValue(), so we get to do a second lookup here
261
                        // This is definitely not the common path, though, so, fine
262
                        originalValue = dict[key];
240✔
263

264
                        if (writtenFields == null || writtenFields.Contains(key))
240✔
265
                        {
160✔
266
                            Dbg.Err($"{elementContext}: Dictionary includes duplicate key `{key.ToString()}`");
160✔
267
                        }
160✔
268
                    }
240✔
269

270
                    Type valueType = referencedValueType;
1,725✔
271
                    if (recorderContext.bespoke_keytypedict)
1,725✔
272
                    {
60✔
273
                        if (referencedKeyType != typeof(Type))
60✔
274
                        {
×
275
                            Dbg.Err($"{elementContext}: Bespoke_Keytypedict used on a dictionary that isn't a Type key");
×
276
                        }
×
277

278
                        // make sure the value of `key` can be implicitly converted to `referencedValueType`
279
                        if (!referencedValueType.IsAssignableFrom((Type)key))
60✔
280
                        {
×
281
                            Dbg.Err($"{elementContext}: Key `{key}` cannot be implicitly converted to {referencedValueType}");
×
282
                        }
×
283

284
                        valueType = (Type)key;
60✔
285
                    }
60✔
286

287
                    writtenFields?.Add(key);
1,725✔
288

289
                    dict[key] = Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, null, UserSettings) }, valueType, originalValue, readerContext, recorderChildContext);
1,725✔
290
                }
1,725✔
291
            }
47,685✔
292
        }
23,380✔
293

294
        public override void ParseHashset(object hashset, Type referencedType, ReaderContext readerContext, Recorder.Context recorderContext, bool permitPatch)
295
        {
985✔
296
            // This is a gigantic pain because HashSet<> doesn't inherit from any non-generic interface that provides the functionality we want
297
            // So we're stuck doing it all through object and reflection
298
            // Thanks, HashSet
299
            // This might be a performance problem and we'll . . . deal with it later I guess?
300
            // This might actually be a good first place to use IL generation.
301

302
            var containsFunction = hashset.GetType().GetMethod("Contains");
985✔
303
            var addFunction = hashset.GetType().GetMethod("Add");
985✔
304

305
            var recorderChildContext = recorderContext.CreateChild();
985✔
306
            var keyParam = new object[1];   // this is just to cut down on GC churn
985✔
307

308
            // avoid the heap allocation if we can
309
            var writtenFields = permitPatch ? new HashSet<object>() : null;
985✔
310

311
            foreach (var fieldElement in xml.Elements())
9,995✔
312
            {
3,520✔
313
                var keyPath = new PathHashSetElement(path);
3,520✔
314
                var elementContext = new InputContext(fileIdentifier, fieldElement, keyPath);
3,520✔
315

316
                // There's a potential bit of ambiguity here if someone does <li /> and expects that to be an actual string named "li".
317
                // Practically, I think this is less likely than someone doing <li></li> and expecting that to be the empty string.
318
                // And there's no other way to express the empty string.
319
                // So . . . we treat that like the empty string.
320
                if (fieldElement.Name.LocalName == "li")
3,520✔
321
                {
3,160✔
322
                    // Treat this like a full node
323
                    var key = Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, keyPath, UserSettings) }, referencedType, null, readerContext, recorderChildContext);
3,160✔
324

325
                    if (key == null)
3,160✔
326
                    {
×
327
                        Dbg.Err($"{elementContext}: HashSet includes null key, skipping");
×
328
                        continue;
×
329
                    }
330

331
                    keyParam[0] = key;
3,160✔
332

333
                    if ((bool)containsFunction.Invoke(hashset, keyParam) && (writtenFields == null || writtenFields.Contains(key)))
3,160✔
334
                    {
160✔
335
                        Dbg.Err($"{elementContext}: HashSet includes duplicate key `{key.ToString()}`");
160✔
336
                    }
160✔
337
                    writtenFields?.Add(key);
3,160✔
338

339
                    addFunction.Invoke(hashset, keyParam);
3,160✔
340
                }
3,160✔
341
                else
342
                {
360✔
343
                    if (fieldElement.HasElements || !fieldElement.GetText().IsNullOrEmpty())
360✔
344
                    {
40✔
345
                        Dbg.Err($"{elementContext}: HashSet non-li member includes data, ignoring");
40✔
346
                    }
40✔
347

348
                    var key = Serialization.ParseString(fieldElement.Name.LocalName, referencedType, null, elementContext);
360✔
349

350
                    if (key == null)
360✔
351
                    {
×
352
                        // it's really rare for this to happen, I think you could do it with a converter but that's it
353
                        Dbg.Err($"{elementContext}: HashSet includes null key, skipping pair");
×
354
                        continue;
×
355
                    }
356

357
                    keyParam[0] = key;
360✔
358

359
                    if ((bool)containsFunction.Invoke(hashset, keyParam) && (writtenFields == null || writtenFields.Contains(key)))
360✔
360
                    {
20✔
361
                        Dbg.Err($"{elementContext}: HashSet includes duplicate key `{key.ToString()}`");
20✔
362
                    }
20✔
363
                    writtenFields?.Add(key);
360✔
364

365
                    addFunction.Invoke(hashset, keyParam);
360✔
366
                }
360✔
367
            }
3,520✔
368
        }
985✔
369

370
        public override void ParseStack(object stack, Type referencedType, ReaderContext readerContext, Recorder.Context recorderContext)
371
        {
55✔
372
            var pushFunction = stack.GetType().GetMethod("Push");
55✔
373

374
            var recorderChildContext = recorderContext.CreateChild();
55✔
375

376
            int index = 0;
55✔
377
            foreach (var fieldElement in xml.Elements())
545✔
378
            {
190✔
379
                var newPath = new PathIndex(path, index);
190✔
380

381
                if (fieldElement.Name.LocalName != "li")
190✔
382
                {
×
383
                    var elementContext = new InputContext(fileIdentifier, fieldElement, newPath);
×
384
                    Dbg.Err($"{elementContext}: Tag should be <li>, is <{fieldElement.Name.LocalName}>");
×
385
                }
×
386

387
                pushFunction.Invoke(stack, new object[] { Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, newPath, UserSettings) }, referencedType, null, readerContext, recorderChildContext) });
190✔
388
                ++index;
190✔
389
            }
190✔
390
        }
55✔
391

392
        public override void ParseQueue(object queue, Type referencedType, ReaderContext readerContext, Recorder.Context recorderContext)
393
        {
55✔
394
            var enqueueFunction = queue.GetType().GetMethod("Enqueue");
55✔
395

396
            var recorderChildContext = recorderContext.CreateChild();
55✔
397

398
            int index = 0;
55✔
399
            foreach (var fieldElement in xml.Elements())
545✔
400
            {
190✔
401
                var newPath = new PathIndex(path, index);
190✔
402

403
                if (fieldElement.Name.LocalName != "li")
190✔
404
                {
×
405
                    var elementContext = new InputContext(fileIdentifier, fieldElement, newPath);
×
406
                    Dbg.Err($"{elementContext}: Tag should be <li>, is <{fieldElement.Name.LocalName}>");
×
407
                }
×
408

409
                enqueueFunction.Invoke(queue, new object[] { Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, newPath, UserSettings) }, referencedType, null, readerContext, recorderChildContext) });
190✔
410
                ++index;
190✔
411
            }
190✔
412
        }
55✔
413

414
        public override void ParseTuple(object[] parameters, Type referencedType, IList<string> parameterNames, ReaderContext readerContext, Recorder.Context recorderContext)
415
        {
1,065✔
416
            int expectedCount = referencedType.GenericTypeArguments.Length;
1,065✔
417
            var recorderChildContext = recorderContext.CreateChild();
1,065✔
418

419
            var elements = xml.Elements().ToList();
1,065✔
420

421
            bool hasNonLi = false;
1,065✔
422
            foreach (var elementField in elements)
9,175✔
423
            {
2,990✔
424
                if (elementField.Name.LocalName != "li")
2,990✔
425
                {
650✔
426
                    hasNonLi = true;
650✔
427
                }
650✔
428
            }
2,990✔
429

430
            if (!hasNonLi)
1,065✔
431
            {
845✔
432
                // Treat it like an indexed array
433

434
                if (elements.Count != parameters.Length)
845✔
435
                {
60✔
436
                    Dbg.Err($"{GetInputContext()}: Tuple expects {expectedCount} parameters but got {elements.Count}");
60✔
437
                }
60✔
438

439
                for (int i = 0; i < Math.Min(parameters.Length, elements.Count); ++i)
6,230✔
440
                {
2,270✔
441
                    parameters[i] = Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(elements[i], fileIdentifier, new PathIndex(path, i), UserSettings) }, referencedType.GenericTypeArguments[i], null, readerContext, recorderChildContext);
2,270✔
442
                }
2,270✔
443

444
                // fill in anything missing
445
                for (int i = Math.Min(parameters.Length, elements.Count); i < parameters.Length; ++i)
1,810✔
446
                {
60✔
447
                    parameters[i] = Serialization.GenerateResultFallback(null, referencedType.GenericTypeArguments[i]);
60✔
448
                }
60✔
449
            }
845✔
450
            else
451
            {
220✔
452
                // We're doing named lookups instead
453
                if (parameterNames == null)
220✔
454
                {
40✔
455
                    parameterNames = UtilMisc.DefaultTupleNames;
40✔
456
                }
40✔
457

458
                if (parameterNames.Count < expectedCount)
220✔
459
                {
×
460
                    Dbg.Err($"{GetInputContext()}: Not enough tuple names (this honestly shouldn't even be possible)");
×
461

462
                    // TODO: handle it
463
                }
×
464

465
                bool[] seen = new bool[expectedCount];
220✔
466
                foreach (var elementItem in elements)
2,060✔
467
                {
700✔
468
                    var newPath = new PathMember(path, elementItem.Name.LocalName); // yeah okay
700✔
469
                    var elementContext = new InputContext(fileIdentifier, elementItem, newPath);
700✔
470

471
                    int index = parameterNames.FirstIndexOf(n => n == elementItem.Name.LocalName);
2,620✔
472

473
                    if (index == -1)
700✔
474
                    {
80✔
475
                        Dbg.Err($"{elementContext}: Found field with unexpected name `{elementItem.Name.LocalName}`");
80✔
476
                        continue;
80✔
477
                    }
478

479
                    if (seen[index])
620✔
480
                    {
20✔
481
                        Dbg.Err($"{elementContext}: Found duplicate of field `{elementItem.Name.LocalName}`");
20✔
482
                    }
20✔
483

484
                    seen[index] = true;
620✔
485
                    parameters[index] = Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(elementItem, fileIdentifier, newPath, UserSettings) }, referencedType.GenericTypeArguments[index], null, readerContext, recorderChildContext);
620✔
486
                }
620✔
487

488
                for (int i = 0; i < seen.Length; ++i)
1,840✔
489
                {
700✔
490
                    if (!seen[i])
700✔
491
                    {
100✔
492
                        Dbg.Err($"{GetInputContext()}: Missing field with name `{parameterNames[i]}`");
100✔
493

494
                        // Patch it up as best we can
495
                        parameters[i] = Serialization.GenerateResultFallback(null, referencedType.GenericTypeArguments[i]);
100✔
496
                    }
100✔
497
                }
700✔
498
            }
220✔
499
        }
1,065✔
500

501
        public override void ParseReflection(object obj, ReaderContext readerContext, Recorder.Context recorderContext)
502
        {
89,541✔
503
            var recorderChildContext = recorderContext.CreateChild();
89,541✔
504
            var setFields = new HashSet<string>();
89,541✔
505

506
            var type = obj.GetType();
89,541✔
507

508
            foreach (var fieldElement in xml.Elements())
897,351✔
509
            {
314,379✔
510
                // Check for fields that have been set multiple times
511
                string fieldName = fieldElement.Name.LocalName;
314,379✔
512
                if (setFields.Contains(fieldName))
314,379✔
513
                {
40✔
514
                    Dbg.Err($"{new InputContext(fileIdentifier, fieldElement, path)}: Duplicate field `{fieldName}`");
40✔
515
                    // Just allow us to fall through; it's an error, but one with a reasonably obvious handling mechanism
516
                }
40✔
517
                setFields.Add(fieldName);
314,379✔
518

519
                var fieldElementInfo = type.GetFieldFromHierarchy(fieldName);
314,379✔
520
                if (fieldElementInfo == null)
314,379✔
521
                {
120✔
522
                    // Try to find a close match, if we can, just for a better error message
523
                    string match = null;
120✔
524
                    string canonicalFieldName = UtilMisc.LooseMatchCanonicalize(fieldName);
120✔
525

526
                    foreach (var testField in type.GetSerializableFieldsFromHierarchy())
660✔
527
                    {
180✔
528
                        if (UtilMisc.LooseMatchCanonicalize(testField.Name) == canonicalFieldName)
180✔
529
                        {
60✔
530
                            match = testField.Name;
60✔
531

532
                            // We could in theory do something overly clever where we try to find the best name, but I really don't care that much; this is meant as a quick suggestion, not an ironclad solution.
533
                            break;
60✔
534
                        }
535
                    }
120✔
536

537
                    if (match != null)
120✔
538
                    {
60✔
539
                        Dbg.Err($"{new InputContext(fileIdentifier, fieldElement, path)}: Field `{fieldName}` does not exist in type {type}; did you mean `{match}`?");
60✔
540
                    }
60✔
541
                    else
542
                    {
60✔
543
                        Dbg.Err($"{new InputContext(fileIdentifier, fieldElement, path)}: Field `{fieldName}` does not exist in type {type}");
60✔
544
                    }
60✔
545

546
                    continue;
120✔
547
                }
548

549
                if (fieldElementInfo.GetCustomAttribute<IndexAttribute>() != null)
314,259✔
550
                {
×
551
                    Dbg.Err($"{new InputContext(fileIdentifier, fieldElement, path)}: Attempting to set index field `{fieldName}`; these are generated by the dec system");
×
552
                    continue;
×
553
                }
554

555
                if (fieldElementInfo.GetCustomAttribute<NonSerializedAttribute>() != null)
314,259✔
556
                {
20✔
557
                    Dbg.Err($"{new InputContext(fileIdentifier, fieldElement, path)}: Attempting to set nonserialized field `{fieldName}`");
20✔
558
                    continue;
20✔
559
                }
560

561
                fieldElementInfo.SetValue(obj, Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, new PathMember(path, fieldName), UserSettings) }, fieldElementInfo.FieldType, fieldElementInfo.GetValue(obj), readerContext, recorderChildContext, fieldInfo: fieldElementInfo));
314,239✔
562
            }
314,239✔
563
        }
89,541✔
564
    }
565
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc