polyxml 0.28.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.28.0 → polyxml-0.29.0}/Cargo.lock +6 -6
  2. {polyxml-0.28.0 → polyxml-0.29.0}/Cargo.toml +1 -1
  3. {polyxml-0.28.0 → polyxml-0.29.0}/PKG-INFO +1 -1
  4. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +128 -9
  5. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/go/mod.rs +92 -0
  6. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/java/codec.rs +29 -4
  7. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/java/mod.rs +13 -8
  8. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/python/mod.rs +59 -1
  9. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/rust/mod.rs +42 -5
  10. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/ir/mod.rs +9 -0
  11. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/parser.rs +122 -7
  12. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/schema.rs +62 -1
  13. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/schema_parser/mod.rs +99 -1
  14. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/serializer.rs +224 -104
  15. polyxml-0.29.0/crates/polyxml-core/tests/fixtures/mixed_content.xsd +17 -0
  16. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +7 -0
  17. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +4 -0
  18. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_go_codegen.rs +5 -0
  19. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_java_codegen.rs +2 -0
  20. polyxml-0.29.0/crates/polyxml-core/tests/test_mixed_content.rs +159 -0
  21. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_python_codegen.rs +10 -0
  22. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_rust_codegen.rs +15 -0
  23. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_schema_audit.rs +1 -0
  24. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_schema_ir.rs +4 -0
  25. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_ts_codegen.rs +5 -0
  26. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-python/src/lib.rs +97 -2
  27. polyxml-0.29.0/crates/polyxml-python/tests/test_mixed_content.py +44 -0
  28. {polyxml-0.28.0 → polyxml-0.29.0}/pyproject.toml +1 -1
  29. {polyxml-0.28.0 → polyxml-0.29.0}/README.md +0 -0
  30. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/Cargo.toml +0 -0
  31. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/README.md +0 -0
  32. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  33. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  34. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  35. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  36. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  37. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  38. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  39. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  40. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/converters.rs +0 -0
  41. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/error.rs +0 -0
  42. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  43. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/json.rs +0 -0
  44. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/lib.rs +0 -0
  45. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/transcoder.rs +0 -0
  46. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/src/value.rs +0 -0
  47. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  48. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  49. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  50. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_core.rs +0 -0
  51. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_json.rs +0 -0
  52. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  53. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  54. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  55. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_security.rs +0 -0
  56. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  57. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  58. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  59. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-python/Cargo.toml +0 -0
  60. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-python/README.md +0 -0
  61. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  62. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
  63. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  64. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-python/tests/test_json.py +0 -0
  65. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  66. {polyxml-0.28.0 → polyxml-0.29.0}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  67. {polyxml-0.28.0 → polyxml-0.29.0}/python/polyxml/AGENT_GUIDE.md +0 -0
  68. {polyxml-0.28.0 → polyxml-0.29.0}/python/polyxml/__init__.py +0 -0
  69. {polyxml-0.28.0 → polyxml-0.29.0}/python/polyxml/compat/__init__.py +0 -0
  70. {polyxml-0.28.0 → polyxml-0.29.0}/python/polyxml/compat/xsdata.py +0 -0
  71. {polyxml-0.28.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.28.0"
611
+ version = "0.29.0"
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.28.0"
632
+ version = "0.29.0"
633
633
  dependencies = [
634
634
  "polyxml",
635
635
  ]
636
636
 
637
637
  [[package]]
638
638
  name = "polyxml-cli"
639
- version = "0.28.0"
639
+ version = "0.29.0"
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.28.0"
653
+ version = "0.29.0"
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.28.0"
663
+ version = "0.29.0"
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.28.0"
673
+ version = "0.29.0"
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.28.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.28.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
@@ -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 {
@@ -584,9 +593,9 @@ impl CSharpCodegen {
584
593
  }
585
594
 
586
595
  // XmlInclude attributes for element-choice polymorphism.
587
- if self.options.emit_xml_attributes && !u.is_lexical() {
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,7 +641,7 @@ impl CSharpCodegen {
632
641
  }
633
642
 
634
643
  let mut branch_attrs = Vec::new();
635
- if self.options.emit_xml_attributes && !u.is_lexical() {
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
647
  if self.options.emit_json_attributes && !u.is_lexical() {
@@ -701,7 +710,7 @@ impl CSharpCodegen {
701
710
  let _ = writeln!(out, "{} {{", indent);
702
711
  let _ = writeln!(out, "{} var value = text.Trim();", indent);
703
712
  for (idx, branch) in u.branches.iter().enumerate() {
704
- let variant = to_csharp_type_name(&branch.variant_name);
713
+ let variant = choice_variant_name(branch);
705
714
  let mapped = self.context.map_type_ref(&branch.type_ref);
706
715
  if let TypeRef::Named(qname) = &branch.type_ref {
707
716
  if let Some(TypeDef::Enum(def)) = ir.types.get(qname) {
@@ -779,7 +788,7 @@ impl CSharpCodegen {
779
788
  );
780
789
  let _ = writeln!(out, "{} {{", indent);
781
790
  for branch in &u.branches {
782
- let variant = to_csharp_type_name(&branch.variant_name);
791
+ let variant = choice_variant_name(branch);
783
792
  let value = if matches!(
784
793
  super::primitive_base(&branch.type_ref, ir),
785
794
  TypeRef::Primitive(PrimitiveType::Date)
@@ -802,6 +811,111 @@ impl CSharpCodegen {
802
811
  let _ = writeln!(out, "{} }};", indent);
803
812
  }
804
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
+
805
919
  fn emit_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR, indent: &str) {
806
920
  let struct_name = type_ident(&s.qname);
807
921
  if let Some(ref doc) = s.documentation {
@@ -852,6 +966,11 @@ impl CSharpCodegen {
852
966
  .map(|f| self.unique_property_name(&f.name, &mut seen_props, Some(&struct_name)))
853
967
  .collect();
854
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
+
855
974
  let has_lexical_union = self.options.emit_xml_attributes && s.fields.iter().any(|f| {
856
975
  matches!(&f.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical()))
857
976
  });
@@ -1232,7 +1351,7 @@ impl CSharpCodegen {
1232
1351
  if let Some(TypeDef::Union(u)) = ir.types.get(qname) {
1233
1352
  let choice_name = type_ident(&u.qname);
1234
1353
  for branch in &u.branches {
1235
- let variant_name = to_csharp_type_name(&branch.variant_name);
1354
+ let variant_name = choice_variant_name(branch);
1236
1355
  parts.push(format!(
1237
1356
  "XmlElement(\"{}\", typeof({}.{}))",
1238
1357
  branch.xml_name, choice_name, variant_name
@@ -279,6 +279,11 @@ impl GoCodegen {
279
279
  for type_def in ir.types.values() {
280
280
  match type_def {
281
281
  TypeDef::Struct(s) => {
282
+ if s.is_mixed && self.options.emit_xml_tags {
283
+ has_xml = true;
284
+ has_fmt = true;
285
+ has_io = true;
286
+ }
282
287
  for f in &s.fields {
283
288
  if self.references_time(&f.type_ref) {
284
289
  has_time = true;
@@ -352,6 +357,14 @@ impl GoCodegen {
352
357
  if has_xml {
353
358
  imports.push("\"encoding/xml\"");
354
359
  }
360
+ if self.options.emit_xml_tags
361
+ && ir
362
+ .types
363
+ .values()
364
+ .any(|def| matches!(def, TypeDef::Struct(s) if s.is_mixed))
365
+ {
366
+ imports.push("\"bytes\"");
367
+ }
355
368
  if has_fmt {
356
369
  imports.push("\"fmt\"");
357
370
  }
@@ -977,11 +990,90 @@ impl GoCodegen {
977
990
 
978
991
  writeln!(out, "}}\n").unwrap();
979
992
 
993
+ if s.is_mixed && self.options.emit_xml_tags {
994
+ self.emit_mixed_struct_xml(out, s, ir, &field_names);
995
+ }
996
+
980
997
  if self.options.validate_facets {
981
998
  self.emit_struct_validator(out, s, ir, &field_names);
982
999
  }
983
1000
  }
984
1001
 
1002
+ fn emit_mixed_struct_xml(
1003
+ &self,
1004
+ out: &mut String,
1005
+ s: &StructDef,
1006
+ ir: &SchemaIR,
1007
+ field_names: &[String],
1008
+ ) {
1009
+ let Some((item_index, union)) =
1010
+ s.fields
1011
+ .iter()
1012
+ .enumerate()
1013
+ .find_map(|(index, field)| match &field.type_ref {
1014
+ TypeRef::Named(qname) => match ir.types.get(qname) {
1015
+ Some(TypeDef::Union(union)) if union.is_mixed_content() => {
1016
+ Some((index, union))
1017
+ }
1018
+ _ => None,
1019
+ },
1020
+ _ => None,
1021
+ })
1022
+ else {
1023
+ return;
1024
+ };
1025
+ let struct_name = type_ident(&s.qname);
1026
+ let item_field = &field_names[item_index];
1027
+ let union_name = type_ident(&union.qname);
1028
+ let mut seen = HashSet::new();
1029
+ let branch_fields = union
1030
+ .branches
1031
+ .iter()
1032
+ .map(|branch| self.unique_field_name(&branch.variant_name, &mut seen))
1033
+ .collect::<Vec<_>>();
1034
+
1035
+ let _ = writeln!(
1036
+ out,
1037
+ "func (v *{struct_name}) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {{"
1038
+ );
1039
+ let _ = writeln!(out, " type Alias {struct_name}");
1040
+ out.push_str(" var attrBytes bytes.Buffer\n attrEncoder := xml.NewEncoder(&attrBytes)\n if err := attrEncoder.EncodeToken(start); err != nil { return err }\n if err := attrEncoder.EncodeToken(start.End()); err != nil { return err }\n if err := attrEncoder.Flush(); err != nil { return err }\n var attrs Alias\n if err := xml.Unmarshal(attrBytes.Bytes(), &attrs); err != nil { return err }\n");
1041
+ let _ = writeln!(out, " *v = {struct_name}(attrs)");
1042
+ let _ = writeln!(out, " v.{item_field} = nil");
1043
+ out.push_str(" for {\n token, err := d.Token()\n if err != nil { if err == io.EOF { return io.ErrUnexpectedEOF }; return err }\n switch element := token.(type) {\n case xml.CharData:\n if len(element) > 0 {\n value := string(element)\n");
1044
+ let _ = writeln!(out, " v.{item_field} = append(v.{item_field}, {union_name}{{{text_field}: &value}})", text_field=branch_fields[0]);
1045
+ out.push_str(" }\n case xml.StartElement:\n switch element.Name.Local {\n");
1046
+ for (branch, branch_field) in union.branches.iter().zip(&branch_fields) {
1047
+ if branch.xml_name == "#text" {
1048
+ continue;
1049
+ }
1050
+ let mapped = self.context.map_type_ref(&branch.type_ref);
1051
+ let _ = writeln!(out, " case {:?}:\n var value {mapped}\n if err := d.DecodeElement(&value, &element); err != nil {{ return err }}\n v.{item_field} = append(v.{item_field}, {union_name}{{{branch_field}: &value}})", branch.xml_name);
1052
+ }
1053
+ out.push_str(" default:\n if err := d.Skip(); err != nil { return err }\n }\n case xml.EndElement:\n if element.Name == start.Name { return nil }\n }\n }\n}\n\n");
1054
+
1055
+ let _ = writeln!(
1056
+ out,
1057
+ "func (v {struct_name}) MarshalXML(e *xml.Encoder, start xml.StartElement) error {{"
1058
+ );
1059
+ let _ = writeln!(
1060
+ out,
1061
+ " type Alias {struct_name}\n attrs := Alias(v)\n attrs.{item_field} = nil"
1062
+ );
1063
+ out.push_str(" raw, err := xml.Marshal(attrs)\n if err != nil { return err }\n decoder := xml.NewDecoder(bytes.NewReader(raw))\n token, err := decoder.Token()\n if err != nil { return err }\n start.Attr = token.(xml.StartElement).Attr\n if err := e.EncodeToken(start); err != nil { return err }\n");
1064
+ let _ = writeln!(out, " for _, item := range v.{item_field} {{");
1065
+ for (branch, branch_field) in union.branches.iter().zip(&branch_fields) {
1066
+ let _ = writeln!(out, " if item.{branch_field} != nil {{");
1067
+ if branch.xml_name == "#text" {
1068
+ let _ = writeln!(out, " if err := e.EncodeToken(xml.CharData(*item.{branch_field})); err != nil {{ return err }}");
1069
+ } else {
1070
+ let _ = writeln!(out, " if err := e.EncodeElement(item.{branch_field}, xml.StartElement{{Name: xml.Name{{Local: {:?}}}}}); err != nil {{ return err }}", branch.xml_name);
1071
+ }
1072
+ out.push_str(" continue\n }\n");
1073
+ }
1074
+ out.push_str(" return fmt.Errorf(\"mixed content item has no selected branch\")\n }\n return e.EncodeToken(start.End())\n}\n\n");
1075
+ }
1076
+
985
1077
  fn resolve_field_type(&self, f: &FieldDef) -> String {
986
1078
  let base_type = self.context.map_type_ref(&f.type_ref);
987
1079
 
@@ -179,7 +179,7 @@ impl JavaCodegen {
179
179
  out.push_str(" String text = reader.getElementText();\n");
180
180
  for b in &u.branches {
181
181
  let expr = self.parse_scalar(&b.type_ref, "text", ir);
182
- let variant = to_java_type_name(&b.variant_name);
182
+ let variant = union_variant_name(b);
183
183
  let _ = writeln!(out, " try {{ return new {name}.{variant}({expr}); }} catch (RuntimeException ignored) {{ }}");
184
184
  }
185
185
  let _ = writeln!(out, " throw new XMLStreamException(\"No {} union member accepts: \" + text);", name);
@@ -187,7 +187,7 @@ impl JavaCodegen {
187
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
188
  for (i, b) in u.branches.iter().enumerate() {
189
189
  let expr = self.read_value(&b.type_ref, ir);
190
- let variant = to_java_type_name(&b.variant_name);
190
+ let variant = union_variant_name(b);
191
191
  let _=writeln!(out," {}if (reader.getLocalName().equals({:?})) result = new {name}.{variant}({expr});",if i==0 {""}else{"else "},b.xml_name);
192
192
  }
193
193
  out.push_str(" else skip(reader);\n }\n return result;\n");
@@ -228,7 +228,7 @@ impl JavaCodegen {
228
228
  }
229
229
  TypeDef::Union(u) => {
230
230
  for (i, b) in u.branches.iter().enumerate() {
231
- let variant = to_java_type_name(&b.variant_name);
231
+ let variant = union_variant_name(b);
232
232
  let _ = writeln!(
233
233
  out,
234
234
  " {}if (value instanceof {name}.{variant} branch) {{",
@@ -242,6 +242,8 @@ impl JavaCodegen {
242
242
  if u.is_lexical() {
243
243
  let text = self.format_scalar(&b.type_ref, access, ir);
244
244
  let _ = writeln!(out, " writer.writeCharacters({text});");
245
+ } else if u.is_mixed_content() && b.xml_name == "#text" {
246
+ let _ = writeln!(out, " writer.writeCharacters({access});");
245
247
  } else {
246
248
  self.emit_write_value(
247
249
  &mut out,
@@ -296,6 +298,26 @@ impl JavaCodegen {
296
298
  if text_field.is_some() {
297
299
  out.push_str(" if (event == XMLStreamConstants.CHARACTERS || event == XMLStreamConstants.CDATA) text.append(reader.getText());\n");
298
300
  }
301
+ if s.is_mixed {
302
+ if let Some((field, id, union)) = fields.iter().find_map(|(field, id)| {
303
+ self.resolve_union_def(&field.type_ref, ir)
304
+ .filter(|union| union.is_mixed_content())
305
+ .map(|union| (field, id, union))
306
+ }) {
307
+ let union_name = type_ident(&union.qname);
308
+ let text_branch = union
309
+ .branches
310
+ .iter()
311
+ .find(|branch| branch.xml_name == "#text");
312
+ if let Some(text_branch) = text_branch {
313
+ let variant = union_variant_name(text_branch);
314
+ let val = format!("new {union_name}.{variant}(reader.getText())");
315
+ out.push_str(" if (event == XMLStreamConstants.CHARACTERS || event == XMLStreamConstants.CDATA || event == XMLStreamConstants.SPACE) {\n");
316
+ self.assign_value(out, field, id, &val, " ");
317
+ out.push_str(" continue;\n }\n");
318
+ }
319
+ }
320
+ }
299
321
  out.push_str(" if (event != XMLStreamConstants.START_ELEMENT) continue;\n");
300
322
  let mut first = true;
301
323
  for (f, id) in &fields {
@@ -306,6 +328,9 @@ impl JavaCodegen {
306
328
  if let Some(u) = self.resolve_union_def(&f.type_ref, ir) {
307
329
  let union_name = type_ident(&u.qname);
308
330
  for branch in &u.branches {
331
+ if branch.xml_name == "#text" {
332
+ continue;
333
+ }
309
334
  let _ = writeln!(
310
335
  out,
311
336
  " {}if (reader.getLocalName().equals({:?})) {{",
@@ -314,7 +339,7 @@ impl JavaCodegen {
314
339
  );
315
340
  first = false;
316
341
  let expr = self.read_value(&branch.type_ref, ir);
317
- let variant = to_java_type_name(&branch.variant_name);
342
+ let variant = union_variant_name(branch);
318
343
  let union_val = format!("new {union_name}.{variant}({expr})");
319
344
  self.assign_value(out, f, id, &union_val, " ");
320
345
  out.push_str(" }\n");
@@ -13,7 +13,7 @@ use crate::codegen::{
13
13
  };
14
14
  use crate::ir::{
15
15
  EnumDef, PrimitiveType, QName, RestrictionFacets, SchemaIR, SimpleTypeDef, StructDef, TypeDef,
16
- TypeRef, UnionDef,
16
+ TypeRef, UnionBranch, UnionDef,
17
17
  };
18
18
 
19
19
  /// Target backend for Java code emission.
@@ -218,6 +218,15 @@ pub(super) fn type_ident(q: &QName) -> String {
218
218
  lookup_type_name(q, || to_java_type_name(&q.local))
219
219
  }
220
220
 
221
+ fn union_variant_name(branch: &UnionBranch) -> String {
222
+ let name = to_java_type_name(&branch.variant_name);
223
+ if matches!(&branch.type_ref, TypeRef::Named(qname) if type_ident(qname) == name) {
224
+ format!("{name}Branch")
225
+ } else {
226
+ name
227
+ }
228
+ }
229
+
221
230
  /// Convert an XML enumeration variant name to SCREAMING_SNAKE_CASE for Java enum constants.
222
231
  pub fn to_java_enum_constant(name: &str) -> String {
223
232
  let normalized = normalize_symbol_name(name);
@@ -632,11 +641,7 @@ impl JavaCodegen {
632
641
  "public static "
633
642
  };
634
643
 
635
- let variant_names: Vec<String> = u
636
- .branches
637
- .iter()
638
- .map(|b| to_java_type_name(&b.variant_name))
639
- .collect();
644
+ let variant_names: Vec<String> = u.branches.iter().map(union_variant_name).collect();
640
645
 
641
646
  let permits_clause = variant_names
642
647
  .iter()
@@ -656,7 +661,7 @@ impl JavaCodegen {
656
661
  .branches
657
662
  .iter()
658
663
  .map(|b| {
659
- let vn = to_java_type_name(&b.variant_name);
664
+ let vn = union_variant_name(b);
660
665
  format!(
661
666
  "@JsonSubTypes.Type(value = {}.{}.class, name = {:?})",
662
667
  java_name, vn, b.xml_name
@@ -674,7 +679,7 @@ impl JavaCodegen {
674
679
  );
675
680
 
676
681
  for branch in &u.branches {
677
- let variant_name = to_java_type_name(&branch.variant_name);
682
+ let variant_name = union_variant_name(branch);
678
683
  let branch_type = self.context.map_type_ref(&branch.type_ref);
679
684
 
680
685
  if let Some(ref doc) = branch.documentation {
@@ -519,6 +519,32 @@ impl PythonCodegen {
519
519
  fn emit_union(&self, out: &mut String, u: &UnionDef) {
520
520
  let union_name = type_ident(&u.qname);
521
521
 
522
+ if u.is_mixed_content() {
523
+ let value_types = u
524
+ .branches
525
+ .iter()
526
+ .map(|branch| self.context.map_type_ref(&branch.type_ref))
527
+ .collect::<Vec<_>>();
528
+ let value_type = value_types
529
+ .into_iter()
530
+ .collect::<std::collections::BTreeSet<_>>()
531
+ .into_iter()
532
+ .collect::<Vec<_>>()
533
+ .join(" | ");
534
+ if self.options.backend == PythonBackend::Pydantic {
535
+ let _ = writeln!(out, "class {}(BaseModel):", union_name);
536
+ } else {
537
+ let _ = writeln!(
538
+ out,
539
+ "@dataclass(slots=True, kw_only=True)\nclass {}:",
540
+ union_name
541
+ );
542
+ }
543
+ out.push_str(" kind: str\n");
544
+ let _ = writeln!(out, " value: {}", value_type);
545
+ return;
546
+ }
547
+
522
548
  if let Some(ref doc) = u.documentation {
523
549
  let _ = writeln!(out, "# {}", doc.trim());
524
550
  }
@@ -595,7 +621,7 @@ impl PythonCodegen {
595
621
  has_body = true;
596
622
  }
597
623
 
598
- if self.options.emit_meta {
624
+ if self.options.emit_meta || s.is_mixed {
599
625
  if has_body {
600
626
  out.push('\n');
601
627
  }
@@ -610,6 +636,38 @@ impl PythonCodegen {
610
636
  if s.is_abstract {
611
637
  out.push_str(" abstract = True\n");
612
638
  }
639
+ if s.is_mixed {
640
+ if let Some(item_field) = s
641
+ .fields
642
+ .iter()
643
+ .find(|field| field.xml_name.is_empty() && field.kind == FieldKind::Element)
644
+ {
645
+ let _ = writeln!(out, " mixed_items = {:?}", item_field.name);
646
+ }
647
+ if let Some(union) = s.fields.iter().find_map(|field| match &field.type_ref {
648
+ TypeRef::Named(qname) => match ir.types.get(qname) {
649
+ Some(TypeDef::Union(union)) if union.is_mixed_content() => Some(union),
650
+ _ => None,
651
+ },
652
+ _ => None,
653
+ }) {
654
+ out.push_str(" mixed_branches = (\n");
655
+ for branch in &union.branches {
656
+ let branch_type = self.context.map_type_ref(&branch.type_ref);
657
+ let namespace = branch
658
+ .namespace
659
+ .as_ref()
660
+ .map(|ns| format!("{ns:?}"))
661
+ .unwrap_or_else(|| "None".into());
662
+ let _ = writeln!(
663
+ out,
664
+ " ({:?}, {:?}, {}, {:?}),",
665
+ branch.variant_name, branch.xml_name, namespace, branch_type
666
+ );
667
+ }
668
+ out.push_str(" )\n");
669
+ }
670
+ }
613
671
  has_body = true;
614
672
  }
615
673