polyxml 0.25.0__tar.gz → 0.27.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.25.0 → polyxml-0.27.0}/Cargo.lock +6 -6
- {polyxml-0.25.0 → polyxml-0.27.0}/Cargo.toml +1 -1
- {polyxml-0.25.0 → polyxml-0.27.0}/PKG-INFO +1 -1
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/java/mod.rs +164 -4
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/java/models.rs +10 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/typescript/mod.rs +15 -0
- polyxml-0.27.0/crates/polyxml-core/src/converters.rs +178 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/parser.rs +26 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/schema.rs +6 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/schema_parser/mod.rs +75 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/serializer.rs +16 -1
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_core.rs +49 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_java_codegen.rs +56 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_pattern_codegen.rs +53 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_xsi_type.rs +15 -2
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-python/tests/test_xsi_type.py +19 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/pyproject.toml +1 -1
- polyxml-0.25.0/crates/polyxml-core/src/converters.rs +0 -80
- {polyxml-0.25.0 → polyxml-0.27.0}/README.md +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/Cargo.toml +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/README.md +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/csharp/mod.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/go/mod.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/mod.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/python/mod.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/codegen/rust/mod.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/error.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/ir/mod.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/json.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/lib.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/transcoder.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/src/value.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_conformance.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_csharp_codegen.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_go_codegen.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_json.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_security.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-python/Cargo.toml +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-python/README.md +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-python/src/lib.rs +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-python/tests/test_conformance.py +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-python/tests/test_generated_models.py +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-python/tests/test_json.py +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/crates/polyxml-python/tests/test_polyxml.py +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/python/polyxml/AGENT_GUIDE.md +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/python/polyxml/__init__.py +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/python/polyxml/compat/__init__.py +0 -0
- {polyxml-0.25.0 → polyxml-0.27.0}/python/polyxml/compat/xsdata.py +0 -0
- {polyxml-0.25.0 → polyxml-0.27.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.27.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.27.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.27.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.27.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.27.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.27.0"
|
|
673
673
|
dependencies = [
|
|
674
674
|
"polyxml",
|
|
675
675
|
"quick-xml",
|
|
@@ -57,6 +57,9 @@ pub struct JavaOptions {
|
|
|
57
57
|
pub emit_direct_codec: bool,
|
|
58
58
|
/// Generate facet validation in compact constructors (default: true)
|
|
59
59
|
pub validate_facets: bool,
|
|
60
|
+
/// Emit Jakarta Bean Validation annotations (adds an optional API dependency).
|
|
61
|
+
#[serde(default)]
|
|
62
|
+
pub bean_validation: bool,
|
|
60
63
|
/// Emit top-level aliases for root elements (default: true)
|
|
61
64
|
pub emit_root_aliases: bool,
|
|
62
65
|
/// Custom header text to prepend to generated files (default: None)
|
|
@@ -72,6 +75,7 @@ impl Default for JavaOptions {
|
|
|
72
75
|
emit_builder: false,
|
|
73
76
|
emit_direct_codec: false,
|
|
74
77
|
validate_facets: true,
|
|
78
|
+
bean_validation: false,
|
|
75
79
|
emit_root_aliases: true,
|
|
76
80
|
custom_header: None,
|
|
77
81
|
}
|
|
@@ -345,6 +349,9 @@ impl JavaCodegen {
|
|
|
345
349
|
out.push_str("import java.time.*;\n");
|
|
346
350
|
out.push_str("import java.math.*;\n");
|
|
347
351
|
out.push_str("import java.util.regex.Pattern;\n");
|
|
352
|
+
if self.options.bean_validation {
|
|
353
|
+
out.push_str("import jakarta.validation.constraints.*;\n");
|
|
354
|
+
}
|
|
348
355
|
|
|
349
356
|
if self.options.backend.is_jackson() {
|
|
350
357
|
out.push('\n');
|
|
@@ -454,6 +461,12 @@ impl JavaCodegen {
|
|
|
454
461
|
|
|
455
462
|
let field_type = self.field_type(field);
|
|
456
463
|
|
|
464
|
+
let bean_annotations = self.bean_annotations(field).join(" ");
|
|
465
|
+
let bean_prefix = if bean_annotations.is_empty() {
|
|
466
|
+
String::new()
|
|
467
|
+
} else {
|
|
468
|
+
format!("{bean_annotations} ")
|
|
469
|
+
};
|
|
457
470
|
if jackson {
|
|
458
471
|
let mut annotations = Vec::new();
|
|
459
472
|
// @JsonProperty
|
|
@@ -482,9 +495,12 @@ impl JavaCodegen {
|
|
|
482
495
|
}
|
|
483
496
|
|
|
484
497
|
let annotation_str = annotations.join(" ");
|
|
485
|
-
components.push(format!(
|
|
498
|
+
components.push(format!(
|
|
499
|
+
"{}{} {} {}",
|
|
500
|
+
bean_prefix, annotation_str, field_type, field_id
|
|
501
|
+
));
|
|
486
502
|
} else {
|
|
487
|
-
components.push(format!("{} {}", field_type, field_id));
|
|
503
|
+
components.push(format!("{}{} {}", bean_prefix, field_type, field_id));
|
|
488
504
|
}
|
|
489
505
|
|
|
490
506
|
if self.options.validate_facets {
|
|
@@ -714,10 +730,26 @@ impl JavaCodegen {
|
|
|
714
730
|
};
|
|
715
731
|
|
|
716
732
|
// Build the value component with optional Jackson annotations
|
|
733
|
+
let mut value_field = crate::ir::FieldDef::new(
|
|
734
|
+
"value",
|
|
735
|
+
"value",
|
|
736
|
+
crate::ir::FieldKind::Text,
|
|
737
|
+
s.base_type.clone(),
|
|
738
|
+
);
|
|
739
|
+
value_field.facets = Some(s.facets.clone());
|
|
740
|
+
let bean_prefix = self.bean_annotations(&value_field).join(" ");
|
|
741
|
+
let bean_prefix = if bean_prefix.is_empty() {
|
|
742
|
+
String::new()
|
|
743
|
+
} else {
|
|
744
|
+
format!("{bean_prefix} ")
|
|
745
|
+
};
|
|
717
746
|
let value_component = if jackson {
|
|
718
|
-
format!(
|
|
747
|
+
format!(
|
|
748
|
+
"{bean_prefix}@JsonValue @JacksonXmlText {} value",
|
|
749
|
+
base_type
|
|
750
|
+
)
|
|
719
751
|
} else {
|
|
720
|
-
format!("{} value", base_type)
|
|
752
|
+
format!("{bean_prefix}{} value", base_type)
|
|
721
753
|
};
|
|
722
754
|
|
|
723
755
|
if checks.is_empty() && !jackson {
|
|
@@ -758,6 +790,129 @@ impl JavaCodegen {
|
|
|
758
790
|
let _ = writeln!(out, "{}}}", indent);
|
|
759
791
|
}
|
|
760
792
|
|
|
793
|
+
fn bean_annotations(&self, field: &crate::ir::FieldDef) -> Vec<String> {
|
|
794
|
+
if !self.options.bean_validation {
|
|
795
|
+
return Vec::new();
|
|
796
|
+
}
|
|
797
|
+
let mut annotations = Vec::new();
|
|
798
|
+
let is_list = field.cardinality.is_list() || field.type_ref.is_list();
|
|
799
|
+
let is_optional = field.cardinality.is_optional() || field.nillable;
|
|
800
|
+
if !is_optional
|
|
801
|
+
&& !matches!(
|
|
802
|
+
self.field_type(field).as_str(),
|
|
803
|
+
"boolean" | "byte" | "short" | "int" | "long" | "float" | "double"
|
|
804
|
+
)
|
|
805
|
+
{
|
|
806
|
+
// Java primitive fields cannot be null; reference fields can.
|
|
807
|
+
annotations.push("@NotNull".to_string());
|
|
808
|
+
}
|
|
809
|
+
if matches!(&field.type_ref, TypeRef::Named(_) | TypeRef::List(_)) {
|
|
810
|
+
annotations.push("@jakarta.validation.Valid".to_string());
|
|
811
|
+
}
|
|
812
|
+
let facets = field.facets.as_ref();
|
|
813
|
+
let Some(facets) = facets else {
|
|
814
|
+
return annotations;
|
|
815
|
+
};
|
|
816
|
+
let primitive = &field.type_ref;
|
|
817
|
+
let is_string = matches!(
|
|
818
|
+
primitive,
|
|
819
|
+
TypeRef::Primitive(
|
|
820
|
+
PrimitiveType::String
|
|
821
|
+
| PrimitiveType::NormalizedString
|
|
822
|
+
| PrimitiveType::Token
|
|
823
|
+
| PrimitiveType::Name
|
|
824
|
+
| PrimitiveType::NCName
|
|
825
|
+
| PrimitiveType::QName
|
|
826
|
+
| PrimitiveType::Language
|
|
827
|
+
| PrimitiveType::NMTOKEN
|
|
828
|
+
| PrimitiveType::NMTOKENS
|
|
829
|
+
| PrimitiveType::AnyUri
|
|
830
|
+
| PrimitiveType::Id
|
|
831
|
+
| PrimitiveType::IdRef
|
|
832
|
+
| PrimitiveType::IdRefs
|
|
833
|
+
| PrimitiveType::Entity
|
|
834
|
+
| PrimitiveType::Entities
|
|
835
|
+
| PrimitiveType::GDay
|
|
836
|
+
| PrimitiveType::GMonth
|
|
837
|
+
| PrimitiveType::GYear
|
|
838
|
+
| PrimitiveType::GYearMonth
|
|
839
|
+
| PrimitiveType::GMonthDay
|
|
840
|
+
)
|
|
841
|
+
);
|
|
842
|
+
let is_numeric = matches!(
|
|
843
|
+
primitive,
|
|
844
|
+
TypeRef::Primitive(
|
|
845
|
+
PrimitiveType::Byte
|
|
846
|
+
| PrimitiveType::Short
|
|
847
|
+
| PrimitiveType::Int
|
|
848
|
+
| PrimitiveType::Integer
|
|
849
|
+
| PrimitiveType::Long
|
|
850
|
+
| PrimitiveType::Decimal
|
|
851
|
+
| PrimitiveType::UnsignedByte
|
|
852
|
+
| PrimitiveType::UnsignedShort
|
|
853
|
+
| PrimitiveType::UnsignedInt
|
|
854
|
+
| PrimitiveType::UnsignedLong
|
|
855
|
+
| PrimitiveType::PositiveInteger
|
|
856
|
+
| PrimitiveType::NegativeInteger
|
|
857
|
+
| PrimitiveType::NonPositiveInteger
|
|
858
|
+
| PrimitiveType::NonNegativeInteger
|
|
859
|
+
| PrimitiveType::Float
|
|
860
|
+
| PrimitiveType::Double
|
|
861
|
+
)
|
|
862
|
+
);
|
|
863
|
+
if is_list || (is_string && !is_optional) {
|
|
864
|
+
if let Some(length) = facets.length {
|
|
865
|
+
annotations.push(format!("@Size(min = {length}, max = {length})"));
|
|
866
|
+
} else if facets.min_length.is_some() || facets.max_length.is_some() {
|
|
867
|
+
annotations.push(format!(
|
|
868
|
+
"@Size(min = {}, max = {})",
|
|
869
|
+
facets.min_length.unwrap_or(0),
|
|
870
|
+
facets.max_length.unwrap_or(i32::MAX as usize)
|
|
871
|
+
));
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if is_string && !is_optional {
|
|
875
|
+
for pattern in &facets.patterns {
|
|
876
|
+
annotations.push(format!(
|
|
877
|
+
"@jakarta.validation.constraints.Pattern(regexp = {pattern:?})"
|
|
878
|
+
));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if is_numeric
|
|
882
|
+
&& !is_list
|
|
883
|
+
&& !is_optional
|
|
884
|
+
&& !matches!(
|
|
885
|
+
primitive,
|
|
886
|
+
TypeRef::Primitive(PrimitiveType::Float | PrimitiveType::Double)
|
|
887
|
+
)
|
|
888
|
+
{
|
|
889
|
+
for (value, minimum, inclusive) in [
|
|
890
|
+
(facets.min_inclusive.as_ref(), true, true),
|
|
891
|
+
(facets.max_inclusive.as_ref(), false, true),
|
|
892
|
+
(facets.min_exclusive.as_ref(), true, false),
|
|
893
|
+
(facets.max_exclusive.as_ref(), false, false),
|
|
894
|
+
] {
|
|
895
|
+
if let Some(value) = value {
|
|
896
|
+
if inclusive && !matches!(primitive, TypeRef::Primitive(PrimitiveType::Decimal))
|
|
897
|
+
{
|
|
898
|
+
if let Ok(integer) = value.parse::<i64>() {
|
|
899
|
+
annotations.push(format!(
|
|
900
|
+
"@{}({integer})",
|
|
901
|
+
if minimum { "Min" } else { "Max" }
|
|
902
|
+
));
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
let annotation = if minimum { "DecimalMin" } else { "DecimalMax" };
|
|
907
|
+
annotations.push(format!(
|
|
908
|
+
"@{annotation}(value = {value:?}, inclusive = {inclusive})"
|
|
909
|
+
));
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
annotations
|
|
914
|
+
}
|
|
915
|
+
|
|
761
916
|
fn build_facet_checks(
|
|
762
917
|
&self,
|
|
763
918
|
var_name: &str,
|
|
@@ -789,6 +944,11 @@ impl JavaCodegen {
|
|
|
789
944
|
| PrimitiveType::IdRefs
|
|
790
945
|
| PrimitiveType::Entity
|
|
791
946
|
| PrimitiveType::Entities
|
|
947
|
+
| PrimitiveType::GDay
|
|
948
|
+
| PrimitiveType::GMonth
|
|
949
|
+
| PrimitiveType::GYear
|
|
950
|
+
| PrimitiveType::GYearMonth
|
|
951
|
+
| PrimitiveType::GMonthDay
|
|
792
952
|
),
|
|
793
953
|
_ => false,
|
|
794
954
|
};
|
|
@@ -16,6 +16,13 @@ impl JavaCodegen {
|
|
|
16
16
|
let implements = interface.map(|v| format!(", {v}")).unwrap_or_default();
|
|
17
17
|
let ty_name = self.context.map_type_ref(ty);
|
|
18
18
|
let _ = writeln!(out, "{indent}public {nested}final class {name} implements java.io.Serializable{implements} {{\n{indent} private static final long serialVersionUID = 1L;");
|
|
19
|
+
if self.options.bean_validation {
|
|
20
|
+
let mut value_field = FieldDef::new("value", "value", FieldKind::Text, ty.clone());
|
|
21
|
+
value_field.facets = facets.cloned();
|
|
22
|
+
for annotation in self.bean_annotations(&value_field) {
|
|
23
|
+
let _ = writeln!(out, "{indent} {annotation}");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
19
26
|
if self.options.backend.is_jackson() {
|
|
20
27
|
let _ = writeln!(out, "{indent} @JsonValue @JacksonXmlText");
|
|
21
28
|
}
|
|
@@ -153,6 +160,9 @@ impl JavaCodegen {
|
|
|
153
160
|
let own = &fields[fields.len() - s.fields.len()..];
|
|
154
161
|
for (f, id) in own {
|
|
155
162
|
self.emit_property_annotations(out, f, &format!("{indent} "));
|
|
163
|
+
for annotation in self.bean_annotations(f) {
|
|
164
|
+
let _ = writeln!(out, "{indent} {annotation}");
|
|
165
|
+
}
|
|
156
166
|
let _ = writeln!(
|
|
157
167
|
out,
|
|
158
168
|
"{indent} private {} {id} = {};",
|
|
@@ -759,6 +759,11 @@ impl TypeScriptCodegen {
|
|
|
759
759
|
| PrimitiveType::IdRefs
|
|
760
760
|
| PrimitiveType::Entity
|
|
761
761
|
| PrimitiveType::Entities
|
|
762
|
+
| PrimitiveType::GDay
|
|
763
|
+
| PrimitiveType::GMonth
|
|
764
|
+
| PrimitiveType::GYear
|
|
765
|
+
| PrimitiveType::GYearMonth
|
|
766
|
+
| PrimitiveType::GMonthDay
|
|
762
767
|
),
|
|
763
768
|
_ => false,
|
|
764
769
|
};
|
|
@@ -836,6 +841,11 @@ impl TypeScriptCodegen {
|
|
|
836
841
|
| PrimitiveType::NCName
|
|
837
842
|
| PrimitiveType::Language
|
|
838
843
|
| PrimitiveType::AnyUri
|
|
844
|
+
| PrimitiveType::GDay
|
|
845
|
+
| PrimitiveType::GMonth
|
|
846
|
+
| PrimitiveType::GYear
|
|
847
|
+
| PrimitiveType::GYearMonth
|
|
848
|
+
| PrimitiveType::GMonthDay
|
|
839
849
|
),
|
|
840
850
|
_ => false,
|
|
841
851
|
};
|
|
@@ -909,6 +919,11 @@ impl TypeScriptCodegen {
|
|
|
909
919
|
| PrimitiveType::NCName
|
|
910
920
|
| PrimitiveType::Language
|
|
911
921
|
| PrimitiveType::AnyUri
|
|
922
|
+
| PrimitiveType::GDay
|
|
923
|
+
| PrimitiveType::GMonth
|
|
924
|
+
| PrimitiveType::GYear
|
|
925
|
+
| PrimitiveType::GYearMonth
|
|
926
|
+
| PrimitiveType::GMonthDay
|
|
912
927
|
),
|
|
913
928
|
_ => false,
|
|
914
929
|
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
use crate::error::{PolyXmlError, Result};
|
|
2
|
+
use crate::ir::PrimitiveType;
|
|
3
|
+
use crate::schema::ScalarType;
|
|
4
|
+
use crate::value::PolyValue;
|
|
5
|
+
|
|
6
|
+
#[inline(always)]
|
|
7
|
+
pub fn trim_bytes(mut b: &[u8]) -> &[u8] {
|
|
8
|
+
while let Some((first, rest)) = b.split_first() {
|
|
9
|
+
if first.is_ascii_whitespace() {
|
|
10
|
+
b = rest;
|
|
11
|
+
} else {
|
|
12
|
+
break;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
while let Some((last, rest)) = b.split_last() {
|
|
16
|
+
if last.is_ascii_whitespace() {
|
|
17
|
+
b = rest;
|
|
18
|
+
} else {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
b
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
pub struct ValueConverter;
|
|
26
|
+
|
|
27
|
+
impl ValueConverter {
|
|
28
|
+
pub fn parse_scalar(
|
|
29
|
+
scalar_type: &ScalarType,
|
|
30
|
+
bytes: &[u8],
|
|
31
|
+
field_name: &str,
|
|
32
|
+
) -> Result<PolyValue> {
|
|
33
|
+
match scalar_type {
|
|
34
|
+
ScalarType::String => {
|
|
35
|
+
let s = std::str::from_utf8(bytes)?;
|
|
36
|
+
Ok(PolyValue::String(s.to_string()))
|
|
37
|
+
}
|
|
38
|
+
ScalarType::Int => {
|
|
39
|
+
let trimmed = trim_bytes(bytes);
|
|
40
|
+
let val: i64 =
|
|
41
|
+
lexical_core::parse(trimmed).map_err(|_| PolyXmlError::ScalarParseError {
|
|
42
|
+
field: field_name.to_string(),
|
|
43
|
+
expected: "integer",
|
|
44
|
+
value: String::from_utf8_lossy(bytes).to_string(),
|
|
45
|
+
})?;
|
|
46
|
+
Ok(PolyValue::Int(val))
|
|
47
|
+
}
|
|
48
|
+
ScalarType::Float => {
|
|
49
|
+
let trimmed = trim_bytes(bytes);
|
|
50
|
+
let val: f64 =
|
|
51
|
+
lexical_core::parse(trimmed).map_err(|_| PolyXmlError::ScalarParseError {
|
|
52
|
+
field: field_name.to_string(),
|
|
53
|
+
expected: "float",
|
|
54
|
+
value: String::from_utf8_lossy(bytes).to_string(),
|
|
55
|
+
})?;
|
|
56
|
+
Ok(PolyValue::Float(val))
|
|
57
|
+
}
|
|
58
|
+
ScalarType::Bool => {
|
|
59
|
+
let trimmed = trim_bytes(bytes);
|
|
60
|
+
match trimmed {
|
|
61
|
+
b"true" | b"1" => Ok(PolyValue::Bool(true)),
|
|
62
|
+
b"false" | b"0" => Ok(PolyValue::Bool(false)),
|
|
63
|
+
_ => Err(PolyXmlError::ScalarParseError {
|
|
64
|
+
field: field_name.to_string(),
|
|
65
|
+
expected: "boolean ('true', 'false', '1', '0')",
|
|
66
|
+
value: String::from_utf8_lossy(bytes).to_string(),
|
|
67
|
+
}),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
ScalarType::Decimal
|
|
71
|
+
| ScalarType::XmlDate
|
|
72
|
+
| ScalarType::XmlDateTime
|
|
73
|
+
| ScalarType::XmlTime
|
|
74
|
+
| ScalarType::XmlDuration
|
|
75
|
+
| ScalarType::Any => {
|
|
76
|
+
let s = std::str::from_utf8(trim_bytes(bytes))?;
|
|
77
|
+
Ok(PolyValue::String(s.to_string()))
|
|
78
|
+
}
|
|
79
|
+
ScalarType::XmlGregorian(kind) => {
|
|
80
|
+
let value = std::str::from_utf8(trim_bytes(bytes))?;
|
|
81
|
+
if !valid_gregorian(*kind, value.as_bytes()) {
|
|
82
|
+
return Err(PolyXmlError::ScalarParseError {
|
|
83
|
+
field: field_name.to_string(),
|
|
84
|
+
expected: "XML Schema Gregorian partial date",
|
|
85
|
+
value: value.to_string(),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
Ok(PolyValue::String(value.to_string()))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn two_digits(bytes: &[u8]) -> Option<u8> {
|
|
95
|
+
if bytes.len() == 2 && bytes.iter().all(u8::is_ascii_digit) {
|
|
96
|
+
Some((bytes[0] - b'0') * 10 + bytes[1] - b'0')
|
|
97
|
+
} else {
|
|
98
|
+
None
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fn valid_timezone(bytes: &[u8]) -> Option<&[u8]> {
|
|
103
|
+
if let Some(value) = bytes.strip_suffix(b"Z") {
|
|
104
|
+
return Some(value);
|
|
105
|
+
}
|
|
106
|
+
let split = bytes
|
|
107
|
+
.iter()
|
|
108
|
+
.rposition(|byte| *byte == b'+' || *byte == b'-')?;
|
|
109
|
+
let suffix = &bytes[split..];
|
|
110
|
+
if suffix.len() != 6 || suffix[3] != b':' {
|
|
111
|
+
return None;
|
|
112
|
+
}
|
|
113
|
+
let hours = two_digits(&suffix[1..3])?;
|
|
114
|
+
let minutes = two_digits(&suffix[4..6])?;
|
|
115
|
+
if hours > 14 || minutes > 59 || (hours == 14 && minutes != 0) {
|
|
116
|
+
return None;
|
|
117
|
+
}
|
|
118
|
+
Some(&bytes[..split])
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn valid_gregorian(kind: PrimitiveType, bytes: &[u8]) -> bool {
|
|
122
|
+
let has_timezone = bytes.ends_with(b"Z")
|
|
123
|
+
|| bytes.contains(&b'+')
|
|
124
|
+
|| (bytes.len() >= 6 && bytes[bytes.len() - 6] == b'-' && bytes[bytes.len() - 3] == b':');
|
|
125
|
+
let value = if has_timezone {
|
|
126
|
+
match valid_timezone(bytes) {
|
|
127
|
+
Some(value) => value,
|
|
128
|
+
None => return false,
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
bytes
|
|
132
|
+
};
|
|
133
|
+
match kind {
|
|
134
|
+
PrimitiveType::GDay => {
|
|
135
|
+
value.len() == 5
|
|
136
|
+
&& value.starts_with(b"---")
|
|
137
|
+
&& two_digits(&value[3..]).is_some_and(|day| (1..=31).contains(&day))
|
|
138
|
+
}
|
|
139
|
+
PrimitiveType::GMonth => {
|
|
140
|
+
value.len() == 4
|
|
141
|
+
&& value.starts_with(b"--")
|
|
142
|
+
&& two_digits(&value[2..]).is_some_and(|month| (1..=12).contains(&month))
|
|
143
|
+
}
|
|
144
|
+
PrimitiveType::GMonthDay => {
|
|
145
|
+
if value.len() != 7 || !value.starts_with(b"--") || value[4] != b'-' {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
let (Some(month), Some(day)) = (two_digits(&value[2..4]), two_digits(&value[5..7]))
|
|
149
|
+
else {
|
|
150
|
+
return false;
|
|
151
|
+
};
|
|
152
|
+
let max_day = match month {
|
|
153
|
+
2 => 29,
|
|
154
|
+
4 | 6 | 9 | 11 => 30,
|
|
155
|
+
1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
|
|
156
|
+
_ => return false,
|
|
157
|
+
};
|
|
158
|
+
day >= 1 && day <= max_day
|
|
159
|
+
}
|
|
160
|
+
PrimitiveType::GYear => valid_year(value),
|
|
161
|
+
PrimitiveType::GYearMonth => {
|
|
162
|
+
if value.len() < 7 || value[value.len() - 3] != b'-' {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
valid_year(&value[..value.len() - 3])
|
|
166
|
+
&& two_digits(&value[value.len() - 2..])
|
|
167
|
+
.is_some_and(|month| (1..=12).contains(&month))
|
|
168
|
+
}
|
|
169
|
+
_ => false,
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
fn valid_year(value: &[u8]) -> bool {
|
|
174
|
+
let digits = value.strip_prefix(b"-").unwrap_or(value);
|
|
175
|
+
digits.len() >= 4
|
|
176
|
+
&& digits.iter().all(u8::is_ascii_digit)
|
|
177
|
+
&& (digits.len() == 4 || digits[0] != b'0')
|
|
178
|
+
}
|
|
@@ -355,6 +355,14 @@ impl XmlDeserializer {
|
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
357
|
ValueType::Nested(sub_schema) => {
|
|
358
|
+
if is_nil {
|
|
359
|
+
stack
|
|
360
|
+
.last_mut()
|
|
361
|
+
.unwrap()
|
|
362
|
+
.push_list_item(field_idx, PolyValue::Null);
|
|
363
|
+
unknown_depth = 1;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
358
366
|
let frame_schema = resolve_record_schema(
|
|
359
367
|
sub_schema,
|
|
360
368
|
e,
|
|
@@ -367,6 +375,12 @@ impl XmlDeserializer {
|
|
|
367
375
|
_ => {}
|
|
368
376
|
},
|
|
369
377
|
ValueType::Nested(sub_schema) => {
|
|
378
|
+
if is_nil {
|
|
379
|
+
stack.last_mut().unwrap().values[field_idx] =
|
|
380
|
+
Some(PolyValue::Null);
|
|
381
|
+
unknown_depth = 1;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
370
384
|
let frame_schema = resolve_record_schema(
|
|
371
385
|
sub_schema,
|
|
372
386
|
e,
|
|
@@ -414,6 +428,13 @@ impl XmlDeserializer {
|
|
|
414
428
|
stack.last_mut().unwrap().push_list_item(field_idx, val);
|
|
415
429
|
}
|
|
416
430
|
ValueType::Nested(sub_schema) => {
|
|
431
|
+
if is_nil {
|
|
432
|
+
stack
|
|
433
|
+
.last_mut()
|
|
434
|
+
.unwrap()
|
|
435
|
+
.push_list_item(field_idx, PolyValue::Null);
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
417
438
|
let frame_schema =
|
|
418
439
|
resolve_record_schema(sub_schema, e, &scope)?;
|
|
419
440
|
let mut frame = StackFrame::new(frame_schema);
|
|
@@ -427,6 +448,11 @@ impl XmlDeserializer {
|
|
|
427
448
|
_ => {}
|
|
428
449
|
},
|
|
429
450
|
ValueType::Nested(sub_schema) => {
|
|
451
|
+
if is_nil {
|
|
452
|
+
stack.last_mut().unwrap().values[field_idx] =
|
|
453
|
+
Some(PolyValue::Null);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
430
456
|
let frame_schema = resolve_record_schema(sub_schema, e, &scope)?;
|
|
431
457
|
let mut frame = StackFrame::new(frame_schema);
|
|
432
458
|
Self::parse_attributes(e, &mut frame)?;
|
|
@@ -19,6 +19,7 @@ pub enum ScalarType {
|
|
|
19
19
|
XmlDateTime,
|
|
20
20
|
XmlTime,
|
|
21
21
|
XmlDuration,
|
|
22
|
+
XmlGregorian(crate::ir::PrimitiveType),
|
|
22
23
|
Any,
|
|
23
24
|
}
|
|
24
25
|
|
|
@@ -218,6 +219,11 @@ impl ModelSchema {
|
|
|
218
219
|
PrimitiveType::DateTime => ScalarType::XmlDateTime,
|
|
219
220
|
PrimitiveType::Time => ScalarType::XmlTime,
|
|
220
221
|
PrimitiveType::Date => ScalarType::XmlDate,
|
|
222
|
+
PrimitiveType::GDay
|
|
223
|
+
| PrimitiveType::GMonth
|
|
224
|
+
| PrimitiveType::GYear
|
|
225
|
+
| PrimitiveType::GYearMonth
|
|
226
|
+
| PrimitiveType::GMonthDay => ScalarType::XmlGregorian(prim),
|
|
221
227
|
PrimitiveType::Int
|
|
222
228
|
| PrimitiveType::Integer
|
|
223
229
|
| PrimitiveType::NonPositiveInteger
|