polyxml 0.30.1__tar.gz → 0.30.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 (71) hide show
  1. {polyxml-0.30.1 → polyxml-0.30.3}/Cargo.lock +6 -6
  2. {polyxml-0.30.1 → polyxml-0.30.3}/Cargo.toml +1 -1
  3. {polyxml-0.30.1 → polyxml-0.30.3}/PKG-INFO +1 -1
  4. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/cpp/mod.rs +7 -5
  5. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/csharp/mod.rs +49 -12
  6. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/mod.rs +10 -0
  7. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/python/mod.rs +16 -6
  8. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/rust/mod.rs +31 -40
  9. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/schema_parser/mod.rs +4 -0
  10. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_cpp_codegen.rs +22 -0
  11. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_csharp_codegen.rs +62 -0
  12. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_python_codegen.rs +12 -0
  13. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_rust_codegen.rs +58 -1
  14. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_schema_ir.rs +20 -0
  15. {polyxml-0.30.1 → polyxml-0.30.3}/pyproject.toml +1 -1
  16. {polyxml-0.30.1 → polyxml-0.30.3}/README.md +0 -0
  17. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/Cargo.toml +0 -0
  18. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/README.md +0 -0
  19. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  20. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  21. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  22. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
  23. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
  24. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  25. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  26. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  27. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  28. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/converters.rs +0 -0
  29. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/error.rs +0 -0
  30. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/ir/mod.rs +0 -0
  31. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  32. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/json.rs +0 -0
  33. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/lib.rs +0 -0
  34. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/parser.rs +0 -0
  35. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/schema.rs +0 -0
  36. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/serializer.rs +0 -0
  37. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/transcoder.rs +0 -0
  38. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/src/value.rs +0 -0
  39. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  40. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
  41. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  42. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  43. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_core.rs +0 -0
  44. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
  45. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  46. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_json.rs +0 -0
  47. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  48. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
  49. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  50. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  51. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  52. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_security.rs +0 -0
  53. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  54. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  55. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  56. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  57. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-python/Cargo.toml +0 -0
  58. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-python/README.md +0 -0
  59. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  60. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-python/src/lib.rs +0 -0
  61. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-python/tests/test_conformance.py +0 -0
  62. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  63. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-python/tests/test_json.py +0 -0
  64. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
  65. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  66. {polyxml-0.30.1 → polyxml-0.30.3}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  67. {polyxml-0.30.1 → polyxml-0.30.3}/python/polyxml/AGENT_GUIDE.md +0 -0
  68. {polyxml-0.30.1 → polyxml-0.30.3}/python/polyxml/__init__.py +0 -0
  69. {polyxml-0.30.1 → polyxml-0.30.3}/python/polyxml/compat/__init__.py +0 -0
  70. {polyxml-0.30.1 → polyxml-0.30.3}/python/polyxml/compat/xsdata.py +0 -0
  71. {polyxml-0.30.1 → polyxml-0.30.3}/python/polyxml/py.typed +0 -0
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.30.1"
611
+ version = "0.30.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.30.1"
632
+ version = "0.30.3"
633
633
  dependencies = [
634
634
  "polyxml",
635
635
  ]
636
636
 
637
637
  [[package]]
638
638
  name = "polyxml-cli"
639
- version = "0.30.1"
639
+ version = "0.30.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.30.1"
653
+ version = "0.30.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.30.1"
663
+ version = "0.30.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.30.1"
673
+ version = "0.30.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.30.1"
6
+ version = "0.30.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.30.1
3
+ Version: 0.30.3
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -656,9 +656,13 @@ concept XmlModel = requires(T a) {{
656
656
  }
657
657
 
658
658
  fn emit_types(&self, out: &mut String, ir: &SchemaIR) {
659
- // Emit simple type aliases first
660
- for type_def in ir.emitted_types() {
661
- if let TypeDef::Simple(simple) = type_def {
659
+ let sorted_qnames = self.topological_sort_types(ir);
660
+
661
+ // Aliases must follow their base aliases, which may sort later by name.
662
+ for qname in &sorted_qnames {
663
+ if let Some(TypeDef::Simple(simple)) =
664
+ ir.types.get(qname).filter(|_| !ir.is_external_type(qname))
665
+ {
662
666
  self.emit_simple_type(out, simple, ir);
663
667
  }
664
668
  }
@@ -671,8 +675,6 @@ concept XmlModel = requires(T a) {{
671
675
  }
672
676
 
673
677
  // Topologically sort structs and unions (DAG order)
674
- let sorted_qnames = self.topological_sort_types(ir);
675
-
676
678
  for qname in sorted_qnames {
677
679
  if ir.is_external_type(&qname) {
678
680
  continue;
@@ -244,13 +244,34 @@ fn type_ident(q: &QName) -> String {
244
244
 
245
245
  fn choice_variant_name(branch: &UnionBranch) -> String {
246
246
  let name = to_csharp_type_name(&branch.variant_name);
247
- if matches!(&branch.type_ref, TypeRef::Named(qname) if type_ident(qname) == name) {
247
+ if matches!(
248
+ name.as_str(),
249
+ "Value" | "Equals" | "GetHashCode" | "ToString"
250
+ ) || matches!(&branch.type_ref, TypeRef::Named(qname) if type_ident(qname) == name)
251
+ {
248
252
  format!("{name}Branch")
249
253
  } else {
250
254
  name
251
255
  }
252
256
  }
253
257
 
258
+ fn simple_value_target(base: &TypeRef, ir: &SchemaIR) -> String {
259
+ let mut target = "Value".to_string();
260
+ let mut current = base;
261
+ let mut seen = HashSet::new();
262
+ while let TypeRef::Named(qname) = current {
263
+ if !seen.insert(qname.clone()) {
264
+ break;
265
+ }
266
+ let Some(TypeDef::Simple(simple)) = ir.types.get(qname) else {
267
+ break;
268
+ };
269
+ target.push_str(".Value");
270
+ current = &simple.base_type;
271
+ }
272
+ target
273
+ }
274
+
254
275
  impl CSharpCodegen {
255
276
  /// Creates a new C# code generator with the given configuration options.
256
277
  pub fn new(options: CSharpOptions) -> Self {
@@ -315,7 +336,7 @@ impl CSharpCodegen {
315
336
  if !simple.facets.patterns.is_empty() {
316
337
  simple.base_type = super::primitive_base(&simple.base_type, ir).clone();
317
338
  }
318
- self.emit_simple(&mut out, &simple, indent);
339
+ self.emit_simple(&mut out, &simple, ir, indent);
319
340
  }
320
341
  }
321
342
 
@@ -510,8 +531,10 @@ impl CSharpCodegen {
510
531
  writeln!(out, "{}}}\n", indent).unwrap();
511
532
  }
512
533
 
513
- fn emit_simple(&self, out: &mut String, s: &SimpleTypeDef, indent: &str) {
514
- if !s.facets.is_empty() || !self.options.use_records {
534
+ fn emit_simple(&self, out: &mut String, s: &SimpleTypeDef, ir: &SchemaIR, indent: &str) {
535
+ // Named simple types may be referenced by structs and choices even
536
+ // when they have no facets, so every one needs a declaration.
537
+ {
515
538
  let type_name = type_ident(&s.qname);
516
539
  let base_type = self.context.map_type_ref(&s.base_type);
517
540
 
@@ -550,7 +573,8 @@ impl CSharpCodegen {
550
573
  .unwrap();
551
574
  if self.options.emit_validation {
552
575
  writeln!(out, "{} public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)\n{} {{", indent, indent).unwrap();
553
- self.emit_facet_checks(out, &s.facets, "Value", &format!("{} ", indent));
576
+ let value = simple_value_target(&s.base_type, ir);
577
+ self.emit_facet_checks(out, &s.facets, &value, &format!("{} ", indent));
554
578
  writeln!(out, "{} yield break;\n{} }}", indent, indent).unwrap();
555
579
  }
556
580
  writeln!(out, "{}}}\n", indent).unwrap();
@@ -579,7 +603,8 @@ impl CSharpCodegen {
579
603
  )
580
604
  .unwrap();
581
605
  writeln!(out, "{} {{", indent).unwrap();
582
- self.emit_facet_checks(out, &s.facets, "Value", &format!("{} ", indent));
606
+ let value = simple_value_target(&s.base_type, ir);
607
+ self.emit_facet_checks(out, &s.facets, &value, &format!("{} ", indent));
583
608
  writeln!(out, "{} yield break;", indent).unwrap();
584
609
  writeln!(out, "{} }}", indent).unwrap();
585
610
  writeln!(out, "{}}}\n", indent).unwrap();
@@ -1076,12 +1101,24 @@ impl CSharpCodegen {
1076
1101
 
1077
1102
  // Collect fields and parameters
1078
1103
  if s.fields.is_empty() {
1079
- writeln!(
1080
- out,
1081
- "{}public {} {}{};",
1082
- indent, record_keyword, struct_name, implements_str
1083
- )
1084
- .unwrap();
1104
+ if self.options.emit_validation {
1105
+ writeln!(
1106
+ out,
1107
+ "{}public {} {}{}",
1108
+ indent, record_keyword, struct_name, implements_str
1109
+ )
1110
+ .unwrap();
1111
+ writeln!(out, "{}{{", indent).unwrap();
1112
+ self.emit_struct_validator(out, s, &prop_names, indent);
1113
+ writeln!(out, "{}}}", indent).unwrap();
1114
+ } else {
1115
+ writeln!(
1116
+ out,
1117
+ "{}public {} {}{};",
1118
+ indent, record_keyword, struct_name, implements_str
1119
+ )
1120
+ .unwrap();
1121
+ }
1085
1122
  writeln!(out).unwrap();
1086
1123
  return;
1087
1124
  }
@@ -24,6 +24,16 @@ pub use python::{
24
24
  pub use rust::{RustCodegen, RustOptions};
25
25
  pub use typescript::{TypeScriptBackend, TypeScriptCodegen, TypeScriptOptions};
26
26
 
27
+ /// Prefix every physical XSD documentation line so embedded newlines cannot
28
+ /// escape a generated source comment.
29
+ pub(crate) fn write_documentation_lines(out: &mut String, prefix: &str, doc: &str) {
30
+ for line in doc.trim().lines() {
31
+ out.push_str(prefix);
32
+ out.push_str(line.trim_end_matches('\r'));
33
+ out.push('\n');
34
+ }
35
+ }
36
+
27
37
  use crate::ir::{FieldDef, PrimitiveType, QName, SchemaIR, StructDef, TypeRef};
28
38
 
29
39
  #[derive(Debug, Error)]
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
6
6
 
7
7
  use crate::codegen::{
8
8
  build_type_name_map, lookup_type_name, normalize_symbol_name, sanitize_keyword,
9
- set_type_name_map, LanguageContext,
9
+ set_type_name_map, write_documentation_lines, LanguageContext,
10
10
  };
11
11
  use crate::ir::{
12
12
  EnumDef, FieldDef, FieldKind, PrimitiveType, QName, RestrictionFacets, SchemaIR, SimpleTypeDef,
@@ -32,6 +32,15 @@ const PATTERN_VALIDATOR_HELPER: &str = r#"def _polyxml_patterns(*patterns: str):
32
32
  pub mod aot;
33
33
  pub use aot::{PythonAotCodegen, PythonAotCrate, PythonAotOptions};
34
34
 
35
+ fn write_python_docstring(out: &mut String, indent: &str, doc: &str) {
36
+ let escaped = doc
37
+ .trim()
38
+ .replace('\\', "\\\\")
39
+ .replace("\"\"\"", "\\\"\\\"\\\"")
40
+ .replace('\r', "");
41
+ let _ = writeln!(out, "{indent}\"\"\"{escaped}\"\"\"");
42
+ }
43
+
35
44
  /// Target backend for Python code emission.
36
45
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
37
46
  pub enum PythonBackend {
@@ -444,7 +453,7 @@ impl PythonCodegen {
444
453
  let base_type = self.context.map_type_ref(&s.base_type);
445
454
 
446
455
  if let Some(ref doc) = s.documentation {
447
- let _ = writeln!(out, "# {}", doc.trim());
456
+ write_documentation_lines(out, "# ", doc);
448
457
  }
449
458
 
450
459
  if self.options.backend == PythonBackend::Pydantic && !s.facets.is_empty() {
@@ -492,7 +501,7 @@ impl PythonCodegen {
492
501
  let _ = writeln!(out, "class {}(StrEnum):", enum_name);
493
502
 
494
503
  if let Some(ref doc) = e.documentation {
495
- let _ = writeln!(out, " \"\"\"{}\"\"\"", doc.trim());
504
+ write_python_docstring(out, " ", doc);
496
505
  }
497
506
 
498
507
  if e.variants.is_empty() {
@@ -549,7 +558,7 @@ impl PythonCodegen {
549
558
  }
550
559
 
551
560
  if let Some(ref doc) = u.documentation {
552
- let _ = writeln!(out, "# {}", doc.trim());
561
+ write_documentation_lines(out, "# ", doc);
553
562
  }
554
563
 
555
564
  if u.branches.is_empty() {
@@ -613,7 +622,8 @@ impl PythonCodegen {
613
622
  let mut has_body = false;
614
623
 
615
624
  if let Some(ref doc) = s.documentation {
616
- let _ = writeln!(out, " \"\"\"{}\"\"\"\n", doc.trim());
625
+ write_python_docstring(out, " ", doc);
626
+ out.push('\n');
617
627
  has_body = true;
618
628
  }
619
629
 
@@ -763,7 +773,7 @@ impl PythonCodegen {
763
773
  };
764
774
 
765
775
  if let Some(ref doc) = field.documentation {
766
- let _ = writeln!(out, " # {}", doc.trim());
776
+ write_documentation_lines(out, " # ", doc);
767
777
  }
768
778
 
769
779
  let _ = writeln!(out, " {}: {} = {}", py_name, field_type, field_call);
@@ -5,8 +5,8 @@ use heck::{AsPascalCase, AsSnakeCase};
5
5
  use serde::{Deserialize, Serialize};
6
6
 
7
7
  use crate::codegen::{
8
- build_type_name_map, flatten_fields, lookup_type_name, sanitize_keyword, set_type_name_map,
9
- LanguageContext,
8
+ build_type_name_map, flatten_fields, lookup_type_name, primitive_base, sanitize_keyword,
9
+ set_type_name_map, write_documentation_lines, LanguageContext,
10
10
  };
11
11
  use crate::ir::{
12
12
  EnumDef, FieldDef, FieldKind, PrimitiveType, QName, SchemaIR, SimpleTypeDef, StructDef,
@@ -279,7 +279,10 @@ impl RustCodegen {
279
279
  self.emit_pymodule(&mut out, ir, &sorted_types);
280
280
  }
281
281
 
282
- out
282
+ // A field may use a raw identifier such as `r#type`. Local codec
283
+ // variables have a `var_` prefix, so the raw marker is neither needed
284
+ // nor valid there (`var_r#type` is not a Rust identifier).
285
+ out.replace("var_r#", "var_")
283
286
  }
284
287
 
285
288
  /// Fixed-point analysis determining which types in SchemaIR require a lifetime parameter `<'a>`.
@@ -456,7 +459,7 @@ impl RustCodegen {
456
459
  let needs_lifetime = types_with_lifetime.contains(&s.qname);
457
460
 
458
461
  if let Some(ref doc) = s.documentation {
459
- let _ = writeln!(out, "/// {}", doc.trim());
462
+ write_documentation_lines(out, "/// ", doc);
460
463
  }
461
464
 
462
465
  let base_type = self.format_rust_type_ref(&s.base_type, types_with_lifetime);
@@ -482,7 +485,7 @@ impl RustCodegen {
482
485
  let enum_name = type_ident(&e.qname);
483
486
 
484
487
  if let Some(ref doc) = e.documentation {
485
- let _ = writeln!(out, "/// {}", doc.trim());
488
+ write_documentation_lines(out, "/// ", doc);
486
489
  }
487
490
 
488
491
  let mut derives = vec!["Debug", "Clone", "Copy", "PartialEq", "Eq", "Hash"];
@@ -520,7 +523,7 @@ impl RustCodegen {
520
523
  seen_variants.insert(var_id.clone());
521
524
 
522
525
  if let Some(ref doc) = variant.documentation {
523
- let _ = writeln!(out, " /// {}", doc.trim());
526
+ write_documentation_lines(out, " /// ", doc);
524
527
  }
525
528
  if self.options.derive_serde {
526
529
  let _ = writeln!(out, " #[serde(rename = \"{}\")]", variant.value);
@@ -595,7 +598,7 @@ impl RustCodegen {
595
598
  let needs_lifetime = types_with_lifetime.contains(&u.qname);
596
599
 
597
600
  if let Some(ref doc) = u.documentation {
598
- let _ = writeln!(out, "/// {}", doc.trim());
601
+ write_documentation_lines(out, "/// ", doc);
599
602
  }
600
603
 
601
604
  let mut derives = vec!["Debug", "Clone", "PartialEq"];
@@ -640,7 +643,7 @@ impl RustCodegen {
640
643
  let branch_type = self.format_rust_type_ref(&branch.type_ref, types_with_lifetime);
641
644
 
642
645
  if let Some(ref doc) = branch.documentation {
643
- let _ = writeln!(out, " /// {}", doc.trim());
646
+ write_documentation_lines(out, " /// ", doc);
644
647
  }
645
648
  if self.options.derive_serde && !u.is_lexical() {
646
649
  let _ = writeln!(out, " #[serde(rename = \"{}\")]", branch.xml_name);
@@ -685,7 +688,7 @@ impl RustCodegen {
685
688
  let needs_lifetime = types_with_lifetime.contains(&s.qname);
686
689
 
687
690
  if let Some(ref doc) = s.documentation {
688
- let _ = writeln!(out, "/// {}", doc.trim());
691
+ write_documentation_lines(out, "/// ", doc);
689
692
  }
690
693
 
691
694
  let mut derives = vec!["Debug", "Clone", "PartialEq"];
@@ -763,7 +766,7 @@ impl RustCodegen {
763
766
  has_boxed: bool,
764
767
  ) {
765
768
  if let Some(ref doc) = field.documentation {
766
- let _ = writeln!(out, " /// {}", doc.trim());
769
+ write_documentation_lines(out, " /// ", doc);
767
770
  }
768
771
 
769
772
  let is_list = field.cardinality.is_list() || field.type_ref.is_list();
@@ -998,8 +1001,17 @@ impl RustCodegen {
998
1001
  }
999
1002
  }
1000
1003
 
1004
+ fn unwrap_type_ref(mut ty: &TypeRef) -> &TypeRef {
1005
+ while let TypeRef::Boxed(inner) | TypeRef::List(inner) = ty {
1006
+ ty = inner;
1007
+ }
1008
+ ty
1009
+ }
1010
+
1001
1011
  fn field_is_string(&self, type_ref: &TypeRef, ir: &SchemaIR) -> bool {
1002
- match type_ref {
1012
+ let ty = Self::unwrap_type_ref(type_ref);
1013
+ let base = primitive_base(ty, ir);
1014
+ match base {
1003
1015
  TypeRef::Primitive(prim) => matches!(
1004
1016
  prim,
1005
1017
  PrimitiveType::String
@@ -1030,19 +1042,14 @@ impl RustCodegen {
1030
1042
  | PrimitiveType::HexBinary
1031
1043
  | PrimitiveType::AnySimpleType
1032
1044
  ),
1033
- TypeRef::Named(qname) => {
1034
- if let Some(TypeDef::Simple(s)) = ir.types.get(qname) {
1035
- self.field_is_string(&s.base_type, ir)
1036
- } else {
1037
- false
1038
- }
1039
- }
1040
- TypeRef::Boxed(inner) | TypeRef::List(inner) => self.field_is_string(inner, ir),
1045
+ _ => false,
1041
1046
  }
1042
1047
  }
1043
1048
 
1044
1049
  fn field_numeric_type(&self, type_ref: &TypeRef, ir: &SchemaIR) -> Option<&'static str> {
1045
- match type_ref {
1050
+ let ty = Self::unwrap_type_ref(type_ref);
1051
+ let base = primitive_base(ty, ir);
1052
+ match base {
1046
1053
  TypeRef::Primitive(prim) => match prim {
1047
1054
  PrimitiveType::Int => Some("i32"),
1048
1055
  PrimitiveType::Integer
@@ -1061,30 +1068,14 @@ impl RustCodegen {
1061
1068
  PrimitiveType::Float => Some("f32"),
1062
1069
  _ => None,
1063
1070
  },
1064
- TypeRef::Named(qname) => {
1065
- if let Some(TypeDef::Simple(s)) = ir.types.get(qname) {
1066
- self.field_numeric_type(&s.base_type, ir)
1067
- } else {
1068
- None
1069
- }
1070
- }
1071
- TypeRef::Boxed(inner) | TypeRef::List(inner) => self.field_numeric_type(inner, ir),
1071
+ _ => None,
1072
1072
  }
1073
1073
  }
1074
1074
 
1075
1075
  fn field_is_bool(&self, type_ref: &TypeRef, ir: &SchemaIR) -> bool {
1076
- match type_ref {
1077
- TypeRef::Primitive(PrimitiveType::Boolean) => true,
1078
- TypeRef::Named(qname) => {
1079
- if let Some(TypeDef::Simple(s)) = ir.types.get(qname) {
1080
- self.field_is_bool(&s.base_type, ir)
1081
- } else {
1082
- false
1083
- }
1084
- }
1085
- TypeRef::Boxed(inner) | TypeRef::List(inner) => self.field_is_bool(inner, ir),
1086
- _ => false,
1087
- }
1076
+ let ty = Self::unwrap_type_ref(type_ref);
1077
+ let base = primitive_base(ty, ir);
1078
+ matches!(base, TypeRef::Primitive(PrimitiveType::Boolean))
1088
1079
  }
1089
1080
 
1090
1081
  fn field_is_enum(&self, type_ref: &TypeRef, ir: &SchemaIR) -> Option<String> {
@@ -504,6 +504,7 @@ impl XsdParser {
504
504
  .push((fields.len(), resolve_qname(&r, target_ns, prefixes)));
505
505
  }
506
506
  skip_subtree(reader)?;
507
+ depth -= 1;
507
508
  }
508
509
  "sequence" | "all" => {
509
510
  let is_unbounded = get_attr_value(e, "maxOccurs")
@@ -560,6 +561,7 @@ impl XsdParser {
560
561
  self.consume_inline_element_type(
561
562
  reader, e, target_ns, prefixes, &name, &mut field, ir,
562
563
  )?;
564
+ depth -= 1;
563
565
  if in_choice {
564
566
  if let Some(frame) = compositor_stack.last_mut() {
565
567
  if frame.kind == CompositorKind::Choice {
@@ -869,6 +871,7 @@ impl XsdParser {
869
871
  self.consume_inline_element_type(
870
872
  reader, e, target_ns, prefixes, "", &mut field, ir,
871
873
  )?;
874
+ depth -= 1;
872
875
  def.fields.push(field);
873
876
  }
874
877
  }
@@ -880,6 +883,7 @@ impl XsdParser {
880
883
  ));
881
884
  }
882
885
  skip_subtree(reader)?;
886
+ depth -= 1;
883
887
  }
884
888
  "any" => def.fields.push(parse_any_field(e)),
885
889
  _ => {}
@@ -11,6 +11,28 @@ use polyxml::ir::{
11
11
  SchemaIR, StructDef, TypeDef, TypeRef, UnionBranch, UnionDef,
12
12
  };
13
13
 
14
+ #[test]
15
+ fn simple_aliases_follow_their_base_types() {
16
+ let mut ir = SchemaIR::new().with_target_namespace("urn:test");
17
+ ir.add_type(TypeDef::Simple(Box::new(polyxml::ir::SimpleTypeDef {
18
+ qname: QName::new(Some("urn:test"), "AreaType"),
19
+ base_type: TypeRef::Named(QName::new(Some("urn:test"), "DoubleNonNegativeType")),
20
+ facets: RestrictionFacets::default(),
21
+ documentation: None,
22
+ })));
23
+ ir.add_type(TypeDef::Simple(Box::new(polyxml::ir::SimpleTypeDef {
24
+ qname: QName::new(Some("urn:test"), "DoubleNonNegativeType"),
25
+ base_type: TypeRef::Primitive(PrimitiveType::Double),
26
+ facets: RestrictionFacets::default(),
27
+ documentation: None,
28
+ })));
29
+ let code = CppCodegen::new(CppOptions::default()).generate_header(&ir);
30
+ assert!(
31
+ code.find("using DoubleNonNegativeType =").unwrap()
32
+ < code.find("using AreaType =").unwrap()
33
+ );
34
+ }
35
+
14
36
  #[test]
15
37
  fn test_cpp_sanitization() {
16
38
  assert_eq!(to_cpp_field_name("class"), "class_");
@@ -12,6 +12,68 @@ use polyxml::ir::{
12
12
  use std::sync::Mutex;
13
13
  use tempfile::tempdir;
14
14
 
15
+ #[test]
16
+ fn emits_unrestricted_simple_types_and_validates_empty_records() {
17
+ let mut ir = SchemaIR::new().with_target_namespace("urn:test");
18
+ ir.add_type(TypeDef::Simple(Box::new(polyxml::ir::SimpleTypeDef {
19
+ qname: QName::new(Some("urn:test"), "DistanceType"),
20
+ base_type: TypeRef::Primitive(PrimitiveType::Double),
21
+ facets: RestrictionFacets::default(),
22
+ documentation: None,
23
+ })));
24
+ ir.add_type(TypeDef::Simple(Box::new(polyxml::ir::SimpleTypeDef {
25
+ qname: QName::new(Some("urn:test"), "PositiveDistanceType"),
26
+ base_type: TypeRef::Named(QName::new(Some("urn:test"), "DistanceType")),
27
+ facets: RestrictionFacets {
28
+ min_inclusive: Some("0".into()),
29
+ ..Default::default()
30
+ },
31
+ documentation: None,
32
+ })));
33
+ ir.add_type(TypeDef::Struct(StructDef {
34
+ qname: QName::new(Some("urn:test"), "EmptyType"),
35
+ base_type: None,
36
+ is_abstract: false,
37
+ is_mixed: false,
38
+ fields: vec![],
39
+ documentation: None,
40
+ }));
41
+ let code = CSharpCodegen::new(CSharpOptions::default()).generate_module(&ir);
42
+ assert!(code.contains("record DistanceType("));
43
+ assert!(code.contains("if (Value.Value < 0)"));
44
+ assert!(code.contains("record EmptyType : IValidatableObject"));
45
+ assert!(code.contains("IEnumerable<ValidationResult> Validate"));
46
+ }
47
+
48
+ #[test]
49
+ fn choice_variants_avoid_record_member_names() {
50
+ let mut ir = SchemaIR::new().with_target_namespace("urn:test");
51
+ ir.add_type(TypeDef::Union(UnionDef {
52
+ qname: QName::new(Some("urn:test"), "ParameterValueType"),
53
+ branches: vec![
54
+ UnionBranch {
55
+ variant_name: "Value".into(),
56
+ xml_name: "Value".into(),
57
+ namespace: None,
58
+ type_ref: TypeRef::Primitive(PrimitiveType::String),
59
+ documentation: None,
60
+ },
61
+ UnionBranch {
62
+ variant_name: "Equals".into(),
63
+ xml_name: "Equals".into(),
64
+ namespace: None,
65
+ type_ref: TypeRef::Primitive(PrimitiveType::Int),
66
+ documentation: None,
67
+ },
68
+ ],
69
+ documentation: None,
70
+ }));
71
+ let code = CSharpCodegen::new(CSharpOptions::default()).generate_module(&ir);
72
+ assert!(code.contains("record ValueBranch("));
73
+ assert!(code.contains("record EqualsBranch("));
74
+ assert!(code.contains("XmlElement(\"Value\")"));
75
+ }
76
+
15
77
  static DOTNET_LOCK: Mutex<()> = Mutex::new(());
16
78
 
17
79
  fn dotnet_command() -> Command {
@@ -5,6 +5,18 @@ use polyxml::ir::{
5
5
  Cardinality, EnumDef, EnumValue, FieldDef, FieldKind, OccursLimit, PrimitiveType, QName,
6
6
  RestrictionFacets, SchemaIR, SimpleTypeDef, StructDef, TypeDef, TypeRef, UnionBranch, UnionDef,
7
7
  };
8
+ use polyxml::schema_parser::XsdParser;
9
+
10
+ #[test]
11
+ fn multiline_xsd_documentation_stays_inside_python_comments() {
12
+ let xsd = r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:test">
13
+ <xs:simpleType name="Code"><xs:annotation><xs:documentation>First line
14
+ Second line</xs:documentation></xs:annotation><xs:restriction base="xs:string"/></xs:simpleType>
15
+ </xs:schema>"#;
16
+ let ir = XsdParser::new().parse_str(xsd).unwrap();
17
+ let code = PythonCodegen::new(PythonOptions::default()).generate_module(&ir);
18
+ assert!(code.contains("# First line\n# Second line"));
19
+ }
8
20
 
9
21
  #[test]
10
22
  fn test_identifier_sanitization() {
@@ -3,7 +3,7 @@ use polyxml::codegen::rust::{
3
3
  };
4
4
  use polyxml::ir::{
5
5
  Cardinality, EnumDef, EnumValue, FieldDef, FieldKind, PrimitiveType, QName, SchemaIR,
6
- StructDef, TypeDef, TypeRef, UnionBranch, UnionDef,
6
+ SimpleTypeDef, StructDef, TypeDef, TypeRef, UnionBranch, UnionDef,
7
7
  };
8
8
  use polyxml::schema_parser::XsdParser;
9
9
 
@@ -35,6 +35,22 @@ fn test_rust_identifier_sanitization() {
35
35
  assert_eq!(to_rust_field_identifier("normalField"), "normal_field");
36
36
  }
37
37
 
38
+ #[test]
39
+ fn multiline_docs_and_raw_field_names_are_valid_source() {
40
+ let xsd = r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:test">
41
+ <xs:complexType name="Sample"><xs:annotation><xs:documentation>First line
42
+ Second line</xs:documentation></xs:annotation><xs:sequence>
43
+ <xs:element name="type" type="xs:string"/>
44
+ </xs:sequence></xs:complexType>
45
+ </xs:schema>"#;
46
+ let ir = XsdParser::new().parse_str(xsd).unwrap();
47
+ let code = RustCodegen::new(RustOptions::default()).generate_module(&ir);
48
+ assert!(code.contains("/// First line\n/// Second line"));
49
+ assert!(code.contains("pub r#type:"));
50
+ assert!(code.contains("var_type"));
51
+ assert!(!code.contains("var_r#"));
52
+ }
53
+
38
54
  #[test]
39
55
  fn test_rust_zero_copy_codegen() {
40
56
  let mut ir = SchemaIR::new().with_target_namespace("https://example.com/crm");
@@ -1147,3 +1163,44 @@ fn test_rust_phf_dispatch_uniquifies_like_field_metas() {
1147
1163
  "bare-string arm leaked into the Option match:\n{code}"
1148
1164
  );
1149
1165
  }
1166
+
1167
+ #[test]
1168
+ fn test_rust_simple_type_cycle_resilience() {
1169
+ let mut ir = SchemaIR::default();
1170
+ let q_a = QName::new(Some("urn:cycle"), "TypeA");
1171
+ let q_b = QName::new(Some("urn:cycle"), "TypeB");
1172
+
1173
+ // Mutual cycle between TypeA and TypeB
1174
+ ir.add_type(TypeDef::Simple(Box::new(SimpleTypeDef {
1175
+ qname: q_a.clone(),
1176
+ base_type: TypeRef::Named(q_b.clone()),
1177
+ facets: Default::default(),
1178
+ documentation: None,
1179
+ })));
1180
+ ir.add_type(TypeDef::Simple(Box::new(SimpleTypeDef {
1181
+ qname: q_b.clone(),
1182
+ base_type: TypeRef::Named(q_a.clone()),
1183
+ facets: Default::default(),
1184
+ documentation: None,
1185
+ })));
1186
+
1187
+ let struct_fields = vec![FieldDef::new(
1188
+ "cyclic_field",
1189
+ "cyclic_field",
1190
+ FieldKind::Element,
1191
+ TypeRef::Named(q_a),
1192
+ )];
1193
+
1194
+ ir.add_type(TypeDef::Struct(StructDef {
1195
+ qname: QName::new(Some("urn:cycle"), "CyclicStruct"),
1196
+ fields: struct_fields,
1197
+ base_type: None,
1198
+ is_abstract: false,
1199
+ is_mixed: false,
1200
+ documentation: None,
1201
+ }));
1202
+
1203
+ let codegen = RustCodegen::new(RustOptions::default());
1204
+ let code = codegen.generate_module(&ir);
1205
+ assert!(code.contains("pub struct CyclicStruct"));
1206
+ }
@@ -7,6 +7,26 @@ use polyxml::ir::{
7
7
  };
8
8
  use polyxml::schema_parser::XsdParser;
9
9
 
10
+ #[test]
11
+ fn consumed_child_subtrees_do_not_swallow_following_types() {
12
+ let xsd = r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:test">
13
+ <xs:complexType name="First"><xs:sequence>
14
+ <xs:element name="nested"><xs:complexType><xs:sequence><xs:element name="value" type="xs:string"/></xs:sequence></xs:complexType></xs:element>
15
+ <xs:group ref="missing"><xs:annotation><xs:documentation>ignored</xs:documentation></xs:annotation></xs:group>
16
+ </xs:sequence></xs:complexType>
17
+ <xs:complexType name="Second"><xs:sequence><xs:element name="other" type="xs:string"/></xs:sequence></xs:complexType>
18
+ </xs:schema>"#;
19
+ let ir = XsdParser::new().parse_str(xsd).unwrap();
20
+ let first = match &ir.types[&QName::new(Some("urn:test"), "First")] {
21
+ TypeDef::Struct(value) => value,
22
+ other => panic!("expected struct: {other:?}"),
23
+ };
24
+ assert_eq!(first.fields.len(), 1);
25
+ assert!(ir
26
+ .types
27
+ .contains_key(&QName::new(Some("urn:test"), "Second")));
28
+ }
29
+
10
30
  #[test]
11
31
  fn test_parse_lexical_union_member_types_and_inline_members() {
12
32
  let xsd = r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:t="urn:test" targetNamespace="urn:test">
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "polyxml"
7
- version = "0.30.1"
7
+ version = "0.30.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