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

aas-core-works / aas-core-codegen / 26633481551

29 May 2026 11:00AM UTC coverage: 84.184% (-0.02%) from 84.205%
26633481551

push

github

web-flow
Add support for list of primitives in C# (#642)

We extend the code generator for C# to support list of primitives, and
add the corresponding live tests.

81 of 103 new or added lines in 4 files covered. (78.64%)

30111 of 35768 relevant lines covered (84.18%)

2.53 hits per line

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

90.63
/aas_core_codegen/csharp/lib/_generate_xmlization.py
1
"""Generate code for XML de/serialization."""
2

3
import io
3✔
4
import textwrap
3✔
5
from typing import Tuple, Optional, List
3✔
6

7
from icontract import ensure, require
3✔
8

9
from aas_core_codegen import intermediate, naming, specific_implementations
3✔
10
from aas_core_codegen.common import (
3✔
11
    Error,
12
    Stripped,
13
    Identifier,
14
    assert_never,
15
    indent_but_first_line,
16
)
17
from aas_core_codegen.csharp import (
3✔
18
    common as csharp_common,
19
    naming as csharp_naming,
20
)
21
from aas_core_codegen.csharp.common import (
3✔
22
    INDENT as I,
23
    INDENT2 as II,
24
    INDENT3 as III,
25
    INDENT4 as IIII,
26
    INDENT5 as IIIII,
27
)
28

29

30
def _generate_skip_whitespace_and_comments() -> Stripped:
3✔
31
    """Generate the function to skip whitespace text and XML comments."""
32
    return Stripped(
3✔
33
        f"""\
34
internal static void SkipNoneWhitespaceAndComments(
35
{I}Xml.XmlReader reader)
36
{{
37
{I}while (
38
{II}!reader.EOF
39
{II}&& (
40
{III}reader.NodeType == Xml.XmlNodeType.None
41
{III}|| reader.NodeType == Xml.XmlNodeType.Whitespace
42
{III}|| reader.NodeType == Xml.XmlNodeType.Comment))
43
{I}{{
44
{II}reader.Read();
45
{I}}}
46
}}"""
47
    )
48

49

50
def _generate_read_whole_content_as_base_64() -> Stripped:
3✔
51
    """Generate the function to read the whole of element's content as bytes."""
52
    return Stripped(
3✔
53
        f"""\
54
/// <summary>
55
/// Read the whole content of an element into memory.
56
/// </summary>
57
private static byte[] ReadWholeContentAsBase64(
58
{I}Xml.XmlReader reader)
59
{{
60
{I}// The capacity of 1024 bytes is an arbitrary,
61
{I}// but plausible default capacity.
62
{I}byte[] buffer = new byte[1024];
63
{I}using System.IO.MemoryStream stream = (
64
{II}new System.IO.MemoryStream(1024));
65
{I}int readBytes;
66
{I}while ((readBytes = reader.ReadContentAsBase64(buffer, 0, 1024)) > 0)
67
{I}{{
68
{II}stream.Write(buffer, 0, readBytes);
69
{I}}}
70
{I}return stream.ToArray();
71
}}"""
72
    )
73

74

75
def _generate_extract_element_name() -> Stripped:
3✔
76
    """Generate the function to strip the prefix and check the namespace."""
77
    return Stripped(
3✔
78
        f"""\
79
/// <summary>
80
/// Check the namespace and extract the element's name.
81
/// </summary>
82
private static string TryElementName(
83
{I}Xml.XmlReader reader,
84
{I}out Reporting.Error? error
85
{I})
86
{{
87
{I}// Pre-condition
88
{I}if (reader.NodeType != Xml.XmlNodeType.Element
89
{II}&& reader.NodeType != Xml.XmlNodeType.EndElement)
90
{I}{{
91
{II}throw new System.InvalidOperationException(
92
{III}"Expected to be at a start or an end element " +
93
{III}$"in {{nameof(TryElementName)}}, " +
94
{III}$"but got: {{reader.NodeType}}");
95
{I}}}
96

97
{I}error = null;
98
{I}if (reader.NamespaceURI != NS)
99
{I}{{
100
{II}error = new Reporting.Error(
101
{III}$"Expected an element within a namespace {{NS}}, " +
102
{III}$"but got: {{reader.NamespaceURI}}");
103
{III}return "";
104
{I}}}
105

106
{I}return reader.LocalName;
107
}}"""
108
    )
109

110

111
def _generate_read_v_element() -> Stripped:
3✔
112
    return Stripped(
3✔
113
        f"""\
114
/// <summary>
115
/// Consume a <c>&lt;v&gt;</c> element from the reader.
116
/// </summary>
117
private static void ReadVElement(
118
{I}Xml.XmlReader reader,
119
{I}out Reporting.Error? error
120
{I})
121
{{
122
{I}if (reader.EOF) {{
123
{II}error = new Reporting.Error(
124
{III}"Expected a <v> element, but got an end-of-file.");
125
{II}return;
126
{I}}}
127

128
{I}if (reader.NodeType != Xml.XmlNodeType.Element)
129
{I}{{
130
{II}error = new Reporting.Error(
131
{III}"Expected a <v> start element, " +
132
{III}$"but got the node of type {{reader.NodeType}} " +
133
{III}$"with the value {{reader.Value}}");
134
{II}return;
135
{I}}}
136

137
{I}string elementName = TryElementName(
138
{II}reader, out error);
139
{I}if (error != null)
140
{I}{{
141
{II}return;
142
{I}}}
143
{I}if (elementName != "v")
144
{I}{{
145
{II}error = new Reporting.Error(
146
{III}"Expected a <v> element, " +
147
{III}$"but got an element {{elementName}}");
148
{II}return;
149
{I}}}
150

151
{I}// We can consume now the start element.
152
{I}reader.Read();
153
}}"""
154
    )
155

156

157
def _generate_read_v_end_element() -> Stripped:
3✔
158
    return Stripped(
3✔
159
        f"""\
160
/// <summary>
161
/// Consume a <c>&lt;/v&gt;</c> element from the reader.
162
/// </summary>
163
private static void ReadVEndElement(
164
{I}Xml.XmlReader reader,
165
{I}out Reporting.Error? error
166
{I})
167
{{
168
{I}if (reader.EOF) {{
169
{II}error = new Reporting.Error(
170
{III}"Expected a </v> element, but got an end-of-file.");
171
{II}return;
172
{I}}}
173

174
{I}if (reader.NodeType != Xml.XmlNodeType.EndElement)
175
{I}{{
176
{II}error = new Reporting.Error(
177
{III}"Expected a </v> end element, " +
178
{III}$"but got the node of type {{reader.NodeType}} " +
179
{III}$"with the value {{reader.Value}}");
180
{II}return;
181
{I}}}
182

183
{I}string elementName = TryElementName(
184
{II}reader, out error);
185
{I}if (error != null)
186
{I}{{
187
{II}return;
188
{I}}}
189
{I}if (elementName != "v")
190
{I}{{
191
{II}error = new Reporting.Error(
192
{III}"Expected a </v> element, " +
193
{III}$"but got an end element {{elementName}}");
194
{II}return;
195
{I}}}
196

197
{I}// We can consume now the end element.
198
{I}reader.Read();
199
}}"""
200
    )
201

202

203
def _generate_read_v_element_as_primitive_functions() -> List[Stripped]:
3✔
204
    result = []  # type: List[Stripped]
3✔
205

206
    for function_name, result_type, deserialization_expr in (
3✔
207
        ("ReadVElementAsBoolean", "bool", "reader.ReadContentAsBoolean()"),
208
        ("ReadVElementAsLong", "long", "reader.ReadContentAsLong()"),
209
        ("ReadVElementAsDouble", "double", "reader.ReadContentAsDouble()"),
210
        ("ReadVElementAsString", "string", "reader.ReadContentAsString()"),
211
    ):
212
        result.append(
3✔
213
            Stripped(
214
                f"""\
215
/// <summary>
216
/// Read the content of a <c>&lt;v&gt;</c> element
217
/// and parse it as {result_type}.
218
/// </summary>
219
private static {result_type}? {function_name}(
220
{I}Xml.XmlReader reader,
221
{I}out Reporting.Error? error
222
{I})
223
{{
224
{I}ReadVElement(reader, out error);
225
{I}if (error != null)
226
{I}{{
227
{II}return null;
228
{I}}}
229

230
{I}{result_type}? result = null;
231
{I}if (!reader.IsEmptyElement)
232
{I}{{
233
{II}if (reader.EOF)
234
{II}{{
235
{III}error = new Reporting.Error(
236
{IIII}"Expected an XML content representing {result_type}, " +
237
{IIII}"but reached the end-of-file");
238
{III}return null;
239
{II}}}
240

241
{II}try
242
{II}{{
243
{III}result = {deserialization_expr};
244
{II}}}
245
{II}catch (System.FormatException exception)
246
{II}{{
247
{III}error = new Reporting.Error(
248
{IIII}$"The content could not be de-serialized as {result_type}: {{exception}}");
249
{III}return null;
250
{II}}}
251
{I}}}
252

253
{I}ReadVEndElement(reader, out error);
254
{I}if (error != null)
255
{I}{{
256
{II}return null;
257
{I}}}
258

259
{I}if (result == null)
260
{I}{{
261
{II}throw new System.InvalidOperationException(
262
{III}"Unexpected result null when there is no error.");
263
{I}}}
264
{I}return result;
265
}}"""
266
            )
267
        )
268

269
    result.append(
3✔
270
        Stripped(
271
            f"""\
272
/// <summary>
273
/// Read a <c>&lt;v&gt;</c> element as base64-encoded bytes.
274
/// </summary>
275
private static byte[]? ReadVElementAsBytes(
276
{I}Xml.XmlReader reader,
277
{I}out Reporting.Error? error
278
{I})
279
{{
280
{I}ReadVElement(reader, out error);
281
{I}if (error != null)
282
{I}{{
283
{II}return null;
284
{I}}}
285

286
{I}byte[]? result;
287
{I}if (reader.IsEmptyElement)
288
{I}{{
289
{II}result = new byte[0];
290
{I}}} else {{
291
{II}if (reader.EOF)
292
{II}{{
293
{III}error = new Reporting.Error(
294
{IIII}"Expected an XML content with base64-encoded bytes, " +
295
{IIII}"but reached the end-of-file");
296
{III}return null;
297
{II}}}
298

299
{II}try
300
{II}{{
301
{III}result = ReadWholeContentAsBase64(reader);
302
{II}}}
303
{II}catch (System.FormatException exception)
304
{II}{{
305
{III}error = new Reporting.Error(
306
{IIII}"The content could not be de-serialized as " +
307
{IIII}$"base64-encoded bytes: {{exception}}");
308
{III}return null;
309
{II}}}
310
{I}}}
311

312
{I}ReadVEndElement(reader, out error);
313
{I}if (error != null)
314
{I}{{
315
{II}return null;
316
{I}}}
317

318
{I}return result;
319
}}"""
320
        )
321
    )
322

323
    return result
3✔
324

325

326
def _generate_read_v_element_as_enumeration(
3✔
327
    enumeration: intermediate.Enumeration,
328
) -> Stripped:
329
    """Generate the function to de-serialize a literal from a ``<v>``."""
330
    enum_name = csharp_naming.enum_name(enumeration.name)
3✔
331
    return Stripped(
3✔
332
        f"""\
333
/// <summary>
334
/// Read a <c>&lt;v&gt;</c> and parse its content as a literal of
335
/// <see cref="Aas.{enum_name}"/>.
336
/// </summary>
337
private static Aas.{enum_name}? ReadVElementAs{enum_name}(
338
{I}Xml.XmlReader reader,
339
{I}out Reporting.Error? error
340
{I})
341
{{
342
{I}string? text = ReadVElementAsString(reader, out error);
343
{I}if (error != null)
344
{I}{{
345
{II}return null;
346
{I}}}
347

348
{I}if (text == null)
349
{I}{{
350
{II}throw new System.InvalidOperationException(
351
{III}"Text must not be null if error is null.");
352
{I}}}
353

354
{I}Aas.{enum_name}? result = Stringification.{enum_name}FromString(
355
{II}text);
356

357
{I}if (result == null)
358
{I}{{
359
{II}error = new Reporting.Error(
360
{III}"The text could not be parsed as enumeration literal " +
361
{III}$"of {enum_name}: {{result}}");
362
{II}return null;
363
{I}}}
364

365
{I}return result;
366
}}"""
367
    )
368

369

370
def _generate_deserialize_primitive_property(
3✔
371
    prop: intermediate.Property, cls: intermediate.ConcreteClass
372
) -> Stripped:
373
    """Generate the snippet to deserialize a property ``prop`` of primitive type."""
374
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
375

376
    a_type = intermediate.try_primitive_type(type_anno)
3✔
377
    assert a_type is not None, f"Unexpected type annotation: {prop.type_annotation}"
3✔
378

379
    deserialization_expr: str
380
    if a_type is intermediate.PrimitiveType.BOOL:
3✔
381
        deserialization_expr = "reader.ReadContentAsBoolean()"
3✔
382
    elif a_type is intermediate.PrimitiveType.INT:
3✔
383
        deserialization_expr = "reader.ReadContentAsLong()"
3✔
384
    elif a_type is intermediate.PrimitiveType.FLOAT:
3✔
385
        deserialization_expr = "reader.ReadContentAsDouble()"
3✔
386
    elif a_type is intermediate.PrimitiveType.STR:
3✔
387
        deserialization_expr = "reader.ReadContentAsString()"
3✔
388
    elif a_type is intermediate.PrimitiveType.BYTEARRAY:
3✔
389
        deserialization_expr = f"""\
3✔
390
DeserializeImplementation.ReadWholeContentAsBase64(
391
{I}reader)"""
392
    else:
393
        assert_never(a_type)
×
394

395
    target_var = csharp_naming.variable_name(Identifier(f"the_{prop.name}"))
3✔
396

397
    prop_name = csharp_naming.property_name(prop.name)
3✔
398
    cls_name = csharp_naming.class_name(cls.name)
3✔
399
    xml_prop_name_literal = csharp_common.string_literal(naming.xml_property(prop.name))
3✔
400

401
    if a_type is intermediate.PrimitiveType.STR:
3✔
402
        empty_handling_body = Stripped(f'{target_var} = "";')
3✔
403
    else:
404
        empty_handling_body = Stripped(
3✔
405
            f"""\
406
error = new Reporting.Error(
407
{I}"The property {prop_name} of an instance of class {cls_name} " +
408
{I}"can not be de-serialized from a self-closing element " +
409
{I}"since it needs content");
410
error.PrependSegment(
411
{I}new Reporting.NameSegment(
412
{II}{xml_prop_name_literal}));
413
return null;"""
414
        )
415

416
    return Stripped(
3✔
417
        f"""\
418
if (isEmptyProperty)
419
{{
420
{I}{indent_but_first_line(empty_handling_body, I)}
421
}}
422
else
423
{{
424
{I}if (reader.EOF)
425
{I}{{
426
{II}error = new Reporting.Error(
427
{III}"Expected an XML content representing " +
428
{III}"the property {prop_name} of an instance of class {cls_name}, " +
429
{III}"but reached the end-of-file");
430
{II}return null;
431
{I}}}
432

433
{I}try
434
{I}{{
435
{II}{target_var} = {indent_but_first_line(deserialization_expr, I)};
436
{I}}}
437
{I}catch (System.Exception exception)
438
{I}{{
439
{II}if (exception is System.FormatException
440
{III}|| exception is System.Xml.XmlException)
441
{II}{{
442
{III}error = new Reporting.Error(
443
{IIII}"The property {prop_name} of an instance of class {cls_name} " +
444
{IIII}$"could not be de-serialized: {{exception.Message}}");
445
{III}error.PrependSegment(
446
{IIII}new Reporting.NameSegment(
447
{IIIII}{xml_prop_name_literal}));
448
{III}return null;
449
{II}}}
450

451
{II}throw;
452
{I}}}
453
}}"""
454
    )
455

456

457
def _generate_deserialize_enumeration_property(
3✔
458
    prop: intermediate.Property, cls: intermediate.ConcreteClass
459
) -> Stripped:
460
    """Generate the snippet to deserialize a property ``prop`` as an enum."""
461
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
462

463
    assert isinstance(type_anno, intermediate.OurTypeAnnotation)
3✔
464

465
    our_type = type_anno.our_type
3✔
466
    assert isinstance(our_type, intermediate.Enumeration)
3✔
467

468
    target_var = csharp_naming.variable_name(Identifier(f"the_{prop.name}"))
3✔
469
    text_var = csharp_naming.variable_name(Identifier(f"text_{prop.name}"))
3✔
470

471
    prop_name = csharp_naming.property_name(prop.name)
3✔
472
    cls_name = csharp_naming.class_name(cls.name)
3✔
473
    enum_name = csharp_naming.enum_name(our_type.name)
3✔
474
    xml_prop_name_literal = csharp_common.string_literal(naming.xml_property(prop.name))
3✔
475

476
    return Stripped(
3✔
477
        f"""\
478
if (isEmptyProperty)
479
{{
480
{I}error = new Reporting.Error(
481
{II}"The property {prop_name} of an instance of class {cls_name} " +
482
{II}"can not be de-serialized from a self-closing element " +
483
{II}"since it needs content");
484
{I}error.PrependSegment(
485
{II}new Reporting.NameSegment(
486
{III}{xml_prop_name_literal}));
487
{I}return null;
488
}}
489

490
{I}if (reader.EOF)
491
{{
492
{II}error = new Reporting.Error(
493
{III}"Expected an XML content representing " +
494
{III}"the property {prop_name} of an instance of class {cls_name}, " +
495
{III}"but reached the end-of-file");
496
{II}return null;
497
}}
498

499
string {text_var};
500
try
501
{{
502
{I}{text_var} = reader.ReadContentAsString();
503
}}
504
catch (System.FormatException exception)
505
{{
506
{I}error = new Reporting.Error(
507
{II}"The property {prop_name} of an instance of class {cls_name} " +
508
{II}$"could not be de-serialized as a string: {{exception}}");
509
{I}error.PrependSegment(
510
{II}new Reporting.NameSegment(
511
{III}{xml_prop_name_literal}));
512
{I}return null;
513
}}
514

515
{target_var} = Stringification.{enum_name}FromString(
516
{I}{text_var});
517

518
if ({target_var} == null)
519
{{
520
{I}error = new Reporting.Error(
521
{II}"The property {prop_name} of an instance of class {cls_name} " +
522
{II}"could not be de-serialized from an unexpected enumeration literal: " +
523
{II}{text_var});
524
{I}error.PrependSegment(
525
{II}new Reporting.NameSegment(
526
{III}{xml_prop_name_literal}));
527
{I}return null;
528
}}"""
529
    )
530

531

532
def _generate_deserialize_interface_property(
3✔
533
    prop: intermediate.Property,
534
    cls: intermediate.ConcreteClass,
535
) -> Stripped:
536
    """Generate the snippet to deserialize a property ``prop`` as an interface."""
537
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
538

539
    assert isinstance(type_anno, intermediate.OurTypeAnnotation)
3✔
540

541
    our_type = type_anno.our_type
3✔
542
    assert isinstance(
3✔
543
        our_type, (intermediate.AbstractClass, intermediate.ConcreteClass)
544
    )
545
    assert our_type.interface is not None
3✔
546

547
    prop_name = csharp_naming.property_name(prop.name)
3✔
548
    cls_name = csharp_naming.class_name(cls.name)
3✔
549

550
    interface_name = csharp_naming.interface_name(our_type.interface.name)
3✔
551

552
    target_var = csharp_naming.variable_name(Identifier(f"the_{prop.name}"))
3✔
553
    xml_prop_name_literal = csharp_common.string_literal(naming.xml_property(prop.name))
3✔
554

555
    return Stripped(
3✔
556
        f"""\
557
if (isEmptyProperty)
558
{{
559
{I}error = new Reporting.Error(
560
{II}$"Expected an XML element within the element {{elementName}} representing " +
561
{II}"the property {prop_name} of an instance of class {cls_name}, " +
562
{II}"but encountered a self-closing element {{elementName}}");
563
{I}return null;
564
}}
565

566
// We need to skip the whitespace here in order to be able to look ahead
567
// the discriminator element shortly.
568
SkipNoneWhitespaceAndComments(reader);
569

570
if (reader.EOF)
571
{{
572
{I}error = new Reporting.Error(
573
{II}$"Expected an XML element within the element {{elementName}} representing " +
574
{II}"the property {prop_name} of an instance of class {cls_name}, " +
575
{II}"but reached the end-of-file");
576
{I}return null;
577
}}
578

579
// Try to look ahead the discriminator name;
580
// we need this name only for the error reporting below.
581
// {interface_name}FromElement will perform more sophisticated
582
// checks.
583
string? discriminatorElementName = null;
584
if (reader.NodeType == Xml.XmlNodeType.Element)
585
{{
586
{I}discriminatorElementName = reader.LocalName;
587
}}
588

589
{target_var} = {interface_name}FromElement(
590
{I}reader, out error);
591

592
if (error != null)
593
{{
594
{I}if (discriminatorElementName != null)
595
{I}{{
596
{II}error.PrependSegment(
597
{III}new Reporting.NameSegment(
598
{IIII}discriminatorElementName));
599
{I}}}
600

601
{I}error.PrependSegment(
602
{II}new Reporting.NameSegment(
603
{III}{xml_prop_name_literal}));
604
{I}return null;
605
}}"""
606
    )
607

608

609
def _generate_deserialize_cls_property(prop: intermediate.Property) -> Stripped:
3✔
610
    """Generate the snippet to deserialize a property ``prop`` as a concrete class."""
611
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
612

613
    assert isinstance(type_anno, intermediate.OurTypeAnnotation)
3✔
614

615
    our_type = type_anno.our_type
3✔
616
    assert isinstance(our_type, intermediate.ConcreteClass)
3✔
617

618
    target_cls_name = csharp_naming.class_name(our_type.name)
3✔
619

620
    target_var = csharp_naming.variable_name(Identifier(f"the_{prop.name}"))
3✔
621
    xml_prop_name_literal = csharp_common.string_literal(naming.xml_property(prop.name))
3✔
622

623
    return Stripped(
3✔
624
        f"""\
625
{target_var} = {target_cls_name}FromSequence(
626
{I}reader, isEmptyProperty, out error);
627

628
if (error != null)
629
{{
630
{I}error.PrependSegment(
631
{II}new Reporting.NameSegment(
632
{III}{xml_prop_name_literal}));
633
{I}return null;
634
}}"""
635
    )
636

637

638
def _generate_deserialize_list_property(prop: intermediate.Property) -> Stripped:
3✔
639
    """Generate the code to de-serialize a property ``prop`` as a list."""
640
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
641

642
    assert isinstance(type_anno, intermediate.ListTypeAnnotation), "Pre-condition"
3✔
643

644
    deserialize_method: Stripped
645

646
    primitive_type = intermediate.try_primitive_type(type_anno.items)
3✔
647

648
    if primitive_type is not None or (
3✔
649
        isinstance(type_anno.items, intermediate.OurTypeAnnotation)
650
        and isinstance(type_anno.items.our_type, intermediate.Enumeration)
651
    ):
652
        if primitive_type is not None:
3✔
653
            if primitive_type is intermediate.PrimitiveType.BOOL:
3✔
654
                deserialize_method = Stripped("ReadVElementAsBoolean")
3✔
655
            elif primitive_type is intermediate.PrimitiveType.INT:
3✔
656
                deserialize_method = Stripped("ReadVElementAsLong")
3✔
657
            elif primitive_type is intermediate.PrimitiveType.FLOAT:
3✔
658
                deserialize_method = Stripped("ReadVElementAsDouble")
3✔
659
            elif primitive_type is intermediate.PrimitiveType.STR:
3✔
660
                deserialize_method = Stripped("ReadVElementAsString")
3✔
661
            elif primitive_type is intermediate.PrimitiveType.BYTEARRAY:
3✔
662
                deserialize_method = Stripped("ReadVElementAsBytes")
3✔
663
            else:
NEW
664
                assert_never(primitive_type)
×
665
        else:
NEW
666
            assert isinstance(
×
667
                type_anno.items, intermediate.OurTypeAnnotation
668
            ) and isinstance(type_anno.items.our_type, intermediate.Enumeration)
669

NEW
670
            enum_name = csharp_naming.enum_name(type_anno.items.our_type.name)
×
NEW
671
            deserialize_method = Stripped(f"ReadVElementAs{enum_name}")
×
672

673
    elif isinstance(type_anno.items, intermediate.OurTypeAnnotation) and isinstance(
3✔
674
        type_anno.items.our_type,
675
        (intermediate.AbstractClass, intermediate.ConcreteClass),
676
    ):
677
        if (
3✔
678
            isinstance(type_anno.items.our_type, intermediate.AbstractClass)
679
            or len(type_anno.items.our_type.concrete_descendants) > 0
680
        ):
681
            deserialize_method = Stripped(
3✔
682
                f"{csharp_naming.interface_name(type_anno.items.our_type.name)}FromElement"
683
            )
684
        else:
685
            deserialize_method = Stripped(
3✔
686
                f"{csharp_naming.class_name(type_anno.items.our_type.name)}FromElement"
687
            )
688
    else:
NEW
689
        raise NotImplementedError(
×
690
            f"(mristin) We only handle XML de/serialization of lists containing atomic "
691
            f"values, but you want to generate the code for a list of type {type_anno}. "
692
            f"Please contact the developers if you need this feature."
693
        )
694

695
    xml_prop_name = naming.xml_property(prop.name)
3✔
696
    xml_prop_name_literal = csharp_common.string_literal(xml_prop_name)
3✔
697

698
    target_var = csharp_naming.variable_name(Identifier(f"the_{prop.name}"))
3✔
699
    index_var = csharp_naming.variable_name(Identifier(f"index_{prop.name}"))
3✔
700

701
    item_type = csharp_common.generate_type(type_anno.items)
3✔
702

703
    body_for_non_empty_property = Stripped(
3✔
704
        f"""\
705
SkipNoneWhitespaceAndComments(reader);
706

707
int {index_var} = 0;
708
while (reader.NodeType == Xml.XmlNodeType.Element)
709
{{
710
{I}{item_type}? item = {deserialize_method}(
711
{II}reader, out error);
712

713
{I}if (error != null)
714
{I}{{
715
{II}error.PrependSegment(
716
{III}new Reporting.IndexSegment(
717
{IIII}{index_var}));
718
error.PrependSegment(
719
{I}new Reporting.NameSegment(
720
{II}{xml_prop_name_literal}));
721
{II}return null;
722
{I}}}
723

724
{I}{target_var}.Add(
725
{II}item
726
{III}?? throw new System.InvalidOperationException(
727
{IIII}"Unexpected item null when error null"));
728

729
{I}{index_var}++;
730
{I}SkipNoneWhitespaceAndComments(reader);
731
}}"""
732
    )
733

734
    return Stripped(
3✔
735
        f"""\
736
{target_var} = new List<{item_type}>();
737

738
if (!isEmptyProperty)
739
{{
740
{I}{indent_but_first_line(body_for_non_empty_property, I)}
741
}}"""
742
    )
743

744

745
@require(lambda prop, cls: id(prop) in cls.property_id_set)
3✔
746
def _generate_deserialize_property(
3✔
747
    prop: intermediate.Property, cls: intermediate.ConcreteClass
748
) -> Tuple[Optional[Stripped], Optional[Error]]:
749
    """Generate the snippet to deserialize the property ``prop`` from the content."""
750
    blocks = []  # type: List[Stripped]
3✔
751

752
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
753

754
    if isinstance(type_anno, intermediate.PrimitiveTypeAnnotation):
3✔
755
        blocks.append(_generate_deserialize_primitive_property(prop=prop, cls=cls))
3✔
756
    elif isinstance(type_anno, intermediate.OurTypeAnnotation):
3✔
757
        our_type = type_anno.our_type
3✔
758
        if isinstance(our_type, intermediate.Enumeration):
3✔
759
            blocks.append(
3✔
760
                _generate_deserialize_enumeration_property(prop=prop, cls=cls)
761
            )
762
        elif isinstance(our_type, intermediate.ConstrainedPrimitive):
3✔
763
            # NOTE (mristin, 2022-04-13):
764
            # The constrained primitives are only verified, but not represented as
765
            # separate classes in the XSD.
766
            blocks.append(_generate_deserialize_primitive_property(prop=prop, cls=cls))
3✔
767
        elif isinstance(
3✔
768
            our_type, (intermediate.ConcreteClass, intermediate.AbstractClass)
769
        ):
770
            if (
3✔
771
                isinstance(our_type, intermediate.AbstractClass)
772
                or len(our_type.concrete_descendants) > 0
773
            ):
774
                blocks.append(
3✔
775
                    _generate_deserialize_interface_property(prop=prop, cls=cls)
776
                )
777
            else:
778
                blocks.append(_generate_deserialize_cls_property(prop=prop))
3✔
779
        else:
780
            assert_never(our_type)
×
781

782
    elif isinstance(type_anno, intermediate.ListTypeAnnotation):
3✔
783
        blocks.append(_generate_deserialize_list_property(prop=prop))
3✔
784

785
    else:
786
        assert_never(type_anno)
×
787

788
    return Stripped("\n\n".join(blocks)), None
3✔
789

790

791
def _generate_deserialize_impl_cls_from_sequence(
3✔
792
    cls: intermediate.ConcreteClass,
793
) -> Tuple[Optional[Stripped], Optional[List[Error]]]:
794
    """Generate the function to de-serialize the ``cls`` from an XML sequence."""
795
    name = csharp_naming.class_name(identifier=cls.name)
3✔
796

797
    description = Stripped(
3✔
798
        f"""\
799
/// <summary>
800
/// Deserialize an instance of class {name} from a sequence of XML elements.
801
/// </summary>
802
/// <remarks>
803
/// If <paramref name="isEmptySequence" /> is set, we should try to deserialize
804
/// the instance from an empty sequence. That is, the parent element
805
/// was a self-closing element.
806
/// </remarks>"""
807
    )
808

809
    # NOTE (mristin, 2022-06-21):
810
    # Hard-wire for the case when no sequence is read
811
    if len(cls.constructor.arguments) == 0:
3✔
812
        return (
×
813
            Stripped(
814
                f"""\
815
{description}
816
internal static Aas.{name} {name}FromSequence(
817
{I}Xml.XmlReader reader,
818
{I}bool isEmptySequence,
819
{I}out Reporting.Error? error)
820
{{
821
{I}error = null;
822
{I}return new Aas.{name}();
823
}}  // internal static Aas.{name}? {name}FromSequence"""
824
            ),
825
            None,
826
        )
827

828
    errors = []  # type: List[Error]
3✔
829

830
    blocks = [
3✔
831
        Stripped("error = null;"),
832
    ]  # type: List[Stripped]
833

834
    assert len(cls.constructor.arguments) > 0, "Otherwise expected hard-wiring above"
3✔
835
    init_target_var_stmts = []  # type: List[Stripped]
3✔
836
    for prop in cls.properties:
3✔
837
        target_type = csharp_common.generate_type(prop.type_annotation)
3✔
838
        target_var = csharp_naming.variable_name(Identifier(f"the_{prop.name}"))
3✔
839

840
        # NOTE (mristin, 2022-04-13):
841
        # This is a poor man's trick to make all temporary variables optional.
842
        # The required constructor arguments / properties will be checked just
843
        # before the constructor as we can not predict in advance which properties
844
        # were actually provided without any lookahead in XML reading.
845
        if not target_type.endswith("?"):
3✔
846
            target_type = Stripped(f"{target_type}?")
3✔
847

848
        init_target_var_stmts.append(Stripped(f"{target_type} {target_var} = null;"))
3✔
849
    blocks.append(Stripped("\n".join(init_target_var_stmts)))
3✔
850

851
    # noinspection PyListCreation
852
    blocks_for_non_empty = []  # type: List[Stripped]
3✔
853

854
    blocks_for_non_empty.append(
3✔
855
        Stripped(
856
            f"""\
857
SkipNoneWhitespaceAndComments(reader);
858
if (reader.EOF)
859
{{
860
{I}error = new Reporting.Error(
861
{II}"Expected an XML element representing " +
862
{II}"a property of an instance of class {name}, " +
863
{II}"but reached the end-of-file");
864
{I}return null;
865
}}"""
866
        )
867
    )
868

869
    case_blocks = []  # type: List[Stripped]
3✔
870
    for prop in cls.properties:
3✔
871
        case_body, error = _generate_deserialize_property(prop=prop, cls=cls)
3✔
872
        if error is not None:
3✔
873
            errors.append(error)
×
874
            continue
×
875

876
        assert case_body is not None
3✔
877

878
        xml_prop_name = naming.xml_property(prop.name)
3✔
879
        xml_prop_name_literal = csharp_common.string_literal(xml_prop_name)
3✔
880
        case_blocks.append(
3✔
881
            Stripped(
882
                f"""\
883
case {xml_prop_name_literal}:
884
{{
885
{I}{indent_but_first_line(case_body, I)}
886
{I}break;
887
}}"""
888
            )
889
        )
890

891
    if len(errors) > 0:
3✔
892
        return None, errors
×
893

894
    case_blocks.append(
3✔
895
        Stripped(
896
            f"""\
897
default:
898
{I}error = new Reporting.Error(
899
{II}"We expected properties of the class {name}, " +
900
{II}"but got an unexpected element " +
901
{II}$"with the name {{elementName}}");
902
{I}return null;"""
903
        )
904
    )
905

906
    switch_body = "\n".join(case_blocks)
3✔
907

908
    blocks_for_non_empty.append(
3✔
909
        Stripped(
910
            f"""\
911
while (true)
912
{{
913
{I}SkipNoneWhitespaceAndComments(reader);
914

915
{I}if (reader.NodeType == Xml.XmlNodeType.EndElement || reader.EOF)
916
{I}{{
917
{II}break;
918
{I}}}
919

920
{I}if (reader.NodeType != Xml.XmlNodeType.Element)
921
{I}{{
922
{II}error = new Reporting.Error(
923
{III}"Expected an XML start element representing " +
924
{III}"a property of an instance of class {name}, " +
925
{III}$"but got the node of type {{reader.NodeType}} " +
926
{III}$"with the value {{reader.Value}}");
927
{II}return null;
928
{I}}}
929

930
{I}string elementName = TryElementName(
931
{II}reader, out error);
932
{I}if (error != null)
933
{I}{{
934
{II}return null;
935
{I}}}
936

937
{I}bool isEmptyProperty = reader.IsEmptyElement;
938

939
{I}// Skip the expected element
940
{I}reader.Read();
941

942
{I}switch (elementName)
943
{I}{{
944
{II}{indent_but_first_line(switch_body, II)}
945
{I}}}
946

947
{I}SkipNoneWhitespaceAndComments(reader);
948

949
{I}if (!isEmptyProperty)
950
{I}{{
951
{II}// Read the end element
952

953
{II}if (reader.EOF)
954
{II}{{
955
{III}error = new Reporting.Error(
956
{IIII}"Expected an XML end element to conclude a property of class {name} " +
957
{IIII}$"with the element name {{elementName}}, " +
958
{IIII}"but got the end-of-file.");
959
{III}return null;
960
{II}}}
961
{II}if (reader.NodeType != Xml.XmlNodeType.EndElement)
962
{II}{{
963
{III}error = new Reporting.Error(
964
{IIII}"Expected an XML end element to conclude a property of class {name} " +
965
{IIII}$"with the element name {{elementName}}, " +
966
{IIII}$"but got the node of type {{reader.NodeType}} " +
967
{IIII}$"with the value {{reader.Value}}");
968
{III}return null;
969
{II}}}
970

971
{II}string endElementName = TryElementName(
972
{III}reader, out error);
973
{II}if (error != null)
974
{II}{{
975
{III}return null;
976
{II}}}
977

978
{II}if (endElementName != elementName)
979
{II}{{
980
{III}error = new Reporting.Error(
981
{IIII}"Expected an XML end element to conclude a property of class {name} " +
982
{IIII}$"with the element name {{elementName}}, " +
983
{IIII}$"but got the end element with the name {{reader.Name}}");
984
{III}return null;
985
{II}}}
986
{II}// Skip the expected end element
987
{II}reader.Read();
988
{I}}}
989
}}"""
990
        )
991
    )
992

993
    body_for_non_empty_sequence = "\n".join(blocks_for_non_empty)
3✔
994
    blocks.append(
3✔
995
        Stripped(
996
            f"""\
997
if (!isEmptySequence)
998
{{
999
{I}{indent_but_first_line(body_for_non_empty_sequence, I)}
1000
}}"""
1001
        )
1002
    )
1003

1004
    # region Check that the mandatory properties have been set
1005

1006
    for prop in cls.properties:
3✔
1007
        prop_csharp = csharp_naming.property_name(prop.name)
3✔
1008
        target_var = csharp_naming.variable_name(Identifier(f"the_{prop.name}"))
3✔
1009

1010
        if not isinstance(prop.type_annotation, intermediate.OptionalTypeAnnotation):
3✔
1011
            blocks.append(
3✔
1012
                Stripped(
1013
                    f"""\
1014
if ({target_var} == null)
1015
{{
1016
{I}error = new Reporting.Error(
1017
{II}"The required property {prop_csharp} has not been given " +
1018
{II}"in the XML representation of an instance of class {name}");
1019
{I}return null;
1020
}}"""
1021
                )
1022
            )
1023

1024
    # endregion
1025

1026
    # region Pass in properties as arguments to the constructor
1027

1028
    property_names = [prop.name for prop in cls.properties]
3✔
1029
    constructor_argument_names = [arg.name for arg in cls.constructor.arguments]
3✔
1030

1031
    # fmt: off
1032
    assert (
3✔
1033
            set(prop.name for prop in cls.properties)
1034
            == set(arg.name for arg in cls.constructor.arguments)
1035
    ), (
1036
        f"Expected the properties to coincide with constructor arguments, "
1037
        f"but they do not for {cls.name!r}:"
1038
        f"{property_names=}, {constructor_argument_names=}"
1039
    )
1040
    # fmt: on
1041

1042
    init_writer = io.StringIO()
3✔
1043
    init_writer.write(f"return new Aas.{name}(\n")
3✔
1044

1045
    for i, arg in enumerate(cls.constructor.arguments):
3✔
1046
        prop = cls.properties_by_name[arg.name]
3✔
1047

1048
        # NOTE (mristin, 2022-04-13):
1049
        # The argument to the constructor may be optional while the property might
1050
        # be required, since we can set the default value in the body of the
1051
        # constructor. However, we can not have an optional property and a required
1052
        # constructor argument as we then would not know how to create the instance.
1053

1054
        if not (
3✔
1055
            intermediate.type_annotations_equal(
1056
                arg.type_annotation, prop.type_annotation
1057
            )
1058
            or intermediate.type_annotations_equal(
1059
                intermediate.beneath_optional(arg.type_annotation),
1060
                prop.type_annotation,
1061
            )
1062
        ):
1063
            errors.append(
×
1064
                Error(
1065
                    arg.parsed.node,
1066
                    f"Expected type annotation for property {prop.name!r} "
1067
                    f"and constructor argument {arg.name!r} "
1068
                    f"of the class {cls.name!r} to have matching types, "
1069
                    f"but they do not: "
1070
                    f"property type is {prop.type_annotation} "
1071
                    f"and argument type is {arg.type_annotation}. "
1072
                    f"Hence we do not know how to generate the call "
1073
                    f"to the constructor in the JSON de-serialization.",
1074
                )
1075
            )
1076
            continue
×
1077

1078
        arg_var = csharp_naming.variable_name(Identifier(f"the_{arg.name}"))
3✔
1079

1080
        init_writer.write(f"{I}{arg_var}")
3✔
1081
        if not isinstance(prop.type_annotation, intermediate.OptionalTypeAnnotation):
3✔
1082
            init_writer.write("\n")
3✔
1083

1084
            # Dedention could not work here due to prefix indention at the very
1085
            # beginning.
1086
            init_writer.write(
3✔
1087
                f"""\
1088
{II} ?? throw new System.InvalidOperationException(
1089
{III}"Unexpected null, had to be handled before")"""
1090
            )
1091

1092
        if i < len(cls.constructor.arguments) - 1:
3✔
1093
            init_writer.write(",\n")
3✔
1094
        else:
1095
            init_writer.write(");")
3✔
1096

1097
    if len(errors) > 0:
3✔
1098
        return None, errors
×
1099

1100
    # endregion
1101

1102
    blocks.append(Stripped(init_writer.getvalue()))
3✔
1103

1104
    writer = io.StringIO()
3✔
1105
    writer.write(
3✔
1106
        f"""\
1107
{description}
1108
internal static Aas.{name}? {name}FromSequence(
1109
{I}Xml.XmlReader reader,
1110
{I}bool isEmptySequence,
1111
{I}out Reporting.Error? error)
1112
{{
1113
"""
1114
    )
1115

1116
    for i, block in enumerate(blocks):
3✔
1117
        if i > 0:
3✔
1118
            writer.write("\n\n")
3✔
1119
        writer.write(textwrap.indent(block, I))
3✔
1120

1121
    writer.write(f"\n}}  // internal static Aas.{name}? {name}FromSequence")
3✔
1122

1123
    return Stripped(writer.getvalue()), None
3✔
1124

1125

1126
def _generate_deserialize_impl_concrete_cls_from_element(
3✔
1127
    cls: intermediate.ConcreteClass,
1128
) -> Stripped:
1129
    """Generate the function to de-serialize a concrete ``cls`` from an XML element."""
1130
    name = csharp_naming.class_name(cls.name)
3✔
1131
    xml_name = naming.xml_class_name(cls.name)
3✔
1132
    xml_name_literal = csharp_common.string_literal(xml_name)
3✔
1133

1134
    # NOTE (mristin, 2022-06-21):
1135
    # We need to propagate nullability. Otherwise, InspectCode complains.
1136
    result_nullability = "?" if len(cls.constructor.arguments) > 0 else ""
3✔
1137

1138
    body = Stripped(
3✔
1139
        f"""\
1140
error = null;
1141

1142
SkipNoneWhitespaceAndComments(reader);
1143

1144
if (reader.EOF)
1145
{{
1146
{I}error = new Reporting.Error(
1147
{II}"Expected an XML element representing an instance of class {name}, " +
1148
{II}"but reached the end-of-file");
1149
{I}return null;
1150
}}
1151

1152
if (reader.NodeType != Xml.XmlNodeType.Element)
1153
{{
1154
{I}error = new Reporting.Error(
1155
{II}"Expected an XML element representing an instance of class {name}, " +
1156
{II}$"but got a node of type {{reader.NodeType}} " +
1157
{II}$"with value {{reader.Value}}");
1158
{I}return null;
1159
}}
1160

1161
string elementName = TryElementName(
1162
    reader, out error);
1163
if (error != null)
1164
{{
1165
{I}return null;
1166
}}
1167

1168
if (elementName != {xml_name_literal})
1169
{{
1170
{I}error = new Reporting.Error(
1171
{II}"Expected an element representing an instance of class {name} " +
1172
{II}$"with element name {xml_name}, but got: {{elementName}}");
1173
{I}return null;
1174
}}
1175

1176
bool isEmptyElement = reader.IsEmptyElement;
1177

1178
// Skip the element node and go to the content
1179
reader.Read();
1180

1181
Aas.{name}{result_nullability} result = (
1182
{I}{name}FromSequence(
1183
{II}reader, isEmptyElement, out error));
1184
if (error != null)
1185
{{
1186
{I}return null;
1187
}}
1188

1189
SkipNoneWhitespaceAndComments(reader);
1190

1191
if (!isEmptyElement)
1192
{{
1193
{I}if (reader.EOF)
1194
{I}{{
1195
{II}error = new Reporting.Error(
1196
{III}"Expected an XML end element concluding an instance of class {name}, " +
1197
{III}"but reached the end-of-file");
1198
{II}return null;
1199
{I}}}
1200

1201
{I}if (reader.NodeType != Xml.XmlNodeType.EndElement)
1202
{I}{{
1203
{II}error = new Reporting.Error(
1204
{III}"Expected an XML end element concluding an instance of class {name}, " +
1205
{III}$"but got a node of type {{reader.NodeType}} " +
1206
{III}$"with value {{reader.Value}}");
1207
{II}return null;
1208
{I}}}
1209

1210
{I}string endElementName = TryElementName(
1211
{II}reader, out error);
1212
{I}if (error != null)
1213
{I}{{
1214
{II}return null;
1215
{I}}}
1216

1217
{I}if (endElementName != elementName)
1218
{I}{{
1219
{II}error = new Reporting.Error(
1220
{III}$"Expected an XML end element with an name {{elementName}}, " +
1221
{III}$"but got: {{endElementName}}");
1222
{II}return null;
1223
{I}}}
1224

1225
{I}// Skip the end element
1226
{I}reader.Read();
1227
}}
1228

1229
return result;"""
1230
    )
1231

1232
    return Stripped(
3✔
1233
        f"""\
1234
/// <summary>
1235
/// Deserialize an instance of class {name} from an XML element.
1236
/// </summary>
1237
internal static Aas.{name}? {name}FromElement(
1238
{I}Xml.XmlReader reader,
1239
{I}out Reporting.Error? error)
1240
{{
1241
{I}{indent_but_first_line(body, I)}
1242
}}  // internal static Aas.{name}? {name}FromElement"""
1243
    )
1244

1245

1246
def _generate_deserialize_impl_interface_from_element(
3✔
1247
    interface: intermediate.Interface,
1248
) -> Stripped:
1249
    """Generate the function to de-serialize an ``interface`` from an XML element."""
1250
    name = csharp_naming.interface_name(interface.name)
3✔
1251

1252
    blocks = [
3✔
1253
        Stripped(
1254
            f"""\
1255
error = null;
1256

1257
SkipNoneWhitespaceAndComments(reader);
1258

1259
if (reader.EOF)
1260
{{
1261
{I}error = new Reporting.Error(
1262
{II}"Expected an XML element, but reached end-of-file");
1263
{I}return null;
1264
}}
1265

1266
if (reader.NodeType != Xml.XmlNodeType.Element)
1267
{{
1268
{I}error = new Reporting.Error(
1269
{II}"Expected an XML element, " +
1270
{II}$"but got a node of type {{reader.NodeType}} " +
1271
{II}$"with value {{reader.Value}}");
1272
{I}return null;
1273
}}"""
1274
        )
1275
    ]  # type: List[Stripped]
1276

1277
    case_stmts = []  # type: List[Stripped]
3✔
1278
    for implementer in interface.implementers:
3✔
1279
        implementer_xml_name_literal = csharp_common.string_literal(
3✔
1280
            naming.xml_class_name(implementer.name)
1281
        )
1282

1283
        implementer_name = csharp_naming.class_name(implementer.name)
3✔
1284

1285
        case_stmts.append(
3✔
1286
            Stripped(
1287
                f"""\
1288
case {implementer_xml_name_literal}:
1289
{I}return {implementer_name}FromElement(
1290
{II}reader, out error);"""
1291
            )
1292
        )
1293

1294
    case_stmts.append(
3✔
1295
        Stripped(
1296
            f"""\
1297
default:
1298
{I}error = new Reporting.Error(
1299
{II}$"Unexpected element with the name {{elementName}}");
1300
{I}return null;"""
1301
        )
1302
    )
1303

1304
    switch_writer = io.StringIO()
3✔
1305
    switch_writer.write(
3✔
1306
        f"""\
1307
string elementName = TryElementName(
1308
{I}reader, out error);
1309
if (error != null)
1310
{{
1311
{I}return null;
1312
}}
1313

1314
switch (elementName)
1315
{{
1316
"""
1317
    )
1318
    for i, case_stmt in enumerate(case_stmts):
3✔
1319
        if i > 0:
3✔
1320
            switch_writer.write("\n")
3✔
1321
        switch_writer.write(textwrap.indent(case_stmt, I))
3✔
1322

1323
    switch_writer.write("\n}")
3✔
1324

1325
    blocks.append(Stripped(switch_writer.getvalue()))
3✔
1326

1327
    writer = io.StringIO()
3✔
1328
    writer.write(
3✔
1329
        f"""\
1330
/// <summary>
1331
/// Deserialize an instance of {name} from an XML element.
1332
/// </summary>
1333
[CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")]
1334
internal static Aas.{name}? {name}FromElement(
1335
{I}Xml.XmlReader reader,
1336
{I}out Reporting.Error? error)
1337
{{
1338
"""
1339
    )
1340

1341
    for i, block in enumerate(blocks):
3✔
1342
        if i > 0:
3✔
1343
            writer.write("\n\n")
3✔
1344
        writer.write(textwrap.indent(block, I))
3✔
1345

1346
    writer.write(f"\n}}  // internal static Aas.{name}? {name}FromElement")
3✔
1347

1348
    return Stripped(writer.getvalue())
3✔
1349

1350

1351
def _generate_deserialize_impl(
3✔
1352
    symbol_table: intermediate.SymbolTable,
1353
    spec_impls: specific_implementations.SpecificImplementations,
1354
) -> Tuple[Optional[Stripped], Optional[List[Error]]]:
1355
    """Generate the implementation for deserialization functions."""
1356
    blocks = [
3✔
1357
        _generate_skip_whitespace_and_comments(),
1358
        _generate_read_whole_content_as_base_64(),
1359
        _generate_extract_element_name(),
1360
        _generate_read_v_element(),
1361
        _generate_read_v_end_element(),
1362
        *_generate_read_v_element_as_primitive_functions(),
1363
    ]  # type: List[Stripped]
1364

1365
    for enumeration in symbol_table.enumerations:
3✔
1366
        blocks.append(_generate_read_v_element_as_enumeration(enumeration))
3✔
1367

1368
    errors = []  # type: List[Error]
3✔
1369

1370
    # NOTE (mristin, 2022-04-13):
1371
    # Enumerations are going to be directly deserialized using
1372
    # ``Stringification``.
1373

1374
    # NOTE (mristin, 2022-04-13):
1375
    # Constrained primitives are only verified, but do not represent a C# type.
1376

1377
    for cls in symbol_table.classes:
3✔
1378
        if cls.is_implementation_specific:
3✔
1379
            implementation_keys = [
×
1380
                specific_implementations.ImplementationKey(
1381
                    f"Xmlization/DeserializeImplementation/"
1382
                    f"{cls.name}_from_element.cs"
1383
                ),
1384
                specific_implementations.ImplementationKey(
1385
                    f"Xmlization/DeserializeImplementation/"
1386
                    f"{cls.name}_from_sequence.cs"
1387
                ),
1388
            ]
1389

1390
            for implementation_key in implementation_keys:
×
1391
                implementation = spec_impls.get(implementation_key, None)
×
1392
                if implementation is None:
×
1393
                    errors.append(
×
1394
                        Error(
1395
                            cls.parsed.node,
1396
                            f"The xmlization snippet is missing "
1397
                            f"for the implementation-specific "
1398
                            f"class {cls.name}: {implementation_key}",
1399
                        )
1400
                    )
1401
                    continue
×
1402
                else:
1403
                    blocks.append(spec_impls[implementation_key])
×
1404
        else:
1405
            if isinstance(cls, intermediate.ConcreteClass):
3✔
1406
                (
3✔
1407
                    block,
1408
                    generation_errors,
1409
                ) = _generate_deserialize_impl_cls_from_sequence(cls=cls)
1410
                if generation_errors is not None:
3✔
1411
                    errors.append(
×
1412
                        Error(
1413
                            cls.parsed.node,
1414
                            f"Failed to generate the XML deserialization code "
1415
                            f"for the class {cls.name}",
1416
                            generation_errors,
1417
                        )
1418
                    )
1419
                else:
1420
                    assert block is not None
3✔
1421
                    blocks.append(block)
3✔
1422

1423
            if cls.interface is not None:
3✔
1424
                blocks.append(
3✔
1425
                    _generate_deserialize_impl_interface_from_element(
1426
                        interface=cls.interface
1427
                    )
1428
                )
1429

1430
            if isinstance(cls, intermediate.ConcreteClass):
3✔
1431
                blocks.append(
3✔
1432
                    _generate_deserialize_impl_concrete_cls_from_element(cls=cls)
1433
                )
1434

1435
    if len(errors) > 0:
3✔
1436
        return None, errors
×
1437

1438
    writer = io.StringIO()
3✔
1439

1440
    writer.write(
3✔
1441
        """\
1442
/// <summary>
1443
/// Implement the deserialization of meta-model classes from XML.
1444
/// </summary>
1445
/// <remarks>
1446
/// The implementation propagates an <see cref="Reporting.Error" /> instead of
1447
/// relying on exceptions. Under the assumption that incorrect data is much less
1448
/// frequent than correct data, this makes the deserialization more
1449
/// efficient.
1450
///
1451
/// However, we do not want to force the client to deal with
1452
/// the <see cref="Reporting.Error" /> class as this is not intuitive.
1453
/// Therefore we distinguish the implementation, realized in
1454
/// <see cref="DeserializeImplementation" />, and the facade given in
1455
/// <see cref="Deserialize" /> class.
1456
/// </remarks>
1457
internal static class DeserializeImplementation
1458
{
1459
"""
1460
    )
1461

1462
    for i, block in enumerate(blocks):
3✔
1463
        if i > 0:
3✔
1464
            writer.write("\n\n")
3✔
1465
        writer.write(textwrap.indent(block, I))
3✔
1466

1467
    writer.write("\n}  // internal static class DeserializeImplementation")
3✔
1468

1469
    return Stripped(writer.getvalue()), None
3✔
1470

1471

1472
def _generate_deserialize_from(name: Identifier) -> Stripped:
3✔
1473
    """Generate the facade method for deserialization of the class or interface."""
1474
    writer = io.StringIO()
3✔
1475

1476
    writer.write(
3✔
1477
        f"""\
1478
/// <summary>
1479
/// Deserialize an instance of {name} from <paramref name="reader" />.
1480
/// </summary>
1481
/// <param name="reader">Initialized XML reader with cursor set to the element</param>
1482
/// <exception cref="Xmlization.Exception">
1483
/// Thrown when the element is not a valid XML
1484
/// representation of {name}.
1485
/// </exception>
1486
"""
1487
    )
1488

1489
    if name.startswith("I"):
3✔
1490
        writer.write(
3✔
1491
            """\
1492
[CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")]"""
1493
        )
1494

1495
    writer.write(
3✔
1496
        f"""\
1497
public static Aas.{name} {name}From(
1498
{I}Xml.XmlReader reader)
1499
{{
1500
{I}DeserializeImplementation.SkipNoneWhitespaceAndComments(reader);
1501

1502
{I}if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.XmlDeclaration)
1503
{I}{{
1504
{II}throw new Xmlization.Exception(
1505
{III}"",
1506
{III}"Unexpected XML declaration when reading an instance " +
1507
{III}"of class {name}, as we expect the reader " +
1508
{III}"to be set at content with MoveToContent");
1509
{I}}}
1510

1511
{I}Aas.{name}? result = (
1512
{II}DeserializeImplementation.{name}FromElement(
1513
{III}reader,
1514
{III}out Reporting.Error? error));
1515
{I}if (error != null)
1516
{I}{{
1517
{II}throw new Xmlization.Exception(
1518
{III}Reporting.GenerateRelativeXPath(error.PathSegments),
1519
{III}error.Cause);
1520
{I}}}
1521
{I}return result
1522
{II}?? throw new System.InvalidOperationException(
1523
{III}"Unexpected output null when error is null");
1524
}}"""
1525
    )
1526

1527
    return Stripped(writer.getvalue())
3✔
1528

1529

1530
def _generate_deserialize(symbol_table: intermediate.SymbolTable) -> Stripped:
3✔
1531
    """Generate the public class ``Deserialize``."""
1532
    blocks = []  # type: List[Stripped]
3✔
1533

1534
    # NOTE (mristin, 2022-04-13):
1535
    # We use stringification for de-serialization of enumerations.
1536

1537
    # NOTE (mristin, 2022-04-13):
1538
    # Constrained primitives are not handled as separate classes, but as
1539
    # primitives, and only verified in the verification.
1540

1541
    for cls in symbol_table.classes:
3✔
1542
        if cls.interface is not None:
3✔
1543
            blocks.append(
3✔
1544
                _generate_deserialize_from(
1545
                    name=csharp_naming.interface_name(cls.interface.name)
1546
                )
1547
            )
1548

1549
        if isinstance(cls, intermediate.ConcreteClass):
3✔
1550
            blocks.append(
3✔
1551
                _generate_deserialize_from(name=csharp_naming.class_name(cls.name))
1552
            )
1553

1554
    writer = io.StringIO()
3✔
1555
    writer.write(
3✔
1556
        """\
1557
/// <summary>
1558
/// Deserialize instances of meta-model classes from XML.
1559
/// </summary>
1560
"""
1561
    )
1562

1563
    first_cls = symbol_table.classes[0] if len(symbol_table.classes) > 0 else None
3✔
1564

1565
    if first_cls is not None:
3✔
1566
        cls_name: str
1567
        if isinstance(first_cls, intermediate.AbstractClass):
3✔
1568
            cls_name = csharp_naming.interface_name(first_cls.name)
3✔
1569
        elif isinstance(first_cls, intermediate.ConcreteClass):
3✔
1570
            cls_name = csharp_naming.class_name(first_cls.name)
3✔
1571
        else:
1572
            assert_never(first_cls)
×
1573

1574
        an_instance_variable = csharp_naming.variable_name(Identifier("an_instance"))
3✔
1575

1576
        writer.write(
3✔
1577
            f"""\
1578
/// <example>
1579
/// Here is an example how to parse an instance of class {cls_name}:
1580
/// <code>
1581
/// var reader = new System.Xml.XmlReader(/* some arguments */);
1582
/// Aas.{cls_name} {an_instance_variable} = Deserialize.{cls_name}From(
1583
/// {I}reader);
1584
/// </code>
1585
/// </example>
1586
///
1587
/// <example>
1588
/// If the elements live in a namespace, you have to supply it. For example:
1589
/// <code>
1590
/// var reader = new System.Xml.XmlReader(/* some arguments */);
1591
/// Aas.{cls_name} {an_instance_variable} = Deserialize.{cls_name}From(
1592
/// {I}reader,
1593
/// {I}"http://www.example.com/5/12");
1594
/// </code>
1595
/// </example>
1596
"""
1597
        )
1598

1599
    writer.write(
3✔
1600
        """\
1601
public static class Deserialize
1602
{
1603
"""
1604
    )
1605

1606
    for i, block in enumerate(blocks):
3✔
1607
        if i > 0:
3✔
1608
            writer.write("\n\n")
3✔
1609
        writer.write(textwrap.indent(block, I))
3✔
1610

1611
    writer.write("\n}  // public static class Deserialize")
3✔
1612

1613
    return Stripped(writer.getvalue())
3✔
1614

1615

1616
def _generate_serialize_primitive_property_as_content(
3✔
1617
    prop: intermediate.Property,
1618
) -> Stripped:
1619
    """Generate the serialization of the primitive-type ``prop`` as XML content."""
1620
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
1621

1622
    a_type = intermediate.try_primitive_type(type_anno)
3✔
1623
    assert (
3✔
1624
        a_type is not None
1625
    ), f"Unexpected non-primitive type of the property {prop.name!r}: {type_anno}"
1626

1627
    prop_name = csharp_naming.property_name(prop.name)
3✔
1628
    xml_prop_name_literal = csharp_common.string_literal(naming.xml_property(prop.name))
3✔
1629

1630
    write_value_block: Stripped
1631

1632
    if (
3✔
1633
        a_type is intermediate.PrimitiveType.BOOL
1634
        or a_type is intermediate.PrimitiveType.INT
1635
        or a_type is intermediate.PrimitiveType.FLOAT
1636
        or a_type is intermediate.PrimitiveType.STR
1637
    ):
1638
        write_value_block = Stripped(
3✔
1639
            f"""\
1640
writer.WriteValue(
1641
{I}that.{prop_name});"""
1642
        )
1643
    elif a_type is intermediate.PrimitiveType.BYTEARRAY:
3✔
1644
        write_value_block = Stripped(
3✔
1645
            f"""\
1646
writer.WriteBase64(
1647
{I}that.{prop_name},
1648
{I}0,
1649
{I}that.{prop_name}.Length);"""
1650
        )
1651
    else:
1652
        assert_never(a_type)
×
1653

1654
    # NOTE (mristin, 2022-06-21):
1655
    # Wrap the write_value_block with property even if we discard it below
1656
    write_value_block = Stripped(
3✔
1657
        f"""\
1658
writer.WriteStartElement(
1659
{I}{xml_prop_name_literal},
1660
{I}NS);
1661

1662
{write_value_block}
1663

1664
writer.WriteEndElement();"""
1665
    )
1666

1667
    if isinstance(prop.type_annotation, intermediate.OptionalTypeAnnotation):
3✔
1668
        if a_type in (
3✔
1669
            intermediate.PrimitiveType.BOOL,
1670
            intermediate.PrimitiveType.INT,
1671
            intermediate.PrimitiveType.FLOAT,
1672
        ):
1673
            write_value_block = Stripped(
3✔
1674
                f"""\
1675
if (that.{prop_name}.HasValue)
1676
{{
1677
{I}writer.WriteStartElement(
1678
{II}{xml_prop_name_literal},
1679
{II}NS);
1680

1681
{I}writer.WriteValue(
1682
{II}that.{prop_name}.Value);
1683

1684
{I}writer.WriteEndElement();
1685
}}"""
1686
            )
1687
        else:
1688
            write_value_block = Stripped(
3✔
1689
                f"""\
1690
if (that.{prop_name} != null)
1691
{{
1692
{I}{indent_but_first_line(write_value_block, I)}
1693
}}"""
1694
            )
1695

1696
    return write_value_block
3✔
1697

1698

1699
def _generate_serialize_enumeration_property_as_content(
3✔
1700
    prop: intermediate.Property,
1701
) -> Stripped:
1702
    """Generate the serialization of an enumeration ``prop`` as XML content."""
1703
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
1704
    assert isinstance(type_anno, intermediate.OurTypeAnnotation) and isinstance(
3✔
1705
        type_anno.our_type, intermediate.Enumeration
1706
    )
1707

1708
    enumeration = type_anno.our_type
3✔
1709

1710
    prop_name = csharp_naming.property_name(prop.name)
3✔
1711
    xml_prop_name_literal = csharp_common.string_literal(naming.xml_property(prop.name))
3✔
1712

1713
    enum_name = csharp_naming.enum_name(enumeration.name)
3✔
1714

1715
    text_var = csharp_naming.variable_name(Identifier(f"text_{prop.name}"))
3✔
1716
    write_value_block = Stripped(
3✔
1717
        f"""\
1718
writer.WriteStartElement(
1719
{I}{xml_prop_name_literal},
1720
{I}NS);
1721

1722
string? {text_var} = Stringification.ToString(
1723
{I}that.{prop_name});
1724
writer.WriteValue(
1725
{I}{text_var}
1726
{II}?? throw new System.ArgumentException(
1727
{III}"Invalid literal for the enumeration {enum_name}: " +
1728
{III}that.{prop_name}.ToString()));
1729

1730
writer.WriteEndElement();"""
1731
    )
1732

1733
    if isinstance(prop.type_annotation, intermediate.OptionalTypeAnnotation):
3✔
1734
        write_value_block = Stripped(
3✔
1735
            f"""\
1736
if (that.{prop_name} != null)
1737
{{
1738
{I}{indent_but_first_line(write_value_block, I)}
1739
}}"""
1740
        )
1741

1742
    return write_value_block
3✔
1743

1744

1745
def _generate_serialize_interface_property_as_content(
3✔
1746
    prop: intermediate.Property,
1747
) -> Stripped:
1748
    """Generate the serialization of an interface as XML content."""
1749
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
1750

1751
    # fmt: off
1752
    assert (
3✔
1753
            isinstance(type_anno, intermediate.OurTypeAnnotation)
1754
            and (
1755
                    isinstance(type_anno.our_type, intermediate.AbstractClass)
1756
                    or (
1757
                            isinstance(type_anno.our_type, intermediate.ConcreteClass)
1758
                            and len(type_anno.our_type.concrete_descendants) > 0
1759
                    )
1760
            )
1761
    ), "See intermediate._translate._verify_only_simple_type_patterns"
1762
    # fmt: on
1763

1764
    prop_name = csharp_naming.property_name(prop.name)
3✔
1765
    xml_prop_name_literal = csharp_common.string_literal(naming.xml_property(prop.name))
3✔
1766

1767
    result = Stripped(
3✔
1768
        f"""\
1769
writer.WriteStartElement(
1770
{I}{xml_prop_name_literal},
1771
{I}NS);
1772

1773
this.Visit(
1774
{I}that.{prop_name},
1775
{I}writer);
1776

1777
writer.WriteEndElement();"""
1778
    )
1779

1780
    if isinstance(prop.type_annotation, intermediate.OptionalTypeAnnotation):
3✔
1781
        result = Stripped(
×
1782
            f"""\
1783
if (that.{prop_name} != null)
1784
{{
1785
{I}{indent_but_first_line(result, I)}
1786
}}"""
1787
        )
1788

1789
    return result
3✔
1790

1791

1792
def _generate_serialize_concrete_class_property_as_sequence(
3✔
1793
    prop: intermediate.Property,
1794
) -> Stripped:
1795
    """Generate the serialization of the class ``prop`` as a sequence of properties."""
1796
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
1797
    assert isinstance(type_anno, intermediate.OurTypeAnnotation)
3✔
1798
    assert isinstance(type_anno.our_type, intermediate.ConcreteClass)
3✔
1799

1800
    cls_to_sequence = csharp_naming.method_name(
3✔
1801
        Identifier(f"{type_anno.our_type.name}_to_sequence")
1802
    )
1803

1804
    prop_name = csharp_naming.property_name(prop.name)
3✔
1805
    xml_prop_name_literal = csharp_common.string_literal(naming.xml_property(prop.name))
3✔
1806

1807
    result = Stripped(
3✔
1808
        f"""\
1809
writer.WriteStartElement(
1810
{I}{xml_prop_name_literal},
1811
{I}NS);
1812

1813
this.{cls_to_sequence}(
1814
{I}that.{prop_name},
1815
{I}writer);
1816

1817
writer.WriteEndElement();"""
1818
    )
1819

1820
    if isinstance(prop.type_annotation, intermediate.OptionalTypeAnnotation):
3✔
1821
        result = Stripped(
3✔
1822
            f"""\
1823
if (that.{prop_name} != null)
1824
{{
1825
{I}{indent_but_first_line(result, I)}
1826
}}"""
1827
        )
1828

1829
    return result
3✔
1830

1831

1832
def _generate_serialize_list_property_as_content(
3✔
1833
    prop: intermediate.Property,
1834
) -> Stripped:
1835
    """Generate the serialization of a list ``prop`` as a sequence of elements."""
1836
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
1837
    assert isinstance(type_anno, intermediate.ListTypeAnnotation)
3✔
1838

1839
    primitive_type = intermediate.try_primitive_type(type_anno.items)
3✔
1840

1841
    body: Stripped
1842

1843
    if primitive_type is not None:
3✔
1844
        write_item_statement: Stripped
1845

1846
        if (
3✔
1847
            primitive_type is intermediate.PrimitiveType.BOOL
1848
            or primitive_type is intermediate.PrimitiveType.INT
1849
            or primitive_type is intermediate.PrimitiveType.FLOAT
1850
            or primitive_type is intermediate.PrimitiveType.STR
1851
        ):
1852
            write_item_statement = Stripped(
3✔
1853
                """\
1854
writer.WriteValue(item);"""
1855
            )
1856
        elif primitive_type is intermediate.PrimitiveType.BYTEARRAY:
3✔
1857
            write_item_statement = Stripped(
3✔
1858
                """\
1859
writer.WriteBase64(item, 0, item.Length);"""
1860
            )
1861
        else:
NEW
1862
            assert_never(primitive_type)
×
1863

1864
        body = Stripped(
3✔
1865
            f"""\
1866
writer.WriteStartElement("v", NS);
1867
{write_item_statement}
1868
writer.WriteEndElement();"""
1869
        )
1870

1871
    elif isinstance(type_anno.items, intermediate.OurTypeAnnotation):
3✔
1872
        our_type = type_anno.items.our_type
3✔
1873

1874
        if isinstance(our_type, intermediate.Enumeration):
3✔
NEW
1875
            enum_name = csharp_naming.enum_name(our_type.name)
×
1876

NEW
1877
            body = Stripped(
×
1878
                f"""\
1879
writer.WriteStartElement("v", NS);
1880
writer.WriteValue(
1881
{I}Stringification.ToString(item)
1882
{II}?? throw new System.ArgumentException(
1883
{III}"Invalid literal for the enumeration {enum_name}: " +
1884
{III}item.ToString()));
1885
writer.WriteEndElement();"""
1886
            )
1887
        elif isinstance(our_type, intermediate.ConstrainedPrimitive):
3✔
NEW
1888
            raise AssertionError("This case should have been handled before.")
×
1889

1890
        elif isinstance(
3✔
1891
            our_type, (intermediate.AbstractClass, intermediate.ConcreteClass)
1892
        ):
1893
            body = Stripped(
3✔
1894
                """\
1895
this.Visit(item, writer);"""
1896
            )
1897
    else:
NEW
1898
        raise NotImplementedError(
×
1899
            "(mristin) We generate currently only the code for serializing lists of "
1900
            "atomic values to XML, but you want to generate the code for a list of "
1901
            f"type {type_anno}. "
1902
            f"Please contact the developers if you need this feature."
1903
        )
1904

1905
    prop_name = csharp_naming.property_name(prop.name)
3✔
1906
    xml_prop_name_literal = csharp_common.string_literal(naming.xml_property(prop.name))
3✔
1907

1908
    result = Stripped(
3✔
1909
        f"""\
1910
writer.WriteStartElement(
1911
{I}{xml_prop_name_literal},
1912
{I}NS);
1913

1914
foreach (var item in that.{prop_name})
1915
{{
1916
{I}{indent_but_first_line(body, I)}
1917
}}
1918

1919
writer.WriteEndElement();"""
1920
    )
1921

1922
    if isinstance(prop.type_annotation, intermediate.OptionalTypeAnnotation):
3✔
1923
        result = Stripped(
3✔
1924
            f"""\
1925
if (that.{prop_name} != null)
1926
{{
1927
{I}{indent_but_first_line(result, I)}
1928
}}"""
1929
        )
1930

1931
    return result
3✔
1932

1933

1934
def _generate_serialize_property_as_content(prop: intermediate.Property) -> Stripped:
3✔
1935
    """Generate the code to serialize the ``prop`` as content of an XML element."""
1936
    type_anno = intermediate.beneath_optional(prop.type_annotation)
3✔
1937

1938
    body = None  # type: Optional[Stripped]
3✔
1939

1940
    if isinstance(type_anno, intermediate.PrimitiveTypeAnnotation):
3✔
1941
        body = _generate_serialize_primitive_property_as_content(prop=prop)
3✔
1942
    elif isinstance(type_anno, intermediate.OurTypeAnnotation):
3✔
1943
        our_type = type_anno.our_type
3✔
1944

1945
        if isinstance(our_type, intermediate.Enumeration):
3✔
1946
            body = _generate_serialize_enumeration_property_as_content(prop=prop)
3✔
1947

1948
        elif isinstance(our_type, intermediate.ConstrainedPrimitive):
3✔
1949
            body = _generate_serialize_primitive_property_as_content(prop=prop)
3✔
1950

1951
        elif isinstance(
3✔
1952
            our_type, (intermediate.AbstractClass, intermediate.ConcreteClass)
1953
        ):
1954
            if (
3✔
1955
                isinstance(our_type, intermediate.AbstractClass)
1956
                or len(our_type.concrete_descendants) > 0
1957
            ):
1958
                body = _generate_serialize_interface_property_as_content(prop=prop)
3✔
1959
            else:
1960
                body = _generate_serialize_concrete_class_property_as_sequence(
3✔
1961
                    prop=prop
1962
                )
1963

1964
        else:
1965
            assert_never(our_type)
×
1966

1967
    elif isinstance(type_anno, intermediate.ListTypeAnnotation):
3✔
1968
        body = _generate_serialize_list_property_as_content(prop=prop)
3✔
1969

1970
    else:
1971
        assert_never(type_anno)
×
1972

1973
    return body
3✔
1974

1975

1976
def _generate_class_to_sequence(cls: intermediate.ConcreteClass) -> Stripped:
3✔
1977
    """Generate the method to write ``cls`` as a sequence of properties as XML."""
1978
    blocks = []  # type: List[Stripped]
3✔
1979

1980
    for prop in cls.properties:
3✔
1981
        body = _generate_serialize_property_as_content(prop=prop)
3✔
1982
        blocks.append(body)
3✔
1983

1984
    interface_name = csharp_naming.interface_name(cls.name)
3✔
1985
    method_name = csharp_naming.method_name(Identifier(f"{cls.name}_to_sequence"))
3✔
1986

1987
    writer = io.StringIO()
3✔
1988

1989
    if len(cls.properties) == 0:
3✔
1990
        blocks.append(Stripped("// Intentionally empty."))
×
1991

1992
        writer.write(
×
1993
            '[CodeAnalysis.SuppressMessage("ReSharper", "UnusedParameter.Local")]\n'
1994
        )
1995

1996
    writer.write(
3✔
1997
        f"""\
1998
private void {method_name}(
1999
{I}Aas.{interface_name} that,
2000
{I}Xml.XmlWriter writer)
2001
{{
2002
"""
2003
    )
2004

2005
    for i, block in enumerate(blocks):
3✔
2006
        if i > 0:
3✔
2007
            writer.write("\n\n")
3✔
2008
        writer.write(textwrap.indent(block, I))
3✔
2009

2010
    writer.write(f"\n}}  // private void {method_name}")
3✔
2011

2012
    return Stripped(writer.getvalue())
3✔
2013

2014

2015
def _generate_visit_for_class(cls: intermediate.ConcreteClass) -> Stripped:
3✔
2016
    """Generate the method to write the ``cls`` as an XML element."""
2017
    interface_name = csharp_naming.interface_name(cls.name)
3✔
2018
    visit_name = csharp_naming.method_name(Identifier(f"visit_{cls.name}"))
3✔
2019

2020
    cls_to_sequence_name = csharp_naming.method_name(
3✔
2021
        Identifier(f"{cls.name}_to_sequence")
2022
    )
2023

2024
    xml_cls_name_literal = csharp_common.string_literal(naming.xml_class_name(cls.name))
3✔
2025

2026
    return Stripped(
3✔
2027
        f"""\
2028
public override void {visit_name}(
2029
{I}Aas.{interface_name} that,
2030
{I}Xml.XmlWriter writer)
2031
{{
2032
{I}writer.WriteStartElement(
2033
{II}{xml_cls_name_literal},
2034
{II}NS);
2035
{I}this.{cls_to_sequence_name}(
2036
{II}that,
2037
{II}writer);
2038
{I}writer.WriteEndElement();
2039
}}"""
2040
    )
2041

2042

2043
@ensure(lambda result: (result[0] is not None) ^ (result[1] is not None))
3✔
2044
def _generate_visitor(
3✔
2045
    symbol_table: intermediate.SymbolTable,
2046
    spec_impls: specific_implementations.SpecificImplementations,
2047
) -> Tuple[Optional[Stripped], Optional[List[Error]]]:
2048
    """Generate a visitor which serializes instances of the meta-model to XML."""
2049
    errors = []  # type: List[Error]
3✔
2050

2051
    blocks = []  # type: List[Stripped]
3✔
2052

2053
    # The abstract classes are directly dispatched by the transformer,
2054
    # so we do not need to handle them separately.
2055

2056
    for cls in symbol_table.concrete_classes:
3✔
2057
        if cls.is_implementation_specific:
3✔
2058
            implementation_keys = [
×
2059
                specific_implementations.ImplementationKey(
2060
                    f"Xmlization/VisitorWithWriter/visit_{cls.name}.cs"
2061
                ),
2062
                specific_implementations.ImplementationKey(
2063
                    f"Xmlization/VisitorWithWriter/{cls.name}_to_sequence.cs"
2064
                ),
2065
            ]
2066

2067
            for implementation_key in implementation_keys:
×
2068
                implementation = spec_impls.get(implementation_key, None)
×
2069
                if implementation is None:
×
2070
                    errors.append(
×
2071
                        Error(
2072
                            cls.parsed.node,
2073
                            f"The xmlization snippet is missing "
2074
                            f"for the implementation-specific "
2075
                            f"class {cls.name}: {implementation_key}",
2076
                        )
2077
                    )
2078
                    continue
×
2079

2080
                blocks.append(spec_impls[implementation_key])
×
2081
        else:
2082
            blocks.append(_generate_class_to_sequence(cls=cls))
3✔
2083

2084
            blocks.append(_generate_visit_for_class(cls=cls))
3✔
2085

2086
    if len(errors) > 0:
3✔
2087
        return None, errors
×
2088

2089
    writer = io.StringIO()
3✔
2090
    writer.write(
3✔
2091
        f"""\
2092
/// <summary>
2093
/// Serialize recursively the instances as XML elements.
2094
/// </summary>
2095
internal class VisitorWithWriter
2096
{I}: Visitation.AbstractVisitorWithContext<Xml.XmlWriter>
2097
{{
2098
"""
2099
    )
2100

2101
    for i, block in enumerate(blocks):
3✔
2102
        if i > 0:
3✔
2103
            writer.write("\n\n")
3✔
2104
        writer.write(textwrap.indent(block, I))
3✔
2105

2106
    writer.write("\n}  // internal class VisitorWithWriter")
3✔
2107

2108
    return Stripped(writer.getvalue()), None
3✔
2109

2110

2111
def _generate_serialize(
3✔
2112
    symbol_table: intermediate.SymbolTable,
2113
) -> Stripped:
2114
    """Generate the static serializer."""
2115
    blocks = [
3✔
2116
        Stripped(
2117
            f"""\
2118
[CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")]
2119
private static readonly VisitorWithWriter _visitorWithWriter = (
2120
{I}new VisitorWithWriter());"""
2121
        ),
2122
        Stripped(
2123
            f"""\
2124
/// <summary>
2125
/// Serialize an instance of the meta-model to XML.
2126
/// </summary>
2127
public static void To(
2128
{I}Aas.IClass that,
2129
{I}Xml.XmlWriter writer)
2130
{{
2131
{I}Serialize._visitorWithWriter.Visit(
2132
{II}that, writer);
2133
}}"""
2134
        ),
2135
    ]  # type: List[Stripped]
2136

2137
    writer = io.StringIO()
3✔
2138
    writer.write(
3✔
2139
        """\
2140
/// <summary>
2141
/// Serialize instances of meta-model classes to XML.
2142
/// </summary>
2143
"""
2144
    )
2145

2146
    first_cls = (
3✔
2147
        symbol_table.classes[0] if len(symbol_table.classes) > 0 else None
2148
    )  # type: Optional[intermediate.ClassUnion]
2149

2150
    if first_cls is not None:
3✔
2151
        cls_name: str
2152
        if isinstance(first_cls, intermediate.AbstractClass):
3✔
2153
            cls_name = csharp_naming.interface_name(first_cls.name)
3✔
2154
        elif isinstance(first_cls, intermediate.ConcreteClass):
3✔
2155
            cls_name = csharp_naming.class_name(first_cls.name)
3✔
2156
        else:
2157
            assert_never(first_cls)
×
2158

2159
        an_instance_variable = csharp_naming.variable_name(Identifier("an_instance"))
3✔
2160

2161
        writer.write(
3✔
2162
            f"""\
2163
/// <example>
2164
/// Here is an example how to serialize an instance of {cls_name}:
2165
/// <code>
2166
/// var {an_instance_variable} = new Aas.{cls_name}(
2167
///     /* ... some constructor arguments ... */
2168
/// );
2169
/// var writer = new System.Xml.XmlWriter( /* some arguments */ );
2170
/// Serialize.To(
2171
/// {I}{an_instance_variable},
2172
/// {I}writer);
2173
/// </code>
2174
/// </example>
2175
"""
2176
        )
2177

2178
    writer.write(
3✔
2179
        """\
2180
public static class Serialize
2181
{
2182
"""
2183
    )
2184

2185
    for i, block in enumerate(blocks):
3✔
2186
        if i > 0:
3✔
2187
            writer.write("\n\n")
3✔
2188
        writer.write(textwrap.indent(block, I))
3✔
2189

2190
    writer.write("\n}  // public static class Serialize")
3✔
2191

2192
    return Stripped(writer.getvalue())
3✔
2193

2194

2195
# fmt: off
2196
@ensure(lambda result: (result[0] is not None) ^ (result[1] is not None))
3✔
2197
@ensure(
3✔
2198
    lambda result:
2199
    not (result[0] is not None) or result[0].endswith('\n'),
2200
    "Trailing newline mandatory for valid end-of-files"
2201
)
2202
# fmt: on
2203
def generate(
3✔
2204
    symbol_table: intermediate.SymbolTable,
2205
    namespace: csharp_common.NamespaceIdentifier,
2206
    spec_impls: specific_implementations.SpecificImplementations,
2207
) -> Tuple[Optional[str], Optional[List[Error]]]:
2208
    """
2209
    Generate code for XML de/serialization.
2210

2211
    The ``namespace`` defines the AAS C# namespace.
2212
    """
2213
    xmlization_blocks = []  # type: List[Stripped]
3✔
2214

2215
    errors = []  # type: List[Error]
3✔
2216

2217
    deserialize_impl_block, deserialize_impl_errors = _generate_deserialize_impl(
3✔
2218
        symbol_table=symbol_table, spec_impls=spec_impls
2219
    )
2220
    if deserialize_impl_errors is not None:
3✔
2221
        errors.extend(deserialize_impl_errors)
×
2222
    else:
2223
        assert deserialize_impl_block is not None
3✔
2224
        xmlization_blocks.append(deserialize_impl_block)
3✔
2225

2226
    xmlization_blocks.append(
3✔
2227
        Stripped(
2228
            f"""\
2229
/// <summary>
2230
/// Represent a critical error during the deserialization.
2231
/// </summary>
2232
public class Exception : System.Exception
2233
{{
2234
{I}public readonly string Path;
2235
{I}public readonly string Cause;
2236
{I}public Exception(string path, string cause)
2237
{II}: base($"{{cause}} at: {{(path == "" ? "the beginning" : path)}}")
2238
{I}{{
2239
{II}Path = path;
2240
{II}Cause = cause;
2241
{I}}}
2242
}}"""
2243
        )
2244
    )
2245

2246
    xmlization_blocks.append(_generate_deserialize(symbol_table=symbol_table))
3✔
2247

2248
    visitor_block, visitor_errors = _generate_visitor(
3✔
2249
        symbol_table=symbol_table, spec_impls=spec_impls
2250
    )
2251
    if visitor_errors is not None:
3✔
2252
        errors.extend(visitor_errors)
×
2253
    else:
2254
        assert visitor_block is not None
3✔
2255
        xmlization_blocks.append(visitor_block)
3✔
2256

2257
    if len(errors) > 0:
3✔
2258
        return None, errors
×
2259

2260
    xmlization_blocks.append(_generate_serialize(symbol_table=symbol_table))
3✔
2261

2262
    xmlization_writer = io.StringIO()
3✔
2263

2264
    xml_namespace_literal = csharp_common.string_literal(
3✔
2265
        symbol_table.meta_model.xml_namespace
2266
    )
2267

2268
    xmlization_writer.write(
3✔
2269
        f"""\
2270
namespace {namespace}
2271
{{
2272
{I}/// <summary>
2273
{I}/// Provide de/serialization of meta-model classes to/from XML.
2274
{I}/// </summary>
2275
{I}public static class Xmlization
2276
{I}{{
2277
{II}/// The XML namespace of the meta-model
2278
{II}[CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")]
2279
{II}public static readonly string NS = (
2280
{III}{xml_namespace_literal});
2281

2282
"""
2283
    )
2284

2285
    for i, xmlization_block in enumerate(xmlization_blocks):
3✔
2286
        if i > 0:
3✔
2287
            xmlization_writer.write("\n\n")
3✔
2288

2289
        xmlization_writer.write(textwrap.indent(xmlization_block, II))
3✔
2290

2291
    xmlization_writer.write(f"\n{I}}}  // public static class Xmlization")
3✔
2292
    xmlization_writer.write(f"\n}}  // namespace {namespace}")
3✔
2293

2294
    using_directives = []  # type: List[Stripped]
3✔
2295
    using_directives.extend(
3✔
2296
        csharp_common.generate_using_aas_directive_if_necessary(namespace)
2297
    )
2298

2299
    using_directives.append(
3✔
2300
        Stripped(
2301
            """\
2302
using CodeAnalysis = System.Diagnostics.CodeAnalysis;
2303
using Xml = System.Xml;
2304

2305
using System.Collections.Generic;  // can't alias"""
2306
        )
2307
    )
2308

2309
    # pylint: disable=line-too-long
2310
    blocks = [
3✔
2311
        csharp_common.WARNING,
2312
        Stripped("\n".join(using_directives)),
2313
        Stripped(xmlization_writer.getvalue()),
2314
        csharp_common.WARNING,
2315
    ]
2316

2317
    writer = io.StringIO()
3✔
2318
    for i, block in enumerate(blocks):
3✔
2319
        if i > 0:
3✔
2320
            writer.write("\n\n")
3✔
2321

2322
        assert not block.startswith("\n")
3✔
2323
        assert not block.endswith("\n")
3✔
2324
        writer.write(block)
3✔
2325

2326
    writer.write("\n")
3✔
2327

2328
    return writer.getvalue(), None
3✔
2329

2330

2331
assert generate.__doc__ is not None
3✔
2332
assert generate.__doc__.strip().startswith(__doc__.strip())
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc