polyxml 0.19.2__tar.gz → 0.20.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {polyxml-0.19.2 → polyxml-0.20.1}/Cargo.lock +5 -5
  2. {polyxml-0.19.2 → polyxml-0.20.1}/Cargo.toml +1 -1
  3. {polyxml-0.19.2 → polyxml-0.20.1}/PKG-INFO +1 -1
  4. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/codegen/cpp/mod.rs +31 -13
  5. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/codegen/csharp/mod.rs +207 -21
  6. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/codegen/go/mod.rs +19 -10
  7. polyxml-0.20.1/crates/polyxml-core/src/codegen/java/codec.rs +480 -0
  8. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/codegen/java/mod.rs +109 -36
  9. polyxml-0.20.1/crates/polyxml-core/src/codegen/java/models.rs +335 -0
  10. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/codegen/mod.rs +49 -2
  11. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/codegen/python/mod.rs +94 -13
  12. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/codegen/rust/mod.rs +24 -13
  13. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/codegen/typescript/mod.rs +21 -12
  14. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/schema_parser/mod.rs +776 -21
  15. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_csharp_codegen.rs +108 -0
  16. polyxml-0.20.1/crates/polyxml-core/tests/test_issue51_audit.rs +581 -0
  17. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_java_codegen.rs +270 -0
  18. {polyxml-0.19.2 → polyxml-0.20.1}/pyproject.toml +1 -1
  19. {polyxml-0.19.2 → polyxml-0.20.1}/README.md +0 -0
  20. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/Cargo.toml +0 -0
  21. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/README.md +0 -0
  22. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  23. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/converters.rs +0 -0
  24. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/error.rs +0 -0
  25. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/ir/mod.rs +0 -0
  26. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  27. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/json.rs +0 -0
  28. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/lib.rs +0 -0
  29. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/parser.rs +0 -0
  30. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/schema.rs +0 -0
  31. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/serializer.rs +0 -0
  32. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/transcoder.rs +0 -0
  33. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/src/value.rs +0 -0
  34. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  35. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_core.rs +0 -0
  36. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  37. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
  38. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_json.rs +0 -0
  39. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
  40. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  41. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
  42. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  43. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_security.rs +0 -0
  44. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  45. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  46. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-python/Cargo.toml +0 -0
  47. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-python/README.md +0 -0
  48. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-python/src/lib.rs +0 -0
  49. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-python/tests/test_conformance.py +0 -0
  50. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  51. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-python/tests/test_json.py +0 -0
  52. {polyxml-0.19.2 → polyxml-0.20.1}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  53. {polyxml-0.19.2 → polyxml-0.20.1}/python/polyxml/AGENT_GUIDE.md +0 -0
  54. {polyxml-0.19.2 → polyxml-0.20.1}/python/polyxml/__init__.py +0 -0
  55. {polyxml-0.19.2 → polyxml-0.20.1}/python/polyxml/compat/__init__.py +0 -0
  56. {polyxml-0.19.2 → polyxml-0.20.1}/python/polyxml/compat/xsdata.py +0 -0
  57. {polyxml-0.19.2 → polyxml-0.20.1}/python/polyxml/py.typed +0 -0
@@ -549,7 +549,7 @@ dependencies = [
549
549
 
550
550
  [[package]]
551
551
  name = "polyxml"
552
- version = "0.19.2"
552
+ version = "0.20.1"
553
553
  dependencies = [
554
554
  "criterion",
555
555
  "heck",
@@ -567,14 +567,14 @@ dependencies = [
567
567
 
568
568
  [[package]]
569
569
  name = "polyxml-c"
570
- version = "0.19.2"
570
+ version = "0.20.1"
571
571
  dependencies = [
572
572
  "polyxml",
573
573
  ]
574
574
 
575
575
  [[package]]
576
576
  name = "polyxml-cli"
577
- version = "0.19.2"
577
+ version = "0.20.1"
578
578
  dependencies = [
579
579
  "clap",
580
580
  "glob",
@@ -588,7 +588,7 @@ dependencies = [
588
588
 
589
589
  [[package]]
590
590
  name = "polyxml-js"
591
- version = "0.19.2"
591
+ version = "0.20.1"
592
592
  dependencies = [
593
593
  "napi",
594
594
  "napi-build",
@@ -598,7 +598,7 @@ dependencies = [
598
598
 
599
599
  [[package]]
600
600
  name = "polyxml-python"
601
- version = "0.19.2"
601
+ version = "0.20.1"
602
602
  dependencies = [
603
603
  "lexical-core",
604
604
  "polyxml",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/polyxml-core", "crates/polyxml-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.19.2"
6
+ version = "0.20.1"
7
7
  edition = "2021"
8
8
  rust-version = "1.80"
9
9
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyxml
3
- Version: 0.19.2
3
+ Version: 0.20.1
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -4,7 +4,9 @@ use std::fmt::Write as FmtWrite;
4
4
  use heck::{AsPascalCase, AsSnakeCase};
5
5
  use serde::{Deserialize, Serialize};
6
6
 
7
- use crate::codegen::{sanitize_keyword, LanguageContext};
7
+ use crate::codegen::{
8
+ build_type_name_map, lookup_type_name, sanitize_keyword, set_type_name_map, LanguageContext,
9
+ };
8
10
  use crate::ir::{
9
11
  EnumDef, PrimitiveType, QName, RestrictionFacets, SchemaIR, SimpleTypeDef, StructDef, TypeDef,
10
12
  TypeRef, UnionDef,
@@ -157,7 +159,7 @@ impl LanguageContext for CppLanguageContext {
157
159
  fn map_type_ref(&self, type_ref: &TypeRef) -> String {
158
160
  match type_ref {
159
161
  TypeRef::Primitive(prim) => self.map_primitive(*prim).to_string(),
160
- TypeRef::Named(qname) => to_cpp_type_name(&qname.local),
162
+ TypeRef::Named(qname) => type_ident(qname),
161
163
  TypeRef::Boxed(inner) => format!("std::unique_ptr<{}>", self.map_type_ref(inner)),
162
164
  TypeRef::List(inner) => format!("std::vector<{}>", self.map_type_ref(inner)),
163
165
  }
@@ -239,6 +241,12 @@ pub struct CppCodegen {
239
241
  context: CppLanguageContext,
240
242
  }
241
243
 
244
+ /// Emitted C++ identifier for a named type, disambiguated across namespaces
245
+ /// for the IR currently being generated (issue #51 item 3).
246
+ fn type_ident(q: &QName) -> String {
247
+ lookup_type_name(q, || to_cpp_type_name(&q.local))
248
+ }
249
+
242
250
  impl CppCodegen {
243
251
  pub fn new(options: CppOptions) -> Self {
244
252
  Self {
@@ -249,6 +257,7 @@ impl CppCodegen {
249
257
 
250
258
  /// Generate complete header-only source (`.hpp`).
251
259
  pub fn generate_header(&self, ir: &SchemaIR) -> String {
260
+ set_type_name_map(build_type_name_map(ir, to_cpp_type_name));
252
261
  let mut out = String::new();
253
262
  if let Some(ref header) = self.options.custom_header {
254
263
  let trimmed = header.trim();
@@ -286,6 +295,7 @@ impl CppCodegen {
286
295
 
287
296
  /// Generate complete C++20 module interface unit (`.cppm`).
288
297
  pub fn generate_module_unit(&self, ir: &SchemaIR, module_name: &str) -> String {
298
+ set_type_name_map(build_type_name_map(ir, to_cpp_type_name));
289
299
  let mut out = String::new();
290
300
  if let Some(ref header) = self.options.custom_header {
291
301
  let trimmed = header.trim();
@@ -470,7 +480,7 @@ endif()
470
480
  // 1. Enums
471
481
  for type_def in ir.types.values() {
472
482
  if let TypeDef::Enum(enum_def) = type_def {
473
- let enum_name = to_cpp_type_name(&enum_def.qname.local);
483
+ let enum_name = type_ident(&enum_def.qname);
474
484
  let full_type = format!("{}::{}", ns, enum_name);
475
485
  writeln!(out, "template <>").unwrap();
476
486
  writeln!(out, "struct glz::meta<{}> {{", full_type).unwrap();
@@ -498,7 +508,7 @@ endif()
498
508
  let sorted_qnames = self.topological_sort_types(ir);
499
509
  for qname in sorted_qnames {
500
510
  if let Some(TypeDef::Struct(s)) = ir.types.get(&qname) {
501
- let struct_name = to_cpp_type_name(&s.qname.local);
511
+ let struct_name = type_ident(&s.qname);
502
512
  let full_type = format!("{}::{}", ns, struct_name);
503
513
  writeln!(out, "template <>").unwrap();
504
514
  writeln!(out, "struct glz::meta<{}> {{", full_type).unwrap();
@@ -570,7 +580,7 @@ concept XmlModel = requires(T a) {{
570
580
  .values()
571
581
  .filter_map(|t| {
572
582
  if let TypeDef::Struct(s) = t {
573
- Some(to_cpp_type_name(&s.qname.local))
583
+ Some(type_ident(&s.qname))
574
584
  } else {
575
585
  None
576
586
  }
@@ -622,7 +632,7 @@ concept XmlModel = requires(T a) {{
622
632
  writeln!(out, "/// {}", line).unwrap();
623
633
  }
624
634
  }
625
- let type_name = to_cpp_type_name(&simple.qname.local);
635
+ let type_name = type_ident(&simple.qname);
626
636
  let base_type = self.context.map_type_ref(&simple.base_type);
627
637
  writeln!(out, "using {} = {};\n", type_name, base_type).unwrap();
628
638
  }
@@ -634,7 +644,7 @@ concept XmlModel = requires(T a) {{
634
644
  }
635
645
  }
636
646
 
637
- let enum_name = to_cpp_type_name(&enum_def.qname.local);
647
+ let enum_name = type_ident(&enum_def.qname);
638
648
  writeln!(out, "enum class {} {{", enum_name).unwrap();
639
649
 
640
650
  for variant in &enum_def.variants {
@@ -668,8 +678,16 @@ concept XmlModel = requires(T a) {{
668
678
  writeln!(out, " return \"\";").unwrap();
669
679
  writeln!(out, "}}\n").unwrap();
670
680
 
671
- // from_string
672
- let func_name = format!("{}_from_string", AsSnakeCase(&enum_def.qname.local));
681
+ // from_string — stem follows the disambiguated type name only when
682
+ // it differs from the natural one, preserving the historical
683
+ // snake-of-local form otherwise (issue #51 item 3).
684
+ let natural = to_cpp_type_name(&enum_def.qname.local);
685
+ let stem = if enum_name == natural {
686
+ AsSnakeCase(&enum_def.qname.local).to_string()
687
+ } else {
688
+ AsSnakeCase(&enum_name).to_string()
689
+ };
690
+ let func_name = format!("{stem}_from_string");
673
691
  writeln!(
674
692
  out,
675
693
  "[[nodiscard]] inline std::optional<{}> {}(std::string_view s) noexcept {{",
@@ -697,7 +715,7 @@ concept XmlModel = requires(T a) {{
697
715
  }
698
716
  }
699
717
 
700
- let union_name = to_cpp_type_name(&u.qname.local);
718
+ let union_name = type_ident(&u.qname);
701
719
 
702
720
  // Check if branches have distinct types and no primitives
703
721
  let mut branch_types = Vec::new();
@@ -758,17 +776,17 @@ concept XmlModel = requires(T a) {{
758
776
  }
759
777
  }
760
778
 
761
- let struct_name = to_cpp_type_name(&s.qname.local);
779
+ let struct_name = type_ident(&s.qname);
762
780
  let mut base_clause = String::new();
763
781
  let mut simple_content_base: Option<String> = None;
764
782
 
765
783
  if let Some(ref base_qname) = s.base_type {
766
784
  if matches!(ir.types.get(base_qname), Some(TypeDef::Struct(_))) {
767
- base_clause = format!(" : public {}", to_cpp_type_name(&base_qname.local));
785
+ base_clause = format!(" : public {}", type_ident(base_qname));
768
786
  } else if let Some(prim) = PrimitiveType::from_xsd_name(&base_qname.local) {
769
787
  simple_content_base = Some(self.context.map_primitive(prim).to_string());
770
788
  } else if let Some(TypeDef::Simple(st)) = ir.types.get(base_qname) {
771
- simple_content_base = Some(to_cpp_type_name(&st.qname.local));
789
+ simple_content_base = Some(type_ident(&st.qname));
772
790
  }
773
791
  }
774
792
 
@@ -8,9 +8,11 @@ use std::fmt::Write as FmtWrite;
8
8
  use heck::{AsLowerCamelCase, AsPascalCase};
9
9
  use serde::{Deserialize, Serialize};
10
10
 
11
- use crate::codegen::{sanitize_keyword, LanguageContext};
11
+ use crate::codegen::{
12
+ build_type_name_map, lookup_type_name, sanitize_keyword, set_type_name_map, LanguageContext,
13
+ };
12
14
  use crate::ir::{
13
- EnumDef, FieldDef, FieldKind, PrimitiveType, RestrictionFacets, SchemaIR, SimpleTypeDef,
15
+ EnumDef, FieldDef, FieldKind, PrimitiveType, QName, RestrictionFacets, SchemaIR, SimpleTypeDef,
14
16
  StructDef, TypeDef, TypeRef, UnionDef,
15
17
  };
16
18
 
@@ -51,6 +53,9 @@ pub struct CSharpOptions {
51
53
  pub emit_validation: bool,
52
54
  /// Record emission kind (record class vs record struct)
53
55
  pub record_kind: CSharpRecordKind,
56
+ /// Emit immutable records (default) instead of mutable classes.
57
+ #[serde(default = "default_use_records")]
58
+ pub use_records: bool,
54
59
  /// Use modern C# 10+ file-scoped namespaces (`namespace Foo;`)
55
60
  pub use_file_scoped_namespaces: bool,
56
61
  /// Emit root element wrapper records or aliases
@@ -59,6 +64,10 @@ pub struct CSharpOptions {
59
64
  pub custom_header: Option<String>,
60
65
  }
61
66
 
67
+ fn default_use_records() -> bool {
68
+ true
69
+ }
70
+
62
71
  impl Default for CSharpOptions {
63
72
  fn default() -> Self {
64
73
  Self {
@@ -69,6 +78,7 @@ impl Default for CSharpOptions {
69
78
  source_gen_context_name: "PolyXmlJsonContext".to_string(),
70
79
  emit_validation: true,
71
80
  record_kind: CSharpRecordKind::Class,
81
+ use_records: true,
72
82
  use_file_scoped_namespaces: true,
73
83
  emit_root_records: true,
74
84
  custom_header: None,
@@ -210,7 +220,7 @@ impl LanguageContext for CSharpLanguageContext {
210
220
  fn map_type_ref(&self, type_ref: &TypeRef) -> String {
211
221
  match type_ref {
212
222
  TypeRef::Primitive(prim) => self.map_primitive(*prim).to_string(),
213
- TypeRef::Named(qname) => to_csharp_type_name(&qname.local),
223
+ TypeRef::Named(qname) => type_ident(qname),
214
224
  TypeRef::Boxed(inner) => self.map_type_ref(inner),
215
225
  TypeRef::List(inner) => format!("List<{}>", self.map_type_ref(inner)),
216
226
  }
@@ -223,6 +233,12 @@ pub struct CSharpCodegen {
223
233
  context: CSharpLanguageContext,
224
234
  }
225
235
 
236
+ /// Emitted C# identifier for a named type, disambiguated across namespaces
237
+ /// for the IR currently being generated (issue #51 item 3).
238
+ fn type_ident(q: &QName) -> String {
239
+ lookup_type_name(q, || to_csharp_type_name(&q.local))
240
+ }
241
+
226
242
  impl CSharpCodegen {
227
243
  /// Creates a new C# code generator with the given configuration options.
228
244
  pub fn new(options: CSharpOptions) -> Self {
@@ -234,6 +250,7 @@ impl CSharpCodegen {
234
250
 
235
251
  /// Emits all types in the given SchemaIR as a single C# compilation unit.
236
252
  pub fn generate_module(&self, ir: &SchemaIR) -> String {
253
+ set_type_name_map(build_type_name_map(ir, to_csharp_type_name));
237
254
  let mut out = String::new();
238
255
 
239
256
  if let Some(ref header) = self.options.custom_header {
@@ -342,7 +359,7 @@ impl CSharpCodegen {
342
359
  // Enums
343
360
  for def in ir.types.values() {
344
361
  if let TypeDef::Enum(e) = def {
345
- let name = to_csharp_type_name(&e.qname.local);
362
+ let name = type_ident(&e.qname);
346
363
  writeln!(out, "{}[JsonSerializable(typeof({}))]", indent, name).unwrap();
347
364
  }
348
365
  }
@@ -350,7 +367,7 @@ impl CSharpCodegen {
350
367
  // Simple types
351
368
  for def in ir.types.values() {
352
369
  if let TypeDef::Simple(s) = def {
353
- let name = to_csharp_type_name(&s.qname.local);
370
+ let name = type_ident(&s.qname);
354
371
  writeln!(out, "{}[JsonSerializable(typeof({}))]", indent, name).unwrap();
355
372
  }
356
373
  }
@@ -358,7 +375,7 @@ impl CSharpCodegen {
358
375
  // Unions
359
376
  for def in ir.types.values() {
360
377
  if let TypeDef::Union(u) = def {
361
- let name = to_csharp_type_name(&u.qname.local);
378
+ let name = type_ident(&u.qname);
362
379
  writeln!(out, "{}[JsonSerializable(typeof({}))]", indent, name).unwrap();
363
380
  }
364
381
  }
@@ -366,12 +383,20 @@ impl CSharpCodegen {
366
383
  // Structs
367
384
  for def in ir.types.values() {
368
385
  if let TypeDef::Struct(s) = def {
369
- let name = to_csharp_type_name(&s.qname.local);
386
+ let name = type_ident(&s.qname);
370
387
  writeln!(out, "{}[JsonSerializable(typeof({}))]", indent, name).unwrap();
371
388
  writeln!(out, "{}[JsonSerializable(typeof(List<{}>))]", indent, name).unwrap();
372
389
  }
373
390
  }
374
391
 
392
+ if !self.options.use_records && self.options.emit_root_records {
393
+ for elem in ir.elements.values() {
394
+ let name = to_csharp_type_name(&elem.qname.local);
395
+ if name != self.context.map_type_ref(&elem.type_ref) {
396
+ writeln!(out, "{}[JsonSerializable(typeof({}))]", indent, name).unwrap();
397
+ }
398
+ }
399
+ }
375
400
  let ctx_name = &self.options.source_gen_context_name;
376
401
  writeln!(
377
402
  out,
@@ -383,7 +408,7 @@ impl CSharpCodegen {
383
408
  }
384
409
 
385
410
  fn emit_enum(&self, out: &mut String, e: &EnumDef, indent: &str) {
386
- let enum_name = to_csharp_type_name(&e.qname.local);
411
+ let enum_name = type_ident(&e.qname);
387
412
  if let Some(ref doc) = e.documentation {
388
413
  self.emit_docstring(out, doc, indent);
389
414
  }
@@ -465,14 +490,51 @@ impl CSharpCodegen {
465
490
  }
466
491
 
467
492
  fn emit_simple(&self, out: &mut String, s: &SimpleTypeDef, indent: &str) {
468
- if !s.facets.is_empty() {
469
- let type_name = to_csharp_type_name(&s.qname.local);
493
+ if !s.facets.is_empty() || !self.options.use_records {
494
+ let type_name = type_ident(&s.qname);
470
495
  let base_type = self.context.map_type_ref(&s.base_type);
471
496
 
472
497
  if let Some(ref doc) = s.documentation {
473
498
  self.emit_docstring(out, doc, indent);
474
499
  }
475
500
 
501
+ if !self.options.use_records {
502
+ writeln!(
503
+ out,
504
+ "{}public class {}{}\n{}{{",
505
+ indent,
506
+ type_name,
507
+ if self.options.emit_validation {
508
+ " : IValidatableObject"
509
+ } else {
510
+ ""
511
+ },
512
+ indent
513
+ )
514
+ .unwrap();
515
+ if self.options.emit_xml_attributes {
516
+ writeln!(out, "{} [XmlText]", indent).unwrap();
517
+ }
518
+ writeln!(
519
+ out,
520
+ "{} public {} Value {{ get; set; }} = default!;",
521
+ indent, base_type
522
+ )
523
+ .unwrap();
524
+ writeln!(
525
+ out,
526
+ "{} public {}() {{ }}\n{} public {}({} value) {{ Value = value; }}",
527
+ indent, type_name, indent, type_name, base_type
528
+ )
529
+ .unwrap();
530
+ if self.options.emit_validation {
531
+ writeln!(out, "{} public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)\n{} {{", indent, indent).unwrap();
532
+ self.emit_facet_checks(out, &s.facets, "Value", &format!("{} ", indent));
533
+ writeln!(out, "{} yield break;\n{} }}", indent, indent).unwrap();
534
+ }
535
+ writeln!(out, "{}}}\n", indent).unwrap();
536
+ return;
537
+ }
476
538
  writeln!(
477
539
  out,
478
540
  "{}public sealed record {}([property: XmlText] {} Value) : IValidatableObject",
@@ -504,7 +566,7 @@ impl CSharpCodegen {
504
566
  }
505
567
 
506
568
  fn emit_union(&self, out: &mut String, u: &UnionDef, indent: &str) {
507
- let choice_name = to_csharp_type_name(&u.qname.local);
569
+ let choice_name = type_ident(&u.qname);
508
570
  if let Some(ref doc) = u.documentation {
509
571
  self.emit_docstring(out, doc, indent);
510
572
  }
@@ -522,7 +584,31 @@ impl CSharpCodegen {
522
584
  }
523
585
  }
524
586
 
525
- writeln!(out, "{}public abstract record {}", indent, choice_name).unwrap();
587
+ if !self.options.use_records && self.options.emit_json_attributes {
588
+ for branch in &u.branches {
589
+ writeln!(
590
+ out,
591
+ "{}[JsonDerivedType(typeof({}.{}), {:?})]",
592
+ indent,
593
+ choice_name,
594
+ to_csharp_type_name(&branch.variant_name),
595
+ branch.xml_name
596
+ )
597
+ .unwrap();
598
+ }
599
+ }
600
+ writeln!(
601
+ out,
602
+ "{}public abstract {} {}",
603
+ indent,
604
+ if self.options.use_records {
605
+ "record"
606
+ } else {
607
+ "class"
608
+ },
609
+ choice_name
610
+ )
611
+ .unwrap();
526
612
  writeln!(out, "{}{{", indent).unwrap();
527
613
 
528
614
  for branch in &u.branches {
@@ -546,6 +632,24 @@ impl CSharpCodegen {
546
632
  format!("[property: {}] ", branch_attrs.join(", "))
547
633
  };
548
634
 
635
+ if !self.options.use_records {
636
+ writeln!(
637
+ out,
638
+ "{} public sealed class {} : {}\n{} {{",
639
+ indent, variant_name, choice_name, indent
640
+ )
641
+ .unwrap();
642
+ writeln!(
643
+ out,
644
+ "{} {}public {} Value {{ get; set; }} = default!;",
645
+ indent,
646
+ xml_attr.replace("[property: ", "["),
647
+ branch_type
648
+ )
649
+ .unwrap();
650
+ writeln!(out, "{} public {}() {{ }}\n{} public {}({} value) {{ Value = value; }}\n{} }}", indent, variant_name, indent, variant_name, branch_type, indent).unwrap();
651
+ continue;
652
+ }
549
653
  writeln!(
550
654
  out,
551
655
  "{} public sealed record {}({}{} Value) : {}",
@@ -567,7 +671,7 @@ impl CSharpCodegen {
567
671
  }
568
672
 
569
673
  fn emit_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR, indent: &str) {
570
- let struct_name = to_csharp_type_name(&s.qname.local);
674
+ let struct_name = type_ident(&s.qname);
571
675
  if let Some(ref doc) = s.documentation {
572
676
  self.emit_docstring(out, doc, indent);
573
677
  }
@@ -595,7 +699,7 @@ impl CSharpCodegen {
595
699
  let mut base_clause = Vec::new();
596
700
  if let Some(ref base_qname) = s.base_type {
597
701
  if matches!(ir.types.get(base_qname), Some(TypeDef::Struct(_))) {
598
- let base_name = to_csharp_type_name(&base_qname.local);
702
+ let base_name = type_ident(base_qname);
599
703
  base_clause.push(base_name);
600
704
  }
601
705
  }
@@ -609,6 +713,39 @@ impl CSharpCodegen {
609
713
  format!(" : {}", base_clause.join(", "))
610
714
  };
611
715
 
716
+ if !self.options.use_records {
717
+ writeln!(
718
+ out,
719
+ "{}public class {}{}\n{}{{",
720
+ indent, struct_name, implements_str, indent
721
+ )
722
+ .unwrap();
723
+ writeln!(out, "{} public {}() {{ }}", indent, struct_name).unwrap();
724
+ for f in &s.fields {
725
+ let name = to_csharp_property_name(&f.name, Some(&struct_name));
726
+ let ty = self.map_field_type(f, ir);
727
+ let attrs = self
728
+ .build_field_attributes(f, ir)
729
+ .replace("[property: ", "[");
730
+ let initial = if f.cardinality.is_list() || f.type_ref.is_list() {
731
+ "new()"
732
+ } else {
733
+ "default!"
734
+ };
735
+ writeln!(
736
+ out,
737
+ "{} {}public {} {} {{ get; set; }} = {};",
738
+ indent, attrs, ty, name, initial
739
+ )
740
+ .unwrap();
741
+ }
742
+ if self.options.emit_validation {
743
+ self.emit_struct_validator(out, s, indent);
744
+ }
745
+ writeln!(out, "{}}}\n", indent).unwrap();
746
+ return;
747
+ }
748
+
612
749
  // Collect fields and parameters
613
750
  if s.fields.is_empty() {
614
751
  writeln!(
@@ -688,8 +825,18 @@ impl CSharpCodegen {
688
825
  }
689
826
 
690
827
  fn emit_struct_validator(&self, out: &mut String, s: &StructDef, indent: &str) {
691
- let struct_name = to_csharp_type_name(&s.qname.local);
692
- let new_kw = if s.base_type.is_some() { "new " } else { "" };
828
+ let struct_name = type_ident(&s.qname);
829
+ let new_kw = if !self.options.use_records {
830
+ if s.base_type.is_some() {
831
+ "override "
832
+ } else {
833
+ "virtual "
834
+ }
835
+ } else if s.base_type.is_some() {
836
+ "new "
837
+ } else {
838
+ ""
839
+ };
693
840
  writeln!(
694
841
  out,
695
842
  "{} public {}IEnumerable<ValidationResult> Validate(ValidationContext validationContext)",
@@ -698,6 +845,9 @@ impl CSharpCodegen {
698
845
  .unwrap();
699
846
  writeln!(out, "{} {{", indent).unwrap();
700
847
 
848
+ if !self.options.use_records && s.base_type.is_some() {
849
+ writeln!(out, "{} foreach (var result in base.Validate(validationContext)) yield return result;", indent).unwrap();
850
+ }
701
851
  let mut has_checks = false;
702
852
  for f in &s.fields {
703
853
  let prop_name = to_csharp_property_name(&f.name, Some(&struct_name));
@@ -799,9 +949,9 @@ impl CSharpCodegen {
799
949
  TypeRef::Primitive(p) => self.context.map_primitive(*p).to_string(),
800
950
  TypeRef::Named(qn) => {
801
951
  if let Some(TypeDef::Union(u)) = ir.types.get(qn) {
802
- to_csharp_type_name(&u.qname.local)
952
+ type_ident(&u.qname)
803
953
  } else {
804
- to_csharp_type_name(&qn.local)
954
+ type_ident(qn)
805
955
  }
806
956
  }
807
957
  TypeRef::Boxed(inner) => self.context.map_type_ref(inner),
@@ -828,7 +978,7 @@ impl CSharpCodegen {
828
978
  // If the field is a choice (UnionDef), emit XmlElement("branchXml", typeof(BranchType))
829
979
  if let TypeRef::Named(ref qname) = f.type_ref {
830
980
  if let Some(TypeDef::Union(u)) = ir.types.get(qname) {
831
- let choice_name = to_csharp_type_name(&u.qname.local);
981
+ let choice_name = type_ident(&u.qname);
832
982
  for branch in &u.branches {
833
983
  let variant_name = to_csharp_type_name(&branch.variant_name);
834
984
  parts.push(format!(
@@ -888,10 +1038,46 @@ impl CSharpCodegen {
888
1038
  writeln!(out, "{}[XmlRoot(\"{}\")]", indent, elem.qname.local).unwrap();
889
1039
  }
890
1040
  }
1041
+ if !self.options.use_records {
1042
+ let can_extend = matches!(&elem.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Struct(_) | TypeDef::Simple(_))));
1043
+ if !can_extend {
1044
+ writeln!(
1045
+ out,
1046
+ "{}public sealed class {}\n{}{{",
1047
+ indent, elem_name, indent
1048
+ )
1049
+ .unwrap();
1050
+ if self.options.emit_xml_attributes {
1051
+ let attribute = if matches!(&elem.type_ref, TypeRef::Named(q)
1052
+ if matches!(ir.types.get(q), Some(TypeDef::Union(_))))
1053
+ {
1054
+ "XmlElement"
1055
+ } else {
1056
+ "XmlText"
1057
+ };
1058
+ writeln!(out, "{} [{}]", indent, attribute).unwrap();
1059
+ }
1060
+ writeln!(
1061
+ out,
1062
+ "{} public {} Value {{ get; set; }} = default!;\n{}}}\n",
1063
+ indent, target_type, indent
1064
+ )
1065
+ .unwrap();
1066
+ continue;
1067
+ }
1068
+ }
891
1069
  writeln!(
892
1070
  out,
893
- "{}public sealed record {} : {}\n{}{{",
894
- indent, elem_name, target_type, indent
1071
+ "{}public sealed {} {} : {}\n{}{{",
1072
+ indent,
1073
+ if self.options.use_records {
1074
+ "record"
1075
+ } else {
1076
+ "class"
1077
+ },
1078
+ elem_name,
1079
+ target_type,
1080
+ indent
895
1081
  )
896
1082
  .unwrap();
897
1083
  writeln!(out, "{} public {}() : base() {{ }}", indent, elem_name).unwrap();