polyxml 0.34.1__tar.gz → 0.34.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. {polyxml-0.34.1 → polyxml-0.34.3}/Cargo.lock +6 -6
  2. {polyxml-0.34.1 → polyxml-0.34.3}/Cargo.toml +1 -1
  3. {polyxml-0.34.1 → polyxml-0.34.3}/PKG-INFO +1 -1
  4. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/csharp/mod.rs +51 -0
  5. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/python/mod.rs +1 -1
  6. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/rust/mod.rs +5 -3
  7. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/schema_parser/mod.rs +95 -26
  8. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_python_codegen.rs +29 -0
  9. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_qualified_attributes.rs +50 -3
  10. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_rust_codegen.rs +54 -0
  11. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_schema_audit.rs +27 -0
  12. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_schema_ir.rs +137 -0
  13. {polyxml-0.34.1 → polyxml-0.34.3}/pyproject.toml +1 -1
  14. {polyxml-0.34.1 → polyxml-0.34.3}/README.md +0 -0
  15. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/Cargo.toml +0 -0
  16. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/README.md +0 -0
  17. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  18. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  19. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  20. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  21. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
  22. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
  23. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  24. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  25. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  26. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  27. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  28. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/converters.rs +0 -0
  29. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/error.rs +0 -0
  30. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/integer.rs +0 -0
  31. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/ir/chunker.rs +0 -0
  32. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/ir/mod.rs +0 -0
  33. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/ir/particle.rs +0 -0
  34. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  35. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/json.rs +0 -0
  36. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/lib.rs +0 -0
  37. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/parser.rs +0 -0
  38. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/schema.rs +0 -0
  39. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/schema_parser/upa.rs +0 -0
  40. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/serializer.rs +0 -0
  41. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/transcoder.rs +0 -0
  42. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/src/value.rs +0 -0
  43. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  44. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
  45. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/fixtures/ts_mixed_content_inheritance.xsd +0 -0
  46. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  47. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_any_attribute.rs +0 -0
  48. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_any_element.rs +0 -0
  49. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  50. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_core.rs +0 -0
  51. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  52. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
  53. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
  54. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  55. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_json.rs +0 -0
  56. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_lexical_lists.rs +0 -0
  57. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  58. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
  59. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  60. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  61. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_security.rs +0 -0
  62. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_temporal_union.rs +0 -0
  63. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  64. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  65. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_ubl_csharp.rs +0 -0
  66. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  67. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_upa.rs +0 -0
  68. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_value_constraints.rs +0 -0
  69. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  70. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-python/Cargo.toml +0 -0
  71. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-python/README.md +0 -0
  72. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  73. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-python/src/lib.rs +0 -0
  74. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-python/tests/test_conformance.py +0 -0
  75. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  76. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-python/tests/test_json.py +0 -0
  77. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
  78. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  79. {polyxml-0.34.1 → polyxml-0.34.3}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  80. {polyxml-0.34.1 → polyxml-0.34.3}/python/polyxml/AGENT_GUIDE.md +0 -0
  81. {polyxml-0.34.1 → polyxml-0.34.3}/python/polyxml/__init__.py +0 -0
  82. {polyxml-0.34.1 → polyxml-0.34.3}/python/polyxml/compat/__init__.py +0 -0
  83. {polyxml-0.34.1 → polyxml-0.34.3}/python/polyxml/compat/xsdata.py +0 -0
  84. {polyxml-0.34.1 → polyxml-0.34.3}/python/polyxml/py.typed +0 -0
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.34.1"
611
+ version = "0.34.3"
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.1"
632
+ version = "0.34.3"
633
633
  dependencies = [
634
634
  "polyxml",
635
635
  ]
636
636
 
637
637
  [[package]]
638
638
  name = "polyxml-cli"
639
- version = "0.34.1"
639
+ version = "0.34.3"
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.1"
653
+ version = "0.34.3"
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.1"
663
+ version = "0.34.3"
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.1"
673
+ version = "0.34.3"
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.1"
6
+ version = "0.34.3"
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.1
3
+ Version: 0.34.3
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,
@@ -1392,6 +1439,8 @@ impl CSharpCodegen {
1392
1439
  }
1393
1440
 
1394
1441
  let has_lexical_union = self.options.emit_xml_attributes && s.fields.iter().any(|f| {
1442
+ (f.kind == FieldKind::Attribute && f.cardinality.is_optional()
1443
+ && matches!(&f.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Enum(_))))) ||
1395
1444
  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
1445
  });
1397
1446
 
@@ -1404,6 +1453,7 @@ impl CSharpCodegen {
1404
1453
  .unwrap();
1405
1454
  for (f, name) in s.fields.iter().zip(&prop_names) {
1406
1455
  if self.emit_default_scalar_proxy(out, f, name, ir, indent)
1456
+ || self.emit_optional_enum_attribute_proxy(out, f, name, ir, indent)
1407
1457
  || self.emit_integer_proxy(out, f, name, ir, indent)
1408
1458
  || self.emit_temporal_proxy(out, f, name, ir, indent)
1409
1459
  {
@@ -1471,6 +1521,7 @@ impl CSharpCodegen {
1471
1521
  writeln!(out, "{} public {}() {{ }}", indent, struct_name).unwrap();
1472
1522
  for (f, name) in s.fields.iter().zip(&prop_names) {
1473
1523
  if self.emit_default_scalar_proxy(out, f, name, ir, indent)
1524
+ || self.emit_optional_enum_attribute_proxy(out, f, name, ir, indent)
1474
1525
  || self.emit_integer_proxy(out, f, name, ir, indent)
1475
1526
  || self.emit_temporal_proxy(out, f, name, ir, indent)
1476
1527
  {
@@ -36,7 +36,7 @@ fn write_python_docstring(out: &mut String, indent: &str, doc: &str) {
36
36
  let escaped = doc
37
37
  .trim()
38
38
  .replace('\\', "\\\\")
39
- .replace("\"\"\"", "\\\"\\\"\\\"")
39
+ .replace('"', "\\\"")
40
40
  .replace('\r', "");
41
41
  let _ = writeln!(out, "{indent}\"\"\"{escaped}\"\"\"");
42
42
  }
@@ -16,7 +16,7 @@ use crate::ir::{
16
16
  /// Options configuring Rust 2021/2024 code generation.
17
17
  #[derive(Debug, Clone, Serialize, Deserialize)]
18
18
  pub struct RustOptions {
19
- /// Use zero-copy Cow<'a, str> and Cow<'a, [u8]> (default: true).
19
+ /// Use zero-copy Cow<'a, str> for XML lexical values (default: true).
20
20
  pub zero_copy: bool,
21
21
  /// Derive serde::{Serialize, Deserialize} (default: true).
22
22
  pub derive_serde: bool,
@@ -147,11 +147,13 @@ impl LanguageContext for RustLanguageContext {
147
147
  PrimitiveType::UnsignedShort => "u16",
148
148
  PrimitiveType::UnsignedByte => "u8",
149
149
 
150
+ // XML carries hex/base64 lexical text, just as the runtime schema does.
151
+ // Preserve that spelling; callers can explicitly decode binary payloads.
150
152
  PrimitiveType::HexBinary | PrimitiveType::Base64Binary => {
151
153
  if self.zero_copy {
152
- "Cow<'a, [u8]>"
154
+ "Cow<'a, str>"
153
155
  } else {
154
- "Vec<u8>"
156
+ "String"
155
157
  }
156
158
  }
157
159
  }
@@ -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]>,
@@ -2554,7 +2610,13 @@ fn collect_chain_patterns(ir: &SchemaIR, q: &QName, visited: &mut HashSet<QName>
2554
2610
  }
2555
2611
 
2556
2612
  fn merge_ir(dest: &mut SchemaIR, src: SchemaIR) {
2557
- dest.upa_documents.extend(src.upa_documents);
2613
+ // Diamond include graphs can replay the same transitive document many times.
2614
+ // Validate each identical source model once; chameleon copies differ by namespace.
2615
+ for document in src.upa_documents {
2616
+ if !dest.upa_documents.contains(&document) {
2617
+ dest.upa_documents.push(document);
2618
+ }
2619
+ }
2558
2620
  dest.abstract_elements.extend(src.abstract_elements);
2559
2621
  dest.ordered_types.extend(src.ordered_types);
2560
2622
  dest.content_models.extend(src.content_models);
@@ -2722,6 +2784,7 @@ fn validate_type_references(ir: &SchemaIR, xml: &str) -> Result<(), SchemaError>
2722
2784
  }
2723
2785
  fn validate_schema_grammar(xml: &str) -> Result<(), SchemaError> {
2724
2786
  let mut reader = Reader::from_str(xml);
2787
+ let mut schema_namespace = None;
2725
2788
  let mut stack = Vec::<(String, HashMap<String, String>)>::new();
2726
2789
  let mut line = 1;
2727
2790
  let mut line_position = 0;
@@ -2748,6 +2811,9 @@ fn validate_schema_grammar(xml: &str) -> Result<(), SchemaError> {
2748
2811
  }
2749
2812
  }
2750
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
+ }
2751
2817
  let position = reader.buffer_position() as usize;
2752
2818
  line += xml[line_position..position]
2753
2819
  .bytes()
@@ -2798,6 +2864,9 @@ fn validate_schema_grammar(xml: &str) -> Result<(), SchemaError> {
2798
2864
  )));
2799
2865
  };
2800
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())
2801
2870
  && attr.key.as_ref() != "ref"
2802
2871
  && PrimitiveType::from_xsd_name(local).is_none()
2803
2872
  {
@@ -793,3 +793,32 @@ fn test_python_sequence_nested_inside_choice_codegen() {
793
793
  assert!(code.contains("first: str | None = field("));
794
794
  assert!(code.contains("second: str | None = field("));
795
795
  }
796
+
797
+ #[test]
798
+ fn quoted_docstrings_preserve_text_and_remain_valid_python() {
799
+ let ir = XsdParser::new().parse_str(r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:simpleType name="Direction"><xs:annotation><xs:documentation>Default is "Outbound"</xs:documentation></xs:annotation><xs:restriction base="xs:string"><xs:enumeration value="Outbound"/></xs:restriction></xs:simpleType><xs:complexType name="Record"><xs:annotation><xs:documentation>Literal \path, "quotes" and """triples"""</xs:documentation></xs:annotation><xs:sequence><xs:element name="direction" type="Direction"/></xs:sequence></xs:complexType></xs:schema>"#).unwrap();
800
+ for backend in [PythonBackend::Dataclass, PythonBackend::Pydantic] {
801
+ let dir = tempfile::tempdir().unwrap();
802
+ let path = dir.path().join("models.py");
803
+ std::fs::write(
804
+ &path,
805
+ PythonCodegen::new(PythonOptions {
806
+ backend,
807
+ ..Default::default()
808
+ })
809
+ .generate_module(&ir),
810
+ )
811
+ .unwrap();
812
+ let expected = serde_json::to_string(&vec![
813
+ "Default is \"Outbound\"",
814
+ r#"Literal \path, "quotes" and """triples""""#,
815
+ ])
816
+ .unwrap();
817
+ let output = std::process::Command::new("python3").arg("-c").arg("import ast,json,sys; tree=ast.parse(open(sys.argv[1]).read()); docs=[ast.get_docstring(n) for n in tree.body if isinstance(n,ast.ClassDef) and ast.get_docstring(n) is not None]; assert docs==json.loads(sys.argv[2]),docs").arg(path).arg(expected).output().unwrap();
818
+ assert!(
819
+ output.status.success(),
820
+ "{}",
821
+ String::from_utf8_lossy(&output.stderr)
822
+ );
823
+ }
824
+ }
@@ -15,11 +15,35 @@ fn global_attribute_references_round_trip_by_namespace() {
15
15
  "/../../research/fixtures/wave6/xlink_shared_attribute/root.xsd"
16
16
  ))
17
17
  .unwrap();
18
+ assert_attribute_round_trip(&ir);
19
+ }
20
+
21
+ #[test]
22
+ fn imported_inline_global_attribute_enum_round_trips() {
23
+ let fixture = tempdir().unwrap();
24
+ fs::copy(
25
+ concat!(
26
+ env!("CARGO_MANIFEST_DIR"),
27
+ "/../../research/fixtures/wave6/xlink_shared_attribute/root.xsd"
28
+ ),
29
+ fixture.path().join("root.xsd"),
30
+ )
31
+ .unwrap();
32
+ fs::write(fixture.path().join("xlink.xsd"), r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.w3.org/1999/xlink">
33
+ <xs:attribute name="type"><xs:simpleType><xs:restriction base="xs:string"><xs:enumeration value="resource"/><xs:enumeration value="arc"/></xs:restriction></xs:simpleType></xs:attribute>
34
+ </xs:schema>"#).unwrap();
35
+ let ir = XsdParser::new()
36
+ .parse_file(fixture.path().join("root.xsd"))
37
+ .unwrap();
38
+ assert_attribute_round_trip(&ir);
39
+ }
40
+
41
+ fn assert_attribute_round_trip(ir: &polyxml::ir::SchemaIR) {
18
42
  let temp = tempdir().unwrap();
19
43
  fs::write(temp.path().join("go.mod"), "module attrs\n\ngo 1.22\n").unwrap();
20
44
  fs::write(
21
45
  temp.path().join("models.go"),
22
- GoCodegen::new(GoOptions::default()).generate_module(&ir),
46
+ GoCodegen::new(GoOptions::default()).generate_module(ir),
23
47
  )
24
48
  .unwrap();
25
49
  fs::write(temp.path().join("models_test.go"), r#"package models
@@ -32,6 +56,8 @@ func TestAttributes(t *testing.T) {
32
56
  for {token,err:=decoder.Token();if err==io.EOF{break};if err!=nil{t.Fatal(err)};if start,ok:=token.(xml.StartElement);ok{for _,attr:=range start.Attr{if attr.Name.Space=="http://www.w3.org/1999/xlink"&&attr.Name.Local=="type"{count++;if attr.Value!="resource"&&attr.Value!="arc"{t.Fatal(attr)}}}}}
33
57
  if count!=2{t.Fatalf("attributes lost: %s",data)}
34
58
  }
59
+ var absent Links; if err:=xml.Unmarshal([]byte(`<Links><Resource/><Arc/></Links>`),&absent);err!=nil{t.Fatal(err)}
60
+ data,err:=xml.Marshal(absent);if err!=nil{t.Fatal(err)};if strings.Contains(string(data),`type=`){t.Fatalf("absent attribute emitted: %s",data)}
35
61
  }"#).unwrap();
36
62
  let result = Command::new("go")
37
63
  .args(["test", "./..."])
@@ -54,7 +80,7 @@ func TestAttributes(t *testing.T) {
54
80
  use_records,
55
81
  ..Default::default()
56
82
  })
57
- .generate_module(&ir),
83
+ .generate_module(ir),
58
84
  )
59
85
  .unwrap();
60
86
  fs::write(temp.path().join("Program.cs"), r#"using System.Xml.Serialization;using System.Xml.Linq;using Models;
@@ -62,9 +88,30 @@ var serializer=new XmlSerializer(typeof(Links));
62
88
  foreach(var document in new[]{"<Links xmlns:xl='http://www.w3.org/1999/xlink'><Resource xl:type='resource'/><Arc xl:type='arc'/></Links>","<Links><Resource xmlns:b='http://www.w3.org/1999/xlink' b:type='resource'/><Arc xmlns:c='http://www.w3.org/1999/xlink' c:type='arc'/></Links>"}){
63
89
  var root=(Links)serializer.Deserialize(new StringReader(document))!;
64
90
  var writer=new StringWriter();serializer.Serialize(writer,root);
91
+ using(var jsonDocument=System.Text.Json.JsonSerializer.SerializeToDocument(root)){
92
+ if(!jsonDocument.RootElement.GetProperty("Resource").TryGetProperty("type",out _))throw new Exception("JSON wire name lost");
93
+ }
65
94
  var xml=XDocument.Parse(writer.ToString());XNamespace ns="http://www.w3.org/1999/xlink";
66
95
  if((string?)xml.Root!.Element("Resource")!.Attribute(ns+"type")!="resource"||(string?)xml.Root.Element("Arc")!.Attribute(ns+"type")!="arc")throw new Exception(writer.ToString());
67
- }"#).unwrap();
96
+ }
97
+ var absent=(Links)serializer.Deserialize(new StringReader("<Links><Resource/><Arc/></Links>"))!;
98
+ var absentWriter=new StringWriter();serializer.Serialize(absentWriter,absent);
99
+ if(XDocument.Parse(absentWriter.ToString()).Descendants().Attributes().Any(a=>a.Name.LocalName=="type"))throw new Exception(absentWriter.ToString());
100
+ var json=System.Text.Json.JsonSerializer.Serialize(absent);
101
+ if(json.Contains("TypeXml"))throw new Exception("XML proxy leaked to JSON: "+json);
102
+ "#).unwrap();
103
+ if ir
104
+ .types
105
+ .values()
106
+ .any(|definition| matches!(definition, polyxml::ir::TypeDef::Enum(_)))
107
+ {
108
+ use std::io::Write;
109
+ let mut program = fs::OpenOptions::new()
110
+ .append(true)
111
+ .open(temp.path().join("Program.cs"))
112
+ .unwrap();
113
+ writeln!(program, r#"bool rejected=false;try{{serializer.Deserialize(new StringReader("<Links xmlns:x='http://www.w3.org/1999/xlink'><Resource x:type='invalid'/><Arc/></Links>"));}}catch(InvalidOperationException){{rejected=true;}}if(!rejected)throw new Exception("invalid enum accepted");"#).unwrap();
114
+ }
68
115
  let result = Command::new("dotnet")
69
116
  .arg("run")
70
117
  .current_dir(temp.path())
@@ -1310,3 +1310,57 @@ fn test_rust_digit_prefixed_module_sanitization() {
1310
1310
  assert!(mod_file.1.contains("pub mod _17_report;"));
1311
1311
  assert!(mod_file.1.contains("pub use _17_report::*;"));
1312
1312
  }
1313
+
1314
+ #[test]
1315
+ fn binary_lexical_values_compile_and_round_trip_in_owned_and_borrowed_modes() {
1316
+ let schema = r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
1317
+ <xs:complexType name="BinaryText"><xs:simpleContent><xs:extension base="xs:hexBinary"/></xs:simpleContent></xs:complexType>
1318
+ <xs:complexType name="Payload"><xs:sequence>
1319
+ <xs:element name="required" type="xs:hexBinary"/>
1320
+ <xs:element name="optional" type="xs:base64Binary" minOccurs="0"/>
1321
+ <xs:element name="repeated" type="xs:hexBinary" maxOccurs="unbounded"/>
1322
+ </xs:sequence><xs:attribute name="encoded" type="xs:base64Binary"/></xs:complexType>
1323
+ <xs:complexType name="Alternative"><xs:choice><xs:element name="hex" type="xs:hexBinary"/><xs:element name="base64" type="xs:base64Binary"/></xs:choice></xs:complexType>
1324
+ </xs:schema>"#;
1325
+ let ir = XsdParser::new().parse_str(schema).unwrap();
1326
+ let core = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
1327
+ for zero_copy in [true, false] {
1328
+ let dir = tempfile::tempdir().unwrap();
1329
+ std::fs::create_dir(dir.path().join("src")).unwrap();
1330
+ std::fs::write(dir.path().join("Cargo.toml"), format!("[package]\nname=\"binary-lexical-test\"\nversion=\"0.0.0\"\nedition=\"2021\"\n[dependencies]\npolyxml={{path={core:?}}}\nquick-xml={{version=\"0.42\",features=[\"serialize\"]}}\nserde={{version=\"1\",features=[\"derive\"]}}\nserde_json=\"1\"\nregex=\"1\"\n")).unwrap();
1331
+ let code = RustCodegen::new(RustOptions {
1332
+ zero_copy,
1333
+ ..Default::default()
1334
+ })
1335
+ .generate_module(&ir);
1336
+ std::fs::write(dir.path().join("src/models.rs"), code).unwrap();
1337
+ std::fs::write(dir.path().join("src/main.rs"), r#"mod models;
1338
+ use models::*;
1339
+ fn main() {
1340
+ let xml="<Payload encoded=\"YWI=\"><required>0aFF</required><optional>YWI=</optional><repeated>00</repeated><repeated>FE</repeated></Payload>";
1341
+ let value=Payload::from_xml(xml).unwrap();
1342
+ assert_eq!(&*value.required,"0aFF"); assert_eq!(&*value.optional.as_ref().unwrap(),"YWI=");
1343
+ assert_eq!(&*value.repeated[1],"FE"); assert_eq!(&*value.encoded.as_ref().unwrap(),"YWI=");
1344
+ assert_eq!(value.to_xml_string().unwrap(),xml);
1345
+ let text=BinaryText::from_xml("<BinaryText>0AFF</BinaryText>").unwrap();
1346
+ assert_eq!(text.to_xml_string().unwrap(),"<BinaryText>0AFF</BinaryText>");
1347
+ for xml in ["<hex>0AFF</hex>","<base64>YWI=</base64>"] { let value=Alternative::from_xml(xml).unwrap(); assert_eq!(value.to_xml_string().unwrap(),xml); }
1348
+ }
1349
+ "#).unwrap();
1350
+ let output = std::process::Command::new("cargo")
1351
+ .args(["run", "--quiet", "--offline"])
1352
+ .env(
1353
+ "CARGO_TARGET_DIR",
1354
+ core.join("../../target/binary-lexical-tests"),
1355
+ )
1356
+ .current_dir(dir.path())
1357
+ .output()
1358
+ .unwrap();
1359
+ assert!(
1360
+ output.status.success(),
1361
+ "zero_copy={zero_copy}: {}\n{}",
1362
+ String::from_utf8_lossy(&output.stdout),
1363
+ String::from_utf8_lossy(&output.stderr)
1364
+ );
1365
+ }
1366
+ }
@@ -576,3 +576,30 @@ fn restricted_simple_types_emit_named_aliases() {
576
576
  let csharp = CSharpCodegen::new(CSharpOptions::default()).generate_module(&ir);
577
577
  assert!(csharp.contains("BicCode"), "csharp type missing:\n{csharp}");
578
578
  }
579
+
580
+ #[test]
581
+ fn diamond_includes_do_not_duplicate_upa_source_documents() {
582
+ let dir = tempdir().unwrap();
583
+ for index in 0usize..16 {
584
+ let includes = (index.saturating_sub(2)..index)
585
+ .map(|previous| format!("<xs:include schemaLocation=\"{previous}.xsd\"/>"))
586
+ .collect::<String>();
587
+ fs::write(dir.path().join(format!("{index}.xsd")), format!("<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">{includes}<xs:complexType name=\"T{index}\"><xs:sequence><xs:element name=\"item{index}\" type=\"xs:string\"/></xs:sequence></xs:complexType></xs:schema>")).unwrap();
588
+ }
589
+ let mut parser = XsdParser::new();
590
+ let ir = parser.parse_file(dir.path().join("15.xsd")).unwrap();
591
+ assert_eq!(ir.types.len(), 16);
592
+ assert_eq!(
593
+ ir.upa_documents.len(),
594
+ 16,
595
+ "each unique source document is retained once"
596
+ );
597
+ assert_eq!(
598
+ parser
599
+ .parse_file(dir.path().join("15.xsd"))
600
+ .unwrap()
601
+ .upa_documents
602
+ .len(),
603
+ 16
604
+ );
605
+ }
@@ -652,3 +652,140 @@ fn schema_errors_preserve_forward_imports_and_annotation_payloads() {
652
652
  assert!(XsdParser::new().parse_str(schema).is_err());
653
653
  }
654
654
  }
655
+
656
+ #[test]
657
+ fn imported_inline_global_attribute_types_are_resolved() {
658
+ let dir = tempdir().unwrap();
659
+ std::fs::write(dir.path().join("attributes.xsd"), r#"<s:schema xmlns:s="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:attrs">
660
+ <s:attribute name="code"><s:annotation><s:documentation>Inline enum</s:documentation></s:annotation><s:simpleType><s:restriction base="s:string"><s:enumeration value="arc"/><s:enumeration value="resource"/></s:restriction></s:simpleType></s:attribute>
661
+ <s:attribute name="number"><s:simpleType><s:restriction base="s:int"><s:minInclusive value="3"/></s:restriction></s:simpleType></s:attribute>
662
+ <s:attribute name="lang"><s:simpleType><s:union memberTypes="s:language"><s:simpleType><s:restriction base="s:string"><s:enumeration value=""/></s:restriction></s:simpleType></s:union></s:simpleType></s:attribute>
663
+ <s:attribute name="plain"/>
664
+ <s:attribute name="annotated"><s:annotation><s:documentation>Untyped</s:documentation></s:annotation></s:attribute>
665
+ </s:schema>"#).unwrap();
666
+ let root = dir.path().join("root.xsd");
667
+ std::fs::write(&root, r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:a="urn:attrs">
668
+ <xs:import namespace="urn:attrs" schemaLocation="attributes.xsd"/>
669
+ <xs:complexType name="Record"><xs:attribute ref="a:code" use="required"/><xs:attribute ref="a:number"/><xs:attribute ref="a:lang"/><xs:attribute ref="a:plain"/><xs:attribute ref="a:annotated"/></xs:complexType>
670
+ </xs:schema>"#).unwrap();
671
+ let ir = XsdParser::new().parse_file(&root).unwrap();
672
+ let TypeDef::Struct(record) = &ir.types[&QName::new(None::<String>, "Record")] else {
673
+ panic!("Record");
674
+ };
675
+ for field in &record.fields {
676
+ assert_eq!(field.namespace.as_deref(), Some("urn:attrs"));
677
+ assert_eq!(
678
+ field.type_ref,
679
+ ir.attributes[&QName::new(Some("urn:attrs"), &field.xml_name)]
680
+ );
681
+ }
682
+ assert_eq!(record.fields[0].cardinality, Cardinality::required_one());
683
+ let TypeRef::Named(enum_name) = &record.fields[0].type_ref else {
684
+ panic!("enum type");
685
+ };
686
+ let TypeDef::Enum(enumeration) = &ir.types[enum_name] else {
687
+ panic!("enum declaration");
688
+ };
689
+ assert_eq!(
690
+ enumeration
691
+ .variants
692
+ .iter()
693
+ .map(|v| v.value.as_str())
694
+ .collect::<Vec<_>>(),
695
+ ["arc", "resource"]
696
+ );
697
+ let TypeRef::Named(number_name) = &record.fields[1].type_ref else {
698
+ panic!("number type");
699
+ };
700
+ let TypeDef::Simple(number) = &ir.types[number_name] else {
701
+ panic!("restriction");
702
+ };
703
+ assert_eq!(number.base_type, TypeRef::Primitive(PrimitiveType::Int));
704
+ assert_eq!(number.facets.min_inclusive.as_deref(), Some("3"));
705
+ let TypeRef::Named(union_name) = &record.fields[2].type_ref else {
706
+ panic!("union type");
707
+ };
708
+ let TypeDef::Union(union) = &ir.types[union_name] else {
709
+ panic!("union declaration");
710
+ };
711
+ assert!(union.is_lexical());
712
+ assert_eq!(union.branches.len(), 2);
713
+ assert_eq!(record.fields[3].type_ref, TypeRef::string());
714
+ assert_eq!(record.fields[4].type_ref, TypeRef::string());
715
+ let invalid = std::fs::read_to_string(&root)
716
+ .unwrap()
717
+ .replace("a:code", "a:missing");
718
+ std::fs::write(&root, invalid).unwrap();
719
+ assert!(XsdParser::new()
720
+ .parse_file(&root)
721
+ .unwrap_err()
722
+ .to_string()
723
+ .contains("Unresolved type"));
724
+ }
725
+
726
+ #[test]
727
+ fn schema_for_schemas_declared_types_are_not_unknown_builtins() {
728
+ let xsd = r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.w3.org/2001/XMLSchema">
729
+ <xs:complexType name="Derived"><xs:complexContent><xs:extension base="xs:openAttrs"><xs:attribute name="name" type="xs:string"/></xs:extension></xs:complexContent></xs:complexType>
730
+ <xs:complexType name="openAttrs"><xs:anyAttribute processContents="lax"/></xs:complexType>
731
+ <xs:element name="Root" type="xs:Derived"/>
732
+ </xs:schema>"#;
733
+ let ir = XsdParser::new().parse_str(xsd).unwrap();
734
+ let ns = Some("http://www.w3.org/2001/XMLSchema");
735
+ let TypeDef::Struct(derived) = &ir.types[&QName::new(ns, "Derived")] else {
736
+ panic!("Derived");
737
+ };
738
+ assert_eq!(derived.base_type, Some(QName::new(ns, "openAttrs")));
739
+ let missing = xsd.replace("base=\"xs:openAttrs\"", "base=\"xs:Misspelled\"");
740
+ assert!(XsdParser::new()
741
+ .parse_str(&missing)
742
+ .unwrap_err()
743
+ .to_string()
744
+ .contains("Misspelled"));
745
+ let normal = r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:element name="Root" type="xs:NotAType"/></xs:schema>"#;
746
+ assert!(XsdParser::new()
747
+ .parse_str(normal)
748
+ .unwrap_err()
749
+ .to_string()
750
+ .contains("Unknown built-in type"));
751
+ }
752
+
753
+ #[test]
754
+ fn inline_list_item_types_preserve_enum_and_union_declarations() {
755
+ for item in [
756
+ r#"<xs:restriction base="xs:string"><xs:enumeration value="one"/><xs:enumeration value="two"/></xs:restriction>"#,
757
+ r#"<xs:union memberTypes="xs:int xs:string"/>"#,
758
+ ] {
759
+ let xsd = format!(
760
+ r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:simpleType name="Values"><xs:list><xs:annotation><xs:documentation>items</xs:documentation></xs:annotation><xs:simpleType>{item}</xs:simpleType></xs:list></xs:simpleType></xs:schema>"#
761
+ );
762
+ let ir = XsdParser::new().parse_str(&xsd).unwrap();
763
+ let TypeDef::Simple(values) = &ir.types[&QName::new(None::<String>, "Values")] else {
764
+ panic!("list");
765
+ };
766
+ let TypeRef::List(inner) = &values.base_type else {
767
+ panic!("list reference");
768
+ };
769
+ let TypeRef::Named(name) = inner.as_ref() else {
770
+ panic!("item declaration");
771
+ };
772
+ match &ir.types[name] {
773
+ TypeDef::Enum(e) => assert_eq!(e.variants.len(), 2),
774
+ TypeDef::Union(u) => {
775
+ assert!(u.is_lexical());
776
+ assert_eq!(u.branches.len(), 2);
777
+ }
778
+ other => panic!("unexpected item: {other:?}"),
779
+ }
780
+ }
781
+ for list in ["<xs:list/>", "<xs:list></xs:list>"] {
782
+ let invalid = format!(
783
+ r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><xs:simpleType name="Values">{list}</xs:simpleType></xs:schema>"#
784
+ );
785
+ assert!(XsdParser::new()
786
+ .parse_str(&invalid)
787
+ .unwrap_err()
788
+ .to_string()
789
+ .contains("xs:list requires"));
790
+ }
791
+ }
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "polyxml"
7
- version = "0.34.1"
7
+ version = "0.34.3"
8
8
  description = "High-performance, polyglot XML data-binding engine built in Rust"
9
9
  authors = [
10
10
  { name = "Bailey Nguyen", email = "bailey.tan.nguyen@gmail.com" }
File without changes