polyxml 0.33.4__tar.gz → 0.33.6__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 (75) hide show
  1. {polyxml-0.33.4 → polyxml-0.33.6}/Cargo.lock +6 -6
  2. {polyxml-0.33.4 → polyxml-0.33.6}/Cargo.toml +1 -1
  3. {polyxml-0.33.4 → polyxml-0.33.6}/PKG-INFO +1 -1
  4. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/csharp/mod.rs +63 -4
  5. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/go/mod.rs +106 -18
  6. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/java/codec.rs +1 -1
  7. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/python/mod.rs +143 -6
  8. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/rust/mod.rs +1 -1
  9. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/ir/mod.rs +7 -0
  10. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/parser.rs +20 -0
  11. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/schema.rs +11 -1
  12. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/schema_parser/mod.rs +181 -11
  13. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_csharp_codegen.rs +149 -0
  14. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_go_codegen.rs +122 -4
  15. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_python_codegen.rs +25 -0
  16. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_schema_ir.rs +37 -0
  17. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-python/src/lib.rs +21 -1
  18. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-python/tests/test_generated_models.py +164 -0
  19. {polyxml-0.33.4 → polyxml-0.33.6}/pyproject.toml +1 -1
  20. {polyxml-0.33.4 → polyxml-0.33.6}/README.md +0 -0
  21. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/Cargo.toml +0 -0
  22. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/README.md +0 -0
  23. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  24. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  25. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  26. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  27. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  28. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  29. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  30. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  31. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  32. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/converters.rs +0 -0
  33. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/error.rs +0 -0
  34. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/ir/chunker.rs +0 -0
  35. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  36. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/json.rs +0 -0
  37. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/lib.rs +0 -0
  38. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/serializer.rs +0 -0
  39. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/transcoder.rs +0 -0
  40. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/src/value.rs +0 -0
  41. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  42. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
  43. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/fixtures/ts_mixed_content_inheritance.xsd +0 -0
  44. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  45. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_any_attribute.rs +0 -0
  46. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_any_element.rs +0 -0
  47. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  48. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_core.rs +0 -0
  49. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  50. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  51. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_json.rs +0 -0
  52. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  53. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
  54. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  55. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  56. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
  57. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  58. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_security.rs +0 -0
  59. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  60. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  61. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  62. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  63. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-python/Cargo.toml +0 -0
  64. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-python/README.md +0 -0
  65. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  66. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-python/tests/test_conformance.py +0 -0
  67. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-python/tests/test_json.py +0 -0
  68. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
  69. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  70. {polyxml-0.33.4 → polyxml-0.33.6}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  71. {polyxml-0.33.4 → polyxml-0.33.6}/python/polyxml/AGENT_GUIDE.md +0 -0
  72. {polyxml-0.33.4 → polyxml-0.33.6}/python/polyxml/__init__.py +0 -0
  73. {polyxml-0.33.4 → polyxml-0.33.6}/python/polyxml/compat/__init__.py +0 -0
  74. {polyxml-0.33.4 → polyxml-0.33.6}/python/polyxml/compat/xsdata.py +0 -0
  75. {polyxml-0.33.4 → polyxml-0.33.6}/python/polyxml/py.typed +0 -0
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.33.4"
611
+ version = "0.33.6"
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.33.4"
632
+ version = "0.33.6"
633
633
  dependencies = [
634
634
  "polyxml",
635
635
  ]
636
636
 
637
637
  [[package]]
638
638
  name = "polyxml-cli"
639
- version = "0.33.4"
639
+ version = "0.33.6"
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.33.4"
653
+ version = "0.33.6"
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.33.4"
663
+ version = "0.33.6"
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.33.4"
673
+ version = "0.33.6"
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.33.4"
6
+ version = "0.33.6"
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.33.4
3
+ Version: 0.33.6
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -273,6 +273,28 @@ fn simple_value_target(base: &TypeRef, ir: &SchemaIR) -> String {
273
273
  }
274
274
 
275
275
  impl CSharpCodegen {
276
+ fn emit_optional_element_omission(
277
+ &self,
278
+ out: &mut String,
279
+ s: &StructDef,
280
+ names: &[String],
281
+ indent: &str,
282
+ ) {
283
+ if self.options.emit_xml_attributes {
284
+ for (field, name) in s.fields.iter().zip(names) {
285
+ if field.kind == FieldKind::Element
286
+ && field.cardinality.min_occurs == 0
287
+ && !field.nillable
288
+ {
289
+ let _ = writeln!(
290
+ out,
291
+ "{indent} public bool ShouldSerialize{name}() => {name} is not null;"
292
+ );
293
+ }
294
+ }
295
+ }
296
+ }
297
+
276
298
  /// Creates a new C# code generator with the given configuration options.
277
299
  pub fn new(options: CSharpOptions) -> Self {
278
300
  Self {
@@ -1002,7 +1024,7 @@ impl CSharpCodegen {
1002
1024
  .map(|f| self.unique_property_name(&f.name, &mut seen_props, Some(&struct_name)))
1003
1025
  .collect();
1004
1026
 
1005
- if s.is_mixed && self.options.emit_xml_attributes {
1027
+ if ir.has_ordered_content(s) && self.options.emit_xml_attributes {
1006
1028
  self.emit_mixed_struct(out, s, ir, indent, &struct_name, &prop_names);
1007
1029
  return;
1008
1030
  }
@@ -1032,7 +1054,12 @@ impl CSharpCodegen {
1032
1054
  "{} public {} {} {{ get; set; }} = default!;",
1033
1055
  indent, ty, name
1034
1056
  );
1035
- let _ = writeln!(out, "{} [XmlElement({:?})]", indent, f.xml_name);
1057
+ let xml_kind = if f.kind == FieldKind::Attribute {
1058
+ "XmlAttribute"
1059
+ } else {
1060
+ "XmlElement"
1061
+ };
1062
+ let _ = writeln!(out, "{} [{}({:?})]", indent, xml_kind, f.xml_name);
1036
1063
  if self.options.emit_json_attributes {
1037
1064
  let _ = writeln!(out, "{} [JsonIgnore]", indent);
1038
1065
  }
@@ -1049,6 +1076,7 @@ impl CSharpCodegen {
1049
1076
  indent, attrs, ty, name
1050
1077
  );
1051
1078
  }
1079
+ self.emit_optional_element_omission(out, s, &prop_names, indent);
1052
1080
  if self.options.emit_validation {
1053
1081
  self.emit_struct_validator(out, s, ir, &prop_names, indent);
1054
1082
  }
@@ -1080,7 +1108,12 @@ impl CSharpCodegen {
1080
1108
  "{} public {} {} {{ get; set; }} = default!;",
1081
1109
  indent, ty, name
1082
1110
  );
1083
- let _ = writeln!(out, "{} [XmlElement({:?})]", indent, f.xml_name);
1111
+ let xml_kind = if f.kind == FieldKind::Attribute {
1112
+ "XmlAttribute"
1113
+ } else {
1114
+ "XmlElement"
1115
+ };
1116
+ let _ = writeln!(out, "{} [{}({:?})]", indent, xml_kind, f.xml_name);
1084
1117
  if self.options.emit_json_attributes {
1085
1118
  let _ = writeln!(out, "{} [JsonIgnore]", indent);
1086
1119
  }
@@ -1103,6 +1136,7 @@ impl CSharpCodegen {
1103
1136
  )
1104
1137
  .unwrap();
1105
1138
  }
1139
+ self.emit_optional_element_omission(out, s, &prop_names, indent);
1106
1140
  if self.options.emit_validation {
1107
1141
  self.emit_struct_validator(out, s, ir, &prop_names, indent);
1108
1142
  }
@@ -1191,6 +1225,8 @@ impl CSharpCodegen {
1191
1225
  }
1192
1226
  writeln!(out, ") {{ }}\n").unwrap();
1193
1227
 
1228
+ self.emit_optional_element_omission(out, s, &prop_names, indent);
1229
+
1194
1230
  // IValidatableObject implementation
1195
1231
  if self.options.emit_validation {
1196
1232
  self.emit_struct_validator(out, s, ir, &prop_names, indent);
@@ -1379,6 +1415,11 @@ impl CSharpCodegen {
1379
1415
  };
1380
1416
 
1381
1417
  if f.cardinality.is_list() {
1418
+ let base_type = if f.nillable {
1419
+ format!("{}?", base_type)
1420
+ } else {
1421
+ base_type
1422
+ };
1382
1423
  if f.cardinality.min_occurs == 0 || f.nillable {
1383
1424
  format!("List<{}>?", base_type)
1384
1425
  } else {
@@ -1422,7 +1463,14 @@ impl CSharpCodegen {
1422
1463
  FieldKind::Text => parts.push("XmlText".to_string()),
1423
1464
  FieldKind::Any => parts.push("XmlAnyElement".to_string()),
1424
1465
  FieldKind::AnyAttribute => parts.push("XmlAnyAttribute".to_string()),
1425
- FieldKind::Element => parts.push(format!("XmlElement(\"{}\")", f.xml_name)),
1466
+ FieldKind::Element => {
1467
+ let nullable = if f.nillable {
1468
+ ", IsNullable = true"
1469
+ } else {
1470
+ ""
1471
+ };
1472
+ parts.push(format!("XmlElement(\"{}\"{})", f.xml_name, nullable));
1473
+ }
1426
1474
  }
1427
1475
  }
1428
1476
  }
@@ -1500,6 +1548,17 @@ impl CSharpCodegen {
1500
1548
  continue;
1501
1549
  }
1502
1550
  }
1551
+ if self.options.use_records
1552
+ && !matches!(&elem.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Struct(_) | TypeDef::Simple(_) | TypeDef::Union(_))))
1553
+ {
1554
+ let xml_attr = if self.options.emit_xml_attributes {
1555
+ "[property: XmlText] "
1556
+ } else {
1557
+ ""
1558
+ };
1559
+ writeln!(out, "{}public sealed record {}({}{} Value)\n{}{{\n{} public {}() : this(default({})!) {{ }}\n{}}}\n", indent, elem_name, xml_attr, target_type, indent, indent, elem_name, target_type, indent).unwrap();
1560
+ continue;
1561
+ }
1503
1562
  writeln!(
1504
1563
  out,
1505
1564
  "{}public sealed {} {} : {}\n{}{{",
@@ -119,7 +119,9 @@ impl LanguageContext for GoLanguageContext {
119
119
  | PrimitiveType::GMonthDay
120
120
  | PrimitiveType::GDay
121
121
  | PrimitiveType::Duration => "string",
122
- PrimitiveType::Date | PrimitiveType::Time | PrimitiveType::DateTime => "time.Time",
122
+ PrimitiveType::Date => "PolyxmlDate",
123
+ PrimitiveType::Time => "PolyxmlTime",
124
+ PrimitiveType::DateTime => "PolyxmlDateTime",
123
125
  PrimitiveType::Base64Binary | PrimitiveType::HexBinary => "[]byte",
124
126
  PrimitiveType::AnyType | PrimitiveType::AnySimpleType => "any",
125
127
  }
@@ -243,6 +245,46 @@ fn type_ident(q: &QName) -> String {
243
245
  }
244
246
 
245
247
  impl GoCodegen {
248
+ fn emit_temporal_types(&self, out: &mut String) {
249
+ for (name, layouts, default_layout) in [
250
+ (
251
+ "PolyxmlDate",
252
+ &["2006-01-02Z07:00", "2006-01-02"][..],
253
+ "2006-01-02Z07:00",
254
+ ),
255
+ (
256
+ "PolyxmlTime",
257
+ &["15:04:05Z07:00", "15:04:05"][..],
258
+ "15:04:05.999999999Z07:00",
259
+ ),
260
+ (
261
+ "PolyxmlDateTime",
262
+ &["2006-01-02T15:04:05Z07:00", "2006-01-02T15:04:05"][..],
263
+ "2006-01-02T15:04:05.999999999Z07:00",
264
+ ),
265
+ ] {
266
+ writeln!(out, "// {name} retains the XSD lexical form, including an absent timezone.\ntype {name} struct {{ time.Time; lexical string }}").unwrap();
267
+ writeln!(out, "func (v *{name}) UnmarshalText(text []byte) error {{\n value := strings.TrimSpace(string(text))").unwrap();
268
+ for layout in layouts {
269
+ writeln!(out, " if parsed, err := time.Parse({layout:?}, value); err == nil {{ *v = {name}{{Time: parsed, lexical: value}}; return nil }}").unwrap();
270
+ }
271
+ writeln!(out, " return fmt.Errorf(\"invalid {name}: %q\", value)\n}}\nfunc (v {name}) MarshalText() ([]byte, error) {{\n layout := {default_layout:?}").unwrap();
272
+ for original_layout in layouts {
273
+ writeln!(out, " if parsed, err := time.Parse({original_layout:?}, v.lexical); err == nil {{\n _, offset := v.Time.Zone(); _, originalOffset := parsed.Zone()\n if v.Time.Equal(parsed) && offset == originalOffset {{ return []byte(v.lexical), nil }}").unwrap();
274
+ if !original_layout.contains("Z07:00") {
275
+ writeln!(
276
+ out,
277
+ " layout = {:?}",
278
+ default_layout.trim_end_matches("Z07:00")
279
+ )
280
+ .unwrap();
281
+ }
282
+ out.push_str(" }\n");
283
+ }
284
+ out.push_str(" return []byte(v.Time.Format(layout)), nil\n}\n\n");
285
+ }
286
+ }
287
+
246
288
  pub fn new(options: GoOptions) -> Self {
247
289
  Self {
248
290
  options,
@@ -278,7 +320,7 @@ impl GoCodegen {
278
320
  for type_def in ir.emitted_types() {
279
321
  match type_def {
280
322
  TypeDef::Struct(s) => {
281
- if s.is_mixed && self.options.emit_xml_tags {
323
+ if ir.has_ordered_content(s) && self.options.emit_xml_tags {
282
324
  has_fmt = true;
283
325
  has_io = true;
284
326
  }
@@ -324,6 +366,9 @@ impl GoCodegen {
324
366
 
325
367
  // Generate types
326
368
  self.emit_types(&mut body, ir);
369
+ if has_time {
370
+ self.emit_temporal_types(&mut body);
371
+ }
327
372
  self.emit_root_aliases(&mut body, ir);
328
373
 
329
374
  // Assemble final output with package and imports
@@ -357,7 +402,7 @@ impl GoCodegen {
357
402
  && ir
358
403
  .types
359
404
  .values()
360
- .any(|def| matches!(def, TypeDef::Struct(s) if s.is_mixed))
405
+ .any(|def| matches!(def, TypeDef::Struct(s) if ir.has_ordered_content(s)))
361
406
  {
362
407
  imports.push("\"bytes\"");
363
408
  }
@@ -466,6 +511,12 @@ impl GoCodegen {
466
511
  let type_name = type_ident(&simple.qname);
467
512
  let base_type = self.context.map_type_ref(&simple.base_type);
468
513
  writeln!(out, "type {} {}\n", type_name, base_type).unwrap();
514
+ if matches!(
515
+ super::primitive_base(&simple.base_type, ir),
516
+ TypeRef::Primitive(PrimitiveType::Date | PrimitiveType::Time | PrimitiveType::DateTime)
517
+ ) {
518
+ writeln!(out, "func (v *{type_name}) UnmarshalText(text []byte) error {{ return (*{base_type})(v).UnmarshalText(text) }}\nfunc (v {type_name}) MarshalText() ([]byte, error) {{ return {base_type}(v).MarshalText() }}\n").unwrap();
519
+ }
469
520
  if self.options.validate_facets && !simple.facets.patterns.is_empty() {
470
521
  writeln!(out, "func (s {}) Validate() error {{", type_name).unwrap();
471
522
  for pattern in &simple.facets.patterns {
@@ -771,7 +822,13 @@ impl GoCodegen {
771
822
  "func (c *{}) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {{",
772
823
  name
773
824
  );
774
- out.push_str(" var raw string\n if err := d.DecodeElement(&raw, &start); err != nil { return err }\n value := strings.TrimSpace(raw)\n *c = ");
825
+ out.push_str(" var raw string\n if err := d.DecodeElement(&raw, &start); err != nil { return err }\n return c.UnmarshalText([]byte(raw))\n}\n\n");
826
+ let _ = writeln!(
827
+ out,
828
+ "func (c *{}) UnmarshalText(text []byte) error {{",
829
+ name
830
+ );
831
+ out.push_str(" value := strings.TrimSpace(string(text))\n *c = ");
775
832
  let _ = writeln!(out, "{}{{}}", name);
776
833
  for (branch, field) in u.branches.iter().zip(&fields) {
777
834
  let mapped = self.context.map_type_ref(&branch.type_ref);
@@ -819,13 +876,13 @@ impl GoCodegen {
819
876
  _ => None,
820
877
  };
821
878
  if matches!(base, TypeRef::Primitive(PrimitiveType::Date)) {
822
- let _ = writeln!(out, " if parsed, err := time.Parse(\"2006-01-02\", value); err == nil {{ v := {}(parsed); c.{} = &v; return nil }}", mapped, field);
879
+ let _ = writeln!(out, " if parsed, err := time.Parse(\"2006-01-02\", value); err == nil {{ v := {}{{Time: parsed, lexical: value}}; c.{} = &v; return nil }}", mapped, field);
823
880
  } else if matches!(base, TypeRef::Primitive(PrimitiveType::DateTime)) {
824
- let _ = writeln!(out, " if parsed, err := time.Parse(time.RFC3339, value); err == nil {{ v := {}(parsed); c.{} = &v; return nil }}", mapped, field);
825
- let _ = writeln!(out, " if parsed, err := time.Parse(\"2006-01-02T15:04:05\", value); err == nil {{ v := {}(parsed); c.{} = &v; return nil }}", mapped, field);
881
+ let _ = writeln!(out, " if parsed, err := time.Parse(time.RFC3339, value); err == nil {{ v := {}{{Time: parsed, lexical: value}}; c.{} = &v; return nil }}", mapped, field);
882
+ let _ = writeln!(out, " if parsed, err := time.Parse(\"2006-01-02T15:04:05\", value); err == nil {{ v := {}{{Time: parsed, lexical: value}}; c.{} = &v; return nil }}", mapped, field);
826
883
  } else if matches!(base, TypeRef::Primitive(PrimitiveType::Time)) {
827
- let _ = writeln!(out, " if parsed, err := time.Parse(\"15:04:05Z07:00\", value); err == nil {{ v := {}(parsed); c.{} = &v; return nil }}", mapped, field);
828
- let _ = writeln!(out, " if parsed, err := time.Parse(\"15:04:05\", value); err == nil {{ v := {}(parsed); c.{} = &v; return nil }}", mapped, field);
884
+ let _ = writeln!(out, " if parsed, err := time.Parse(\"15:04:05Z07:00\", value); err == nil {{ v := {}{{Time: parsed, lexical: value}}; c.{} = &v; return nil }}", mapped, field);
885
+ let _ = writeln!(out, " if parsed, err := time.Parse(\"15:04:05\", value); err == nil {{ v := {}{{Time: parsed, lexical: value}}; c.{} = &v; return nil }}", mapped, field);
829
886
  } else if let Some(kind) = numeric {
830
887
  let parse = match kind {
831
888
  "signed" => "strconv.ParseInt(value, 10, 64)",
@@ -881,11 +938,22 @@ impl GoCodegen {
881
938
  "func (c {}) MarshalXML(e *xml.Encoder, start xml.StartElement) error {{",
882
939
  name
883
940
  );
941
+ out.push_str(" text, err := c.MarshalText()\n if err != nil { return err }\n return e.EncodeElement(string(text), start)\n}\n\n");
942
+ let _ = writeln!(out, "func (c {}) MarshalText() ([]byte, error) {{", name);
884
943
  out.push_str(" count := 0\n var value string\n");
885
944
  for (branch, field) in u.branches.iter().zip(&fields) {
886
945
  if matches!(&branch.type_ref, TypeRef::Named(qname) if matches!(ir.types.get(qname), Some(TypeDef::Union(inner)) if inner.is_lexical()))
887
946
  {
888
- let _ = writeln!(out, " if c.{field} != nil {{ data, err := xml.Marshal(c.{field}); if err != nil {{ return err }}; if err := xml.Unmarshal(data, &value); err != nil {{ return err }}; count++ }}");
947
+ let _ = writeln!(out, " if c.{field} != nil {{ data, err := xml.Marshal(c.{field}); if err != nil {{ return nil, err }}; if err := xml.Unmarshal(data, &value); err != nil {{ return nil, err }}; count++ }}");
948
+ continue;
949
+ }
950
+ if matches!(
951
+ super::primitive_base(&branch.type_ref, ir),
952
+ TypeRef::Primitive(
953
+ PrimitiveType::Date | PrimitiveType::Time | PrimitiveType::DateTime
954
+ )
955
+ ) {
956
+ let _ = writeln!(out, " if c.{field} != nil {{ text, err := c.{field}.MarshalText(); if err != nil {{ return nil, err }}; count++; value = string(text) }}");
889
957
  continue;
890
958
  }
891
959
  let expr = if matches!(
@@ -912,7 +980,7 @@ impl GoCodegen {
912
980
  field, expr
913
981
  );
914
982
  }
915
- out.push_str(" if count != 1 { return fmt.Errorf(\"lexical union requires exactly one member\") }\n return e.EncodeElement(value, start)\n}\n\n");
983
+ out.push_str(" if count != 1 { return nil, fmt.Errorf(\"lexical union requires exactly one member\") }\n return []byte(value), nil\n}\n\n");
916
984
  }
917
985
 
918
986
  fn emit_struct(&self, out: &mut String, s: &StructDef, ir: &SchemaIR) {
@@ -1036,7 +1104,7 @@ impl GoCodegen {
1036
1104
 
1037
1105
  writeln!(out, "}}\n").unwrap();
1038
1106
 
1039
- if s.is_mixed && self.options.emit_xml_tags {
1107
+ if ir.has_ordered_content(s) && self.options.emit_xml_tags {
1040
1108
  self.emit_mixed_struct_xml(out, s, ir, &field_names);
1041
1109
  }
1042
1110
 
@@ -1162,7 +1230,9 @@ impl GoCodegen {
1162
1230
  let xml_val = match f.kind {
1163
1231
  FieldKind::Attribute => {
1164
1232
  let attr_name = match &f.namespace {
1165
- Some(ns) if !ns.is_empty() => format!("{} {}", ns, f.xml_name),
1233
+ Some(ns) if !ns.is_empty() && is_namespaced_ref(f, s, ir) => {
1234
+ format!("{} {}", ns, f.xml_name)
1235
+ }
1166
1236
  _ => f.xml_name.clone(),
1167
1237
  };
1168
1238
  if is_opt {
@@ -1175,10 +1245,11 @@ impl GoCodegen {
1175
1245
  FieldKind::Any => ",any".to_string(),
1176
1246
  FieldKind::AnyAttribute => ",any,attr".to_string(),
1177
1247
  FieldKind::Element => {
1178
- let elem_name = if is_namespaced_ref(f, s, ir) {
1179
- format!("{} {}", f.namespace.as_ref().unwrap(), f.xml_name)
1180
- } else {
1181
- f.xml_name.clone()
1248
+ let elem_name = match &f.namespace {
1249
+ Some(ns) if !ns.is_empty() && is_namespaced_ref(f, s, ir) => {
1250
+ format!("{} {}", ns, f.xml_name)
1251
+ }
1252
+ _ => f.xml_name.clone(),
1182
1253
  };
1183
1254
  if f.xml_name.is_empty() {
1184
1255
  ",any".to_string()
@@ -1630,7 +1701,24 @@ impl GoCodegen {
1630
1701
  }
1631
1702
  }
1632
1703
  }
1633
- writeln!(out, "type {} = {}\n", elem_alias, target_type).unwrap();
1704
+ if self.options.emit_xml_tags {
1705
+ let namespace = elem.qname.namespace.as_deref().unwrap_or("");
1706
+ writeln!(out, "type {} {}\n", elem_alias, target_type).unwrap();
1707
+ if matches!(&elem.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Struct(s)) if s.is_abstract))
1708
+ {
1709
+ writeln!(out, "func (r {elem_alias}) Selected() string {{ return {target_type}(r).Selected() }}\n").unwrap();
1710
+ }
1711
+ writeln!(out, "func (r *{elem_alias}) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {{\n if start.Name.Local != {:?} || start.Name.Space != {:?} {{ return fmt.Errorf(\"unexpected root element: %v\", start.Name) }}\n return d.DecodeElement((*{target_type})(r), &start)\n}}\n", elem.qname.local, namespace).unwrap();
1712
+ let bind_name = if matches!(&elem.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Struct(_))))
1713
+ {
1714
+ " value.XMLName = start.Name\n"
1715
+ } else {
1716
+ ""
1717
+ };
1718
+ writeln!(out, "func (r {elem_alias}) MarshalXML(e *xml.Encoder, start xml.StartElement) error {{\n start.Name = xml.Name{{Local: {:?}, Space: {:?}}}\n value := {target_type}(r)\n{bind_name} return e.EncodeElement(value, start)\n}}\n", elem.qname.local, namespace).unwrap();
1719
+ } else {
1720
+ writeln!(out, "type {} = {}\n", elem_alias, target_type).unwrap();
1721
+ }
1634
1722
  }
1635
1723
  }
1636
1724
  }
@@ -298,7 +298,7 @@ impl JavaCodegen {
298
298
  if text_field.is_some() {
299
299
  out.push_str(" if (event == XMLStreamConstants.CHARACTERS || event == XMLStreamConstants.CDATA) text.append(reader.getText());\n");
300
300
  }
301
- if s.is_mixed {
301
+ if ir.has_ordered_content(s) {
302
302
  if let Some((field, id, union)) = fields.iter().find_map(|(field, id)| {
303
303
  self.resolve_union_def(&field.type_ref, ir)
304
304
  .filter(|union| union.is_mixed_content())
@@ -305,7 +305,7 @@ impl PythonCodegen {
305
305
  }
306
306
 
307
307
  fn emit_imports(&self, out: &mut String, ir: &SchemaIR) {
308
- let mut has_structs = false;
308
+ let mut has_structs = self.options.emit_root_aliases && !ir.elements.is_empty();
309
309
  let mut has_enums = false;
310
310
  let mut has_decimal = false;
311
311
  let mut has_annotated = false;
@@ -634,7 +634,7 @@ impl PythonCodegen {
634
634
  has_body = true;
635
635
  }
636
636
 
637
- if self.options.emit_meta || s.is_mixed {
637
+ if self.options.emit_meta || ir.has_ordered_content(s) {
638
638
  if has_body {
639
639
  out.push('\n');
640
640
  }
@@ -644,6 +644,7 @@ impl PythonCodegen {
644
644
  out.push_str(" class Meta:\n");
645
645
  }
646
646
  let _ = writeln!(out, " name = \"{}\"", s.qname.local);
647
+ out.push_str(" strict_root = False\n");
647
648
  if let Some(ref ns) = s.qname.namespace {
648
649
  let _ = writeln!(out, " namespace = \"{}\"", ns);
649
650
  }
@@ -652,8 +653,10 @@ impl PythonCodegen {
652
653
  // derivations.
653
654
  if s.is_abstract {
654
655
  out.push_str(" abstract = True\n");
656
+ } else if struct_base.is_some() {
657
+ out.push_str(" abstract = False\n");
655
658
  }
656
- if s.is_mixed {
659
+ if ir.has_ordered_content(s) {
657
660
  if let Some(item_field) = s
658
661
  .fields
659
662
  .iter()
@@ -722,7 +725,28 @@ impl PythonCodegen {
722
725
  out.push_str(" ) -> bytes:\n");
723
726
  out.push_str(" \"\"\"Serialize this model instance into XML bytes.\"\"\"\n");
724
727
  out.push_str(" import polyxml\n");
725
- out.push_str(" return polyxml.serialize(self, indent=indent, namespaces=namespaces, ns_map=ns_map)\n\n");
728
+ let mut ancestors = BTreeSet::new();
729
+ let mut base = s.base_type.as_ref();
730
+ while let Some(q) = base {
731
+ if !ancestors.insert(q.clone()) {
732
+ break;
733
+ }
734
+ base = match ir.types.get(q) {
735
+ Some(TypeDef::Struct(parent)) => parent.base_type.as_ref(),
736
+ _ => None,
737
+ };
738
+ }
739
+ let roots = ir.elements.values().filter(|element| matches!(&element.type_ref, TypeRef::Named(q) if ancestors.contains(q) && matches!(ir.types.get(q), Some(TypeDef::Struct(parent)) if parent.is_abstract))).collect::<Vec<_>>();
740
+ if let [root] = roots.as_slice() {
741
+ let root_name = AsPascalCase(&root.qname.local).to_string();
742
+ if !ir.types.keys().any(|q| type_ident(q) == root_name) {
743
+ let _ = writeln!(out, " return polyxml.serialize(self, target_type={}, indent=indent, namespaces=namespaces, ns_map=ns_map)\n", root_name);
744
+ } else {
745
+ out.push_str(" return polyxml.serialize(self, indent=indent, namespaces=namespaces, ns_map=ns_map)\n\n");
746
+ }
747
+ } else {
748
+ out.push_str(" return polyxml.serialize(self, indent=indent, namespaces=namespaces, ns_map=ns_map)\n\n");
749
+ }
726
750
  out.push_str(" @classmethod\n");
727
751
  out.push_str(" def from_json(cls, data: bytes | str) -> Self:\n");
728
752
  out.push_str(" \"\"\"Deserialize JSON bytes or string into this model.\"\"\"\n");
@@ -1077,11 +1101,124 @@ impl PythonCodegen {
1077
1101
  }
1078
1102
 
1079
1103
  for element in ir.elements.values() {
1080
- let el_name = AsPascalCase(&element.qname.local).to_string();
1104
+ let mut el_name = AsPascalCase(&element.qname.local).to_string();
1105
+ let root_count = ir
1106
+ .elements
1107
+ .values()
1108
+ .filter(|other| AsPascalCase(&other.qname.local).to_string() == el_name)
1109
+ .count();
1110
+ if root_count == 1
1111
+ && declared_names.contains(&el_name)
1112
+ && self.options.emit_meta
1113
+ && matches!(&element.type_ref, TypeRef::Named(q) if q == &element.qname && type_ident(q) == el_name && matches!(ir.types.get(q), Some(TypeDef::Struct(_))))
1114
+ {
1115
+ let _ = writeln!(
1116
+ out,
1117
+ "\n{}.Meta.strict_root = True\n{}.Meta.name = {:?}",
1118
+ el_name, el_name, element.qname.local
1119
+ );
1120
+ continue;
1121
+ }
1122
+ if declared_names.contains(&el_name) {
1123
+ let base = format!("{}Element", el_name);
1124
+ el_name = base.clone();
1125
+ let mut suffix = 2;
1126
+ while declared_names.contains(&el_name) {
1127
+ el_name = format!("{base}{suffix}");
1128
+ suffix += 1;
1129
+ }
1130
+ }
1081
1131
  if !declared_names.contains(&el_name) {
1082
1132
  let target_type = self.context.map_type_ref(&element.type_ref);
1083
1133
  if el_name != target_type {
1084
- let _ = writeln!(out, "\ntype {} = {}", el_name, target_type);
1134
+ if matches!(&element.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Struct(_))))
1135
+ {
1136
+ if self.options.backend != PythonBackend::Pydantic {
1137
+ let _ = writeln!(
1138
+ out,
1139
+ "\n@dataclass(slots={}, kw_only={})",
1140
+ if self.options.slots { "True" } else { "False" },
1141
+ if self.options.kw_only {
1142
+ "True"
1143
+ } else {
1144
+ "False"
1145
+ }
1146
+ );
1147
+ }
1148
+ let meta_base = if self.options.emit_meta {
1149
+ format!("({}.Meta)", target_type)
1150
+ } else {
1151
+ String::new()
1152
+ };
1153
+ let _ = writeln!(out, "class {}({}):\n class Meta{}:\n name = {:?}\n namespace = {:?}\n strict_root = True", el_name, target_type, meta_base, element.qname.local, element.qname.namespace.as_deref().unwrap_or(""));
1154
+ let _ = writeln!(out, " root_type = {}", target_type);
1155
+ } else {
1156
+ let fields = if let TypeRef::Named(q) = &element.type_ref {
1157
+ if let Some(TypeDef::Union(union)) = ir
1158
+ .types
1159
+ .get(q)
1160
+ .filter(|t| matches!(t, TypeDef::Union(u) if !u.is_lexical()))
1161
+ {
1162
+ union
1163
+ .branches
1164
+ .iter()
1165
+ .map(|branch| {
1166
+ let mut field = FieldDef::new(
1167
+ &branch.variant_name,
1168
+ &branch.xml_name,
1169
+ FieldKind::Element,
1170
+ branch.type_ref.clone(),
1171
+ );
1172
+ field.namespace = branch.namespace.clone();
1173
+ field.cardinality = crate::ir::Cardinality::optional_one();
1174
+ field
1175
+ })
1176
+ .collect()
1177
+ } else {
1178
+ vec![FieldDef::new(
1179
+ "value",
1180
+ "",
1181
+ FieldKind::Text,
1182
+ element.type_ref.clone(),
1183
+ )]
1184
+ }
1185
+ } else {
1186
+ let mut value = FieldDef::new(
1187
+ "value",
1188
+ "",
1189
+ FieldKind::Text,
1190
+ element.type_ref.clone(),
1191
+ );
1192
+ if matches!(
1193
+ element.type_ref,
1194
+ TypeRef::Primitive(
1195
+ PrimitiveType::AnyType | PrimitiveType::AnySimpleType
1196
+ )
1197
+ ) {
1198
+ value.cardinality = crate::ir::Cardinality::optional_one();
1199
+ } else if matches!(
1200
+ element.type_ref,
1201
+ TypeRef::Primitive(PrimitiveType::String)
1202
+ ) {
1203
+ value.default_value = Some(String::new());
1204
+ }
1205
+ vec![value]
1206
+ };
1207
+ let wrapper = StructDef {
1208
+ qname: QName::new(element.qname.namespace.clone(), &el_name),
1209
+ base_type: None,
1210
+ is_abstract: false,
1211
+ is_mixed: false,
1212
+ fields,
1213
+ documentation: element.documentation.clone(),
1214
+ };
1215
+ self.emit_struct(out, &wrapper, ir);
1216
+ let _ = writeln!(
1217
+ out,
1218
+ "{}.Meta.strict_root = True\n{}.Meta.name = {:?}",
1219
+ el_name, el_name, element.qname.local
1220
+ );
1221
+ }
1085
1222
  declared_names.insert(el_name);
1086
1223
  }
1087
1224
  }
@@ -1630,7 +1630,7 @@ impl RustCodegen {
1630
1630
  out.push_str(" },\n");
1631
1631
 
1632
1632
  out.push_str(" Event::End(e) if e.local_name().as_ref() == start.local_name().as_ref() => break,\n");
1633
- if s.is_mixed {
1633
+ if ir.has_ordered_content(s) {
1634
1634
  let item_union = field_metas.iter().find_map(|meta| {
1635
1635
  self.resolve_union_def(&meta.field.type_ref, ir)
1636
1636
  .filter(|u| u.is_mixed_content())