polyxml 0.26.0__tar.gz → 0.28.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 (69) hide show
  1. {polyxml-0.26.0 → polyxml-0.28.0}/Cargo.lock +7 -6
  2. {polyxml-0.26.0 → polyxml-0.28.0}/Cargo.toml +1 -1
  3. {polyxml-0.26.0 → polyxml-0.28.0}/PKG-INFO +1 -1
  4. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/Cargo.toml +1 -0
  5. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +197 -6
  6. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/go/mod.rs +189 -13
  7. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/java/codec.rs +33 -19
  8. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/java/mod.rs +12 -2
  9. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/python/mod.rs +21 -0
  10. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/rust/mod.rs +140 -4
  11. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +63 -0
  12. polyxml-0.28.0/crates/polyxml-core/src/converters.rs +279 -0
  13. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/ir/mod.rs +12 -0
  14. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/schema.rs +36 -2
  15. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/schema_parser/mod.rs +182 -3
  16. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/serializer.rs +23 -1
  17. polyxml-0.28.0/crates/polyxml-core/tests/fixtures/lexical_union.xsd +38 -0
  18. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_core.rs +49 -0
  19. polyxml-0.28.0/crates/polyxml-core/tests/test_lexical_union_codegen.rs +107 -0
  20. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_pattern_codegen.rs +53 -0
  21. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_schema_ir.rs +32 -0
  22. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-python/src/lib.rs +60 -3
  23. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-python/tests/test_polyxml.py +36 -0
  24. {polyxml-0.26.0 → polyxml-0.28.0}/pyproject.toml +1 -1
  25. polyxml-0.26.0/crates/polyxml-core/src/converters.rs +0 -80
  26. {polyxml-0.26.0 → polyxml-0.28.0}/README.md +0 -0
  27. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/README.md +0 -0
  28. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  29. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  30. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  31. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  32. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  33. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  34. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  35. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/error.rs +0 -0
  36. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  37. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/json.rs +0 -0
  38. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/lib.rs +0 -0
  39. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/parser.rs +0 -0
  40. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/transcoder.rs +0 -0
  41. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/src/value.rs +0 -0
  42. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  43. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  44. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  45. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
  46. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
  47. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  48. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_json.rs +0 -0
  49. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
  50. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  51. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
  52. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  53. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_security.rs +0 -0
  54. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  55. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  56. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  57. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  58. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-python/Cargo.toml +0 -0
  59. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-python/README.md +0 -0
  60. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  61. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
  62. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  63. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-python/tests/test_json.py +0 -0
  64. {polyxml-0.26.0 → polyxml-0.28.0}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  65. {polyxml-0.26.0 → polyxml-0.28.0}/python/polyxml/AGENT_GUIDE.md +0 -0
  66. {polyxml-0.26.0 → polyxml-0.28.0}/python/polyxml/__init__.py +0 -0
  67. {polyxml-0.26.0 → polyxml-0.28.0}/python/polyxml/compat/__init__.py +0 -0
  68. {polyxml-0.26.0 → polyxml-0.28.0}/python/polyxml/compat/xsdata.py +0 -0
  69. {polyxml-0.26.0 → polyxml-0.28.0}/python/polyxml/py.typed +0 -0
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.26.0"
611
+ version = "0.28.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.26.0"
632
+ version = "0.28.0"
632
633
  dependencies = [
633
634
  "polyxml",
634
635
  ]
635
636
 
636
637
  [[package]]
637
638
  name = "polyxml-cli"
638
- version = "0.26.0"
639
+ version = "0.28.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.26.0"
653
+ version = "0.28.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.26.0"
663
+ version = "0.28.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.26.0"
673
+ version = "0.28.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.26.0"
6
+ version = "0.28.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.26.0
3
+ Version: 0.28.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"] }
@@ -313,7 +313,7 @@ impl CSharpCodegen {
313
313
  // Emit Unions (Choices)
314
314
  for def in ir.types.values() {
315
315
  if let TypeDef::Union(u) = def {
316
- self.emit_union(&mut out, u, indent);
316
+ self.emit_union(&mut out, u, ir, indent);
317
317
  }
318
318
  }
319
319
 
@@ -577,14 +577,14 @@ impl CSharpCodegen {
577
577
  }
578
578
  }
579
579
 
580
- fn emit_union(&self, out: &mut String, u: &UnionDef, indent: &str) {
580
+ fn emit_union(&self, out: &mut String, u: &UnionDef, ir: &SchemaIR, indent: &str) {
581
581
  let choice_name = type_ident(&u.qname);
582
582
  if let Some(ref doc) = u.documentation {
583
583
  self.emit_docstring(out, doc, indent);
584
584
  }
585
585
 
586
- // XmlInclude attributes for polymorphism
587
- if self.options.emit_xml_attributes {
586
+ // XmlInclude attributes for element-choice polymorphism.
587
+ if self.options.emit_xml_attributes && !u.is_lexical() {
588
588
  for branch in &u.branches {
589
589
  let variant_name = to_csharp_type_name(&branch.variant_name);
590
590
  writeln!(
@@ -632,10 +632,10 @@ impl CSharpCodegen {
632
632
  }
633
633
 
634
634
  let mut branch_attrs = Vec::new();
635
- if self.options.emit_xml_attributes {
635
+ if self.options.emit_xml_attributes && !u.is_lexical() {
636
636
  branch_attrs.push(format!("XmlElement(\"{}\")", branch.xml_name));
637
637
  }
638
- if self.options.emit_json_attributes {
638
+ if self.options.emit_json_attributes && !u.is_lexical() {
639
639
  branch_attrs.push(format!("JsonPropertyName(\"{}\")", branch.xml_name));
640
640
  }
641
641
  let xml_attr = if branch_attrs.is_empty() {
@@ -679,9 +679,129 @@ impl CSharpCodegen {
679
679
  writeln!(out).unwrap();
680
680
  }
681
681
 
682
+ if u.is_lexical() {
683
+ self.emit_lexical_union_helpers(out, u, ir, indent);
684
+ }
682
685
  writeln!(out, "{}}}\n", indent).unwrap();
683
686
  }
684
687
 
688
+ fn emit_lexical_union_helpers(
689
+ &self,
690
+ out: &mut String,
691
+ u: &UnionDef,
692
+ ir: &SchemaIR,
693
+ indent: &str,
694
+ ) {
695
+ let name = type_ident(&u.qname);
696
+ let _ = writeln!(
697
+ out,
698
+ "{} public static {} Parse(string text)",
699
+ indent, name
700
+ );
701
+ let _ = writeln!(out, "{} {{", indent);
702
+ let _ = writeln!(out, "{} var value = text.Trim();", indent);
703
+ for (idx, branch) in u.branches.iter().enumerate() {
704
+ let variant = to_csharp_type_name(&branch.variant_name);
705
+ let mapped = self.context.map_type_ref(&branch.type_ref);
706
+ if let TypeRef::Named(qname) = &branch.type_ref {
707
+ if let Some(TypeDef::Enum(def)) = ir.types.get(qname) {
708
+ for item in &def.variants {
709
+ let item_name = to_csharp_variant_name(&item.name);
710
+ let _ = writeln!(
711
+ out,
712
+ "{} if (value == {:?}) return new {}({}.{});",
713
+ indent, item.value, variant, mapped, item_name
714
+ );
715
+ }
716
+ continue;
717
+ }
718
+ }
719
+ let simple = match &branch.type_ref {
720
+ TypeRef::Named(qname) => ir.types.get(qname).and_then(|def| match def {
721
+ TypeDef::Simple(s) => Some(s.as_ref()),
722
+ _ => None,
723
+ }),
724
+ _ => None,
725
+ };
726
+ let base = super::primitive_base(&branch.type_ref, ir);
727
+ let base_name = self.context.map_type_ref(base);
728
+ let wrap = if simple.is_some() {
729
+ format!("new {}(candidate{})", mapped, idx)
730
+ } else {
731
+ format!("candidate{}", idx)
732
+ };
733
+ if base_name == "string" {
734
+ if let Some(simple) = simple.filter(|s| !s.facets.patterns.is_empty()) {
735
+ let checks = simple
736
+ .facets
737
+ .patterns
738
+ .iter()
739
+ .map(|pattern| {
740
+ format!("Regex.IsMatch(value, {:?})", format!(r"\A(?:{pattern})\z"))
741
+ })
742
+ .collect::<Vec<_>>()
743
+ .join(" && ");
744
+ let _ = writeln!(
745
+ out,
746
+ "{} if ({}) {{ var candidate{} = value; return new {}({}); }}",
747
+ indent, checks, idx, variant, wrap
748
+ );
749
+ } else {
750
+ let _ = writeln!(
751
+ out,
752
+ "{} {{ var candidate{} = value; return new {}({}); }}",
753
+ indent, idx, variant, wrap
754
+ );
755
+ }
756
+ } else if base_name == "bool" {
757
+ let _ = writeln!(out, "{} if (value == \"true\" || value == \"1\") {{ var candidate{} = true; return new {}({}); }}", indent, idx, variant, wrap);
758
+ let _ = writeln!(out, "{} if (value == \"false\" || value == \"0\") {{ var candidate{} = false; return new {}({}); }}", indent, idx, variant, wrap);
759
+ } else if base_name == "DateOnly" {
760
+ 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);
761
+ } else {
762
+ let _ = writeln!(
763
+ out,
764
+ "{} if ({}.TryParse(value, out var candidate{})) return new {}({});",
765
+ indent, base_name, idx, variant, wrap
766
+ );
767
+ }
768
+ }
769
+ let _ = writeln!(
770
+ out,
771
+ "{} throw new FormatException(\"No {} union member accepts the value\");",
772
+ indent, name
773
+ );
774
+ let _ = writeln!(out, "{} }}", indent);
775
+ let _ = writeln!(
776
+ out,
777
+ "{} public string ToXmlString() => this switch",
778
+ indent
779
+ );
780
+ let _ = writeln!(out, "{} {{", indent);
781
+ for branch in &u.branches {
782
+ let variant = to_csharp_type_name(&branch.variant_name);
783
+ let value = if matches!(
784
+ super::primitive_base(&branch.type_ref, ir),
785
+ TypeRef::Primitive(PrimitiveType::Date)
786
+ ) {
787
+ "item.Value.ToString(\"yyyy-MM-dd\", System.Globalization.CultureInfo.InvariantCulture)".to_string()
788
+ } else {
789
+ match &branch.type_ref {
790
+ TypeRef::Named(qname) if matches!(ir.types.get(qname), Some(TypeDef::Enum(_))) => "item.Value.ToXmlValue()".to_string(),
791
+ TypeRef::Named(qname) if matches!(ir.types.get(qname), Some(TypeDef::Simple(_))) => "Convert.ToString(item.Value.Value, System.Globalization.CultureInfo.InvariantCulture) ?? \"\"".to_string(),
792
+ _ => "Convert.ToString(item.Value, System.Globalization.CultureInfo.InvariantCulture) ?? \"\"".to_string(),
793
+ }
794
+ };
795
+ let _ = writeln!(out, "{} {} item => {},", indent, variant, value);
796
+ }
797
+ let _ = writeln!(
798
+ out,
799
+ "{} _ => throw new InvalidOperationException()",
800
+ indent
801
+ );
802
+ let _ = writeln!(out, "{} }};", indent);
803
+ }
804
+
685
805
  fn emit_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR, indent: &str) {
686
806
  let struct_name = type_ident(&s.qname);
687
807
  if let Some(ref doc) = s.documentation {
@@ -732,6 +852,55 @@ impl CSharpCodegen {
732
852
  .map(|f| self.unique_property_name(&f.name, &mut seen_props, Some(&struct_name)))
733
853
  .collect();
734
854
 
855
+ let has_lexical_union = self.options.emit_xml_attributes && s.fields.iter().any(|f| {
856
+ matches!(&f.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical()))
857
+ });
858
+
859
+ if self.options.use_records && has_lexical_union {
860
+ writeln!(
861
+ out,
862
+ "{}public record {}{}\n{}{{",
863
+ indent, struct_name, implements_str, indent
864
+ )
865
+ .unwrap();
866
+ for (f, name) in s.fields.iter().zip(&prop_names) {
867
+ let ty = self.map_field_type(f, ir);
868
+ if let TypeRef::Named(q) = &f.type_ref {
869
+ if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical()) {
870
+ let _ = writeln!(out, "{} [XmlIgnore]", indent);
871
+ if self.options.emit_json_attributes {
872
+ let _ =
873
+ writeln!(out, "{} [JsonPropertyName({:?})]", indent, f.xml_name);
874
+ }
875
+ let _ = writeln!(
876
+ out,
877
+ "{} public {} {} {{ get; set; }} = default!;",
878
+ indent, ty, name
879
+ );
880
+ let _ = writeln!(out, "{} [XmlElement({:?})]", indent, f.xml_name);
881
+ if self.options.emit_json_attributes {
882
+ let _ = writeln!(out, "{} [JsonIgnore]", indent);
883
+ }
884
+ let _ = writeln!(out, "{} public string? {}Xml {{ get => {}?.ToXmlString(); set => {} = value is null ? default! : {}.Parse(value); }}", indent, name, name, name, type_ident(q));
885
+ continue;
886
+ }
887
+ }
888
+ let attrs = self
889
+ .build_field_attributes(f, ir)
890
+ .replace("[property: ", "[");
891
+ let _ = writeln!(
892
+ out,
893
+ "{} {}public {} {} {{ get; set; }} = default!;",
894
+ indent, attrs, ty, name
895
+ );
896
+ }
897
+ if self.options.emit_validation {
898
+ self.emit_struct_validator(out, s, &prop_names, indent);
899
+ }
900
+ writeln!(out, "{}}}\n", indent).unwrap();
901
+ return;
902
+ }
903
+
735
904
  if !self.options.use_records {
736
905
  writeln!(
737
906
  out,
@@ -742,6 +911,28 @@ impl CSharpCodegen {
742
911
  writeln!(out, "{} public {}() {{ }}", indent, struct_name).unwrap();
743
912
  for (f, name) in s.fields.iter().zip(&prop_names) {
744
913
  let ty = self.map_field_type(f, ir);
914
+ if let TypeRef::Named(q) = &f.type_ref {
915
+ if self.options.emit_xml_attributes
916
+ && matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical())
917
+ {
918
+ let _ = writeln!(out, "{} [XmlIgnore]", indent);
919
+ if self.options.emit_json_attributes {
920
+ let _ =
921
+ writeln!(out, "{} [JsonPropertyName({:?})]", indent, f.xml_name);
922
+ }
923
+ let _ = writeln!(
924
+ out,
925
+ "{} public {} {} {{ get; set; }} = default!;",
926
+ indent, ty, name
927
+ );
928
+ let _ = writeln!(out, "{} [XmlElement({:?})]", indent, f.xml_name);
929
+ if self.options.emit_json_attributes {
930
+ let _ = writeln!(out, "{} [JsonIgnore]", indent);
931
+ }
932
+ let _ = writeln!(out, "{} public string? {}Xml {{ get => {}?.ToXmlString(); set => {} = value is null ? default! : {}.Parse(value); }}", indent, name, name, name, type_ident(q));
933
+ continue;
934
+ }
935
+ }
745
936
  let attrs = self
746
937
  .build_field_attributes(f, ir)
747
938
  .replace("[property: ", "[");
@@ -255,15 +255,22 @@ impl GoCodegen {
255
255
  set_type_name_map(build_type_name_map(ir, to_go_type_name));
256
256
  let mut body = String::new();
257
257
  let mut has_time = false;
258
- let has_patterns = self.options.validate_facets
259
- && ir.types.values().any(|t| match t {
260
- TypeDef::Simple(s) => !s.facets.patterns.is_empty(),
261
- TypeDef::Struct(s) => s
262
- .fields
263
- .iter()
264
- .any(|f| f.facets.as_ref().is_some_and(|f| !f.patterns.is_empty())),
265
- _ => false,
266
- });
258
+ let lexical_patterns = ir.types.values().any(|def| match def {
259
+ TypeDef::Union(u) if u.is_lexical() => u.branches.iter().any(|branch| {
260
+ matches!(&branch.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Simple(s)) if !s.facets.patterns.is_empty()))
261
+ }),
262
+ _ => false,
263
+ });
264
+ let has_patterns = lexical_patterns
265
+ || (self.options.validate_facets
266
+ && ir.types.values().any(|t| match t {
267
+ TypeDef::Simple(s) => !s.facets.patterns.is_empty(),
268
+ TypeDef::Struct(s) => s
269
+ .fields
270
+ .iter()
271
+ .any(|f| f.facets.as_ref().is_some_and(|f| !f.patterns.is_empty())),
272
+ _ => false,
273
+ }));
267
274
  let mut has_fmt = has_patterns;
268
275
  let mut has_io = false;
269
276
  let mut has_xml = self.options.emit_xml_tags;
@@ -288,12 +295,22 @@ impl GoCodegen {
288
295
  has_fmt = true;
289
296
  }
290
297
  }
291
- TypeDef::Union(_) => {
292
- if self.options.validate_choice_exclusivity {
298
+ TypeDef::Union(u) => {
299
+ if u.branches
300
+ .iter()
301
+ .any(|branch| self.references_time(&branch.type_ref))
302
+ {
303
+ has_time = true;
304
+ }
305
+ if self.options.validate_choice_exclusivity && !u.is_lexical() {
293
306
  has_fmt = true;
294
307
  has_io = true;
295
308
  has_xml = true;
296
309
  }
310
+ if u.is_lexical() {
311
+ has_fmt = true;
312
+ has_xml = true;
313
+ }
297
314
  }
298
315
  TypeDef::Simple(st) => {
299
316
  if self.references_time(&st.base_type) {
@@ -344,6 +361,14 @@ impl GoCodegen {
344
361
  if has_patterns {
345
362
  imports.push("\"regexp\"");
346
363
  }
364
+ if ir
365
+ .types
366
+ .values()
367
+ .any(|def| matches!(def, TypeDef::Union(u) if u.is_lexical()))
368
+ {
369
+ imports.push("\"strconv\"");
370
+ imports.push("\"strings\"");
371
+ }
347
372
  if has_time {
348
373
  imports.push("\"time\"");
349
374
  }
@@ -399,7 +424,7 @@ impl GoCodegen {
399
424
  // Emit choices (unions)
400
425
  for type_def in ir.types.values() {
401
426
  if let TypeDef::Union(u) = type_def {
402
- self.emit_union(out, u);
427
+ self.emit_union(out, u, ir);
403
428
  }
404
429
  }
405
430
 
@@ -509,7 +534,11 @@ impl GoCodegen {
509
534
  writeln!(out, "}}\n").unwrap();
510
535
  }
511
536
 
512
- fn emit_union(&self, out: &mut String, u: &UnionDef) {
537
+ fn emit_union(&self, out: &mut String, u: &UnionDef, ir: &SchemaIR) {
538
+ if u.is_lexical() {
539
+ self.emit_lexical_union(out, u, ir);
540
+ return;
541
+ }
513
542
  let choice_name = type_ident(&u.qname);
514
543
  if let Some(ref doc) = u.documentation {
515
544
  for line in doc.lines() {
@@ -689,6 +718,153 @@ impl GoCodegen {
689
718
  }
690
719
  }
691
720
 
721
+ fn emit_lexical_union(&self, out: &mut String, u: &UnionDef, ir: &SchemaIR) {
722
+ let name = type_ident(&u.qname);
723
+ let mut seen = HashSet::new();
724
+ let fields = u
725
+ .branches
726
+ .iter()
727
+ .map(|b| self.unique_field_name(&b.variant_name, &mut seen))
728
+ .collect::<Vec<_>>();
729
+ let _ = writeln!(out, "type {} struct {{", name);
730
+ for (branch, field) in u.branches.iter().zip(&fields) {
731
+ let _ = writeln!(
732
+ out,
733
+ " {} *{} `json:{:?}`",
734
+ field,
735
+ self.context.map_type_ref(&branch.type_ref),
736
+ format!("{},omitempty", field)
737
+ );
738
+ }
739
+ out.push_str("}\n\n");
740
+ let _ = writeln!(out, "func (c {}) Selected() string {{", name);
741
+ for field in &fields {
742
+ let _ = writeln!(out, " if c.{} != nil {{ return {:?} }}", field, field);
743
+ }
744
+ out.push_str(" return \"\"\n}\n\n");
745
+ let _ = writeln!(
746
+ out,
747
+ "func (c *{}) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {{",
748
+ name
749
+ );
750
+ out.push_str(" var raw string\n if err := d.DecodeElement(&raw, &start); err != nil { return err }\n value := strings.TrimSpace(raw)\n *c = ");
751
+ let _ = writeln!(out, "{}{{}}", name);
752
+ for (branch, field) in u.branches.iter().zip(&fields) {
753
+ let mapped = self.context.map_type_ref(&branch.type_ref);
754
+ if let TypeRef::Named(qname) = &branch.type_ref {
755
+ if let Some(TypeDef::Enum(def)) = ir.types.get(qname) {
756
+ let comparisons = def
757
+ .variants
758
+ .iter()
759
+ .map(|v| format!("value == {:?}", v.value))
760
+ .collect::<Vec<_>>()
761
+ .join(" || ");
762
+ let _ = writeln!(
763
+ out,
764
+ " if {} {{ v := {}(value); c.{} = &v; return nil }}",
765
+ comparisons, mapped, field
766
+ );
767
+ continue;
768
+ }
769
+ }
770
+ let base = super::primitive_base(&branch.type_ref, ir);
771
+ let numeric = match base {
772
+ TypeRef::Primitive(
773
+ PrimitiveType::Int
774
+ | PrimitiveType::Integer
775
+ | PrimitiveType::Long
776
+ | PrimitiveType::Short
777
+ | PrimitiveType::Byte,
778
+ ) => Some("signed"),
779
+ TypeRef::Primitive(
780
+ PrimitiveType::UnsignedInt
781
+ | PrimitiveType::UnsignedLong
782
+ | PrimitiveType::UnsignedShort
783
+ | PrimitiveType::UnsignedByte
784
+ | PrimitiveType::PositiveInteger
785
+ | PrimitiveType::NonNegativeInteger,
786
+ ) => Some("unsigned"),
787
+ TypeRef::Primitive(
788
+ PrimitiveType::Float | PrimitiveType::Double | PrimitiveType::Decimal,
789
+ ) => Some("float"),
790
+ _ => None,
791
+ };
792
+ if matches!(base, TypeRef::Primitive(PrimitiveType::Date)) {
793
+ let _ = writeln!(out, " if parsed, err := time.Parse(\"2006-01-02\", value); err == nil {{ v := {}(parsed); c.{} = &v; return nil }}", mapped, field);
794
+ } else if let Some(kind) = numeric {
795
+ let parse = match kind {
796
+ "signed" => "strconv.ParseInt(value, 10, 64)",
797
+ "unsigned" => "strconv.ParseUint(value, 10, 64)",
798
+ _ => "strconv.ParseFloat(value, 64)",
799
+ };
800
+ let _ = writeln!(out, " if parsed, err := {}; err == nil {{ v := {}(parsed); c.{} = &v; return nil }}", parse, mapped, field);
801
+ } else if matches!(base, TypeRef::Primitive(PrimitiveType::Boolean)) {
802
+ let _ = writeln!(out, " if parsed, err := strconv.ParseBool(value); err == nil {{ v := {}(parsed); c.{} = &v; return nil }}", mapped, field);
803
+ } else {
804
+ let simple = match &branch.type_ref {
805
+ TypeRef::Named(qname) => ir.types.get(qname).and_then(|def| match def {
806
+ TypeDef::Simple(s) => Some(s.as_ref()),
807
+ _ => None,
808
+ }),
809
+ _ => None,
810
+ };
811
+ if let Some(simple) = simple.filter(|s| !s.facets.patterns.is_empty()) {
812
+ let checks = simple
813
+ .facets
814
+ .patterns
815
+ .iter()
816
+ .map(|pattern| {
817
+ format!(
818
+ "regexp.MustCompile({:?}).MatchString(value)",
819
+ format!("^(?:{pattern})$")
820
+ )
821
+ })
822
+ .collect::<Vec<_>>()
823
+ .join(" && ");
824
+ let _ = writeln!(
825
+ out,
826
+ " if {} {{ v := {}(value); c.{} = &v; return nil }}",
827
+ checks, mapped, field
828
+ );
829
+ } else {
830
+ let _ = writeln!(
831
+ out,
832
+ " {{ v := {}(value); c.{} = &v; return nil }}",
833
+ mapped, field
834
+ );
835
+ }
836
+ }
837
+ }
838
+ let _ = writeln!(
839
+ out,
840
+ " return fmt.Errorf(\"invalid lexical value for {}: %q\", value)",
841
+ name
842
+ );
843
+ out.push_str("}\n\n");
844
+ let _ = writeln!(
845
+ out,
846
+ "func (c {}) MarshalXML(e *xml.Encoder, start xml.StartElement) error {{",
847
+ name
848
+ );
849
+ out.push_str(" count := 0\n var value string\n");
850
+ for (branch, field) in u.branches.iter().zip(&fields) {
851
+ let expr = if matches!(
852
+ super::primitive_base(&branch.type_ref, ir),
853
+ TypeRef::Primitive(PrimitiveType::Date)
854
+ ) {
855
+ format!("c.{}.Format(\"2006-01-02\")", field)
856
+ } else {
857
+ format!("fmt.Sprint(*c.{})", field)
858
+ };
859
+ let _ = writeln!(
860
+ out,
861
+ " if c.{} != nil {{ count++; value = {} }}",
862
+ field, expr
863
+ );
864
+ }
865
+ out.push_str(" if count != 1 { return fmt.Errorf(\"lexical union requires exactly one member\") }\n return e.EncodeElement(value, start)\n}\n\n");
866
+ }
867
+
692
868
  fn emit_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR) {
693
869
  let struct_name = type_ident(&s.qname);
694
870
  if let Some(ref doc) = s.documentation {
@@ -40,10 +40,9 @@ impl JavaCodegen {
40
40
  check_type(&f.type_ref, ir)?;
41
41
  if matches!(f.kind, FieldKind::Attribute | FieldKind::Text) {
42
42
  if let TypeRef::Named(q) = &f.type_ref {
43
- if matches!(
44
- ir.types.get(q),
45
- Some(TypeDef::Struct(_) | TypeDef::Union(_))
46
- ) {
43
+ if matches!(ir.types.get(q), Some(TypeDef::Struct(_)))
44
+ || matches!(ir.types.get(q), Some(TypeDef::Union(u)) if !u.is_lexical())
45
+ {
47
46
  return Err(format!(
48
47
  "non-scalar XML text/attribute {}.{}",
49
48
  name, f.name
@@ -176,13 +175,23 @@ impl JavaCodegen {
176
175
  );
177
176
  }
178
177
  TypeDef::Union(u) => {
179
- let _=writeln!(out," {name} result = null;\n while (reader.hasNext()) {{\n int event = reader.next();\n if (event == XMLStreamConstants.END_ELEMENT) return result;\n if (event != XMLStreamConstants.START_ELEMENT) continue;");
180
- for (i, b) in u.branches.iter().enumerate() {
181
- let expr = self.read_value(&b.type_ref, ir);
182
- let variant = to_java_type_name(&b.variant_name);
183
- let _=writeln!(out," {}if (reader.getLocalName().equals({:?})) result = new {name}.{variant}({expr});",if i==0 {""}else{"else "},b.xml_name);
178
+ if u.is_lexical() {
179
+ out.push_str(" String text = reader.getElementText();\n");
180
+ for b in &u.branches {
181
+ let expr = self.parse_scalar(&b.type_ref, "text", ir);
182
+ let variant = to_java_type_name(&b.variant_name);
183
+ let _ = writeln!(out, " try {{ return new {name}.{variant}({expr}); }} catch (RuntimeException ignored) {{ }}");
184
+ }
185
+ let _ = writeln!(out, " throw new XMLStreamException(\"No {} union member accepts: \" + text);", name);
186
+ } else {
187
+ let _=writeln!(out," {name} result = null;\n while (reader.hasNext()) {{\n int event = reader.next();\n if (event == XMLStreamConstants.END_ELEMENT) return result;\n if (event != XMLStreamConstants.START_ELEMENT) continue;");
188
+ for (i, b) in u.branches.iter().enumerate() {
189
+ let expr = self.read_value(&b.type_ref, ir);
190
+ let variant = to_java_type_name(&b.variant_name);
191
+ let _=writeln!(out," {}if (reader.getLocalName().equals({:?})) result = new {name}.{variant}({expr});",if i==0 {""}else{"else "},b.xml_name);
192
+ }
193
+ out.push_str(" else skip(reader);\n }\n return result;\n");
184
194
  }
185
- out.push_str(" else skip(reader);\n }\n return result;\n");
186
195
  }
187
196
  }
188
197
  out.push_str(" } catch (IllegalArgumentException | java.time.DateTimeException ex) { throw new XMLStreamException(\"Invalid XML value\", reader.getLocation(), ex); }\n }\n");
@@ -230,15 +239,20 @@ impl JavaCodegen {
230
239
  } else {
231
240
  "branch.getValue()"
232
241
  };
233
- self.emit_write_value(
234
- &mut out,
235
- &b.type_ref,
236
- access,
237
- &b.xml_name,
238
- u.qname.namespace.as_deref().unwrap_or(""),
239
- ir,
240
- " ",
241
- );
242
+ if u.is_lexical() {
243
+ let text = self.format_scalar(&b.type_ref, access, ir);
244
+ let _ = writeln!(out, " writer.writeCharacters({text});");
245
+ } else {
246
+ self.emit_write_value(
247
+ &mut out,
248
+ &b.type_ref,
249
+ access,
250
+ &b.xml_name,
251
+ u.qname.namespace.as_deref().unwrap_or(""),
252
+ ir,
253
+ " ",
254
+ );
255
+ }
242
256
  out.push_str(" }\n");
243
257
  }
244
258
  }