polyxml 0.34.4__tar.gz → 0.34.5__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 (84) hide show
  1. {polyxml-0.34.4 → polyxml-0.34.5}/Cargo.lock +6 -6
  2. {polyxml-0.34.4 → polyxml-0.34.5}/Cargo.toml +1 -1
  3. {polyxml-0.34.4 → polyxml-0.34.5}/PKG-INFO +1 -1
  4. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/csharp/mod.rs +232 -55
  5. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/go/mod.rs +1 -7
  6. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/rust/mod.rs +15 -1
  7. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_csharp_codegen.rs +4 -1
  8. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_go_codegen.rs +29 -0
  9. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_qualified_attributes.rs +92 -0
  10. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_rust_codegen.rs +38 -0
  11. {polyxml-0.34.4 → polyxml-0.34.5}/pyproject.toml +1 -1
  12. {polyxml-0.34.4 → polyxml-0.34.5}/README.md +0 -0
  13. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/Cargo.toml +0 -0
  14. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/README.md +0 -0
  15. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  16. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  17. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  18. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  19. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
  20. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  21. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  22. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  23. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  24. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
  25. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  26. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/converters.rs +0 -0
  27. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/error.rs +0 -0
  28. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/integer.rs +0 -0
  29. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/ir/chunker.rs +0 -0
  30. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/ir/mod.rs +0 -0
  31. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/ir/particle.rs +0 -0
  32. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  33. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/json.rs +0 -0
  34. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/lib.rs +0 -0
  35. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/parser.rs +0 -0
  36. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/schema.rs +0 -0
  37. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
  38. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/schema_parser/upa.rs +0 -0
  39. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/serializer.rs +0 -0
  40. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/transcoder.rs +0 -0
  41. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/src/value.rs +0 -0
  42. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  43. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
  44. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/fixtures/ts_mixed_content_inheritance.xsd +0 -0
  45. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  46. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_any_attribute.rs +0 -0
  47. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_any_element.rs +0 -0
  48. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  49. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_core.rs +0 -0
  50. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  51. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  52. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_json.rs +0 -0
  53. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_lexical_lists.rs +0 -0
  54. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  55. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
  56. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  57. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
  58. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  59. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  60. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  61. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_security.rs +0 -0
  62. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_temporal_union.rs +0 -0
  63. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  64. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  65. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_ubl_csharp.rs +0 -0
  66. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  67. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_upa.rs +0 -0
  68. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_value_constraints.rs +0 -0
  69. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  70. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-python/Cargo.toml +0 -0
  71. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-python/README.md +0 -0
  72. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  73. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-python/src/lib.rs +0 -0
  74. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-python/tests/test_conformance.py +0 -0
  75. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  76. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-python/tests/test_json.py +0 -0
  77. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
  78. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  79. {polyxml-0.34.4 → polyxml-0.34.5}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  80. {polyxml-0.34.4 → polyxml-0.34.5}/python/polyxml/AGENT_GUIDE.md +0 -0
  81. {polyxml-0.34.4 → polyxml-0.34.5}/python/polyxml/__init__.py +0 -0
  82. {polyxml-0.34.4 → polyxml-0.34.5}/python/polyxml/compat/__init__.py +0 -0
  83. {polyxml-0.34.4 → polyxml-0.34.5}/python/polyxml/compat/xsdata.py +0 -0
  84. {polyxml-0.34.4 → polyxml-0.34.5}/python/polyxml/py.typed +0 -0
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.34.4"
611
+ version = "0.34.5"
612
612
  dependencies = [
613
613
  "criterion",
614
614
  "heck",
@@ -629,14 +629,14 @@ dependencies = [
629
629
 
630
630
  [[package]]
631
631
  name = "polyxml-c"
632
- version = "0.34.4"
632
+ version = "0.34.5"
633
633
  dependencies = [
634
634
  "polyxml",
635
635
  ]
636
636
 
637
637
  [[package]]
638
638
  name = "polyxml-cli"
639
- version = "0.34.4"
639
+ version = "0.34.5"
640
640
  dependencies = [
641
641
  "clap",
642
642
  "glob",
@@ -650,7 +650,7 @@ dependencies = [
650
650
 
651
651
  [[package]]
652
652
  name = "polyxml-js"
653
- version = "0.34.4"
653
+ version = "0.34.5"
654
654
  dependencies = [
655
655
  "napi",
656
656
  "napi-build",
@@ -660,7 +660,7 @@ dependencies = [
660
660
 
661
661
  [[package]]
662
662
  name = "polyxml-python"
663
- version = "0.34.4"
663
+ version = "0.34.5"
664
664
  dependencies = [
665
665
  "lexical-core",
666
666
  "polyxml",
@@ -670,7 +670,7 @@ dependencies = [
670
670
 
671
671
  [[package]]
672
672
  name = "polyxml-wasm"
673
- version = "0.34.4"
673
+ version = "0.34.5"
674
674
  dependencies = [
675
675
  "polyxml",
676
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.34.4"
6
+ version = "0.34.5"
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.34.4
3
+ Version: 0.34.5
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -277,7 +277,7 @@ fn root_names(ir: &SchemaIR) -> HashMap<QName, String> {
277
277
  names
278
278
  }
279
279
 
280
- fn choice_variant_name(branch: &UnionBranch) -> String {
280
+ fn choice_variant_base(branch: &UnionBranch) -> String {
281
281
  let name = to_csharp_type_name(&branch.variant_name);
282
282
  if matches!(
283
283
  name.as_str(),
@@ -290,6 +290,33 @@ fn choice_variant_name(branch: &UnionBranch) -> String {
290
290
  }
291
291
  }
292
292
 
293
+ fn choice_variant_names(union: &UnionDef) -> Vec<String> {
294
+ let mut used = HashSet::from([type_ident(&union.qname)]);
295
+ for branch in &union.branches {
296
+ let mut ty = &branch.type_ref;
297
+ while let TypeRef::List(inner) | TypeRef::Boxed(inner) = ty {
298
+ ty = inner;
299
+ }
300
+ if let TypeRef::Named(name) = ty {
301
+ used.insert(type_ident(name));
302
+ }
303
+ }
304
+ union
305
+ .branches
306
+ .iter()
307
+ .map(|branch| {
308
+ let base = choice_variant_base(branch);
309
+ let mut name = base.clone();
310
+ let mut suffix = 2;
311
+ while !used.insert(name.clone()) {
312
+ name = format!("{base}{suffix}");
313
+ suffix += 1;
314
+ }
315
+ name
316
+ })
317
+ .collect()
318
+ }
319
+
293
320
  fn simple_value_target(base: &TypeRef, ir: &SchemaIR) -> String {
294
321
  let mut target = "Value".to_string();
295
322
  let mut current = base;
@@ -298,6 +325,10 @@ fn simple_value_target(base: &TypeRef, ir: &SchemaIR) -> String {
298
325
  if !seen.insert(qname.clone()) {
299
326
  break;
300
327
  }
328
+ if matches!(ir.types.get(qname), Some(TypeDef::Enum(_))) {
329
+ target.push_str(".ToXmlValue()");
330
+ break;
331
+ }
301
332
  let Some(TypeDef::Simple(simple)) = ir.types.get(qname) else {
302
333
  break;
303
334
  };
@@ -340,6 +371,41 @@ impl CSharpCodegen {
340
371
 
341
372
  /// Emits all types in the given SchemaIR as a single C# compilation unit.
342
373
  pub fn generate_module(&self, ir: &SchemaIR) -> String {
374
+ let has_duplicate_items = ir.types.values().any(|def| {
375
+ let TypeDef::Union(union) = def else {
376
+ return false;
377
+ };
378
+ if !union.is_mixed_content() {
379
+ return false;
380
+ }
381
+ let mut seen = HashSet::new();
382
+ union
383
+ .branches
384
+ .iter()
385
+ .any(|b| !seen.insert((&b.xml_name, &b.namespace, &b.type_ref)))
386
+ });
387
+ let mut canonical = if has_duplicate_items {
388
+ std::borrow::Cow::Owned(ir.clone())
389
+ } else {
390
+ std::borrow::Cow::Borrowed(ir)
391
+ };
392
+ if let std::borrow::Cow::Owned(canonical) = &mut canonical {
393
+ for def in canonical.types.values_mut() {
394
+ if let TypeDef::Union(union) = def {
395
+ if union.is_mixed_content() {
396
+ let mut seen = HashSet::new();
397
+ union.branches.retain(|b| {
398
+ seen.insert((
399
+ b.xml_name.clone(),
400
+ b.namespace.clone(),
401
+ b.type_ref.clone(),
402
+ ))
403
+ });
404
+ }
405
+ }
406
+ }
407
+ }
408
+ let ir = canonical.as_ref();
343
409
  set_type_name_map(build_type_name_map(ir, to_csharp_type_name));
344
410
  let mut out = String::new();
345
411
 
@@ -438,6 +504,13 @@ impl CSharpCodegen {
438
504
  writeln!(out, "}}").unwrap();
439
505
  }
440
506
 
507
+ if out.contains(".Select(") || out.contains(".Any(") {
508
+ out = out.replacen(
509
+ "using System.Collections.Generic;",
510
+ "using System.Collections.Generic;\nusing System.Linq;",
511
+ 1,
512
+ );
513
+ }
441
514
  out
442
515
  }
443
516
 
@@ -749,8 +822,7 @@ impl CSharpCodegen {
749
822
 
750
823
  // XmlInclude attributes for element-choice polymorphism.
751
824
  if self.options.emit_xml_attributes && !u.is_lexical() && !u.is_mixed_content() {
752
- for branch in &u.branches {
753
- let variant_name = choice_variant_name(branch);
825
+ for variant_name in choice_variant_names(u) {
754
826
  writeln!(
755
827
  out,
756
828
  "{}[XmlInclude(typeof({}.{}))]",
@@ -761,14 +833,11 @@ impl CSharpCodegen {
761
833
  }
762
834
 
763
835
  if !self.options.use_records && self.options.emit_json_attributes {
764
- for branch in &u.branches {
836
+ for (branch, variant_name) in u.branches.iter().zip(choice_variant_names(u)) {
765
837
  writeln!(
766
838
  out,
767
839
  "{}[JsonDerivedType(typeof({}.{}), {:?})]",
768
- indent,
769
- choice_name,
770
- choice_variant_name(branch),
771
- branch.xml_name
840
+ indent, choice_name, variant_name, branch.xml_name
772
841
  )
773
842
  .unwrap();
774
843
  }
@@ -787,8 +856,7 @@ impl CSharpCodegen {
787
856
  .unwrap();
788
857
  writeln!(out, "{}{{", indent).unwrap();
789
858
 
790
- for branch in &u.branches {
791
- let variant_name = choice_variant_name(branch);
859
+ for (branch, variant_name) in u.branches.iter().zip(choice_variant_names(u)) {
792
860
  let branch_type = self.context.map_type_ref(&branch.type_ref);
793
861
 
794
862
  if let Some(ref doc) = branch.documentation {
@@ -864,8 +932,11 @@ impl CSharpCodegen {
864
932
  );
865
933
  let _ = writeln!(out, "{} {{", indent);
866
934
  let _ = writeln!(out, "{} var value = text.Trim();", indent);
867
- for (idx, branch) in u.branches.iter().enumerate() {
868
- let variant = choice_variant_name(branch);
935
+ let mut exhaustive = false;
936
+ for (idx, (branch, variant_name)) in
937
+ u.branches.iter().zip(choice_variant_names(u)).enumerate()
938
+ {
939
+ let variant = variant_name;
869
940
  let mapped = self.context.map_type_ref(&branch.type_ref);
870
941
  if let TypeRef::Named(qname) = &branch.type_ref {
871
942
  if let Some(TypeDef::Enum(def)) = ir.types.get(qname) {
@@ -894,6 +965,11 @@ impl CSharpCodegen {
894
965
  } else {
895
966
  format!("candidate{}", idx)
896
967
  };
968
+ if matches!(base, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Union(nested)) if nested.is_lexical()))
969
+ {
970
+ writeln!(out, "{indent} try {{ var candidate{idx}={base_name}.Parse(value);return new {variant}({wrap}); }}catch(FormatException){{}}").unwrap();
971
+ continue;
972
+ }
897
973
  if let Some(p) = super::unbounded_integer(&branch.type_ref, ir) {
898
974
  let kind = match p {
899
975
  PrimitiveType::PositiveInteger => 1,
@@ -927,6 +1003,8 @@ impl CSharpCodegen {
927
1003
  "{} {{ var candidate{} = value; return new {}({}); }}",
928
1004
  indent, idx, variant, wrap
929
1005
  );
1006
+ exhaustive = true;
1007
+ break;
930
1008
  }
931
1009
  } else if base_name == "bool" {
932
1010
  let _ = writeln!(out, "{} if (value == \"true\" || value == \"1\") {{ var candidate{} = true; return new {}({}); }}", indent, idx, variant, wrap);
@@ -943,16 +1021,18 @@ impl CSharpCodegen {
943
1021
  );
944
1022
  }
945
1023
  }
946
- let _ = writeln!(
947
- out,
948
- "{} throw new FormatException(\"No {} union member accepts the value\");",
949
- indent, name
950
- );
1024
+ if !exhaustive {
1025
+ let _ = writeln!(
1026
+ out,
1027
+ "{} throw new FormatException(\"No {} union member accepts the value\");",
1028
+ indent, name
1029
+ );
1030
+ }
951
1031
  let _ = writeln!(out, "{} }}", indent);
952
1032
  let _ = writeln!(out, "{indent} private string? _lexical;\n{indent} private string? _parsedCanonical;\n{indent} private static {name} Preserve({name} member, string lexical) {{ member._parsedCanonical = member.FormatXmlValue(); member._lexical = lexical; return member; }}\n{indent} public string ToXmlString() {{ var canonical = FormatXmlValue(); return canonical == _parsedCanonical ? _lexical! : canonical; }}\n{indent} private string FormatXmlValue() => this switch");
953
1033
  let _ = writeln!(out, "{} {{", indent);
954
- for branch in &u.branches {
955
- let variant = choice_variant_name(branch);
1034
+ for (branch, variant_name) in u.branches.iter().zip(choice_variant_names(u)) {
1035
+ let variant = variant_name;
956
1036
  let value = if matches!(
957
1037
  super::primitive_base(&branch.type_ref, ir),
958
1038
  TypeRef::Primitive(PrimitiveType::Date)
@@ -970,6 +1050,7 @@ impl CSharpCodegen {
970
1050
  "item.Value.ToString(\"HH:mm:ssK\", System.Globalization.CultureInfo.InvariantCulture)".to_string()
971
1051
  } else {
972
1052
  match &branch.type_ref {
1053
+ TypeRef::Named(qname) if matches!(ir.types.get(qname), Some(TypeDef::Union(nested)) if nested.is_lexical()) => "item.Value.ToXmlString()".to_string(),
973
1054
  TypeRef::Named(qname) if matches!(ir.types.get(qname), Some(TypeDef::Enum(_))) => "item.Value.ToXmlValue()".to_string(),
974
1055
  TypeRef::Named(qname) if matches!(ir.types.get(qname), Some(TypeDef::Simple(_))) => "Convert.ToString(item.Value.Value, System.Globalization.CultureInfo.InvariantCulture) ?? \"\"".to_string(),
975
1056
  _ => "Convert.ToString(item.Value, System.Globalization.CultureInfo.InvariantCulture) ?? \"\"".to_string(),
@@ -1031,17 +1112,30 @@ impl CSharpCodegen {
1031
1112
  "{indent} [XmlIgnore] {json}public {ty} {name} {{get;set;}} = {default};"
1032
1113
  )
1033
1114
  .unwrap();
1115
+ let optional = ty.ends_with('?');
1034
1116
  let validation = if field.cardinality.is_list() {
1035
- format!("{name}?.Select(v=>PolyxmlIntegerLexical.Validate(v,{kind})).ToList()")
1036
- } else {
1117
+ if optional {
1118
+ format!("{name}?.Select(v=>PolyxmlIntegerLexical.Validate(v,{kind})).ToList()")
1119
+ } else {
1120
+ format!("{name}.Select(v=>PolyxmlIntegerLexical.Validate(v,{kind})).ToList()")
1121
+ }
1122
+ } else if optional {
1037
1123
  format!("{name} is null ? null : PolyxmlIntegerLexical.Validate({name},{kind})")
1124
+ } else {
1125
+ format!("PolyxmlIntegerLexical.Validate({name} ?? throw new InvalidOperationException(\"Missing required integer\"),{kind})")
1038
1126
  };
1039
1127
  let parse = if field.cardinality.is_list() {
1040
- format!("value?.Select(v=>PolyxmlIntegerLexical.Validate(v,{kind})).ToList()!")
1128
+ if optional {
1129
+ format!("value?.Select(v=>PolyxmlIntegerLexical.Validate(v,{kind})).ToList()")
1130
+ } else {
1131
+ format!("value.Select(v=>PolyxmlIntegerLexical.Validate(v,{kind})).ToList()")
1132
+ }
1133
+ } else if optional {
1134
+ format!("value is null ? null : PolyxmlIntegerLexical.Validate(value,{kind})")
1041
1135
  } else {
1042
- format!("value is null ? default! : PolyxmlIntegerLexical.Validate(value,{kind})")
1136
+ format!("PolyxmlIntegerLexical.Validate(value,{kind})")
1043
1137
  };
1044
- writeln!(out,"{indent} {attrs}{ignore}public {ty} {name}Xml {{ get => {validation}!; set => {name} = {parse}; }}").unwrap();
1138
+ writeln!(out,"{indent} {attrs}{ignore}public {ty} {name}Xml {{ get => {validation}; set => {name} = {parse}; }}").unwrap();
1045
1139
  true
1046
1140
  }
1047
1141
 
@@ -1166,6 +1260,22 @@ impl CSharpCodegen {
1166
1260
  }
1167
1261
  let ty = self.map_field_type(field, ir);
1168
1262
  let base = ty.trim_end_matches('?');
1263
+ let can_be_null = ty.ends_with('?')
1264
+ || !(matches!(
1265
+ base,
1266
+ "bool"
1267
+ | "byte"
1268
+ | "sbyte"
1269
+ | "short"
1270
+ | "ushort"
1271
+ | "int"
1272
+ | "uint"
1273
+ | "long"
1274
+ | "ulong"
1275
+ | "float"
1276
+ | "double"
1277
+ | "decimal"
1278
+ ) || matches!(&field.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Enum(_)))));
1169
1279
  let namespace = field
1170
1280
  .namespace
1171
1281
  .as_ref()
@@ -1199,8 +1309,13 @@ impl CSharpCodegen {
1199
1309
  } else {
1200
1310
  format!("object.Equals(value, {expected})")
1201
1311
  };
1312
+ let present = if ty.ends_with('?') {
1313
+ "value is not null && "
1314
+ } else {
1315
+ ""
1316
+ };
1202
1317
  writeln!(out, "{indent} private {ty} _{name} = {initial};
1203
- {indent} [XmlIgnore] public {ty} {name} {{ get => _{name}; set {{ if (value is not null && !({equal})) throw new System.ComponentModel.DataAnnotations.ValidationException(\"Fixed value constraint violated for {name}\"); _{name} = value; }} }}").unwrap();
1318
+ {indent} [XmlIgnore] public {ty} {name} {{ get => _{name}; set {{ if ({present}!({equal})) throw new System.ComponentModel.DataAnnotations.ValidationException(\"Fixed value constraint violated for {name}\"); _{name} = value; }} }}").unwrap();
1204
1319
  } else {
1205
1320
  let initial = if field.kind == FieldKind::Attribute {
1206
1321
  parse(&format!("{default:?}"))
@@ -1224,10 +1339,15 @@ impl CSharpCodegen {
1224
1339
  field.xml_name
1225
1340
  )
1226
1341
  .unwrap();
1342
+ let null_guard = if can_be_null {
1343
+ format!("if ({name} is null) return null; ")
1344
+ } else {
1345
+ String::new()
1346
+ };
1227
1347
  if let Some(kind) = integer_kind {
1228
- writeln!(out,"{indent} get {{ if ({name} is null) return null; return PolyxmlIntegerLexical.Validate({name},{kind}); }}").unwrap();
1348
+ writeln!(out,"{indent} get {{ {null_guard} return PolyxmlIntegerLexical.Validate({name},{kind}); }}").unwrap();
1229
1349
  } else {
1230
- writeln!(out, "{indent} get {{ if ({name} is null) return null; var writer = new System.IO.StringWriter(System.Globalization.CultureInfo.InvariantCulture); new XmlSerializer(typeof({base}), new XmlRootAttribute(\"value\")).Serialize(writer, {name}); return System.Xml.Linq.XDocument.Parse(writer.ToString()).Root!.Value; }}").unwrap();
1350
+ writeln!(out, "{indent} get {{ {null_guard}var writer = new System.IO.StringWriter(System.Globalization.CultureInfo.InvariantCulture); new XmlSerializer(typeof({base}), new XmlRootAttribute(\"value\")).Serialize(writer, {name}); return System.Xml.Linq.XDocument.Parse(writer.ToString()).Root!.Value; }}").unwrap();
1231
1351
  }
1232
1352
  let text = if field.kind == FieldKind::Attribute {
1233
1353
  "value".into()
@@ -1274,12 +1394,24 @@ impl CSharpCodegen {
1274
1394
  };
1275
1395
  let item_name = &prop_names[item_index];
1276
1396
  let union_name = type_ident(&union.qname);
1397
+ let text_variant = union
1398
+ .branches
1399
+ .iter()
1400
+ .zip(choice_variant_names(union))
1401
+ .find(|(branch, _)| branch.xml_name == "#text")
1402
+ .map(|(_, name)| name)
1403
+ .expect("mixed union text branch");
1277
1404
  let class_kind = if self.options.use_records {
1278
1405
  "record"
1279
1406
  } else {
1280
1407
  "class"
1281
1408
  };
1282
- let _ = writeln!(out, "{indent}public {class_kind} {struct_name} : System.Xml.Serialization.IXmlSerializable\n{indent}{{");
1409
+ let validation_interface = if self.options.emit_validation {
1410
+ ", IValidatableObject"
1411
+ } else {
1412
+ ""
1413
+ };
1414
+ let _ = writeln!(out, "{indent}public {class_kind} {struct_name} : System.Xml.Serialization.IXmlSerializable{validation_interface}\n{indent}{{");
1283
1415
  for (field, prop_name) in s.fields.iter().zip(prop_names) {
1284
1416
  let ty = self.map_field_type(field, ir);
1285
1417
  if field.kind == FieldKind::Attribute {
@@ -1307,22 +1439,23 @@ impl CSharpCodegen {
1307
1439
  if field.kind == FieldKind::Attribute {
1308
1440
  let ty = self.map_field_type(field, ir);
1309
1441
  let base_type = ty.trim_end_matches('?');
1310
- 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);
1442
+ let namespace = field.namespace.as_deref().unwrap_or("");
1443
+ let _ = writeln!(out, " {{ var raw = reader.GetAttribute({:?}, {namespace:?}); if (raw is not null) {prop_name} = ({ty})new XmlSerializer(typeof({base_type}), new XmlRootAttribute(\"value\")).Deserialize(new System.IO.StringReader(\"<value>\" + System.Security.SecurityElement.Escape(raw) + \"</value>\"))!; }}", field.xml_name);
1311
1444
  }
1312
1445
  }
1313
1446
  let _ = writeln!(out, " {item_name} = new List<{union_name}>();");
1314
1447
  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");
1315
1448
  let _ = writeln!(
1316
1449
  out,
1317
- " {item_name}.Add(new {union_name}.Text(reader.Value));"
1450
+ " {item_name}.Add(new {union_name}.{text_variant}(reader.Value));"
1318
1451
  );
1319
1452
  out.push_str(" reader.Read();\n break;\n case System.Xml.XmlNodeType.Element:\n switch (reader.LocalName)\n {\n");
1320
- for branch in &union.branches {
1453
+ for (branch, variant) in union.branches.iter().zip(choice_variant_names(union)) {
1321
1454
  if branch.xml_name == "#text" {
1322
1455
  continue;
1323
1456
  }
1324
1457
  let branch_type = self.context.map_type_ref(&branch.type_ref);
1325
- let variant = choice_variant_name(branch);
1458
+
1326
1459
  let namespace = branch.namespace.as_deref().unwrap_or("");
1327
1460
  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);
1328
1461
  }
@@ -1334,12 +1467,34 @@ impl CSharpCodegen {
1334
1467
  );
1335
1468
  for (field, prop_name) in s.fields.iter().zip(prop_names) {
1336
1469
  if field.kind == FieldKind::Attribute {
1337
- let _ = writeln!(out, " if ({prop_name} is not null) writer.WriteAttributeString({:?}, System.Convert.ToString({prop_name}, System.Globalization.CultureInfo.InvariantCulture));", field.xml_name);
1470
+ let ty = self.map_field_type(field, ir);
1471
+ let base = ty.trim_end_matches('?');
1472
+ let is_value = matches!(
1473
+ base,
1474
+ "bool"
1475
+ | "byte"
1476
+ | "sbyte"
1477
+ | "short"
1478
+ | "ushort"
1479
+ | "int"
1480
+ | "uint"
1481
+ | "long"
1482
+ | "ulong"
1483
+ | "float"
1484
+ | "double"
1485
+ | "decimal"
1486
+ ) || matches!(&field.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Enum(_))));
1487
+ let guard = if ty.ends_with('?') || !is_value {
1488
+ format!("if ({prop_name} is not null) ")
1489
+ } else {
1490
+ String::new()
1491
+ };
1492
+ let namespace = field.namespace.as_deref().unwrap_or("");
1493
+ let _ = writeln!(out, " {guard}{{ var text = new System.IO.StringWriter(System.Globalization.CultureInfo.InvariantCulture); new XmlSerializer(typeof({base}), new XmlRootAttribute(\"value\")).Serialize(text, {prop_name}); writer.WriteAttributeString({:?}, {namespace:?}, System.Xml.Linq.XDocument.Parse(text.ToString()).Root!.Value); }}", field.xml_name);
1338
1494
  }
1339
1495
  }
1340
1496
  let _ = writeln!(out, " foreach (var item in {item_name})\n {{\n switch (item)\n {{");
1341
- for branch in &union.branches {
1342
- let variant = choice_variant_name(branch);
1497
+ for (branch, variant) in union.branches.iter().zip(choice_variant_names(union)) {
1343
1498
  if branch.xml_name == "#text" {
1344
1499
  let _ = writeln!(out, " case {union_name}.{variant} text: writer.WriteString(text.Value); break;");
1345
1500
  } else {
@@ -1349,10 +1504,27 @@ impl CSharpCodegen {
1349
1504
  }
1350
1505
  }
1351
1506
  out.push_str(" }\n }\n");
1352
- let _ = writeln!(out, "{indent} }}\n{indent}}}\n");
1507
+ let _ = writeln!(out, "{indent} }}");
1508
+ if self.options.emit_validation {
1509
+ self.emit_struct_validator(out, s, ir, prop_names, indent);
1510
+ }
1511
+ let _ = writeln!(out, "{indent}}}\n");
1353
1512
  }
1354
1513
 
1355
1514
  fn emit_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR, indent: &str) {
1515
+ if ir.has_ordered_content(s) && s.base_type.is_some() {
1516
+ let fields = super::flatten_fields(s, ir);
1517
+ if fields.iter().any(|f| {
1518
+ matches!(&f.type_ref, TypeRef::Named(q)
1519
+ if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_mixed_content()))
1520
+ }) {
1521
+ let mut flat = s.clone();
1522
+ flat.fields = fields.into_iter().cloned().collect();
1523
+ flat.base_type = None;
1524
+ self.emit_struct(out, &flat, ir, indent);
1525
+ return;
1526
+ }
1527
+ }
1356
1528
  // Reuse inherited XML members. Simple-content IR includes a text
1357
1529
  // placeholder for non-inheriting targets; restrictions can repeat
1358
1530
  // attribute declarations already present on the generated base.
@@ -1436,7 +1608,13 @@ impl CSharpCodegen {
1436
1608
  .map(|f| self.unique_property_name(&f.name, &mut seen_props, Some(&struct_name)))
1437
1609
  .collect();
1438
1610
 
1439
- if ir.has_ordered_content(s) && self.options.emit_xml_attributes {
1611
+ if ir.has_ordered_content(s)
1612
+ && self.options.emit_xml_attributes
1613
+ && s.fields.iter().any(|f| {
1614
+ matches!(&f.type_ref, TypeRef::Named(q)
1615
+ if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_mixed_content()))
1616
+ })
1617
+ {
1440
1618
  self.emit_mixed_struct(out, s, ir, indent, &struct_name, &prop_names);
1441
1619
  return;
1442
1620
  }
@@ -1769,16 +1947,15 @@ impl CSharpCodegen {
1769
1947
  .as_ref()
1770
1948
  .map(|b| matches!(ir.types.get(b), Some(TypeDef::Struct(_))))
1771
1949
  .unwrap_or(false);
1772
- let new_kw = if !self.options.use_records {
1773
- if has_struct_base {
1774
- "override "
1775
- } else {
1776
- "virtual "
1777
- }
1950
+ let new_kw = if self.options.use_records
1951
+ && self.options.record_kind == CSharpRecordKind::Struct
1952
+ && !ir.has_ordered_content(s)
1953
+ {
1954
+ ""
1778
1955
  } else if has_struct_base {
1779
- "new "
1956
+ "override "
1780
1957
  } else {
1781
- ""
1958
+ "virtual "
1782
1959
  };
1783
1960
  writeln!(
1784
1961
  out,
@@ -1788,7 +1965,7 @@ impl CSharpCodegen {
1788
1965
  .unwrap();
1789
1966
  writeln!(out, "{} {{", indent).unwrap();
1790
1967
 
1791
- if !self.options.use_records && has_struct_base {
1968
+ if has_struct_base {
1792
1969
  writeln!(out, "{} foreach (var result in base.Validate(validationContext)) yield return result;", indent).unwrap();
1793
1970
  }
1794
1971
  let mut has_checks = false;
@@ -1872,11 +2049,12 @@ impl CSharpCodegen {
1872
2049
  target: &str,
1873
2050
  indent: &str,
1874
2051
  ) {
2052
+ let member = target.split('.').next().unwrap_or(target);
1875
2053
  if let Some(min_len) = facets.min_length {
1876
2054
  writeln!(
1877
2055
  out,
1878
2056
  "{}if ({}.Length < {}) yield return new ValidationResult(\"{} length must be >= {}\", [nameof({})]);",
1879
- indent, target, min_len, target, min_len, target
2057
+ indent, target, min_len, target, min_len, member
1880
2058
  )
1881
2059
  .unwrap();
1882
2060
  }
@@ -1884,7 +2062,7 @@ impl CSharpCodegen {
1884
2062
  writeln!(
1885
2063
  out,
1886
2064
  "{}if ({}.Length > {}) yield return new ValidationResult(\"{} length must be <= {}\", [nameof({})]);",
1887
- indent, target, max_len, target, max_len, target
2065
+ indent, target, max_len, target, max_len, member
1888
2066
  )
1889
2067
  .unwrap();
1890
2068
  }
@@ -1895,7 +2073,7 @@ impl CSharpCodegen {
1895
2073
  writeln!(
1896
2074
  out,
1897
2075
  "{}if (!Regex.IsMatch({}.ToString() ?? \"\", \"{}\")) yield return new ValidationResult(\"{} does not match pattern {}\", [nameof({})]);",
1898
- indent, target, escaped, target, escaped, target
2076
+ indent, target, escaped, target, escaped, member
1899
2077
  )
1900
2078
  .unwrap();
1901
2079
  }
@@ -1903,7 +2081,7 @@ impl CSharpCodegen {
1903
2081
  writeln!(
1904
2082
  out,
1905
2083
  "{}if ({} < {}) yield return new ValidationResult(\"{} must be >= {}\", [nameof({})]);",
1906
- indent, target, min_inc, target, min_inc, target
2084
+ indent, target, min_inc, target, min_inc, member
1907
2085
  )
1908
2086
  .unwrap();
1909
2087
  }
@@ -1911,7 +2089,7 @@ impl CSharpCodegen {
1911
2089
  writeln!(
1912
2090
  out,
1913
2091
  "{}if ({} > {}) yield return new ValidationResult(\"{} must be <= {}\", [nameof({})]);",
1914
- indent, target, max_inc, target, max_inc, target
2092
+ indent, target, max_inc, target, max_inc, member
1915
2093
  )
1916
2094
  .unwrap();
1917
2095
  }
@@ -1919,7 +2097,7 @@ impl CSharpCodegen {
1919
2097
  writeln!(
1920
2098
  out,
1921
2099
  "{}if ({} <= {}) yield return new ValidationResult(\"{} must be > {}\", [nameof({})]);",
1922
- indent, target, min_exc, target, min_exc, target
2100
+ indent, target, min_exc, target, min_exc, member
1923
2101
  )
1924
2102
  .unwrap();
1925
2103
  }
@@ -1927,7 +2105,7 @@ impl CSharpCodegen {
1927
2105
  writeln!(
1928
2106
  out,
1929
2107
  "{}if ({} >= {}) yield return new ValidationResult(\"{} must be < {}\", [nameof({})]);",
1930
- indent, target, max_exc, target, max_exc, target
2108
+ indent, target, max_exc, target, max_exc, member
1931
2109
  )
1932
2110
  .unwrap();
1933
2111
  }
@@ -1984,8 +2162,7 @@ impl CSharpCodegen {
1984
2162
  if let Some(qname) = named_qname {
1985
2163
  if let Some(TypeDef::Union(u)) = ir.types.get(qname) {
1986
2164
  let choice_name = type_ident(&u.qname);
1987
- for branch in &u.branches {
1988
- let variant_name = choice_variant_name(branch);
2165
+ for (branch, variant_name) in u.branches.iter().zip(choice_variant_names(u)) {
1989
2166
  parts.push(format!(
1990
2167
  "XmlElement(\"{}\", typeof({}.{}), Namespace = {:?})",
1991
2168
  branch.xml_name,
@@ -419,13 +419,7 @@ impl GoCodegen {
419
419
  if body.contains("xml.") {
420
420
  imports.push("\"encoding/xml\"");
421
421
  }
422
- if self.options.emit_xml_tags
423
- && (!ir.content_models.is_empty()
424
- || ir
425
- .types
426
- .values()
427
- .any(|def| matches!(def, TypeDef::Struct(s) if ir.has_ordered_content(s))))
428
- {
422
+ if body.contains("bytes.") {
429
423
  imports.push("\"bytes\"");
430
424
  }
431
425
  if has_fmt || body.contains("fmt.") {
@@ -345,7 +345,7 @@ impl RustCodegen {
345
345
  let chunk_size = self.options.chunk_size.unwrap_or(250);
346
346
 
347
347
  let safe_mod = sanitize_rust_module_name(base_name);
348
- let mod_export = if safe_mod == base_name {
348
+ let mut mod_export = if safe_mod == base_name {
349
349
  format!("pub mod {base_name};\npub use {base_name}::*;\n")
350
350
  } else {
351
351
  format!("#[path = \"{base_name}.rs\"]\npub mod {safe_mod};\npub use {safe_mod}::*;\n")
@@ -353,6 +353,13 @@ impl RustCodegen {
353
353
 
354
354
  if !should_split {
355
355
  let code = self.generate_module(ir);
356
+ if !code.lines().any(|line| line.starts_with("pub ")) {
357
+ mod_export = mod_export
358
+ .lines()
359
+ .filter(|line| !line.starts_with("pub use "))
360
+ .map(|line| format!("{line}\n"))
361
+ .collect();
362
+ }
356
363
  if base_name == "mod" {
357
364
  return vec![("mod.rs".to_string(), code)];
358
365
  }
@@ -365,6 +372,13 @@ impl RustCodegen {
365
372
  let plan = ir.partition_topological_chunks(chunk_size);
366
373
  if plan.chunks.len() <= 1 {
367
374
  let code = self.generate_module(ir);
375
+ if !code.lines().any(|line| line.starts_with("pub ")) {
376
+ mod_export = mod_export
377
+ .lines()
378
+ .filter(|line| !line.starts_with("pub use "))
379
+ .map(|line| format!("{line}\n"))
380
+ .collect();
381
+ }
368
382
  if base_name == "mod" {
369
383
  return vec![("mod.rs".to_string(), code)];
370
384
  }
@@ -79,6 +79,9 @@ static DOTNET_LOCK: Mutex<()> = Mutex::new(());
79
79
  #[test]
80
80
  fn particle_wire_structure_round_trips() {
81
81
  for (schema, root, documents) in [
82
+ (r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:element name="Root"><xs:complexType mixed="true"><xs:sequence><xs:element name="Description" type="xs:string" minOccurs="0"/><xs:element name="Link" type="xs:string"/><xs:element name="Description" type="xs:string" minOccurs="0"/></xs:sequence></xs:complexType></xs:element></xs:schema>"#, "Root", vec!["<Root><Description>first</Description><Link>middle</Link><Description>second</Description></Root>"]),
83
+ (r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:complexType name="VehicleType"><xs:simpleContent><xs:extension base="xs:string"/></xs:simpleContent></xs:complexType><xs:complexType name="OtherType"><xs:simpleContent><xs:extension base="xs:string"/></xs:simpleContent></xs:complexType><xs:element name="Root"><xs:complexType mixed="true"><xs:choice minOccurs="0" maxOccurs="unbounded"><xs:element name="Vehicle" type="VehicleType"/><xs:element name="VehicleType" type="OtherType"/></xs:choice></xs:complexType></xs:element></xs:schema>"#, "Root", vec!["<Root><Vehicle>one</Vehicle><VehicleType>two</VehicleType></Root>"]),
84
+ (r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:simpleType name="Phase"><xs:restriction base="xs:string"><xs:enumeration value="on"/><xs:enumeration value="off"/></xs:restriction></xs:simpleType><xs:complexType name="Base" mixed="true"><xs:choice minOccurs="0" maxOccurs="unbounded"><xs:element name="ItemA" type="xs:string"/><xs:element name="item_a" type="xs:string"/></xs:choice><xs:attribute name="code" type="xs:string"/><xs:attribute name="enabled" type="xs:boolean" use="required"/><xs:attribute name="phase" type="Phase" use="required"/></xs:complexType><xs:complexType name="Derived" mixed="true"><xs:complexContent mixed="true"><xs:extension base="Base"><xs:attribute name="extra" type="xs:string"/></xs:extension></xs:complexContent></xs:complexType><xs:element name="Root" type="Derived"/></xs:schema>"#, "Root", vec![r#"<Root code="base" extra="leaf" enabled="true" phase="on">before<ItemA>A</ItemA>between<item_a>B</item_a>after</Root>"#]),
82
85
  (r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:element name="Query"><xs:complexType><xs:sequence><xs:element name="Equals" type="xs:string"/><xs:element name="EqualsValue" type="xs:string"/><xs:element name="GetHashCode" type="xs:string"/><xs:element name="ToString" type="xs:string"/></xs:sequence></xs:complexType></xs:element></xs:schema>"#, "Query", vec!["<Query><Equals>a</Equals><EqualsValue>b</EqualsValue><GetHashCode>c</GetHashCode><ToString>d</ToString></Query>"]),
83
86
  (include_str!("../../../research/fixtures/nested_sequence_choice.xsd"), "Root", vec!["<Root><First>A</First><Second>B</Second></Root>", "<Root><Alternative>C</Alternative></Root>"]),
84
87
  (include_str!("../../../research/fixtures/repeated_sequence.xsd"), "Root", vec!["<Root><First>A1</First><Second>B1</Second><First>A2</First><Second>B2</Second></Root>"]),
@@ -98,7 +101,7 @@ using System.Xml.Serialization;
98
101
  using System.Xml.Linq;
99
102
  using ParticleModels;
100
103
  var serializer = new XmlSerializer(typeof({root}));
101
- string Structure(XElement e) => e.Name.ToString()+"["+(e.HasElements ? string.Concat(e.Elements().Select(Structure)) : e.Value)+"]";
104
+ string Structure(XElement e) => e.Name.ToString()+"("+string.Join(",",e.Attributes().Where(a=>!a.IsNamespaceDeclaration).OrderBy(a=>a.Name.ToString()).Select(a=>a.Name+"="+a.Value))+")["+string.Concat(e.Nodes().Select(n=>n is XElement child ? Structure(child) : n is XText text ? text.Value : ""))+"]";
102
105
  foreach (var document in new [] {{ {documents} }}) {{
103
106
  var value = serializer.Deserialize(new StringReader(document))!;
104
107
  var writer = new StringWriter(); serializer.Serialize(writer, value);
@@ -1449,3 +1449,32 @@ func TestIntegers(t *testing.T) {
1449
1449
  String::from_utf8_lossy(&result.stderr)
1450
1450
  );
1451
1451
  }
1452
+
1453
+ #[test]
1454
+ fn imported_ordered_types_do_not_add_unused_bytes_import() {
1455
+ let mut ir=XsdParser::new().parse_str(r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:complexType name="Foreign" mixed="true"><xs:sequence><xs:element name="child" type="xs:string"/></xs:sequence></xs:complexType><xs:complexType name="Local"><xs:attribute name="id" type="xs:string"/></xs:complexType></xs:schema>"#).unwrap();
1456
+ for name in ir.types.keys() {
1457
+ if name.local != "Local" {
1458
+ ir.external_types.insert(name.clone(), "shared".into());
1459
+ }
1460
+ }
1461
+ ir.content_models.clear();
1462
+ let dir = tempdir().unwrap();
1463
+ fs::write(dir.path().join("go.mod"), "module importcheck\n\ngo 1.22\n").unwrap();
1464
+ fs::write(
1465
+ dir.path().join("models.go"),
1466
+ GoCodegen::new(GoOptions::default()).generate_module(&ir),
1467
+ )
1468
+ .unwrap();
1469
+ let result = Command::new("go")
1470
+ .args(["test", "./..."])
1471
+ .current_dir(dir.path())
1472
+ .output()
1473
+ .unwrap();
1474
+ assert!(
1475
+ result.status.success(),
1476
+ "{}{}",
1477
+ String::from_utf8_lossy(&result.stdout),
1478
+ String::from_utf8_lossy(&result.stderr)
1479
+ );
1480
+ }
@@ -168,3 +168,95 @@ var json=System.Text.Json.JsonSerializer.Serialize(value);if(!json.Contains("att
168
168
  );
169
169
  }
170
170
  }
171
+
172
+ #[test]
173
+ fn derived_validators_preserve_base_constraints() {
174
+ use polyxml::ir::{QName, RestrictionFacets, TypeDef};
175
+ let mut ir=XsdParser::new().parse_str(r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:complexType name="Base"><xs:attribute name="code" type="xs:string"/></xs:complexType><xs:complexType name="Derived"><xs:complexContent><xs:extension base="Base"><xs:attribute name="extra" type="xs:string"/></xs:extension></xs:complexContent></xs:complexType></xs:schema>"#).unwrap();
176
+ let TypeDef::Struct(base) = ir.types.get_mut(&QName::local("Base")).unwrap() else {
177
+ panic!()
178
+ };
179
+ base.fields[0].facets = Some(RestrictionFacets {
180
+ min_length: Some(3),
181
+ ..Default::default()
182
+ });
183
+ for use_records in [true, false] {
184
+ let temp = tempdir().unwrap();
185
+ fs::write(temp.path().join("App.csproj"),r#"<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable><WarningsAsErrors>CS0108;CS0109;CS0114</WarningsAsErrors></PropertyGroup></Project>"#).unwrap();
186
+ fs::write(
187
+ temp.path().join("Models.cs"),
188
+ CSharpCodegen::new(CSharpOptions {
189
+ namespace: "Models".into(),
190
+ use_records,
191
+ ..Default::default()
192
+ })
193
+ .generate_module(&ir),
194
+ )
195
+ .unwrap();
196
+ fs::write(temp.path().join("Program.cs"),r#"using Models;using System.ComponentModel.DataAnnotations;
197
+ var value=new Derived {Code="x",Extra="leaf"};
198
+ if(!value.Validate(new ValidationContext(value)).Any())throw new Exception("base constraint lost");
199
+ if(!((Base)value).Validate(new ValidationContext(value)).Any())throw new Exception("base dispatch lost");
200
+ var results=new List<ValidationResult>();if(Validator.TryValidateObject(value,new ValidationContext(value),results,true))throw new Exception("interface validation lost");
201
+ var valid=new Derived {Code="valid",Extra="leaf"};if(valid.Validate(new ValidationContext(valid)).Any())throw new Exception("valid value rejected");
202
+ "#).unwrap();
203
+ let output = Command::new("dotnet")
204
+ .args(["run", "--disable-build-servers"])
205
+ .current_dir(temp.path())
206
+ .output()
207
+ .unwrap();
208
+ assert!(
209
+ output.status.success(),
210
+ "records={use_records}: {}{}",
211
+ String::from_utf8_lossy(&output.stdout),
212
+ String::from_utf8_lossy(&output.stderr)
213
+ );
214
+ }
215
+ }
216
+
217
+ #[test]
218
+ fn lexical_dependencies_compile_and_round_trip_without_implicit_imports() {
219
+ let ir=XsdParser::new().parse_str(r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
220
+ <xs:simpleType name="Code"><xs:restriction base="xs:string"><xs:enumeration value="A"/><xs:enumeration value="B"/><xs:enumeration value="LONG"/></xs:restriction></xs:simpleType>
221
+ <xs:simpleType name="ShortCode"><xs:restriction base="Code"><xs:maxLength value="1"/></xs:restriction></xs:simpleType><xs:simpleType name="Inner"><xs:union memberTypes="Code xs:int"/></xs:simpleType>
222
+ <xs:simpleType name="Outer"><xs:union memberTypes="Inner xs:token xs:anyURI"/></xs:simpleType>
223
+ <xs:simpleType name="Numbers"><xs:list itemType="xs:int"/></xs:simpleType>
224
+ <xs:complexType name="Payload"><xs:sequence><xs:element name="Flag" type="xs:boolean" default="true"/><xs:element name="Count" type="xs:int" fixed="7"/><xs:element name="Huge" type="xs:integer"/><xs:element name="Values" type="Numbers"/><xs:element name="Reason" type="Outer"/></xs:sequence><xs:attribute name="mode" type="Code" use="required" fixed="A"/></xs:complexType>
225
+ </xs:schema>"#).unwrap();
226
+ for use_records in [true, false] {
227
+ let temp = tempdir().unwrap();
228
+ fs::write(temp.path().join("App.csproj"),r#"<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0</TargetFramework><Nullable>enable</Nullable><TreatWarningsAsErrors>true</TreatWarningsAsErrors></PropertyGroup></Project>"#).unwrap();
229
+ fs::write(
230
+ temp.path().join("Models.cs"),
231
+ CSharpCodegen::new(CSharpOptions {
232
+ namespace: "Models".into(),
233
+ use_records,
234
+ ..Default::default()
235
+ })
236
+ .generate_module(&ir),
237
+ )
238
+ .unwrap();
239
+ fs::write(temp.path().join("Program.cs"),r#"using System;using System.IO;using System.Xml.Serialization;using System.Xml.Linq;using Models;using System.ComponentModel.DataAnnotations;
240
+ var invalidCode=new ShortCode((Code)2);if(!invalidCode.Validate(new ValidationContext(invalidCode)).GetEnumerator().MoveNext())throw new Exception("enum lexical facet ignored");
241
+ var validCode=new ShortCode(Code.A);if(validCode.Validate(new ValidationContext(validCode)).GetEnumerator().MoveNext())throw new Exception("valid enum rejected");
242
+ foreach(var lexical in new[]{"A","42","fallback"}) {
243
+ var serializer=new XmlSerializer(typeof(Payload));var value=(Payload)serializer.Deserialize(new StringReader("<Payload mode='A'><Flag/><Count>7</Count><Huge>123456789012345678901234567890</Huge><Values>1 2</Values><Reason>"+lexical+"</Reason></Payload>"))!;
244
+ if(!value.Flag||value.Count!=7||value.Huge!="123456789012345678901234567890"||value.Values.Value.Count!=2||value.Values.Value[1]!=2||value.Reason.ToXmlString()!=lexical)throw new Exception("lexical input lost");
245
+ var writer=new StringWriter();serializer.Serialize(writer,value);var root=XDocument.Parse(writer.ToString()).Root!;
246
+ if(root.Attribute("mode")!.Value!="A"||root.Element("Flag")!.Value!="true"||root.Element("Count")!.Value!="7"||root.Element("Values")!.Value!="1 2"||root.Element("Reason")!.Value!=lexical)throw new Exception(writer.ToString());
247
+ value.Huge=null!;try{serializer.Serialize(new StringWriter(),value);throw new Exception("missing required integer emitted");}catch(InvalidOperationException){}
248
+ }
249
+ "#).unwrap();
250
+ let output = Command::new("dotnet")
251
+ .args(["run", "--disable-build-servers"])
252
+ .current_dir(temp.path())
253
+ .output()
254
+ .unwrap();
255
+ assert!(
256
+ output.status.success(),
257
+ "records={use_records}: {}{}",
258
+ String::from_utf8_lossy(&output.stdout),
259
+ String::from_utf8_lossy(&output.stderr)
260
+ );
261
+ }
262
+ }
@@ -1423,3 +1423,41 @@ fn main() {
1423
1423
  );
1424
1424
  }
1425
1425
  }
1426
+
1427
+ #[test]
1428
+ fn empty_modules_compile_without_unused_reexports() {
1429
+ let core = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
1430
+ for split_units in [false, true] {
1431
+ let dir = tempfile::tempdir().unwrap();
1432
+ std::fs::create_dir_all(dir.path().join("src/models")).unwrap();
1433
+ let ir = SchemaIR::new();
1434
+ for (name, code) in RustCodegen::new(RustOptions {
1435
+ split_units: Some(split_units),
1436
+ ..Default::default()
1437
+ })
1438
+ .generate_files(&ir, "models")
1439
+ {
1440
+ std::fs::write(dir.path().join("src/models").join(name), code).unwrap();
1441
+ }
1442
+ std::fs::write(
1443
+ dir.path().join("src/lib.rs"),
1444
+ "#![deny(unused_imports)]\npub mod models;\n",
1445
+ )
1446
+ .unwrap();
1447
+ std::fs::write(dir.path().join("Cargo.toml"),format!("[package]\nname=\"empty-module-test\"\nversion=\"0.0.0\"\nedition=\"2021\"\n[dependencies]\npolyxml={{path={core:?}}}\nquick-xml=\"0.42\"\nserde={{version=\"1\",features=[\"derive\"]}}\nserde_json=\"1\"\n")).unwrap();
1448
+ let result = std::process::Command::new("cargo")
1449
+ .args(["check", "--quiet", "--offline"])
1450
+ .env(
1451
+ "CARGO_TARGET_DIR",
1452
+ core.join("../../target/binary-lexical-tests"),
1453
+ )
1454
+ .current_dir(dir.path())
1455
+ .output()
1456
+ .unwrap();
1457
+ assert!(
1458
+ result.status.success(),
1459
+ "split={split_units}: {}",
1460
+ String::from_utf8_lossy(&result.stderr)
1461
+ );
1462
+ }
1463
+ }
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "polyxml"
7
- version = "0.34.4"
7
+ version = "0.34.5"
8
8
  description = "High-performance, polyglot XML data-binding engine built in Rust"
9
9
  authors = [
10
10
  { name = "Bailey Nguyen", email = "bailey.tan.nguyen@gmail.com" }
File without changes