polyxml 0.24.0__tar.gz → 0.25.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.
- {polyxml-0.24.0 → polyxml-0.25.0}/Cargo.lock +6 -6
- {polyxml-0.24.0 → polyxml-0.25.0}/Cargo.toml +1 -1
- {polyxml-0.24.0 → polyxml-0.25.0}/PKG-INFO +4 -4
- {polyxml-0.24.0/crates/polyxml-python → polyxml-0.25.0}/README.md +3 -3
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +91 -22
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +83 -22
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/go/mod.rs +142 -28
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/java/codec.rs +45 -2
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/java/mod.rs +4 -2
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/mod.rs +64 -1
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/python/mod.rs +4 -2
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/rust/mod.rs +6 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +32 -13
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/schema_parser/mod.rs +131 -50
- polyxml-0.25.0/crates/polyxml-core/tests/test_adversarial_identifiers.rs +285 -0
- polyxml-0.25.0/crates/polyxml-core/tests/test_unbounded_choice.rs +283 -0
- {polyxml-0.24.0 → polyxml-0.25.0/crates/polyxml-python}/README.md +3 -3
- {polyxml-0.24.0 → polyxml-0.25.0}/pyproject.toml +1 -1
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/Cargo.toml +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/README.md +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/converters.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/error.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/json.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/lib.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/parser.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/schema.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/serializer.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/transcoder.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/src/value.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_core.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_json.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_security.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-python/Cargo.toml +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-python/src/lib.rs +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-python/tests/test_json.py +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/python/polyxml/AGENT_GUIDE.md +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/python/polyxml/__init__.py +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/python/polyxml/compat/__init__.py +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/python/polyxml/compat/xsdata.py +0 -0
- {polyxml-0.24.0 → polyxml-0.25.0}/python/polyxml/py.typed +0 -0
|
@@ -608,7 +608,7 @@ dependencies = [
|
|
|
608
608
|
|
|
609
609
|
[[package]]
|
|
610
610
|
name = "polyxml"
|
|
611
|
-
version = "0.
|
|
611
|
+
version = "0.25.0"
|
|
612
612
|
dependencies = [
|
|
613
613
|
"criterion",
|
|
614
614
|
"heck",
|
|
@@ -628,14 +628,14 @@ dependencies = [
|
|
|
628
628
|
|
|
629
629
|
[[package]]
|
|
630
630
|
name = "polyxml-c"
|
|
631
|
-
version = "0.
|
|
631
|
+
version = "0.25.0"
|
|
632
632
|
dependencies = [
|
|
633
633
|
"polyxml",
|
|
634
634
|
]
|
|
635
635
|
|
|
636
636
|
[[package]]
|
|
637
637
|
name = "polyxml-cli"
|
|
638
|
-
version = "0.
|
|
638
|
+
version = "0.25.0"
|
|
639
639
|
dependencies = [
|
|
640
640
|
"clap",
|
|
641
641
|
"glob",
|
|
@@ -649,7 +649,7 @@ dependencies = [
|
|
|
649
649
|
|
|
650
650
|
[[package]]
|
|
651
651
|
name = "polyxml-js"
|
|
652
|
-
version = "0.
|
|
652
|
+
version = "0.25.0"
|
|
653
653
|
dependencies = [
|
|
654
654
|
"napi",
|
|
655
655
|
"napi-build",
|
|
@@ -659,7 +659,7 @@ dependencies = [
|
|
|
659
659
|
|
|
660
660
|
[[package]]
|
|
661
661
|
name = "polyxml-python"
|
|
662
|
-
version = "0.
|
|
662
|
+
version = "0.25.0"
|
|
663
663
|
dependencies = [
|
|
664
664
|
"lexical-core",
|
|
665
665
|
"polyxml",
|
|
@@ -669,7 +669,7 @@ dependencies = [
|
|
|
669
669
|
|
|
670
670
|
[[package]]
|
|
671
671
|
name = "polyxml-wasm"
|
|
672
|
-
version = "0.
|
|
672
|
+
version = "0.25.0"
|
|
673
673
|
dependencies = [
|
|
674
674
|
"polyxml",
|
|
675
675
|
"quick-xml",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polyxml
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -62,9 +62,9 @@ pip install polyxml
|
|
|
62
62
|
|
|
63
63
|
## Features
|
|
64
64
|
|
|
65
|
-
- **⚡
|
|
66
|
-
- **🌊 Streaming `iterparse()`**:
|
|
67
|
-
- **📦
|
|
65
|
+
- **⚡ Fast typed XML binding**: 10.0x faster deserialization and 23.5x faster serialization than `xsdata` on the [10,000-item catalog benchmark](../../benchmarks/python/results.md).
|
|
66
|
+
- **🌊 Streaming `iterparse()`**: Process records incrementally; measure memory on your own document structure.
|
|
67
|
+
- **📦 Binary Serialization**: Native `dumps_binary()` and `loads_binary()` using MessagePack (`msgspec`).
|
|
68
68
|
- **🎯 Full Type Support**: Dataclasses and Pydantic v2 models with zero boilerplate.
|
|
69
69
|
|
|
70
70
|
---
|
|
@@ -29,9 +29,9 @@ pip install polyxml
|
|
|
29
29
|
|
|
30
30
|
## Features
|
|
31
31
|
|
|
32
|
-
- **⚡
|
|
33
|
-
- **🌊 Streaming `iterparse()`**:
|
|
34
|
-
- **📦
|
|
32
|
+
- **⚡ Fast typed XML binding**: 10.0x faster deserialization and 23.5x faster serialization than `xsdata` on the [10,000-item catalog benchmark](../../benchmarks/python/results.md).
|
|
33
|
+
- **🌊 Streaming `iterparse()`**: Process records incrementally; measure memory on your own document structure.
|
|
34
|
+
- **📦 Binary Serialization**: Native `dumps_binary()` and `loads_binary()` using MessagePack (`msgspec`).
|
|
35
35
|
- **🎯 Full Type Support**: Dataclasses and Pydantic v2 models with zero boilerplate.
|
|
36
36
|
|
|
37
37
|
---
|
|
@@ -5,7 +5,8 @@ use heck::{AsPascalCase, AsSnakeCase};
|
|
|
5
5
|
use serde::{Deserialize, Serialize};
|
|
6
6
|
|
|
7
7
|
use crate::codegen::{
|
|
8
|
-
build_type_name_map, lookup_type_name,
|
|
8
|
+
build_type_name_map, lookup_type_name, normalize_symbol_name, sanitize_keyword,
|
|
9
|
+
set_type_name_map, LanguageContext,
|
|
9
10
|
};
|
|
10
11
|
use crate::ir::{
|
|
11
12
|
EnumDef, PrimitiveType, QName, RestrictionFacets, SchemaIR, SimpleTypeDef, StructDef, TypeDef,
|
|
@@ -224,7 +225,11 @@ pub fn to_cpp_field_name(raw: &str) -> String {
|
|
|
224
225
|
|
|
225
226
|
/// Converts an enumeration variant raw value into a safe C++ scoped enum identifier.
|
|
226
227
|
pub fn to_cpp_enum_variant(raw: &str) -> String {
|
|
227
|
-
|
|
228
|
+
if raw.trim().is_empty() {
|
|
229
|
+
return "Unknown".to_string();
|
|
230
|
+
}
|
|
231
|
+
let normalized = normalize_symbol_name(raw);
|
|
232
|
+
let pascal = AsPascalCase(&normalized).to_string();
|
|
228
233
|
let safe = if pascal.is_empty() {
|
|
229
234
|
"Unknown".to_string()
|
|
230
235
|
} else if pascal.starts_with(|c: char| c.is_ascii_digit()) {
|
|
@@ -255,6 +260,39 @@ impl CppCodegen {
|
|
|
255
260
|
}
|
|
256
261
|
}
|
|
257
262
|
|
|
263
|
+
fn unique_field_name(
|
|
264
|
+
&self,
|
|
265
|
+
struct_name: &str,
|
|
266
|
+
raw: &str,
|
|
267
|
+
seen: &mut HashSet<String>,
|
|
268
|
+
) -> String {
|
|
269
|
+
let base = to_cpp_field_name(raw);
|
|
270
|
+
let mut name = base.clone();
|
|
271
|
+
if name.eq_ignore_ascii_case(struct_name) {
|
|
272
|
+
name.push_str("_value");
|
|
273
|
+
}
|
|
274
|
+
let stem = name.clone();
|
|
275
|
+
let mut index = 2;
|
|
276
|
+
while seen.contains(&name) || name.eq_ignore_ascii_case(struct_name) {
|
|
277
|
+
name = format!("{}_{}", stem, index);
|
|
278
|
+
index += 1;
|
|
279
|
+
}
|
|
280
|
+
seen.insert(name.clone());
|
|
281
|
+
name
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
fn unique_enum_variant(&self, raw: &str, seen: &mut HashSet<String>) -> String {
|
|
285
|
+
let base = to_cpp_enum_variant(raw);
|
|
286
|
+
let mut name = base.clone();
|
|
287
|
+
let mut index = 2;
|
|
288
|
+
while seen.contains(&name) {
|
|
289
|
+
name = format!("{}_{}", base, index);
|
|
290
|
+
index += 1;
|
|
291
|
+
}
|
|
292
|
+
seen.insert(name.clone());
|
|
293
|
+
name
|
|
294
|
+
}
|
|
295
|
+
|
|
258
296
|
/// Generate complete header-only source (`.hpp`).
|
|
259
297
|
pub fn generate_header(&self, ir: &SchemaIR) -> String {
|
|
260
298
|
set_type_name_map(build_type_name_map(ir, to_cpp_type_name));
|
|
@@ -501,8 +539,15 @@ endif()
|
|
|
501
539
|
writeln!(out, " static constexpr auto value = enumerate();").unwrap();
|
|
502
540
|
} else {
|
|
503
541
|
writeln!(out, " static constexpr auto value = enumerate(").unwrap();
|
|
504
|
-
|
|
505
|
-
|
|
542
|
+
let mut seen = HashSet::new();
|
|
543
|
+
let variant_names: Vec<String> = enum_def
|
|
544
|
+
.variants
|
|
545
|
+
.iter()
|
|
546
|
+
.map(|v| self.unique_enum_variant(&v.name, &mut seen))
|
|
547
|
+
.collect();
|
|
548
|
+
for (i, (v, var_name)) in
|
|
549
|
+
enum_def.variants.iter().zip(&variant_names).enumerate()
|
|
550
|
+
{
|
|
506
551
|
let comma = if i + 1 < enum_def.variants.len() {
|
|
507
552
|
","
|
|
508
553
|
} else {
|
|
@@ -527,6 +572,7 @@ endif()
|
|
|
527
572
|
writeln!(out, " using T = {};", full_type).unwrap();
|
|
528
573
|
|
|
529
574
|
let mut field_bindings = Vec::new();
|
|
575
|
+
let mut seen = HashSet::new();
|
|
530
576
|
|
|
531
577
|
// Check for simple_content_base "value"
|
|
532
578
|
if let Some(ref base_qname) = s.base_type {
|
|
@@ -538,12 +584,13 @@ endif()
|
|
|
538
584
|
&& (PrimitiveType::from_xsd_name(&base_qname.local).is_some()
|
|
539
585
|
|| matches!(ir.types.get(base_qname), Some(TypeDef::Simple(_))))
|
|
540
586
|
{
|
|
587
|
+
seen.insert("value".to_string());
|
|
541
588
|
field_bindings.push(r#""value", &T::value"#.to_string());
|
|
542
589
|
}
|
|
543
590
|
}
|
|
544
591
|
|
|
545
592
|
for f in &s.fields {
|
|
546
|
-
let field_name =
|
|
593
|
+
let field_name = self.unique_field_name(&struct_name, &f.name, &mut seen);
|
|
547
594
|
field_bindings.push(format!("{:?}, &T::{}", f.xml_name, field_name));
|
|
548
595
|
}
|
|
549
596
|
|
|
@@ -671,11 +718,17 @@ concept XmlModel = requires(T a) {{
|
|
|
671
718
|
let enum_name = type_ident(&enum_def.qname);
|
|
672
719
|
writeln!(out, "enum class {} {{", enum_name).unwrap();
|
|
673
720
|
|
|
674
|
-
|
|
721
|
+
let mut seen = HashSet::new();
|
|
722
|
+
let variant_names: Vec<String> = enum_def
|
|
723
|
+
.variants
|
|
724
|
+
.iter()
|
|
725
|
+
.map(|v| self.unique_enum_variant(&v.name, &mut seen))
|
|
726
|
+
.collect();
|
|
727
|
+
|
|
728
|
+
for (variant, var_name) in enum_def.variants.iter().zip(&variant_names) {
|
|
675
729
|
if let Some(ref doc) = variant.documentation {
|
|
676
730
|
writeln!(out, " /// {}", doc).unwrap();
|
|
677
731
|
}
|
|
678
|
-
let var_name = to_cpp_enum_variant(&variant.name);
|
|
679
732
|
writeln!(out, " {},", var_name).unwrap();
|
|
680
733
|
}
|
|
681
734
|
writeln!(out, "}};\n").unwrap();
|
|
@@ -689,8 +742,7 @@ concept XmlModel = requires(T a) {{
|
|
|
689
742
|
)
|
|
690
743
|
.unwrap();
|
|
691
744
|
writeln!(out, " switch (value) {{").unwrap();
|
|
692
|
-
for variant in
|
|
693
|
-
let var_name = to_cpp_enum_variant(&variant.name);
|
|
745
|
+
for (variant, var_name) in enum_def.variants.iter().zip(&variant_names) {
|
|
694
746
|
writeln!(
|
|
695
747
|
out,
|
|
696
748
|
" case {}::{}: return \"{}\";",
|
|
@@ -718,8 +770,7 @@ concept XmlModel = requires(T a) {{
|
|
|
718
770
|
enum_name, func_name
|
|
719
771
|
)
|
|
720
772
|
.unwrap();
|
|
721
|
-
for variant in
|
|
722
|
-
let var_name = to_cpp_enum_variant(&variant.name);
|
|
773
|
+
for (variant, var_name) in enum_def.variants.iter().zip(&variant_names) {
|
|
723
774
|
writeln!(
|
|
724
775
|
out,
|
|
725
776
|
" if (s == \"{}\") return {}::{};",
|
|
@@ -816,7 +867,7 @@ concept XmlModel = requires(T a) {{
|
|
|
816
867
|
|
|
817
868
|
writeln!(out, "struct {}{} {{", struct_name, base_clause).unwrap();
|
|
818
869
|
|
|
819
|
-
if let Some(base_type_str) = simple_content_base {
|
|
870
|
+
if let Some(ref base_type_str) = simple_content_base {
|
|
820
871
|
let has_value_field = s
|
|
821
872
|
.fields
|
|
822
873
|
.iter()
|
|
@@ -828,8 +879,6 @@ concept XmlModel = requires(T a) {{
|
|
|
828
879
|
|| base_type_str.starts_with("std::uint")
|
|
829
880
|
{
|
|
830
881
|
" = 0"
|
|
831
|
-
} else if base_type_str == "bool" {
|
|
832
|
-
" = false"
|
|
833
882
|
} else {
|
|
834
883
|
" = {}"
|
|
835
884
|
};
|
|
@@ -837,12 +886,27 @@ concept XmlModel = requires(T a) {{
|
|
|
837
886
|
}
|
|
838
887
|
}
|
|
839
888
|
|
|
889
|
+
let mut seen = HashSet::new();
|
|
890
|
+
if simple_content_base.is_some() {
|
|
891
|
+
let has_value_field = s
|
|
892
|
+
.fields
|
|
893
|
+
.iter()
|
|
894
|
+
.any(|f| f.name == "value" || f.kind == crate::ir::FieldKind::Text);
|
|
895
|
+
if !has_value_field {
|
|
896
|
+
seen.insert("value".to_string());
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
let field_names: Vec<String> = s
|
|
900
|
+
.fields
|
|
901
|
+
.iter()
|
|
902
|
+
.map(|f| self.unique_field_name(&struct_name, &f.name, &mut seen))
|
|
903
|
+
.collect();
|
|
904
|
+
|
|
840
905
|
// Fields
|
|
841
|
-
for f in
|
|
906
|
+
for (f, field_name) in s.fields.iter().zip(&field_names) {
|
|
842
907
|
if let Some(ref doc) = f.documentation {
|
|
843
908
|
writeln!(out, " /// {}", doc).unwrap();
|
|
844
909
|
}
|
|
845
|
-
let field_name = to_cpp_field_name(&f.name);
|
|
846
910
|
let (field_type, init_val) = self.resolve_field_type_and_init(f);
|
|
847
911
|
writeln!(out, " {} {}{};", field_type, field_name, init_val).unwrap();
|
|
848
912
|
}
|
|
@@ -857,7 +921,7 @@ concept XmlModel = requires(T a) {{
|
|
|
857
921
|
}
|
|
858
922
|
|
|
859
923
|
if self.options.validate_facets {
|
|
860
|
-
self.emit_struct_validator(out, s, ir);
|
|
924
|
+
self.emit_struct_validator(out, s, ir, &field_names);
|
|
861
925
|
}
|
|
862
926
|
|
|
863
927
|
writeln!(out, "}};\n").unwrap();
|
|
@@ -929,13 +993,19 @@ concept XmlModel = requires(T a) {{
|
|
|
929
993
|
}
|
|
930
994
|
}
|
|
931
995
|
|
|
932
|
-
fn emit_struct_validator(
|
|
996
|
+
fn emit_struct_validator(
|
|
997
|
+
&self,
|
|
998
|
+
out: &mut String,
|
|
999
|
+
s: &StructDef,
|
|
1000
|
+
ir: &SchemaIR,
|
|
1001
|
+
field_names: &[String],
|
|
1002
|
+
) {
|
|
933
1003
|
writeln!(out, "\n [[nodiscard]] bool validate() const noexcept {{").unwrap();
|
|
934
1004
|
|
|
935
1005
|
let mut has_checks = false;
|
|
936
|
-
for f in
|
|
1006
|
+
for (f, field_name) in s.fields.iter().zip(field_names) {
|
|
937
1007
|
if let Some(simple) = super::patterned_simple(&f.type_ref, ir) {
|
|
938
|
-
let field =
|
|
1008
|
+
let field = field_name.clone();
|
|
939
1009
|
let name = type_ident(&simple.qname);
|
|
940
1010
|
let is_string = self
|
|
941
1011
|
.context
|
|
@@ -963,7 +1033,6 @@ concept XmlModel = requires(T a) {{
|
|
|
963
1033
|
}
|
|
964
1034
|
}
|
|
965
1035
|
if let Some(ref facets) = f.facets {
|
|
966
|
-
let field_name = to_cpp_field_name(&f.name);
|
|
967
1036
|
let is_opt = f.cardinality.is_optional() || f.nillable;
|
|
968
1037
|
|
|
969
1038
|
if is_opt {
|
|
@@ -976,7 +1045,7 @@ concept XmlModel = requires(T a) {{
|
|
|
976
1045
|
);
|
|
977
1046
|
writeln!(out, " }}").unwrap();
|
|
978
1047
|
} else {
|
|
979
|
-
self.emit_facet_checks(out, facets,
|
|
1048
|
+
self.emit_facet_checks(out, facets, field_name, " ");
|
|
980
1049
|
}
|
|
981
1050
|
has_checks = true;
|
|
982
1051
|
}
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
//! Emits idiomatic C# 12 records with primary constructors, standard System.Xml.Serialization
|
|
4
4
|
//! attributes, polymorphic xs:choice abstract records, and IValidatableObject facet boundary checks.
|
|
5
5
|
|
|
6
|
+
use std::collections::HashSet;
|
|
6
7
|
use std::fmt::Write as FmtWrite;
|
|
7
8
|
|
|
8
9
|
use heck::{AsLowerCamelCase, AsPascalCase};
|
|
9
10
|
use serde::{Deserialize, Serialize};
|
|
10
11
|
|
|
11
12
|
use crate::codegen::{
|
|
12
|
-
build_type_name_map, lookup_type_name,
|
|
13
|
+
build_type_name_map, lookup_type_name, normalize_symbol_name, sanitize_keyword,
|
|
14
|
+
set_type_name_map, LanguageContext,
|
|
13
15
|
};
|
|
14
16
|
use crate::ir::{
|
|
15
17
|
EnumDef, FieldDef, FieldKind, PrimitiveType, QName, RestrictionFacets, SchemaIR, SimpleTypeDef,
|
|
@@ -135,7 +137,8 @@ pub fn to_csharp_param_name(raw: &str) -> String {
|
|
|
135
137
|
|
|
136
138
|
/// Sanitizes an identifier into a PascalCase C# enum variant.
|
|
137
139
|
pub fn to_csharp_variant_name(raw: &str) -> String {
|
|
138
|
-
let
|
|
140
|
+
let normalized = normalize_symbol_name(raw);
|
|
141
|
+
let pascal = AsPascalCase(&normalized).to_string();
|
|
139
142
|
let safe = if pascal.is_empty() {
|
|
140
143
|
"Value".to_string()
|
|
141
144
|
} else if pascal.starts_with(|c: char| c.is_ascii_digit()) {
|
|
@@ -381,6 +384,7 @@ impl CSharpCodegen {
|
|
|
381
384
|
if let TypeDef::Union(u) = def {
|
|
382
385
|
let name = type_ident(&u.qname);
|
|
383
386
|
writeln!(out, "{}[JsonSerializable(typeof({}))]", indent, name).unwrap();
|
|
387
|
+
writeln!(out, "{}[JsonSerializable(typeof(List<{}>))]", indent, name).unwrap();
|
|
384
388
|
}
|
|
385
389
|
}
|
|
386
390
|
|
|
@@ -428,8 +432,14 @@ impl CSharpCodegen {
|
|
|
428
432
|
writeln!(out, "{}public enum {}", indent, enum_name).unwrap();
|
|
429
433
|
writeln!(out, "{}{{", indent).unwrap();
|
|
430
434
|
|
|
431
|
-
|
|
432
|
-
|
|
435
|
+
let mut seen_variants = HashSet::new();
|
|
436
|
+
let variant_names: Vec<String> = e
|
|
437
|
+
.variants
|
|
438
|
+
.iter()
|
|
439
|
+
.map(|v| self.unique_variant_name(&v.name, &mut seen_variants))
|
|
440
|
+
.collect();
|
|
441
|
+
|
|
442
|
+
for (variant, variant_name) in e.variants.iter().zip(&variant_names) {
|
|
433
443
|
if let Some(ref doc) = variant.documentation {
|
|
434
444
|
self.emit_docstring(out, doc, &format!("{} ", indent));
|
|
435
445
|
}
|
|
@@ -453,8 +463,7 @@ impl CSharpCodegen {
|
|
|
453
463
|
)
|
|
454
464
|
.unwrap();
|
|
455
465
|
writeln!(out, "{} {{", indent).unwrap();
|
|
456
|
-
for
|
|
457
|
-
let variant_name = to_csharp_variant_name(&variant.name);
|
|
466
|
+
for variant_name in &variant_names {
|
|
458
467
|
writeln!(
|
|
459
468
|
out,
|
|
460
469
|
"{} {}.{} => true,",
|
|
@@ -473,8 +482,7 @@ impl CSharpCodegen {
|
|
|
473
482
|
)
|
|
474
483
|
.unwrap();
|
|
475
484
|
writeln!(out, "{} {{", indent).unwrap();
|
|
476
|
-
for variant in
|
|
477
|
-
let variant_name = to_csharp_variant_name(&variant.name);
|
|
485
|
+
for (variant, variant_name) in e.variants.iter().zip(&variant_names) {
|
|
478
486
|
writeln!(
|
|
479
487
|
out,
|
|
480
488
|
"{} {}.{} => \"{}\",",
|
|
@@ -717,6 +725,13 @@ impl CSharpCodegen {
|
|
|
717
725
|
format!(" : {}", base_clause.join(", "))
|
|
718
726
|
};
|
|
719
727
|
|
|
728
|
+
let mut seen_props = HashSet::new();
|
|
729
|
+
let prop_names: Vec<String> = s
|
|
730
|
+
.fields
|
|
731
|
+
.iter()
|
|
732
|
+
.map(|f| self.unique_property_name(&f.name, &mut seen_props, Some(&struct_name)))
|
|
733
|
+
.collect();
|
|
734
|
+
|
|
720
735
|
if !self.options.use_records {
|
|
721
736
|
writeln!(
|
|
722
737
|
out,
|
|
@@ -725,8 +740,7 @@ impl CSharpCodegen {
|
|
|
725
740
|
)
|
|
726
741
|
.unwrap();
|
|
727
742
|
writeln!(out, "{} public {}() {{ }}", indent, struct_name).unwrap();
|
|
728
|
-
for f in
|
|
729
|
-
let name = to_csharp_property_name(&f.name, Some(&struct_name));
|
|
743
|
+
for (f, name) in s.fields.iter().zip(&prop_names) {
|
|
730
744
|
let ty = self.map_field_type(f, ir);
|
|
731
745
|
let attrs = self
|
|
732
746
|
.build_field_attributes(f, ir)
|
|
@@ -744,7 +758,7 @@ impl CSharpCodegen {
|
|
|
744
758
|
.unwrap();
|
|
745
759
|
}
|
|
746
760
|
if self.options.emit_validation {
|
|
747
|
-
self.emit_struct_validator(out, s, indent);
|
|
761
|
+
self.emit_struct_validator(out, s, &prop_names, indent);
|
|
748
762
|
}
|
|
749
763
|
writeln!(out, "{}}}\n", indent).unwrap();
|
|
750
764
|
return;
|
|
@@ -764,8 +778,7 @@ impl CSharpCodegen {
|
|
|
764
778
|
|
|
765
779
|
writeln!(out, "{}public {} {}(", indent, record_keyword, struct_name).unwrap();
|
|
766
780
|
|
|
767
|
-
for (i, f) in s.fields.iter().enumerate() {
|
|
768
|
-
let prop_name = to_csharp_property_name(&f.name, Some(&struct_name));
|
|
781
|
+
for (i, (f, prop_name)) in s.fields.iter().zip(&prop_names).enumerate() {
|
|
769
782
|
let field_type = self.map_field_type(f, ir);
|
|
770
783
|
let is_opt = f.cardinality.is_optional()
|
|
771
784
|
|| f.nillable
|
|
@@ -822,14 +835,49 @@ impl CSharpCodegen {
|
|
|
822
835
|
|
|
823
836
|
// IValidatableObject implementation
|
|
824
837
|
if self.options.emit_validation {
|
|
825
|
-
self.emit_struct_validator(out, s, indent);
|
|
838
|
+
self.emit_struct_validator(out, s, &prop_names, indent);
|
|
826
839
|
}
|
|
827
840
|
|
|
828
841
|
writeln!(out, "{}}}\n", indent).unwrap();
|
|
829
842
|
}
|
|
830
843
|
|
|
831
|
-
fn
|
|
832
|
-
|
|
844
|
+
fn unique_property_name(
|
|
845
|
+
&self,
|
|
846
|
+
name: &str,
|
|
847
|
+
seen: &mut HashSet<String>,
|
|
848
|
+
enclosing: Option<&str>,
|
|
849
|
+
) -> String {
|
|
850
|
+
let base = to_csharp_property_name(name, enclosing);
|
|
851
|
+
let mut candidate = base.clone();
|
|
852
|
+
let mut counter = 1;
|
|
853
|
+
while seen.contains(&candidate) {
|
|
854
|
+
counter += 1;
|
|
855
|
+
candidate = format!("{}{}", base, counter);
|
|
856
|
+
}
|
|
857
|
+
seen.insert(candidate.clone());
|
|
858
|
+
candidate
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
fn unique_variant_name(&self, name: &str, seen: &mut HashSet<String>) -> String {
|
|
862
|
+
let base = to_csharp_variant_name(name);
|
|
863
|
+
let mut candidate = base.clone();
|
|
864
|
+
let mut counter = 1;
|
|
865
|
+
while seen.contains(&candidate) {
|
|
866
|
+
counter += 1;
|
|
867
|
+
candidate = format!("{}{}", base, counter);
|
|
868
|
+
}
|
|
869
|
+
seen.insert(candidate.clone());
|
|
870
|
+
candidate
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
fn emit_struct_validator(
|
|
874
|
+
&self,
|
|
875
|
+
out: &mut String,
|
|
876
|
+
s: &StructDef,
|
|
877
|
+
prop_names: &[String],
|
|
878
|
+
indent: &str,
|
|
879
|
+
) {
|
|
880
|
+
let _struct_name = type_ident(&s.qname);
|
|
833
881
|
let new_kw = if !self.options.use_records {
|
|
834
882
|
if s.base_type.is_some() {
|
|
835
883
|
"override "
|
|
@@ -853,8 +901,7 @@ impl CSharpCodegen {
|
|
|
853
901
|
writeln!(out, "{} foreach (var result in base.Validate(validationContext)) yield return result;", indent).unwrap();
|
|
854
902
|
}
|
|
855
903
|
let mut has_checks = false;
|
|
856
|
-
for f in
|
|
857
|
-
let prop_name = to_csharp_property_name(&f.name, Some(&struct_name));
|
|
904
|
+
for (f, prop_name) in s.fields.iter().zip(prop_names) {
|
|
858
905
|
let is_opt = f.cardinality.is_optional()
|
|
859
906
|
|| f.nillable
|
|
860
907
|
|| (f.cardinality.is_list() && f.cardinality.min_occurs == 0);
|
|
@@ -866,12 +913,12 @@ impl CSharpCodegen {
|
|
|
866
913
|
self.emit_facet_checks(
|
|
867
914
|
out,
|
|
868
915
|
facets,
|
|
869
|
-
|
|
916
|
+
prop_name,
|
|
870
917
|
&format!("{} ", indent),
|
|
871
918
|
);
|
|
872
919
|
writeln!(out, "{} }}", indent).unwrap();
|
|
873
920
|
} else {
|
|
874
|
-
self.emit_facet_checks(out, facets,
|
|
921
|
+
self.emit_facet_checks(out, facets, prop_name, &format!("{} ", indent));
|
|
875
922
|
}
|
|
876
923
|
has_checks = true;
|
|
877
924
|
}
|
|
@@ -982,7 +1029,15 @@ impl CSharpCodegen {
|
|
|
982
1029
|
|
|
983
1030
|
if self.options.emit_xml_attributes {
|
|
984
1031
|
// If the field is a choice (UnionDef), emit XmlElement("branchXml", typeof(BranchType))
|
|
985
|
-
|
|
1032
|
+
let named_qname = match &f.type_ref {
|
|
1033
|
+
TypeRef::Named(q) => Some(q),
|
|
1034
|
+
TypeRef::List(inner) | TypeRef::Boxed(inner) => match &**inner {
|
|
1035
|
+
TypeRef::Named(q) => Some(q),
|
|
1036
|
+
_ => None,
|
|
1037
|
+
},
|
|
1038
|
+
_ => None,
|
|
1039
|
+
};
|
|
1040
|
+
if let Some(qname) = named_qname {
|
|
986
1041
|
if let Some(TypeDef::Union(u)) = ir.types.get(qname) {
|
|
987
1042
|
let choice_name = type_ident(&u.qname);
|
|
988
1043
|
for branch in &u.branches {
|
|
@@ -1008,7 +1063,13 @@ impl CSharpCodegen {
|
|
|
1008
1063
|
if self.options.emit_json_attributes {
|
|
1009
1064
|
let json_name = match f.kind {
|
|
1010
1065
|
FieldKind::Text => "value",
|
|
1011
|
-
_ =>
|
|
1066
|
+
_ => {
|
|
1067
|
+
if f.xml_name.is_empty() {
|
|
1068
|
+
&f.name
|
|
1069
|
+
} else {
|
|
1070
|
+
&f.xml_name
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1012
1073
|
};
|
|
1013
1074
|
if f.kind != FieldKind::Any && f.kind != FieldKind::AnyAttribute {
|
|
1014
1075
|
parts.push(format!("JsonPropertyName(\"{}\")", json_name));
|