polyxml 0.34.8__tar.gz → 0.35.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {polyxml-0.34.8 → polyxml-0.35.0}/Cargo.lock +18 -18
  2. {polyxml-0.34.8 → polyxml-0.35.0}/Cargo.toml +1 -1
  3. {polyxml-0.34.8 → polyxml-0.35.0}/PKG-INFO +1 -1
  4. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/rust/mod.rs +147 -1
  5. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/schema.rs +38 -8
  6. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/schema_parser/upa.rs +66 -19
  7. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_rust_codegen.rs +49 -0
  8. polyxml-0.35.0/crates/polyxml-core/tests/test_variant_registry.rs +75 -0
  9. {polyxml-0.34.8 → polyxml-0.35.0}/pyproject.toml +1 -1
  10. {polyxml-0.34.8 → polyxml-0.35.0}/README.md +0 -0
  11. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/Cargo.toml +0 -0
  12. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/README.md +0 -0
  13. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
  14. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
  15. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
  16. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
  17. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
  18. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
  19. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
  20. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
  21. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
  22. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/mod.rs +0 -0
  23. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
  24. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
  25. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
  26. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/converters.rs +0 -0
  27. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/error.rs +0 -0
  28. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/integer.rs +0 -0
  29. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/ir/chunker.rs +0 -0
  30. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
  31. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/ir/particle.rs +0 -0
  32. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
  33. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/json.rs +0 -0
  34. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/lib.rs +0 -0
  35. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/parser.rs +0 -0
  36. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/pattern.rs +0 -0
  37. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/schema_parser/mod.rs +0 -0
  38. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/serializer.rs +0 -0
  39. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/transcoder.rs +0 -0
  40. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/src/value.rs +0 -0
  41. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
  42. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
  43. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/fixtures/ts_mixed_content_inheritance.xsd +0 -0
  44. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
  45. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_any_attribute.rs +0 -0
  46. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_any_element.rs +0 -0
  47. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
  48. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_core.rs +0 -0
  49. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
  50. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
  51. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_default_literal_codegen.rs +0 -0
  52. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_enum_literal_codegen.rs +0 -0
  53. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
  54. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_inherited_simple_content.rs +0 -0
  55. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_inherited_text_codegen.rs +0 -0
  56. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
  57. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_json.rs +0 -0
  58. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_lexical_lists.rs +0 -0
  59. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
  60. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_mixed_branch_lookup.rs +0 -0
  61. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
  62. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_mixed_nil.rs +0 -0
  63. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_namespace_entities.rs +0 -0
  64. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
  65. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
  66. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_qualified_attributes.rs +0 -0
  67. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_runtime_fast_paths.rs +0 -0
  68. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
  69. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_schema_attribute_entities.rs +0 -0
  70. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
  71. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
  72. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_security.rs +0 -0
  73. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_temporal_union.rs +0 -0
  74. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
  75. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
  76. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_ubl_csharp.rs +0 -0
  77. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
  78. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_upa.rs +0 -0
  79. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_value_constraints.rs +0 -0
  80. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
  81. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-python/Cargo.toml +0 -0
  82. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-python/README.md +0 -0
  83. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
  84. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-python/src/lib.rs +0 -0
  85. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
  86. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
  87. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-python/tests/test_json.py +0 -0
  88. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
  89. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
  90. {polyxml-0.34.8 → polyxml-0.35.0}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
  91. {polyxml-0.34.8 → polyxml-0.35.0}/python/polyxml/AGENT_GUIDE.md +0 -0
  92. {polyxml-0.34.8 → polyxml-0.35.0}/python/polyxml/__init__.py +0 -0
  93. {polyxml-0.34.8 → polyxml-0.35.0}/python/polyxml/compat/__init__.py +0 -0
  94. {polyxml-0.34.8 → polyxml-0.35.0}/python/polyxml/compat/xsdata.py +0 -0
  95. {polyxml-0.34.8 → polyxml-0.35.0}/python/polyxml/py.typed +0 -0
@@ -474,9 +474,9 @@ dependencies = [
474
474
 
475
475
  [[package]]
476
476
  name = "napi-build"
477
- version = "2.5.0"
477
+ version = "2.6.0"
478
478
  source = "registry+https://github.com/rust-lang/crates.io-index"
479
- checksum = "0941983559cd62bfa7f04646a16428967a847365adeef27711e4e3927046bbed"
479
+ checksum = "b899b545d3aa6dca985939059f258c5488d34e4ecf39c274e20009748f4b846d"
480
480
 
481
481
  [[package]]
482
482
  name = "napi-derive"
@@ -608,7 +608,7 @@ dependencies = [
608
608
 
609
609
  [[package]]
610
610
  name = "polyxml"
611
- version = "0.34.8"
611
+ version = "0.35.0"
612
612
  dependencies = [
613
613
  "criterion",
614
614
  "heck",
@@ -629,14 +629,14 @@ dependencies = [
629
629
 
630
630
  [[package]]
631
631
  name = "polyxml-c"
632
- version = "0.34.8"
632
+ version = "0.35.0"
633
633
  dependencies = [
634
634
  "polyxml",
635
635
  ]
636
636
 
637
637
  [[package]]
638
638
  name = "polyxml-cli"
639
- version = "0.34.8"
639
+ version = "0.35.0"
640
640
  dependencies = [
641
641
  "clap",
642
642
  "glob",
@@ -650,7 +650,7 @@ dependencies = [
650
650
 
651
651
  [[package]]
652
652
  name = "polyxml-js"
653
- version = "0.34.8"
653
+ version = "0.35.0"
654
654
  dependencies = [
655
655
  "napi",
656
656
  "napi-build",
@@ -660,7 +660,7 @@ dependencies = [
660
660
 
661
661
  [[package]]
662
662
  name = "polyxml-python"
663
- version = "0.34.8"
663
+ version = "0.35.0"
664
664
  dependencies = [
665
665
  "lexical-core",
666
666
  "polyxml",
@@ -670,7 +670,7 @@ dependencies = [
670
670
 
671
671
  [[package]]
672
672
  name = "polyxml-wasm"
673
- version = "0.34.8"
673
+ version = "0.35.0"
674
674
  dependencies = [
675
675
  "polyxml",
676
676
  "quick-xml",
@@ -704,9 +704,9 @@ dependencies = [
704
704
 
705
705
  [[package]]
706
706
  name = "pyo3"
707
- version = "0.29.2"
707
+ version = "0.29.3"
708
708
  source = "registry+https://github.com/rust-lang/crates.io-index"
709
- checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b"
709
+ checksum = "700d18fa267b73b9b521fd7e13580e2f446916f176cacee1ab63fcc8191f1655"
710
710
  dependencies = [
711
711
  "libc",
712
712
  "once_cell",
@@ -718,18 +718,18 @@ dependencies = [
718
718
 
719
719
  [[package]]
720
720
  name = "pyo3-build-config"
721
- version = "0.29.2"
721
+ version = "0.29.3"
722
722
  source = "registry+https://github.com/rust-lang/crates.io-index"
723
- checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9"
723
+ checksum = "7b3fc0c4d08f6bb10e71fe39dfb9e2f59c6eb6854e22ec8092f50c69a4499adb"
724
724
  dependencies = [
725
725
  "target-lexicon",
726
726
  ]
727
727
 
728
728
  [[package]]
729
729
  name = "pyo3-ffi"
730
- version = "0.29.2"
730
+ version = "0.29.3"
731
731
  source = "registry+https://github.com/rust-lang/crates.io-index"
732
- checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327"
732
+ checksum = "dfc0b8e19df29aad7086cf977bb0c2a2f143e30567eb113e9cf72b62ca698330"
733
733
  dependencies = [
734
734
  "libc",
735
735
  "pyo3-build-config",
@@ -737,9 +737,9 @@ dependencies = [
737
737
 
738
738
  [[package]]
739
739
  name = "pyo3-macros"
740
- version = "0.29.2"
740
+ version = "0.29.3"
741
741
  source = "registry+https://github.com/rust-lang/crates.io-index"
742
- checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c"
742
+ checksum = "6100e8a4b5eba53afaa5ed078364851a0b2499c553a44c31026b929049b49dc6"
743
743
  dependencies = [
744
744
  "proc-macro2",
745
745
  "pyo3-macros-backend",
@@ -749,9 +749,9 @@ dependencies = [
749
749
 
750
750
  [[package]]
751
751
  name = "pyo3-macros-backend"
752
- version = "0.29.2"
752
+ version = "0.29.3"
753
753
  source = "registry+https://github.com/rust-lang/crates.io-index"
754
- checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952"
754
+ checksum = "6143877a16e82b5a727b7127ff4cd86858a24a28d745d72f43e6f227c7b1bdb3"
755
755
  dependencies = [
756
756
  "heck",
757
757
  "proc-macro2",
@@ -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.8"
6
+ version = "0.35.0"
7
7
  edition = "2021"
8
8
  rust-version = "1.80"
9
9
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: polyxml
3
- Version: 0.34.8
3
+ Version: 0.35.0
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -1940,6 +1940,136 @@ impl RustCodegen {
1940
1940
  out.push_str(" }\n");
1941
1941
 
1942
1942
  out.push_str("}\n");
1943
+ self.emit_incremental_roots(out, s, ir, types_with_lifetime);
1944
+ }
1945
+
1946
+ /// Only expose the producer API where ordinary item codecs preserve the schema.
1947
+ fn emit_incremental_roots(
1948
+ &self,
1949
+ out: &mut String,
1950
+ s: &StructDef,
1951
+ ir: &SchemaIR,
1952
+ lifetimes: &HashSet<QName>,
1953
+ ) {
1954
+ fn supported(
1955
+ s: &StructDef,
1956
+ ir: &SchemaIR,
1957
+ namespace: &Option<String>,
1958
+ seen: &mut HashSet<QName>,
1959
+ ) -> bool {
1960
+ if ir
1961
+ .upa_documents
1962
+ .iter()
1963
+ .find_map(|doc| doc.uniform_sequence(&s.qname, namespace))
1964
+ != Some(true)
1965
+ {
1966
+ return false;
1967
+ }
1968
+ if !seen.insert(s.qname.clone()) {
1969
+ return false;
1970
+ }
1971
+ if s.base_type.is_some()
1972
+ || s.is_abstract
1973
+ || s.is_mixed
1974
+ || ir.ordered_types.contains(&s.qname)
1975
+ {
1976
+ return false;
1977
+ }
1978
+ if ir
1979
+ .content_models
1980
+ .get(&s.qname)
1981
+ .is_some_and(|p| p.has_choice() || p.has_reference() || p.has_repeated_sequence())
1982
+ {
1983
+ return false;
1984
+ }
1985
+ s.fields.iter().all(|f| {
1986
+ if f.nillable
1987
+ || f.is_cycle_cut
1988
+ || f.xml_name.contains(':')
1989
+ || f.type_ref.is_list()
1990
+ || f.type_ref.is_boxed()
1991
+ {
1992
+ return false;
1993
+ }
1994
+ match f.kind {
1995
+ FieldKind::Element if &f.namespace != namespace => return false,
1996
+ FieldKind::Attribute if f.namespace.is_some() => return false,
1997
+ FieldKind::Element | FieldKind::Attribute | FieldKind::Text => {}
1998
+ _ => return false,
1999
+ }
2000
+ match &f.type_ref {
2001
+ TypeRef::Named(q) => match ir.types.get(q) {
2002
+ Some(TypeDef::Struct(child)) => {
2003
+ supported(child, ir, namespace, &mut seen.clone())
2004
+ }
2005
+ Some(TypeDef::Enum(_)) => true,
2006
+ _ => false,
2007
+ },
2008
+ TypeRef::Primitive(_) => true,
2009
+ _ => false,
2010
+ }
2011
+ })
2012
+ }
2013
+ if s.fields.len() != 1
2014
+ || s.fields[0].kind != FieldKind::Element
2015
+ || !s.fields[0].cardinality.is_list()
2016
+ {
2017
+ return;
2018
+ }
2019
+ let field = &s.fields[0];
2020
+ let mut methods = HashSet::new();
2021
+ for root in ir.elements.values() {
2022
+ if root.type_ref != TypeRef::Named(s.qname.clone())
2023
+ || root.nillable
2024
+ || !supported(s, ir, &root.qname.namespace, &mut HashSet::new())
2025
+ {
2026
+ continue;
2027
+ }
2028
+ let name = type_ident(&s.qname);
2029
+ let item_type = self.format_rust_type_ref(&field.type_ref, lifetimes);
2030
+ let impl_type = if self.options.zero_copy && lifetimes.contains(&s.qname) {
2031
+ format!("impl<'a> {name}<'a>")
2032
+ } else {
2033
+ format!("impl {name}")
2034
+ };
2035
+ let base_method = format!("write_{}_items", AsSnakeCase(&root.qname.local));
2036
+ let mut method = base_method.clone();
2037
+ let mut suffix = 2;
2038
+ while !methods.insert(method.clone()) {
2039
+ method = format!("{base_method}_{suffix}");
2040
+ suffix += 1;
2041
+ }
2042
+ writeln!(out, "\n{impl_type} {{").unwrap();
2043
+ out.push_str(" /// Produce a document incrementally, retaining only the current item.\n /// Errors may leave partial XML in the sink; publish files only after success.\n");
2044
+ writeln!(out, " pub fn {method}<W: std::io::Write, I: IntoIterator<Item = {item_type}>>(sink: &mut W, items: I) -> Result<()> {{").unwrap();
2045
+ out.push_str(" let writer = &mut Writer::new(sink);\n");
2046
+ writeln!(
2047
+ out,
2048
+ " let {}start = BytesStart::new({:?});",
2049
+ if root.qname.namespace.is_some() {
2050
+ "mut "
2051
+ } else {
2052
+ ""
2053
+ },
2054
+ root.qname.local
2055
+ )
2056
+ .unwrap();
2057
+ if let Some(ns) = &root.qname.namespace {
2058
+ writeln!(out, " start.push_attribute((\"xmlns\", {ns:?}));").unwrap();
2059
+ }
2060
+ out.push_str(" writer.write_event(Event::Start(start))?;\n let mut count = 0usize;\n");
2061
+ let mut checks = String::new();
2062
+ if let crate::ir::OccursLimit::Count(max) = field.cardinality.max_occurs {
2063
+ writeln!(checks, " if count == {max} {{ return Err(PolyXmlError::SchemaError(\"maxOccurs exceeded\".into())); }}").unwrap();
2064
+ }
2065
+ checks.push_str(" count = count.checked_add(1).ok_or_else(|| PolyXmlError::SchemaError(\"item count overflow\".into()))?;\n");
2066
+ self.emit_pattern_check(&mut checks, field, "&item.to_string()", ir);
2067
+ self.emit_element_serialize_from(out, field, "items", ir, Some("items"), &checks);
2068
+ if field.cardinality.min_occurs > 0 {
2069
+ writeln!(out, " if count < {} {{ return Err(PolyXmlError::SchemaError(\"minOccurs not satisfied\".into())); }}", field.cardinality.min_occurs).unwrap();
2070
+ }
2071
+ writeln!(out, " writer.write_event(Event::End(BytesEnd::new({:?})))?;\n Ok(())\n }}\n}}", root.qname.local).unwrap();
2072
+ }
1943
2073
  }
1944
2074
 
1945
2075
  fn emit_integer_bounds(
@@ -2406,6 +2536,18 @@ impl RustCodegen {
2406
2536
  field: &FieldDef,
2407
2537
  rust_name: &str,
2408
2538
  ir: &SchemaIR,
2539
+ ) {
2540
+ self.emit_element_serialize_from(out, field, rust_name, ir, None, "");
2541
+ }
2542
+
2543
+ fn emit_element_serialize_from(
2544
+ &self,
2545
+ out: &mut String,
2546
+ field: &FieldDef,
2547
+ rust_name: &str,
2548
+ ir: &SchemaIR,
2549
+ source: Option<&str>,
2550
+ before_item: &str,
2409
2551
  ) {
2410
2552
  let is_list = field.cardinality.is_list() || field.type_ref.is_list();
2411
2553
  let is_optional = field.cardinality.is_optional() || field.nillable;
@@ -2415,7 +2557,11 @@ impl RustCodegen {
2415
2557
  let is_enum = self.field_is_enum(&field.type_ref, ir).is_some();
2416
2558
 
2417
2559
  if is_list {
2418
- let _ = writeln!(out, " for item in &self.{} {{", rust_name);
2560
+ let source = source
2561
+ .map(str::to_owned)
2562
+ .unwrap_or_else(|| format!("&self.{rust_name}"));
2563
+ let _ = writeln!(out, " for item in {source} {{");
2564
+ out.push_str(before_item);
2419
2565
  if is_string {
2420
2566
  let _ = writeln!(
2421
2567
  out,
@@ -1,4 +1,5 @@
1
1
  use std::collections::HashMap;
2
+ use std::sync::atomic::{AtomicBool, Ordering};
2
3
  use std::sync::{Arc, RwLock};
3
4
 
4
5
  #[derive(Debug, Clone, PartialEq, Eq)]
@@ -106,6 +107,12 @@ impl FieldSchema {
106
107
  }
107
108
  }
108
109
 
110
+ #[derive(Debug, Default)]
111
+ struct VariantRegistry {
112
+ schemas: RwLock<Vec<Arc<ModelSchema>>>,
113
+ populated: AtomicBool,
114
+ }
115
+
109
116
  #[derive(Debug, Clone)]
110
117
  pub struct ModelSchema {
111
118
  pub name: String,
@@ -125,7 +132,7 @@ pub struct ModelSchema {
125
132
  pub content_pattern: Option<regex::Regex>,
126
133
  /// Concrete derivations eligible for `xsi:type` dispatch. Python may
127
134
  /// refresh this registry when subclasses are defined after first use.
128
- variants: Arc<RwLock<Vec<Arc<ModelSchema>>>>,
135
+ variants: Arc<VariantRegistry>,
129
136
  }
130
137
 
131
138
  impl ModelSchema {
@@ -136,12 +143,27 @@ impl ModelSchema {
136
143
  /// Register the concrete derivations eligible for `xsi:type` dispatch.
137
144
  /// Replaces the registry when subclasses are discovered after first use.
138
145
  pub fn set_variants(&self, variants: Vec<Arc<ModelSchema>>) {
139
- *self.variants.write().unwrap_or_else(|p| p.into_inner()) = variants;
146
+ let mut registered = self
147
+ .variants
148
+ .schemas
149
+ .write()
150
+ .unwrap_or_else(|p| p.into_inner());
151
+ *registered = variants;
152
+ // Publish emptiness while holding the registry lock. Cloned schemas
153
+ // share this flag, so later registration/clearing cannot leave a stale
154
+ // per-schema dispatch plan. Nonempty lookups still use the lock.
155
+ self.variants
156
+ .populated
157
+ .store(!registered.is_empty(), Ordering::Release);
140
158
  }
141
159
 
142
160
  /// Registered derivations for `xsi:type` dispatch (empty when none).
143
161
  pub fn variants(&self) -> Vec<Arc<ModelSchema>> {
162
+ if !self.has_variants() {
163
+ return Vec::new();
164
+ }
144
165
  self.variants
166
+ .schemas
145
167
  .read()
146
168
  .unwrap_or_else(|p| p.into_inner())
147
169
  .clone()
@@ -149,16 +171,16 @@ impl ModelSchema {
149
171
 
150
172
  /// Whether any `xsi:type` derivations are registered for this type.
151
173
  pub fn has_variants(&self) -> bool {
152
- !self
153
- .variants
154
- .read()
155
- .unwrap_or_else(|p| p.into_inner())
156
- .is_empty()
174
+ self.variants.populated.load(Ordering::Acquire)
157
175
  }
158
176
 
159
177
  /// Look up a derivation by the QName local part of an `xsi:type` value.
160
178
  pub fn find_variant(&self, local: &[u8]) -> Option<Arc<ModelSchema>> {
179
+ if !self.has_variants() {
180
+ return None;
181
+ }
161
182
  self.variants
183
+ .schemas
162
184
  .read()
163
185
  .unwrap_or_else(|p| p.into_inner())
164
186
  .iter()
@@ -168,7 +190,11 @@ impl ModelSchema {
168
190
 
169
191
  /// Look up a derivation by its complete QName.
170
192
  pub fn find_variant_qname(&self, namespace: &str, local: &[u8]) -> Option<Arc<ModelSchema>> {
193
+ if !self.has_variants() {
194
+ return None;
195
+ }
171
196
  self.variants
197
+ .schemas
172
198
  .read()
173
199
  .unwrap_or_else(|p| p.into_inner())
174
200
  .iter()
@@ -180,7 +206,11 @@ impl ModelSchema {
180
206
 
181
207
  /// Whether `candidate` is a registered derivation of `self`.
182
208
  pub fn matches_variant(&self, candidate: &ModelSchema) -> bool {
209
+ if !self.has_variants() {
210
+ return false;
211
+ }
183
212
  self.variants
213
+ .schemas
184
214
  .read()
185
215
  .unwrap_or_else(|p| p.into_inner())
186
216
  .iter()
@@ -664,7 +694,7 @@ impl ModelSchemaBuilder {
664
694
  is_abstract: self.is_abstract,
665
695
  strict_root: self.strict_root,
666
696
  content_pattern: self.content_pattern,
667
- variants: Arc::new(RwLock::new(Vec::new())),
697
+ variants: Arc::new(VariantRegistry::default()),
668
698
  })
669
699
  }
670
700
  }
@@ -15,6 +15,8 @@ pub struct Document {
15
15
  groups: BTreeMap<QName, Model>,
16
16
  named: BTreeMap<QName, Model>,
17
17
  anonymous: Vec<(String, Model)>,
18
+ #[serde(default)]
19
+ unsupported_attribute_types: BTreeSet<QName>,
18
20
  }
19
21
  #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20
22
  enum Model {
@@ -115,14 +117,7 @@ fn node(
115
117
  Event::Start(child)
116
118
  if matches!(
117
119
  strip_prefix(child.name().into_inner()),
118
- "annotation"
119
- | "simpleType"
120
- | "attribute"
121
- | "attributeGroup"
122
- | "anyAttribute"
123
- | "unique"
124
- | "key"
125
- | "keyref"
120
+ "annotation" | "simpleType" | "unique" | "key" | "keyref"
126
121
  ) =>
127
122
  {
128
123
  reader.read_to_end(child.name())?;
@@ -130,14 +125,7 @@ fn node(
130
125
  Event::Empty(child)
131
126
  if matches!(
132
127
  strip_prefix(child.name().into_inner()),
133
- "annotation"
134
- | "simpleType"
135
- | "attribute"
136
- | "attributeGroup"
137
- | "anyAttribute"
138
- | "unique"
139
- | "key"
140
- | "keyref"
128
+ "annotation" | "simpleType" | "unique" | "key" | "keyref"
141
129
  ) => {}
142
130
  Event::Start(child) => {
143
131
  result
@@ -256,6 +244,28 @@ fn particle(node: &Node, ns: Option<&str>, qualified: bool) -> Option<Model> {
256
244
  Some(repeat(node, model))
257
245
  }
258
246
  impl Document {
247
+ /// Whether a named type has plain sequence content in one element namespace.
248
+ /// Source QNames retain local `form` and `elementFormDefault` distinctions.
249
+ pub(crate) fn uniform_sequence(
250
+ &self,
251
+ name: &QName,
252
+ namespace: &Option<String>,
253
+ ) -> Option<bool> {
254
+ fn uniform(model: &Model, namespace: &Option<String>) -> bool {
255
+ match model {
256
+ Model::Element(q, _) => &q.namespace == namespace,
257
+ Model::Sequence(parts) => parts.iter().all(|p| uniform(p, namespace)),
258
+ Model::Repeat(part, _, _) if matches!(part.as_ref(), Model::Element(..)) => {
259
+ uniform(part, namespace)
260
+ }
261
+ _ => false,
262
+ }
263
+ }
264
+ self.named.get(name).map(|model| {
265
+ !self.unsupported_attribute_types.contains(name) && uniform(model, namespace)
266
+ })
267
+ }
268
+
259
269
  pub fn parse(xml: &str) -> Result<Self, SchemaError> {
260
270
  let mut reader = Reader::from_str(xml);
261
271
  let root = loop {
@@ -273,13 +283,38 @@ impl Document {
273
283
  groups: BTreeMap::new(),
274
284
  named: BTreeMap::new(),
275
285
  anonymous: Vec::new(),
286
+ unsupported_attribute_types: BTreeSet::new(),
276
287
  };
277
- fn collect(node: &Node, document: &mut Document, qualified: bool) {
288
+ fn qualified_attributes(node: &Node, default_qualified: bool) -> bool {
289
+ matches!(node.tag.as_str(), "attributeGroup" | "anyAttribute")
290
+ || (node.tag == "attribute"
291
+ && (node.attr("ref").is_some()
292
+ || match node.attr("form") {
293
+ Some("qualified") => true,
294
+ Some("unqualified") => false,
295
+ _ => default_qualified,
296
+ }))
297
+ || node
298
+ .children
299
+ .iter()
300
+ .any(|child| qualified_attributes(child, default_qualified))
301
+ }
302
+ fn collect(
303
+ node: &Node,
304
+ document: &mut Document,
305
+ qualified: bool,
306
+ attributes_qualified: bool,
307
+ ) {
278
308
  let ns = document.namespace.as_deref();
279
309
  if node.tag == "complexType" {
280
310
  let model = content(node, ns, qualified);
281
311
  if let Some(name) = node.attr("name") {
282
312
  document.named.insert(QName::new(ns, name), model);
313
+ if qualified_attributes(node, attributes_qualified) {
314
+ document
315
+ .unsupported_attribute_types
316
+ .insert(QName::new(ns, name));
317
+ }
283
318
  } else {
284
319
  document.anonymous.push((
285
320
  format!("anonymous complexType at byte {}", node.position),
@@ -288,7 +323,7 @@ impl Document {
288
323
  }
289
324
  }
290
325
  for child in &node.children {
291
- collect(child, document, qualified);
326
+ collect(child, document, qualified, attributes_qualified);
292
327
  }
293
328
  }
294
329
  for child in &root.children {
@@ -301,7 +336,12 @@ impl Document {
301
336
  }
302
337
  }
303
338
  }
304
- collect(&root, &mut document, qualified);
339
+ collect(
340
+ &root,
341
+ &mut document,
342
+ qualified,
343
+ root.attr("attributeFormDefault") == Some("qualified"),
344
+ );
305
345
  Ok(document)
306
346
  }
307
347
  pub fn adopt_namespace(&mut self, namespace: &str) {
@@ -344,6 +384,13 @@ impl Document {
344
384
  _ => {}
345
385
  }
346
386
  }
387
+ self.unsupported_attribute_types = std::mem::take(&mut self.unsupported_attribute_types)
388
+ .into_iter()
389
+ .map(|mut q| {
390
+ name(&mut q, namespace);
391
+ q
392
+ })
393
+ .collect();
347
394
  for collection in [&mut self.groups, &mut self.named] {
348
395
  *collection = std::mem::take(collection)
349
396
  .into_iter()
@@ -1505,3 +1505,52 @@ assert_eq!(parsed,value);let object:serde_json::Value=serde_json::from_str(&json
1505
1505
  );
1506
1506
  }
1507
1507
  }
1508
+
1509
+ #[test]
1510
+ fn incremental_root_writer_is_schema_scoped() {
1511
+ let xsd = include_str!("../../../tests/fixtures/incremental/items.xsd");
1512
+ for zero_copy in [true, false] {
1513
+ let ir = XsdParser::new().parse_str(xsd).unwrap();
1514
+ let code = RustCodegen::new(RustOptions {
1515
+ zero_copy,
1516
+ ..Default::default()
1517
+ })
1518
+ .generate_module(&ir);
1519
+ assert!(code.contains("pub fn write_batch_items"));
1520
+ assert!(code.contains("for item in items"));
1521
+ assert!(code.contains("start.push_attribute((\"xmlns\", \"urn:items\"))"));
1522
+ assert!(code.contains("if count < 1"));
1523
+ assert!(code.contains("item.encode_xml(writer, Some(\"Item\"))"));
1524
+ let bounded = xsd.replace("maxOccurs=\"unbounded\"", "maxOccurs=\"2\"");
1525
+ let ir = XsdParser::new().parse_str(&bounded).unwrap();
1526
+ let code = RustCodegen::new(RustOptions::default()).generate_module(&ir);
1527
+ assert!(code.contains("if count == 2"));
1528
+ for unsupported in [
1529
+ xsd.replace(
1530
+ "elementFormDefault=\"qualified\"",
1531
+ "elementFormDefault=\"qualified\" attributeFormDefault=\"qualified\"",
1532
+ ),
1533
+ xsd.replace("<xs:sequence>", "<xs:sequence minOccurs=\"0\">"),
1534
+ xsd.replace(
1535
+ "name=\"Item\" type=\"t:ItemType\"",
1536
+ "name=\"Item\" type=\"t:ItemType\" nillable=\"true\"",
1537
+ ),
1538
+ xsd.replace(
1539
+ "elementFormDefault=\"qualified\"",
1540
+ "elementFormDefault=\"unqualified\"",
1541
+ ),
1542
+ xsd.replace(
1543
+ "name=\"Batch\" type=\"t:BatchType\"",
1544
+ "name=\"Batch\" type=\"t:BatchType\" nillable=\"true\"",
1545
+ ),
1546
+ ] {
1547
+ let ir = XsdParser::new().parse_str(&unsupported).unwrap();
1548
+ let code = RustCodegen::new(RustOptions::default()).generate_module(&ir);
1549
+ assert!(
1550
+ !code.contains("pub fn write_batch_items"),
1551
+ "{unsupported}\n{:?}",
1552
+ ir.upa_documents
1553
+ );
1554
+ }
1555
+ }
1556
+ }
@@ -0,0 +1,75 @@
1
+ use std::sync::Arc;
2
+
3
+ use polyxml::schema::ModelSchema;
4
+
5
+ #[test]
6
+ fn registration_and_clearing_are_visible_to_schema_clones() {
7
+ let base = ModelSchema::builder("Base").build();
8
+ let clone = (*base).clone();
9
+ let derived = ModelSchema::builder("Derived")
10
+ .namespace("urn:derived")
11
+ .build();
12
+ assert!(!clone.has_variants());
13
+ assert!(clone.variants().is_empty());
14
+ base.set_variants(vec![Arc::clone(&derived)]);
15
+ assert!(clone.has_variants());
16
+ assert!(Arc::ptr_eq(&clone.variants()[0], &derived));
17
+ assert!(Arc::ptr_eq(
18
+ &clone.find_variant(b"Derived").unwrap(),
19
+ &derived
20
+ ));
21
+ assert!(clone.find_variant_qname("urn:other", b"Derived").is_none());
22
+ assert!(Arc::ptr_eq(
23
+ &clone.find_variant_qname("urn:derived", b"Derived").unwrap(),
24
+ &derived
25
+ ));
26
+ assert!(clone.matches_variant(&derived));
27
+ clone.set_variants(Vec::new());
28
+ assert!(!base.has_variants());
29
+ assert!(base.variants().is_empty());
30
+ assert!(base.find_variant(b"Derived").is_none());
31
+ assert!(!base.matches_variant(&derived));
32
+ clone.set_variants(vec![Arc::clone(&derived)]);
33
+ assert!(base.has_variants());
34
+ assert!(base.matches_variant(&derived));
35
+ }
36
+
37
+ #[test]
38
+ fn concurrent_registry_updates_keep_complete_variant_snapshots() {
39
+ let base = ModelSchema::builder("Base").build();
40
+ let derived = ModelSchema::builder("Derived").build();
41
+ std::thread::scope(|scope| {
42
+ scope.spawn(|| {
43
+ for index in 0..4096 {
44
+ base.set_variants(if index % 2 == 0 {
45
+ Vec::new()
46
+ } else {
47
+ vec![Arc::clone(&derived)]
48
+ });
49
+ }
50
+ base.set_variants(vec![Arc::clone(&derived)]);
51
+ });
52
+ for _ in 0..4 {
53
+ scope.spawn(|| {
54
+ for _ in 0..4096 {
55
+ // These calls can observe different registry revisions.
56
+ // Each populated snapshot must contain the original Arc.
57
+ let _ = base.has_variants();
58
+ let variants = base.variants();
59
+ assert!(variants.len() <= 1);
60
+ for variant in variants {
61
+ assert!(Arc::ptr_eq(&variant, &derived));
62
+ }
63
+ if let Some(variant) = base.find_variant(b"Derived") {
64
+ assert!(Arc::ptr_eq(&variant, &derived));
65
+ }
66
+ }
67
+ });
68
+ }
69
+ });
70
+ assert!(base.has_variants());
71
+ assert!(Arc::ptr_eq(&base.variants()[0], &derived));
72
+ base.set_variants(Vec::new());
73
+ assert!(!base.has_variants());
74
+ assert!(base.variants().is_empty());
75
+ }
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "polyxml"
7
- version = "0.34.8"
7
+ version = "0.35.0"
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