polyxml 0.34.2__tar.gz → 0.34.4__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 (85) hide show
  1. {polyxml-0.34.2 → polyxml-0.34.4}/Cargo.lock +6 -6
  2. {polyxml-0.34.2 → polyxml-0.34.4}/Cargo.toml +1 -1
  3. {polyxml-0.34.2 → polyxml-0.34.4}/PKG-INFO +1 -1
  4. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/csharp/mod.rs +100 -1
  5. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/rust/mod.rs +78 -8
  6. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/schema_parser/mod.rs +88 -25
  7. polyxml-0.34.4/crates/polyxml-core/tests/test_qualified_attributes.rs +170 -0
  8. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_rust_codegen.rs +60 -1
  9. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_schema_ir.rs +137 -0
  10. {polyxml-0.34.2 → polyxml-0.34.4}/pyproject.toml +1 -1
  11. polyxml-0.34.2/crates/polyxml-core/tests/test_qualified_attributes.rs +0 -80
  12. {polyxml-0.34.2 → polyxml-0.34.4}/README.md +0 -0
  13. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/Cargo.toml +0 -0
  14. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/README.md +0 -0
  15. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  16. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  17. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  18. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  19. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
  20. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
  21. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  22. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  23. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  24. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  25. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
  26. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  27. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/converters.rs +0 -0
  28. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/error.rs +0 -0
  29. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/integer.rs +0 -0
  30. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/ir/chunker.rs +0 -0
  31. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/ir/mod.rs +0 -0
  32. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/ir/particle.rs +0 -0
  33. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  34. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/json.rs +0 -0
  35. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/lib.rs +0 -0
  36. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/parser.rs +0 -0
  37. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/schema.rs +0 -0
  38. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/schema_parser/upa.rs +0 -0
  39. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/serializer.rs +0 -0
  40. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/transcoder.rs +0 -0
  41. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/src/value.rs +0 -0
  42. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  43. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
  44. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/fixtures/ts_mixed_content_inheritance.xsd +0 -0
  45. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  46. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_any_attribute.rs +0 -0
  47. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_any_element.rs +0 -0
  48. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  49. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_core.rs +0 -0
  50. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  51. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
  52. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
  53. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  54. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_json.rs +0 -0
  55. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_lexical_lists.rs +0 -0
  56. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  57. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
  58. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  59. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
  60. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  61. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  62. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_security.rs +0 -0
  63. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_temporal_union.rs +0 -0
  64. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  65. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  66. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_ubl_csharp.rs +0 -0
  67. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  68. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_upa.rs +0 -0
  69. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_value_constraints.rs +0 -0
  70. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  71. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-python/Cargo.toml +0 -0
  72. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-python/README.md +0 -0
  73. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  74. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-python/src/lib.rs +0 -0
  75. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-python/tests/test_conformance.py +0 -0
  76. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  77. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-python/tests/test_json.py +0 -0
  78. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
  79. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  80. {polyxml-0.34.2 → polyxml-0.34.4}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  81. {polyxml-0.34.2 → polyxml-0.34.4}/python/polyxml/AGENT_GUIDE.md +0 -0
  82. {polyxml-0.34.2 → polyxml-0.34.4}/python/polyxml/__init__.py +0 -0
  83. {polyxml-0.34.2 → polyxml-0.34.4}/python/polyxml/compat/__init__.py +0 -0
  84. {polyxml-0.34.2 → polyxml-0.34.4}/python/polyxml/compat/xsdata.py +0 -0
  85. {polyxml-0.34.2 → polyxml-0.34.4}/python/polyxml/py.typed +0 -0
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.34.2"
611
+ version = "0.34.4"
612
612
  dependencies = [
613
613
  "criterion",
614
614
  "heck",
@@ -629,14 +629,14 @@ dependencies = [
629
629
 
630
630
  [[package]]
631
631
  name = "polyxml-c"
632
- version = "0.34.2"
632
+ version = "0.34.4"
633
633
  dependencies = [
634
634
  "polyxml",
635
635
  ]
636
636
 
637
637
  [[package]]
638
638
  name = "polyxml-cli"
639
- version = "0.34.2"
639
+ version = "0.34.4"
640
640
  dependencies = [
641
641
  "clap",
642
642
  "glob",
@@ -650,7 +650,7 @@ dependencies = [
650
650
 
651
651
  [[package]]
652
652
  name = "polyxml-js"
653
- version = "0.34.2"
653
+ version = "0.34.4"
654
654
  dependencies = [
655
655
  "napi",
656
656
  "napi-build",
@@ -660,7 +660,7 @@ dependencies = [
660
660
 
661
661
  [[package]]
662
662
  name = "polyxml-python"
663
- version = "0.34.2"
663
+ version = "0.34.4"
664
664
  dependencies = [
665
665
  "lexical-core",
666
666
  "polyxml",
@@ -670,7 +670,7 @@ dependencies = [
670
670
 
671
671
  [[package]]
672
672
  name = "polyxml-wasm"
673
- version = "0.34.2"
673
+ version = "0.34.4"
674
674
  dependencies = [
675
675
  "polyxml",
676
676
  "quick-xml",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/polyxml-core", "crates/polyxml-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.34.2"
6
+ version = "0.34.4"
7
7
  edition = "2021"
8
8
  rust-version = "1.80"
9
9
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyxml
3
- Version: 0.34.2
3
+ Version: 0.34.4
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -1099,6 +1099,53 @@ impl CSharpCodegen {
1099
1099
  true
1100
1100
  }
1101
1101
 
1102
+ fn emit_optional_enum_attribute_proxy(
1103
+ &self,
1104
+ out: &mut String,
1105
+ field: &FieldDef,
1106
+ name: &str,
1107
+ ir: &SchemaIR,
1108
+ indent: &str,
1109
+ ) -> bool {
1110
+ if !self.options.emit_xml_attributes
1111
+ || field.kind != FieldKind::Attribute
1112
+ || !field.cardinality.is_optional()
1113
+ || field.cardinality.is_list()
1114
+ {
1115
+ return false;
1116
+ }
1117
+ let TypeRef::Named(qname) = &field.type_ref else {
1118
+ return false;
1119
+ };
1120
+ if !matches!(ir.types.get(qname), Some(TypeDef::Enum(_))) {
1121
+ return false;
1122
+ }
1123
+ let ty = self.map_field_type(field, ir);
1124
+ let base = ty.trim_end_matches('?');
1125
+ let namespace = field
1126
+ .namespace
1127
+ .as_ref()
1128
+ .map(|ns| format!(", Namespace = {ns:?}"))
1129
+ .unwrap_or_default();
1130
+ if self.options.emit_json_attributes {
1131
+ writeln!(out, "{indent} [JsonPropertyName({:?})]", field.xml_name).unwrap();
1132
+ }
1133
+ writeln!(
1134
+ out,
1135
+ "{indent} [XmlIgnore] public {ty} {name} {{ get; set; }}"
1136
+ )
1137
+ .unwrap();
1138
+ if self.options.emit_json_attributes {
1139
+ // Keep the XML lexical proxy out of JSON, preserving the typed property.
1140
+ writeln!(out, "{indent} [JsonIgnore]").unwrap();
1141
+ }
1142
+ writeln!(out, "{indent} [XmlAttribute({:?}{namespace})] public string? {name}Xml {{
1143
+ {indent} get => {name}?.ToXmlValue();
1144
+ {indent} set => {name} = value is null ? null : ({base})new XmlSerializer(typeof({base}), new XmlRootAttribute(\"value\")).Deserialize(new System.IO.StringReader(\"<value>\" + System.Security.SecurityElement.Escape(value) + \"</value>\"))!;
1145
+ {indent} }}", field.xml_name).unwrap();
1146
+ true
1147
+ }
1148
+
1102
1149
  fn emit_default_scalar_proxy(
1103
1150
  &self,
1104
1151
  out: &mut String,
@@ -1379,7 +1426,10 @@ impl CSharpCodegen {
1379
1426
  format!(" : {}", base_clause.join(", "))
1380
1427
  };
1381
1428
 
1382
- let mut seen_props = HashSet::new();
1429
+ // Reserve the actual generated names on every ancestor. Distinct
1430
+ // XML members (e.g. nameOfClass attribute and NameOfClass element)
1431
+ // can normalize to the same C# identifier.
1432
+ let mut seen_props = self.inherited_property_names(s, ir, &mut HashSet::new());
1383
1433
  let prop_names: Vec<String> = s
1384
1434
  .fields
1385
1435
  .iter()
@@ -1392,6 +1442,8 @@ impl CSharpCodegen {
1392
1442
  }
1393
1443
 
1394
1444
  let has_lexical_union = self.options.emit_xml_attributes && s.fields.iter().any(|f| {
1445
+ (f.kind == FieldKind::Attribute && f.cardinality.is_optional()
1446
+ && matches!(&f.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Enum(_))))) ||
1395
1447
  matches!(f.type_ref, TypeRef::Primitive(p) if p.is_unbounded_integer()) || matches!(f.type_ref, TypeRef::Primitive(PrimitiveType::Date | PrimitiveType::DateTime | PrimitiveType::Time | PrimitiveType::Duration)) || f.default_value.is_some() || f.fixed_value.is_some() || matches!(&f.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical()))
1396
1448
  });
1397
1449
 
@@ -1404,6 +1456,7 @@ impl CSharpCodegen {
1404
1456
  .unwrap();
1405
1457
  for (f, name) in s.fields.iter().zip(&prop_names) {
1406
1458
  if self.emit_default_scalar_proxy(out, f, name, ir, indent)
1459
+ || self.emit_optional_enum_attribute_proxy(out, f, name, ir, indent)
1407
1460
  || self.emit_integer_proxy(out, f, name, ir, indent)
1408
1461
  || self.emit_temporal_proxy(out, f, name, ir, indent)
1409
1462
  {
@@ -1471,6 +1524,7 @@ impl CSharpCodegen {
1471
1524
  writeln!(out, "{} public {}() {{ }}", indent, struct_name).unwrap();
1472
1525
  for (f, name) in s.fields.iter().zip(&prop_names) {
1473
1526
  if self.emit_default_scalar_proxy(out, f, name, ir, indent)
1527
+ || self.emit_optional_enum_attribute_proxy(out, f, name, ir, indent)
1474
1528
  || self.emit_integer_proxy(out, f, name, ir, indent)
1475
1529
  || self.emit_temporal_proxy(out, f, name, ir, indent)
1476
1530
  {
@@ -1627,6 +1681,51 @@ impl CSharpCodegen {
1627
1681
  writeln!(out, "{}}}\n", indent).unwrap();
1628
1682
  }
1629
1683
 
1684
+ fn inherited_property_names(
1685
+ &self,
1686
+ s: &StructDef,
1687
+ ir: &SchemaIR,
1688
+ visited: &mut HashSet<QName>,
1689
+ ) -> HashSet<String> {
1690
+ let Some(base) = s.base_type.as_ref() else {
1691
+ return HashSet::new();
1692
+ };
1693
+ if !visited.insert(base.clone()) {
1694
+ return HashSet::new();
1695
+ }
1696
+ let Some(TypeDef::Struct(parent)) = ir.types.get(base) else {
1697
+ return HashSet::new();
1698
+ };
1699
+ let mut names = self.inherited_property_names(parent, ir, visited);
1700
+ let mut ancestor_fields = Vec::new();
1701
+ let mut next = parent.base_type.as_ref();
1702
+ let mut seen = HashSet::new();
1703
+ while let Some(qname) = next {
1704
+ if !seen.insert(qname.clone()) {
1705
+ break;
1706
+ }
1707
+ let Some(TypeDef::Struct(ancestor)) = ir.types.get(qname) else {
1708
+ break;
1709
+ };
1710
+ ancestor_fields.extend(ancestor.fields.iter());
1711
+ next = ancestor.base_type.as_ref();
1712
+ }
1713
+ let enclosing = type_ident(&parent.qname);
1714
+ for field in &parent.fields {
1715
+ let inherited = ancestor_fields.iter().any(|f| {
1716
+ (field.kind == FieldKind::Text && f.kind == FieldKind::Text)
1717
+ || (field.kind == f.kind
1718
+ && field.xml_name == f.xml_name
1719
+ && field.namespace == f.namespace
1720
+ && field.name == f.name)
1721
+ });
1722
+ if !inherited {
1723
+ self.unique_property_name(&field.name, &mut names, Some(&enclosing));
1724
+ }
1725
+ }
1726
+ names
1727
+ }
1728
+
1630
1729
  fn unique_property_name(
1631
1730
  &self,
1632
1731
  name: &str,
@@ -1333,7 +1333,10 @@ impl RustCodegen {
1333
1333
  if field.kind != FieldKind::Element {
1334
1334
  continue;
1335
1335
  }
1336
- let tags: Vec<String> = match self.resolve_union_def(&field.type_ref, ir) {
1336
+ let tags: Vec<String> = match self
1337
+ .resolve_union_def(&field.type_ref, ir)
1338
+ .filter(|_| field.xml_name.is_empty())
1339
+ {
1337
1340
  Some(u) => u
1338
1341
  .branches
1339
1342
  .iter()
@@ -1495,7 +1498,10 @@ impl RustCodegen {
1495
1498
  }
1496
1499
 
1497
1500
  for meta in &elem_fields {
1498
- if let Some(union_def) = self.resolve_union_def(&meta.field.type_ref, ir) {
1501
+ if let Some(union_def) = self
1502
+ .resolve_union_def(&meta.field.type_ref, ir)
1503
+ .filter(|_| meta.field.xml_name.is_empty())
1504
+ {
1499
1505
  let branch_tags: Vec<_> = union_def
1500
1506
  .branches
1501
1507
  .iter()
@@ -1575,7 +1581,10 @@ impl RustCodegen {
1575
1581
  );
1576
1582
  }
1577
1583
  for meta in &elem_fields {
1578
- if let Some(union_def) = self.resolve_union_def(&meta.field.type_ref, ir) {
1584
+ if let Some(union_def) = self
1585
+ .resolve_union_def(&meta.field.type_ref, ir)
1586
+ .filter(|_| meta.field.xml_name.is_empty())
1587
+ {
1579
1588
  let branch_tags: Vec<_> = union_def
1580
1589
  .branches
1581
1590
  .iter()
@@ -2069,10 +2078,14 @@ impl RustCodegen {
2069
2078
  } else {
2070
2079
  let target_type = self.format_rust_type_ref(&field.type_ref, types_with_lifetime);
2071
2080
  let target_clean = target_type.split('<').next().unwrap_or(&target_type);
2081
+ let decode = if self.resolve_union_def(&field.type_ref, ir).is_some() {
2082
+ "decode_xml_wrapped"
2083
+ } else {
2084
+ "decode_xml"
2085
+ };
2072
2086
  let _ = writeln!(
2073
2087
  out,
2074
- " let val = {}::decode_xml(reader, &e)?;",
2075
- target_clean
2088
+ " let val = {target_clean}::{decode}(reader, &e)?;"
2076
2089
  );
2077
2090
  let final_val = if is_boxed { "Box::new(val)" } else { "val" };
2078
2091
  if is_list {
@@ -2102,6 +2115,10 @@ impl RustCodegen {
2102
2115
  let is_list = field.cardinality.is_list() || field.type_ref.is_list();
2103
2116
  let is_boxed = field.is_cycle_cut || field.type_ref.is_boxed();
2104
2117
 
2118
+ if self.resolve_union_def(&field.type_ref, ir).is_some() {
2119
+ let _ = writeln!(out, " return Err(PolyXmlError::SchemaError(\"Missing choice in {}\".into()));", field.xml_name);
2120
+ return;
2121
+ }
2105
2122
  if self.field_is_string(&field.type_ref, ir) {
2106
2123
  self.emit_pattern_check(out, field, "\"\"", ir);
2107
2124
  if self.options.zero_copy {
@@ -2399,7 +2416,15 @@ impl RustCodegen {
2399
2416
  field.xml_name
2400
2417
  );
2401
2418
  } else if self.resolve_union_def(&field.type_ref, ir).is_some() {
2402
- let _ = writeln!(out, " item.encode_xml(writer, None)?;",);
2419
+ if field.xml_name.is_empty() {
2420
+ let _ = writeln!(out, " item.encode_xml(writer, None)?;");
2421
+ } else {
2422
+ let _ = writeln!(
2423
+ out,
2424
+ " item.encode_xml_wrapped(writer, {:?})?;",
2425
+ field.xml_name
2426
+ );
2427
+ }
2403
2428
  } else {
2404
2429
  let _ = writeln!(
2405
2430
  out,
@@ -2451,7 +2476,15 @@ impl RustCodegen {
2451
2476
  field.xml_name
2452
2477
  );
2453
2478
  } else if self.resolve_union_def(&field.type_ref, ir).is_some() {
2454
- let _ = writeln!(out, " val.encode_xml(writer, None)?;",);
2479
+ if field.xml_name.is_empty() {
2480
+ let _ = writeln!(out, " val.encode_xml(writer, None)?;");
2481
+ } else {
2482
+ let _ = writeln!(
2483
+ out,
2484
+ " val.encode_xml_wrapped(writer, {:?})?;",
2485
+ field.xml_name
2486
+ );
2487
+ }
2455
2488
  } else {
2456
2489
  let _ = writeln!(
2457
2490
  out,
@@ -2506,7 +2539,15 @@ impl RustCodegen {
2506
2539
  field.xml_name
2507
2540
  );
2508
2541
  } else if self.resolve_union_def(&field.type_ref, ir).is_some() {
2509
- let _ = writeln!(out, " self.{}.encode_xml(writer, None)?;", rust_name);
2542
+ if field.xml_name.is_empty() {
2543
+ let _ = writeln!(out, " self.{}.encode_xml(writer, None)?;", rust_name);
2544
+ } else {
2545
+ let _ = writeln!(
2546
+ out,
2547
+ " self.{}.encode_xml_wrapped(writer, {:?})?;",
2548
+ rust_name, field.xml_name
2549
+ );
2550
+ }
2510
2551
  } else {
2511
2552
  let _ = writeln!(
2512
2553
  out,
@@ -2540,6 +2581,35 @@ impl RustCodegen {
2540
2581
  out.push('\n');
2541
2582
  let _ = writeln!(out, "{} {{", impl_header);
2542
2583
 
2584
+ let reader_lifetime = if needs_lifetime { "'a" } else { "'_" };
2585
+ let _ = writeln!(out, " pub fn decode_xml_wrapped(reader: &mut Reader<&{reader_lifetime} [u8]>, start: &BytesStart<'_>) -> Result<Self> {{");
2586
+ out.push_str(r#" let mut value = None;
2587
+ loop {
2588
+ match reader.read_event()? {
2589
+ Event::Start(e) => {
2590
+ if value.is_some() { return Err(PolyXmlError::SchemaError("Multiple choice alternatives".into())); }
2591
+ value = Some(Self::decode_xml(reader, &e)?);
2592
+ }
2593
+ Event::Empty(e) => {
2594
+ if value.is_some() { return Err(PolyXmlError::SchemaError("Multiple choice alternatives".into())); }
2595
+ let mut empty_reader = Reader::from_str("");
2596
+ value = Some(Self::decode_xml(&mut empty_reader, &e)?);
2597
+ }
2598
+ Event::End(e) if e.name() == start.name() => break,
2599
+ Event::Eof => return Err(PolyXmlError::SchemaError("Unclosed choice wrapper".into())),
2600
+ _ => {}
2601
+ }
2602
+ }
2603
+ value.ok_or_else(|| PolyXmlError::SchemaError("Missing choice alternative".into()))
2604
+ }
2605
+ pub fn encode_xml_wrapped<W: std::io::Write>(&self, writer: &mut Writer<W>, tag: &str) -> Result<()> {
2606
+ writer.write_event(Event::Start(BytesStart::new(tag)))?;
2607
+ self.encode_xml(writer, None)?;
2608
+ writer.write_event(Event::End(BytesEnd::new(tag)))?;
2609
+ Ok(())
2610
+ }
2611
+ "#);
2612
+
2543
2613
  let from_xml_sig = if needs_lifetime {
2544
2614
  "pub fn from_xml(xml: &'a str) -> Result<Self>"
2545
2615
  } else {
@@ -350,20 +350,13 @@ impl XsdParser {
350
350
  }
351
351
  }
352
352
  "attribute" => {
353
- if let (Some(name), Some(ty)) =
354
- (get_attr_value(e, "name"), get_attr_value(e, "type"))
355
- {
356
- ir.attributes
357
- .entry(QName::new(target_namespace.as_deref(), name))
358
- .or_insert_with(|| {
359
- resolve_type_ref(
360
- &ty,
361
- target_namespace.as_deref(),
362
- &prefixes,
363
- )
364
- });
365
- }
366
- skip_subtree(&mut reader)?;
353
+ self.parse_global_attribute(
354
+ &mut reader,
355
+ e,
356
+ target_namespace.as_deref(),
357
+ &prefixes,
358
+ &mut ir,
359
+ )?;
367
360
  }
368
361
  "group" => {
369
362
  // Named model group definition.
@@ -445,18 +438,19 @@ impl XsdParser {
445
438
  }
446
439
  }
447
440
  "attribute" => {
448
- if let (Some(name), Some(ty)) =
449
- (get_attr_value(e, "name"), get_attr_value(e, "type"))
450
- {
451
- ir.attributes
452
- .entry(QName::new(target_namespace.as_deref(), name))
453
- .or_insert_with(|| {
441
+ if let Some(name) = get_attr_value(e, "name") {
442
+ let type_ref = get_attr_value(e, "type")
443
+ .map(|ty| {
454
444
  resolve_type_ref(
455
445
  &ty,
456
446
  target_namespace.as_deref(),
457
447
  &prefixes,
458
448
  )
459
- });
449
+ })
450
+ .unwrap_or_else(TypeRef::string);
451
+ ir.attributes
452
+ .entry(QName::new(target_namespace.as_deref(), name))
453
+ .or_insert(type_ref);
460
454
  }
461
455
  }
462
456
  "group" => {
@@ -1225,6 +1219,7 @@ impl XsdParser {
1225
1219
  let mut facets = RestrictionFacets::default();
1226
1220
  let mut enum_values = Vec::new();
1227
1221
  let mut union_branches: Option<Vec<UnionBranch>> = None;
1222
+ let mut list_requires_inline = false;
1228
1223
  let mut documentation = None;
1229
1224
  let mut buf = Vec::new();
1230
1225
 
@@ -1252,14 +1247,30 @@ impl XsdParser {
1252
1247
  &item, target_ns, prefixes,
1253
1248
  )));
1254
1249
  } else {
1255
- return Err(SchemaError::Malformed(
1256
- "xs:list requires a supported itemType".into(),
1257
- ));
1250
+ list_requires_inline = true;
1258
1251
  }
1259
1252
  }
1260
1253
  "union" => {
1261
1254
  union_branches = Some(parse_union_members(e, target_ns, prefixes));
1262
1255
  }
1256
+ "simpleType" if list_requires_inline => {
1257
+ let item_name =
1258
+ unique_type_name(ir, target_ns, &format!("{}Item", qname.local));
1259
+ if let Some(item) = self.parse_simple_type(
1260
+ reader,
1261
+ e,
1262
+ target_ns,
1263
+ prefixes,
1264
+ Some(item_name),
1265
+ ir,
1266
+ )? {
1267
+ base_type =
1268
+ TypeRef::List(Box::new(TypeRef::Named(item.qname().clone())));
1269
+ ir.add_type(item);
1270
+ list_requires_inline = false;
1271
+ }
1272
+ depth -= 1;
1273
+ }
1263
1274
  "simpleType" if union_branches.is_some() => {
1264
1275
  let branch_index = union_branches.as_ref().unwrap().len() + 1;
1265
1276
  let inline_name = unique_type_name(
@@ -1380,7 +1391,12 @@ impl XsdParser {
1380
1391
  _ => {}
1381
1392
  }
1382
1393
  }
1383
- Event::End(_) => {
1394
+ Event::End(ref e) => {
1395
+ if strip_prefix(e.name().into_inner()) == "list" && list_requires_inline {
1396
+ return Err(SchemaError::Malformed(
1397
+ "xs:list requires itemType or an inline simpleType".into(),
1398
+ ));
1399
+ }
1384
1400
  depth -= 1;
1385
1401
  }
1386
1402
  Event::Eof => break,
@@ -1465,6 +1481,46 @@ impl XsdParser {
1465
1481
  }
1466
1482
  }
1467
1483
 
1484
+ fn parse_global_attribute(
1485
+ &self,
1486
+ reader: &mut Reader<&[u8]>,
1487
+ start: &BytesStart,
1488
+ target_ns: Option<&str>,
1489
+ prefixes: &HashMap<String, String>,
1490
+ ir: &mut SchemaIR,
1491
+ ) -> Result<(), SchemaError> {
1492
+ let Some(name) = get_attr_value(start, "name") else {
1493
+ return skip_subtree(reader);
1494
+ };
1495
+ let mut type_ref = get_attr_value(start, "type")
1496
+ .map(|ty| resolve_type_ref(&ty, target_ns, prefixes))
1497
+ .unwrap_or_else(TypeRef::string);
1498
+ loop {
1499
+ match reader.read_event()? {
1500
+ Event::Start(e) if strip_prefix(e.name().into_inner()) == "simpleType" => {
1501
+ let anonymous_name =
1502
+ unique_type_name(ir, target_ns, &format!("{name}AttributeType"));
1503
+ if let Some(definition) = self.parse_simple_type(
1504
+ reader,
1505
+ &e,
1506
+ target_ns,
1507
+ prefixes,
1508
+ Some(anonymous_name),
1509
+ ir,
1510
+ )? {
1511
+ type_ref = TypeRef::Named(definition.qname().clone());
1512
+ ir.add_type(definition);
1513
+ }
1514
+ }
1515
+ Event::Start(_) => skip_subtree(reader)?,
1516
+ Event::End(_) | Event::Eof => break,
1517
+ _ => {}
1518
+ }
1519
+ }
1520
+ ir.attributes.insert(QName::new(target_ns, name), type_ref);
1521
+ Ok(())
1522
+ }
1523
+
1468
1524
  fn parse_global_element(
1469
1525
  &mut self,
1470
1526
  reader: &mut Reader<&[u8]>,
@@ -2728,6 +2784,7 @@ fn validate_type_references(ir: &SchemaIR, xml: &str) -> Result<(), SchemaError>
2728
2784
  }
2729
2785
  fn validate_schema_grammar(xml: &str) -> Result<(), SchemaError> {
2730
2786
  let mut reader = Reader::from_str(xml);
2787
+ let mut schema_namespace = None;
2731
2788
  let mut stack = Vec::<(String, HashMap<String, String>)>::new();
2732
2789
  let mut line = 1;
2733
2790
  let mut line_position = 0;
@@ -2754,6 +2811,9 @@ fn validate_schema_grammar(xml: &str) -> Result<(), SchemaError> {
2754
2811
  }
2755
2812
  }
2756
2813
  let local = strip_prefix(element.name().into_inner());
2814
+ if stack.is_empty() && local == "schema" {
2815
+ schema_namespace = get_attr_value(&element, "targetNamespace");
2816
+ }
2757
2817
  let position = reader.buffer_position() as usize;
2758
2818
  line += xml[line_position..position]
2759
2819
  .bytes()
@@ -2804,6 +2864,9 @@ fn validate_schema_grammar(xml: &str) -> Result<(), SchemaError> {
2804
2864
  )));
2805
2865
  };
2806
2866
  if namespace == "http://www.w3.org/2001/XMLSchema"
2867
+ // The schema-for-schemas declares named types in this
2868
+ // namespace; the resolved IR validates their existence.
2869
+ && schema_namespace.as_deref() != Some(namespace.as_str())
2807
2870
  && attr.key.as_ref() != "ref"
2808
2871
  && PrimitiveType::from_xsd_name(local).is_none()
2809
2872
  {