polyxml 0.27.0__tar.gz → 0.29.0__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 (71) hide show
  1. {polyxml-0.27.0 → polyxml-0.29.0}/Cargo.lock +7 -6
  2. {polyxml-0.27.0 → polyxml-0.29.0}/Cargo.toml +1 -1
  3. {polyxml-0.27.0 → polyxml-0.29.0}/PKG-INFO +1 -1
  4. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/Cargo.toml +1 -0
  5. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +321 -11
  6. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/go/mod.rs +281 -13
  7. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/java/codec.rs +60 -21
  8. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/java/mod.rs +15 -10
  9. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/python/mod.rs +80 -1
  10. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/rust/mod.rs +182 -9
  11. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +48 -0
  12. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/converters.rs +102 -1
  13. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/ir/mod.rs +21 -0
  14. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/parser.rs +122 -7
  15. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/schema.rs +92 -3
  16. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/schema_parser/mod.rs +206 -4
  17. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/serializer.rs +232 -105
  18. polyxml-0.29.0/crates/polyxml-core/tests/fixtures/lexical_union.xsd +38 -0
  19. polyxml-0.29.0/crates/polyxml-core/tests/fixtures/mixed_content.xsd +17 -0
  20. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +7 -0
  21. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +4 -0
  22. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_go_codegen.rs +5 -0
  23. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_java_codegen.rs +2 -0
  24. polyxml-0.29.0/crates/polyxml-core/tests/test_lexical_union_codegen.rs +107 -0
  25. polyxml-0.29.0/crates/polyxml-core/tests/test_mixed_content.rs +159 -0
  26. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_python_codegen.rs +10 -0
  27. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_rust_codegen.rs +15 -0
  28. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_schema_audit.rs +1 -0
  29. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_schema_ir.rs +36 -0
  30. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_ts_codegen.rs +5 -0
  31. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-python/src/lib.rs +157 -5
  32. polyxml-0.29.0/crates/polyxml-python/tests/test_mixed_content.py +44 -0
  33. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-python/tests/test_polyxml.py +36 -0
  34. {polyxml-0.27.0 → polyxml-0.29.0}/pyproject.toml +1 -1
  35. {polyxml-0.27.0 → polyxml-0.29.0}/README.md +0 -0
  36. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/README.md +0 -0
  37. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  38. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  39. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  40. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  41. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  42. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  43. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  44. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/error.rs +0 -0
  45. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  46. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/json.rs +0 -0
  47. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/lib.rs +0 -0
  48. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/transcoder.rs +0 -0
  49. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/src/value.rs +0 -0
  50. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  51. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  52. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_core.rs +0 -0
  53. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_json.rs +0 -0
  54. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  55. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  56. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_security.rs +0 -0
  57. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  58. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  59. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  60. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-python/Cargo.toml +0 -0
  61. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-python/README.md +0 -0
  62. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  63. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
  64. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  65. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-python/tests/test_json.py +0 -0
  66. {polyxml-0.27.0 → polyxml-0.29.0}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  67. {polyxml-0.27.0 → polyxml-0.29.0}/python/polyxml/AGENT_GUIDE.md +0 -0
  68. {polyxml-0.27.0 → polyxml-0.29.0}/python/polyxml/__init__.py +0 -0
  69. {polyxml-0.27.0 → polyxml-0.29.0}/python/polyxml/compat/__init__.py +0 -0
  70. {polyxml-0.27.0 → polyxml-0.29.0}/python/polyxml/compat/xsdata.py +0 -0
  71. {polyxml-0.27.0 → polyxml-0.29.0}/python/polyxml/py.typed +0 -0
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.27.0"
611
+ version = "0.29.0"
612
612
  dependencies = [
613
613
  "criterion",
614
614
  "heck",
@@ -619,6 +619,7 @@ dependencies = [
619
619
  "phf_codegen",
620
620
  "pretty_assertions",
621
621
  "quick-xml",
622
+ "regex",
622
623
  "serde",
623
624
  "serde_json",
624
625
  "smallvec",
@@ -628,14 +629,14 @@ dependencies = [
628
629
 
629
630
  [[package]]
630
631
  name = "polyxml-c"
631
- version = "0.27.0"
632
+ version = "0.29.0"
632
633
  dependencies = [
633
634
  "polyxml",
634
635
  ]
635
636
 
636
637
  [[package]]
637
638
  name = "polyxml-cli"
638
- version = "0.27.0"
639
+ version = "0.29.0"
639
640
  dependencies = [
640
641
  "clap",
641
642
  "glob",
@@ -649,7 +650,7 @@ dependencies = [
649
650
 
650
651
  [[package]]
651
652
  name = "polyxml-js"
652
- version = "0.27.0"
653
+ version = "0.29.0"
653
654
  dependencies = [
654
655
  "napi",
655
656
  "napi-build",
@@ -659,7 +660,7 @@ dependencies = [
659
660
 
660
661
  [[package]]
661
662
  name = "polyxml-python"
662
- version = "0.27.0"
663
+ version = "0.29.0"
663
664
  dependencies = [
664
665
  "lexical-core",
665
666
  "polyxml",
@@ -669,7 +670,7 @@ dependencies = [
669
670
 
670
671
  [[package]]
671
672
  name = "polyxml-wasm"
672
- version = "0.27.0"
673
+ version = "0.29.0"
673
674
  dependencies = [
674
675
  "polyxml",
675
676
  "quick-xml",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/polyxml-core", "crates/polyxml-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.27.0"
6
+ version = "0.29.0"
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.27.0
3
+ Version: 0.29.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -21,6 +21,7 @@ serde_json = "1.0"
21
21
  minijinja = { version = "2.8", features = ["builtins"] }
22
22
  heck = "0.5"
23
23
  phf_codegen = "0.14.0"
24
+ regex = "1.11"
24
25
 
25
26
  [dev-dependencies]
26
27
  criterion = { version = "0.8", default-features = false, features = ["cargo_bench_support"] }
@@ -15,7 +15,7 @@ use crate::codegen::{
15
15
  };
16
16
  use crate::ir::{
17
17
  EnumDef, FieldDef, FieldKind, PrimitiveType, QName, RestrictionFacets, SchemaIR, SimpleTypeDef,
18
- StructDef, TypeDef, TypeRef, UnionDef,
18
+ StructDef, TypeDef, TypeRef, UnionBranch, UnionDef,
19
19
  };
20
20
 
21
21
  /// Record emission kind: class vs struct.
@@ -242,6 +242,15 @@ fn type_ident(q: &QName) -> String {
242
242
  lookup_type_name(q, || to_csharp_type_name(&q.local))
243
243
  }
244
244
 
245
+ fn choice_variant_name(branch: &UnionBranch) -> String {
246
+ let name = to_csharp_type_name(&branch.variant_name);
247
+ if matches!(&branch.type_ref, TypeRef::Named(qname) if type_ident(qname) == name) {
248
+ format!("{name}Branch")
249
+ } else {
250
+ name
251
+ }
252
+ }
253
+
245
254
  impl CSharpCodegen {
246
255
  /// Creates a new C# code generator with the given configuration options.
247
256
  pub fn new(options: CSharpOptions) -> Self {
@@ -313,7 +322,7 @@ impl CSharpCodegen {
313
322
  // Emit Unions (Choices)
314
323
  for def in ir.types.values() {
315
324
  if let TypeDef::Union(u) = def {
316
- self.emit_union(&mut out, u, indent);
325
+ self.emit_union(&mut out, u, ir, indent);
317
326
  }
318
327
  }
319
328
 
@@ -577,16 +586,16 @@ impl CSharpCodegen {
577
586
  }
578
587
  }
579
588
 
580
- fn emit_union(&self, out: &mut String, u: &UnionDef, indent: &str) {
589
+ fn emit_union(&self, out: &mut String, u: &UnionDef, ir: &SchemaIR, indent: &str) {
581
590
  let choice_name = type_ident(&u.qname);
582
591
  if let Some(ref doc) = u.documentation {
583
592
  self.emit_docstring(out, doc, indent);
584
593
  }
585
594
 
586
- // XmlInclude attributes for polymorphism
587
- if self.options.emit_xml_attributes {
595
+ // XmlInclude attributes for element-choice polymorphism.
596
+ if self.options.emit_xml_attributes && !u.is_lexical() && !u.is_mixed_content() {
588
597
  for branch in &u.branches {
589
- let variant_name = to_csharp_type_name(&branch.variant_name);
598
+ let variant_name = choice_variant_name(branch);
590
599
  writeln!(
591
600
  out,
592
601
  "{}[XmlInclude(typeof({}.{}))]",
@@ -603,7 +612,7 @@ impl CSharpCodegen {
603
612
  "{}[JsonDerivedType(typeof({}.{}), {:?})]",
604
613
  indent,
605
614
  choice_name,
606
- to_csharp_type_name(&branch.variant_name),
615
+ choice_variant_name(branch),
607
616
  branch.xml_name
608
617
  )
609
618
  .unwrap();
@@ -624,7 +633,7 @@ impl CSharpCodegen {
624
633
  writeln!(out, "{}{{", indent).unwrap();
625
634
 
626
635
  for branch in &u.branches {
627
- let variant_name = to_csharp_type_name(&branch.variant_name);
636
+ let variant_name = choice_variant_name(branch);
628
637
  let branch_type = self.context.map_type_ref(&branch.type_ref);
629
638
 
630
639
  if let Some(ref doc) = branch.documentation {
@@ -632,10 +641,10 @@ impl CSharpCodegen {
632
641
  }
633
642
 
634
643
  let mut branch_attrs = Vec::new();
635
- if self.options.emit_xml_attributes {
644
+ if self.options.emit_xml_attributes && !u.is_lexical() && !u.is_mixed_content() {
636
645
  branch_attrs.push(format!("XmlElement(\"{}\")", branch.xml_name));
637
646
  }
638
- if self.options.emit_json_attributes {
647
+ if self.options.emit_json_attributes && !u.is_lexical() {
639
648
  branch_attrs.push(format!("JsonPropertyName(\"{}\")", branch.xml_name));
640
649
  }
641
650
  let xml_attr = if branch_attrs.is_empty() {
@@ -679,9 +688,234 @@ impl CSharpCodegen {
679
688
  writeln!(out).unwrap();
680
689
  }
681
690
 
691
+ if u.is_lexical() {
692
+ self.emit_lexical_union_helpers(out, u, ir, indent);
693
+ }
682
694
  writeln!(out, "{}}}\n", indent).unwrap();
683
695
  }
684
696
 
697
+ fn emit_lexical_union_helpers(
698
+ &self,
699
+ out: &mut String,
700
+ u: &UnionDef,
701
+ ir: &SchemaIR,
702
+ indent: &str,
703
+ ) {
704
+ let name = type_ident(&u.qname);
705
+ let _ = writeln!(
706
+ out,
707
+ "{} public static {} Parse(string text)",
708
+ indent, name
709
+ );
710
+ let _ = writeln!(out, "{} {{", indent);
711
+ let _ = writeln!(out, "{} var value = text.Trim();", indent);
712
+ for (idx, branch) in u.branches.iter().enumerate() {
713
+ let variant = choice_variant_name(branch);
714
+ let mapped = self.context.map_type_ref(&branch.type_ref);
715
+ if let TypeRef::Named(qname) = &branch.type_ref {
716
+ if let Some(TypeDef::Enum(def)) = ir.types.get(qname) {
717
+ for item in &def.variants {
718
+ let item_name = to_csharp_variant_name(&item.name);
719
+ let _ = writeln!(
720
+ out,
721
+ "{} if (value == {:?}) return new {}({}.{});",
722
+ indent, item.value, variant, mapped, item_name
723
+ );
724
+ }
725
+ continue;
726
+ }
727
+ }
728
+ let simple = match &branch.type_ref {
729
+ TypeRef::Named(qname) => ir.types.get(qname).and_then(|def| match def {
730
+ TypeDef::Simple(s) => Some(s.as_ref()),
731
+ _ => None,
732
+ }),
733
+ _ => None,
734
+ };
735
+ let base = super::primitive_base(&branch.type_ref, ir);
736
+ let base_name = self.context.map_type_ref(base);
737
+ let wrap = if simple.is_some() {
738
+ format!("new {}(candidate{})", mapped, idx)
739
+ } else {
740
+ format!("candidate{}", idx)
741
+ };
742
+ if base_name == "string" {
743
+ if let Some(simple) = simple.filter(|s| !s.facets.patterns.is_empty()) {
744
+ let checks = simple
745
+ .facets
746
+ .patterns
747
+ .iter()
748
+ .map(|pattern| {
749
+ format!("Regex.IsMatch(value, {:?})", format!(r"\A(?:{pattern})\z"))
750
+ })
751
+ .collect::<Vec<_>>()
752
+ .join(" && ");
753
+ let _ = writeln!(
754
+ out,
755
+ "{} if ({}) {{ var candidate{} = value; return new {}({}); }}",
756
+ indent, checks, idx, variant, wrap
757
+ );
758
+ } else {
759
+ let _ = writeln!(
760
+ out,
761
+ "{} {{ var candidate{} = value; return new {}({}); }}",
762
+ indent, idx, variant, wrap
763
+ );
764
+ }
765
+ } else if base_name == "bool" {
766
+ let _ = writeln!(out, "{} if (value == \"true\" || value == \"1\") {{ var candidate{} = true; return new {}({}); }}", indent, idx, variant, wrap);
767
+ let _ = writeln!(out, "{} if (value == \"false\" || value == \"0\") {{ var candidate{} = false; return new {}({}); }}", indent, idx, variant, wrap);
768
+ } else if base_name == "DateOnly" {
769
+ let _ = writeln!(out, "{} if (DateOnly.TryParseExact(value, \"yyyy-MM-dd\", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out var candidate{})) return new {}({});", indent, idx, variant, wrap);
770
+ } else {
771
+ let _ = writeln!(
772
+ out,
773
+ "{} if ({}.TryParse(value, out var candidate{})) return new {}({});",
774
+ indent, base_name, idx, variant, wrap
775
+ );
776
+ }
777
+ }
778
+ let _ = writeln!(
779
+ out,
780
+ "{} throw new FormatException(\"No {} union member accepts the value\");",
781
+ indent, name
782
+ );
783
+ let _ = writeln!(out, "{} }}", indent);
784
+ let _ = writeln!(
785
+ out,
786
+ "{} public string ToXmlString() => this switch",
787
+ indent
788
+ );
789
+ let _ = writeln!(out, "{} {{", indent);
790
+ for branch in &u.branches {
791
+ let variant = choice_variant_name(branch);
792
+ let value = if matches!(
793
+ super::primitive_base(&branch.type_ref, ir),
794
+ TypeRef::Primitive(PrimitiveType::Date)
795
+ ) {
796
+ "item.Value.ToString(\"yyyy-MM-dd\", System.Globalization.CultureInfo.InvariantCulture)".to_string()
797
+ } else {
798
+ match &branch.type_ref {
799
+ TypeRef::Named(qname) if matches!(ir.types.get(qname), Some(TypeDef::Enum(_))) => "item.Value.ToXmlValue()".to_string(),
800
+ TypeRef::Named(qname) if matches!(ir.types.get(qname), Some(TypeDef::Simple(_))) => "Convert.ToString(item.Value.Value, System.Globalization.CultureInfo.InvariantCulture) ?? \"\"".to_string(),
801
+ _ => "Convert.ToString(item.Value, System.Globalization.CultureInfo.InvariantCulture) ?? \"\"".to_string(),
802
+ }
803
+ };
804
+ let _ = writeln!(out, "{} {} item => {},", indent, variant, value);
805
+ }
806
+ let _ = writeln!(
807
+ out,
808
+ "{} _ => throw new InvalidOperationException()",
809
+ indent
810
+ );
811
+ let _ = writeln!(out, "{} }};", indent);
812
+ }
813
+
814
+ fn emit_mixed_struct(
815
+ &self,
816
+ out: &mut String,
817
+ s: &StructDef,
818
+ ir: &SchemaIR,
819
+ indent: &str,
820
+ struct_name: &str,
821
+ prop_names: &[String],
822
+ ) {
823
+ let Some((item_index, union)) =
824
+ s.fields
825
+ .iter()
826
+ .enumerate()
827
+ .find_map(|(index, field)| match &field.type_ref {
828
+ TypeRef::Named(qname) => match ir.types.get(qname) {
829
+ Some(TypeDef::Union(union)) if union.is_mixed_content() => {
830
+ Some((index, union))
831
+ }
832
+ _ => None,
833
+ },
834
+ _ => None,
835
+ })
836
+ else {
837
+ return;
838
+ };
839
+ let item_name = &prop_names[item_index];
840
+ let union_name = type_ident(&union.qname);
841
+ let class_kind = if self.options.use_records {
842
+ "record"
843
+ } else {
844
+ "class"
845
+ };
846
+ let _ = writeln!(out, "{indent}public {class_kind} {struct_name} : System.Xml.Serialization.IXmlSerializable\n{indent}{{");
847
+ for (field, prop_name) in s.fields.iter().zip(prop_names) {
848
+ let ty = self.map_field_type(field, ir);
849
+ if field.kind == FieldKind::Attribute {
850
+ if self.options.emit_json_attributes {
851
+ let _ = writeln!(out, "{indent} [JsonPropertyName({:?})]", field.xml_name);
852
+ }
853
+ let _ = writeln!(
854
+ out,
855
+ "{indent} public {ty} {prop_name} {{ get; set; }} = default!;"
856
+ );
857
+ } else if field.kind == FieldKind::Element && prop_name == item_name {
858
+ let _ = writeln!(out, "{indent} [XmlIgnore]\n{indent} public List<{union_name}> {item_name} {{ get; set; }} = new();");
859
+ }
860
+ }
861
+ let _ = writeln!(
862
+ out,
863
+ "{indent} public System.Xml.Schema.XmlSchema? GetSchema() => null;"
864
+ );
865
+ let _ = writeln!(
866
+ out,
867
+ "{indent} public void ReadXml(System.Xml.XmlReader reader)\n{indent} {{"
868
+ );
869
+ out.push_str(" reader.MoveToContent();\n");
870
+ for (field, prop_name) in s.fields.iter().zip(prop_names) {
871
+ if field.kind == FieldKind::Attribute {
872
+ let ty = self.map_field_type(field, ir);
873
+ let base_type = ty.trim_end_matches('?');
874
+ let _ = writeln!(out, " {{ var raw = reader.GetAttribute({:?}); if (raw is not null) {prop_name} = ({ty})System.Convert.ChangeType(raw, typeof({base_type}), System.Globalization.CultureInfo.InvariantCulture); }}", field.xml_name);
875
+ }
876
+ }
877
+ let _ = writeln!(out, " {item_name} = new List<{union_name}>();");
878
+ out.push_str(" bool empty = reader.IsEmptyElement;\n reader.ReadStartElement();\n if (empty) return;\n while (reader.NodeType != System.Xml.XmlNodeType.EndElement && !reader.EOF)\n {\n switch (reader.NodeType)\n {\n case System.Xml.XmlNodeType.Text:\n case System.Xml.XmlNodeType.CDATA:\n case System.Xml.XmlNodeType.Whitespace:\n case System.Xml.XmlNodeType.SignificantWhitespace:\n");
879
+ let _ = writeln!(
880
+ out,
881
+ " {item_name}.Add(new {union_name}.Text(reader.Value));"
882
+ );
883
+ out.push_str(" reader.Read();\n break;\n case System.Xml.XmlNodeType.Element:\n switch (reader.LocalName)\n {\n");
884
+ for branch in &union.branches {
885
+ if branch.xml_name == "#text" {
886
+ continue;
887
+ }
888
+ let branch_type = self.context.map_type_ref(&branch.type_ref);
889
+ let variant = choice_variant_name(branch);
890
+ let namespace = branch.namespace.as_deref().unwrap_or("");
891
+ let _ = writeln!(out, " case {:?}:\n {{ var serializer = new XmlSerializer(typeof({branch_type}), new XmlRootAttribute({:?}) {{ Namespace = {:?} }}); {item_name}.Add(new {union_name}.{variant}(({branch_type})serializer.Deserialize(reader)!)); break; }}", branch.xml_name, branch.xml_name, namespace);
892
+ }
893
+ out.push_str(" default: reader.Skip(); break;\n }\n break;\n default: reader.Read(); break;\n }\n }\n reader.ReadEndElement();\n");
894
+ let _ = writeln!(out, "{indent} }}");
895
+ let _ = writeln!(
896
+ out,
897
+ "{indent} public void WriteXml(System.Xml.XmlWriter writer)\n{indent} {{"
898
+ );
899
+ for (field, prop_name) in s.fields.iter().zip(prop_names) {
900
+ if field.kind == FieldKind::Attribute {
901
+ let _ = writeln!(out, " if ({prop_name} is not null) writer.WriteAttributeString({:?}, System.Convert.ToString({prop_name}, System.Globalization.CultureInfo.InvariantCulture));", field.xml_name);
902
+ }
903
+ }
904
+ let _ = writeln!(out, " foreach (var item in {item_name})\n {{\n switch (item)\n {{");
905
+ for branch in &union.branches {
906
+ let variant = choice_variant_name(branch);
907
+ if branch.xml_name == "#text" {
908
+ let _ = writeln!(out, " case {union_name}.{variant} text: writer.WriteString(text.Value); break;");
909
+ } else {
910
+ let branch_type = self.context.map_type_ref(&branch.type_ref);
911
+ let namespace = branch.namespace.as_deref().unwrap_or("");
912
+ let _ = writeln!(out, " case {union_name}.{variant} value: new XmlSerializer(typeof({branch_type}), new XmlRootAttribute({:?}) {{ Namespace = {:?} }}).Serialize(writer, value.Value); break;", branch.xml_name, namespace);
913
+ }
914
+ }
915
+ out.push_str(" }\n }\n");
916
+ let _ = writeln!(out, "{indent} }}\n{indent}}}\n");
917
+ }
918
+
685
919
  fn emit_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR, indent: &str) {
686
920
  let struct_name = type_ident(&s.qname);
687
921
  if let Some(ref doc) = s.documentation {
@@ -732,6 +966,60 @@ impl CSharpCodegen {
732
966
  .map(|f| self.unique_property_name(&f.name, &mut seen_props, Some(&struct_name)))
733
967
  .collect();
734
968
 
969
+ if s.is_mixed && self.options.emit_xml_attributes {
970
+ self.emit_mixed_struct(out, s, ir, indent, &struct_name, &prop_names);
971
+ return;
972
+ }
973
+
974
+ let has_lexical_union = self.options.emit_xml_attributes && s.fields.iter().any(|f| {
975
+ matches!(&f.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical()))
976
+ });
977
+
978
+ if self.options.use_records && has_lexical_union {
979
+ writeln!(
980
+ out,
981
+ "{}public record {}{}\n{}{{",
982
+ indent, struct_name, implements_str, indent
983
+ )
984
+ .unwrap();
985
+ for (f, name) in s.fields.iter().zip(&prop_names) {
986
+ let ty = self.map_field_type(f, ir);
987
+ if let TypeRef::Named(q) = &f.type_ref {
988
+ if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical()) {
989
+ let _ = writeln!(out, "{} [XmlIgnore]", indent);
990
+ if self.options.emit_json_attributes {
991
+ let _ =
992
+ writeln!(out, "{} [JsonPropertyName({:?})]", indent, f.xml_name);
993
+ }
994
+ let _ = writeln!(
995
+ out,
996
+ "{} public {} {} {{ get; set; }} = default!;",
997
+ indent, ty, name
998
+ );
999
+ let _ = writeln!(out, "{} [XmlElement({:?})]", indent, f.xml_name);
1000
+ if self.options.emit_json_attributes {
1001
+ let _ = writeln!(out, "{} [JsonIgnore]", indent);
1002
+ }
1003
+ let _ = writeln!(out, "{} public string? {}Xml {{ get => {}?.ToXmlString(); set => {} = value is null ? default! : {}.Parse(value); }}", indent, name, name, name, type_ident(q));
1004
+ continue;
1005
+ }
1006
+ }
1007
+ let attrs = self
1008
+ .build_field_attributes(f, ir)
1009
+ .replace("[property: ", "[");
1010
+ let _ = writeln!(
1011
+ out,
1012
+ "{} {}public {} {} {{ get; set; }} = default!;",
1013
+ indent, attrs, ty, name
1014
+ );
1015
+ }
1016
+ if self.options.emit_validation {
1017
+ self.emit_struct_validator(out, s, &prop_names, indent);
1018
+ }
1019
+ writeln!(out, "{}}}\n", indent).unwrap();
1020
+ return;
1021
+ }
1022
+
735
1023
  if !self.options.use_records {
736
1024
  writeln!(
737
1025
  out,
@@ -742,6 +1030,28 @@ impl CSharpCodegen {
742
1030
  writeln!(out, "{} public {}() {{ }}", indent, struct_name).unwrap();
743
1031
  for (f, name) in s.fields.iter().zip(&prop_names) {
744
1032
  let ty = self.map_field_type(f, ir);
1033
+ if let TypeRef::Named(q) = &f.type_ref {
1034
+ if self.options.emit_xml_attributes
1035
+ && matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical())
1036
+ {
1037
+ let _ = writeln!(out, "{} [XmlIgnore]", indent);
1038
+ if self.options.emit_json_attributes {
1039
+ let _ =
1040
+ writeln!(out, "{} [JsonPropertyName({:?})]", indent, f.xml_name);
1041
+ }
1042
+ let _ = writeln!(
1043
+ out,
1044
+ "{} public {} {} {{ get; set; }} = default!;",
1045
+ indent, ty, name
1046
+ );
1047
+ let _ = writeln!(out, "{} [XmlElement({:?})]", indent, f.xml_name);
1048
+ if self.options.emit_json_attributes {
1049
+ let _ = writeln!(out, "{} [JsonIgnore]", indent);
1050
+ }
1051
+ let _ = writeln!(out, "{} public string? {}Xml {{ get => {}?.ToXmlString(); set => {} = value is null ? default! : {}.Parse(value); }}", indent, name, name, name, type_ident(q));
1052
+ continue;
1053
+ }
1054
+ }
745
1055
  let attrs = self
746
1056
  .build_field_attributes(f, ir)
747
1057
  .replace("[property: ", "[");
@@ -1041,7 +1351,7 @@ impl CSharpCodegen {
1041
1351
  if let Some(TypeDef::Union(u)) = ir.types.get(qname) {
1042
1352
  let choice_name = type_ident(&u.qname);
1043
1353
  for branch in &u.branches {
1044
- let variant_name = to_csharp_type_name(&branch.variant_name);
1354
+ let variant_name = choice_variant_name(branch);
1045
1355
  parts.push(format!(
1046
1356
  "XmlElement(\"{}\", typeof({}.{}))",
1047
1357
  branch.xml_name, choice_name, variant_name