polyxml 0.33.7__tar.gz → 0.33.9__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.33.7 → polyxml-0.33.9}/Cargo.lock +6 -6
- {polyxml-0.33.7 → polyxml-0.33.9}/Cargo.toml +1 -1
- {polyxml-0.33.7 → polyxml-0.33.9}/PKG-INFO +1 -1
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/csharp/mod.rs +40 -10
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/go/mod.rs +50 -6
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/python/mod.rs +11 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/ir/mod.rs +4 -0
- polyxml-0.33.9/crates/polyxml-core/src/ir/particle.rs +60 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/parser.rs +39 -3
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/schema.rs +77 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/schema_parser/mod.rs +113 -1
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/serializer.rs +52 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_csharp_codegen.rs +44 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_go_codegen.rs +45 -0
- polyxml-0.33.9/crates/polyxml-core/tests/test_value_constraints.rs +30 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-python/src/lib.rs +16 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-python/tests/test_generated_models.py +78 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/pyproject.toml +1 -1
- {polyxml-0.33.7 → polyxml-0.33.9}/README.md +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/Cargo.toml +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/README.md +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/benches/core_benchmarks.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/benches/support/tag_dispatch_fixtures.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/benches/tag_dispatch.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/cpp/mod.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/java/codec.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/java/mod.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/java/models.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/mod.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/python/aot.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/rust/mod.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/codegen/typescript/mod.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/converters.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/error.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/ir/chunker.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/ir/tarjan.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/json.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/lib.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/transcoder.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/src/value.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/fixtures/lexical_union.xsd +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/fixtures/mixed_content.xsd +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/fixtures/ts_mixed_content_inheritance.xsd +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_adversarial_identifiers.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_any_attribute.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_any_element.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_conformance.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_core.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_cpp_codegen.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_java_codegen.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_json.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_lexical_union_codegen.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_mixed_content.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_pattern_codegen.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_python_codegen.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_qualified_attributes.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_rust_codecs.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_rust_codegen.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_schema_audit.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_schema_ir.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_security.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_transcoder.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_ts_codegen.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_unbounded_choice.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-core/tests/test_xsi_type.rs +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-python/Cargo.toml +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-python/README.md +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-python/benches/aot_vs_dataclass.py +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-python/tests/test_conformance.py +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-python/tests/test_json.py +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-python/tests/test_mixed_content.py +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-python/tests/test_polyxml.py +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/crates/polyxml-python/tests/test_xsi_type.py +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/python/polyxml/AGENT_GUIDE.md +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/python/polyxml/__init__.py +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/python/polyxml/compat/__init__.py +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/python/polyxml/compat/xsdata.py +0 -0
- {polyxml-0.33.7 → polyxml-0.33.9}/python/polyxml/py.typed +0 -0
|
@@ -608,7 +608,7 @@ dependencies = [
|
|
|
608
608
|
|
|
609
609
|
[[package]]
|
|
610
610
|
name = "polyxml"
|
|
611
|
-
version = "0.33.
|
|
611
|
+
version = "0.33.9"
|
|
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.33.
|
|
632
|
+
version = "0.33.9"
|
|
633
633
|
dependencies = [
|
|
634
634
|
"polyxml",
|
|
635
635
|
]
|
|
636
636
|
|
|
637
637
|
[[package]]
|
|
638
638
|
name = "polyxml-cli"
|
|
639
|
-
version = "0.33.
|
|
639
|
+
version = "0.33.9"
|
|
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.33.
|
|
653
|
+
version = "0.33.9"
|
|
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.33.
|
|
663
|
+
version = "0.33.9"
|
|
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.33.
|
|
673
|
+
version = "0.33.9"
|
|
674
674
|
dependencies = [
|
|
675
675
|
"polyxml",
|
|
676
676
|
"quick-xml",
|
|
@@ -877,11 +877,11 @@ impl CSharpCodegen {
|
|
|
877
877
|
ir: &SchemaIR,
|
|
878
878
|
indent: &str,
|
|
879
879
|
) -> bool {
|
|
880
|
-
let Some(default) = field.default_value.as_ref() else {
|
|
880
|
+
let Some(default) = field.default_value.as_ref().or(field.fixed_value.as_ref()) else {
|
|
881
881
|
return false;
|
|
882
882
|
};
|
|
883
883
|
if !self.options.emit_xml_attributes
|
|
884
|
-
|| field.kind
|
|
884
|
+
|| !matches!(field.kind, FieldKind::Element | FieldKind::Attribute)
|
|
885
885
|
|| field.cardinality.is_list()
|
|
886
886
|
|| matches!(field.type_ref, TypeRef::Named(ref q) if matches!(ir.types.get(q), Some(TypeDef::Struct(_) | TypeDef::Union(_))))
|
|
887
887
|
{
|
|
@@ -894,19 +894,49 @@ impl CSharpCodegen {
|
|
|
894
894
|
.as_ref()
|
|
895
895
|
.map(|ns| format!(", Namespace = {ns:?}"))
|
|
896
896
|
.unwrap_or_default();
|
|
897
|
+
let parse = |text: &str| {
|
|
898
|
+
format!("({base})new XmlSerializer(typeof({base}), new XmlRootAttribute(\"value\")).Deserialize(new System.IO.StringReader(\"<value>\" + System.Security.SecurityElement.Escape({text}) + \"</value>\"))!")
|
|
899
|
+
};
|
|
900
|
+
if let Some(fixed) = &field.fixed_value {
|
|
901
|
+
let expected = parse(&format!("{fixed:?}"));
|
|
902
|
+
let initial = if field.cardinality.is_optional() && field.kind == FieldKind::Element {
|
|
903
|
+
"default!".to_string()
|
|
904
|
+
} else {
|
|
905
|
+
expected.clone()
|
|
906
|
+
};
|
|
907
|
+
writeln!(out, "{indent} private {ty} _{name} = {initial};
|
|
908
|
+
{indent} [XmlIgnore] public {ty} {name} {{ get => _{name}; set {{ if (value is not null && !object.Equals(value, {expected})) throw new System.ComponentModel.DataAnnotations.ValidationException(\"Fixed value constraint violated for {name}\"); _{name} = value; }} }}").unwrap();
|
|
909
|
+
} else {
|
|
910
|
+
let initial = if field.kind == FieldKind::Attribute {
|
|
911
|
+
parse(&format!("{default:?}"))
|
|
912
|
+
} else {
|
|
913
|
+
"default!".to_string()
|
|
914
|
+
};
|
|
915
|
+
writeln!(
|
|
916
|
+
out,
|
|
917
|
+
"{indent} [XmlIgnore] public {ty} {name} {{ get; set; }} = {initial};"
|
|
918
|
+
)
|
|
919
|
+
.unwrap();
|
|
920
|
+
}
|
|
921
|
+
let kind = if field.kind == FieldKind::Attribute {
|
|
922
|
+
"XmlAttribute"
|
|
923
|
+
} else {
|
|
924
|
+
"XmlElement"
|
|
925
|
+
};
|
|
897
926
|
writeln!(
|
|
898
927
|
out,
|
|
899
|
-
"{indent} [
|
|
900
|
-
)
|
|
901
|
-
.unwrap();
|
|
902
|
-
writeln!(
|
|
903
|
-
out,
|
|
904
|
-
"{indent} [XmlElement({:?}{namespace})] public string? {name}Xml {{",
|
|
928
|
+
"{indent} [{kind}({:?}{namespace})] public string? {name}Xml {{",
|
|
905
929
|
field.xml_name
|
|
906
930
|
)
|
|
907
931
|
.unwrap();
|
|
908
932
|
writeln!(out, "{indent} get {{ if ({name} is null) return null; var writer = new System.IO.StringWriter(System.Globalization.CultureInfo.InvariantCulture); new XmlSerializer(typeof({base}), new XmlRootAttribute(\"value\")).Serialize(writer, {name}); return System.Xml.Linq.XDocument.Parse(writer.ToString()).Root!.Value; }}").unwrap();
|
|
909
|
-
|
|
933
|
+
let text = if field.kind == FieldKind::Attribute {
|
|
934
|
+
"value".into()
|
|
935
|
+
} else {
|
|
936
|
+
format!("value.Length == 0 ? {default:?} : value")
|
|
937
|
+
};
|
|
938
|
+
let parsed = parse("text");
|
|
939
|
+
writeln!(out, "{indent} set {{ if (value is null) {{ {name} = default!; return; }} var text = {text}; {name} = {parsed}; }}
|
|
910
940
|
{indent} }}").unwrap();
|
|
911
941
|
if field.cardinality.is_optional() || field.nillable {
|
|
912
942
|
writeln!(
|
|
@@ -1079,7 +1109,7 @@ impl CSharpCodegen {
|
|
|
1079
1109
|
}
|
|
1080
1110
|
|
|
1081
1111
|
let has_lexical_union = self.options.emit_xml_attributes && s.fields.iter().any(|f| {
|
|
1082
|
-
f.default_value.is_some() || matches!(&f.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical()))
|
|
1112
|
+
f.default_value.is_some() || f.fixed_value.is_some() || matches!(&f.type_ref, TypeRef::Named(q) if matches!(ir.types.get(q), Some(TypeDef::Union(u)) if u.is_lexical()))
|
|
1083
1113
|
});
|
|
1084
1114
|
|
|
1085
1115
|
if self.options.use_records && has_lexical_union {
|
|
@@ -371,6 +371,10 @@ impl GoCodegen {
|
|
|
371
371
|
}
|
|
372
372
|
self.emit_root_aliases(&mut body, ir);
|
|
373
373
|
|
|
374
|
+
if self.options.emit_xml_tags && !ir.content_models.is_empty() {
|
|
375
|
+
self.emit_content_helpers(&mut body);
|
|
376
|
+
}
|
|
377
|
+
|
|
374
378
|
// Assemble final output with package and imports
|
|
375
379
|
let mut out = String::new();
|
|
376
380
|
if let Some(ref header) = self.options.custom_header {
|
|
@@ -399,20 +403,21 @@ impl GoCodegen {
|
|
|
399
403
|
imports.push("\"encoding/xml\"");
|
|
400
404
|
}
|
|
401
405
|
if self.options.emit_xml_tags
|
|
402
|
-
&& ir
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
+
&& (!ir.content_models.is_empty()
|
|
407
|
+
|| ir
|
|
408
|
+
.types
|
|
409
|
+
.values()
|
|
410
|
+
.any(|def| matches!(def, TypeDef::Struct(s) if ir.has_ordered_content(s))))
|
|
406
411
|
{
|
|
407
412
|
imports.push("\"bytes\"");
|
|
408
413
|
}
|
|
409
414
|
if has_fmt || body.contains("fmt.") {
|
|
410
415
|
imports.push("\"fmt\"");
|
|
411
416
|
}
|
|
412
|
-
if has_io {
|
|
417
|
+
if has_io || body.contains("io.") {
|
|
413
418
|
imports.push("\"io\"");
|
|
414
419
|
}
|
|
415
|
-
if has_patterns {
|
|
420
|
+
if has_patterns || body.contains("regexp.") {
|
|
416
421
|
imports.push("\"regexp\"");
|
|
417
422
|
}
|
|
418
423
|
if ir
|
|
@@ -1108,11 +1113,50 @@ impl GoCodegen {
|
|
|
1108
1113
|
self.emit_mixed_struct_xml(out, s, ir, &field_names);
|
|
1109
1114
|
}
|
|
1110
1115
|
|
|
1116
|
+
if self.options.emit_xml_tags && !ir.has_ordered_content(s) && !s.is_abstract {
|
|
1117
|
+
if let Some(model) = ir.content_models.get(&s.qname) {
|
|
1118
|
+
self.emit_content_codec(out, s, &format!("^(?:{})$", model.pattern()));
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1111
1121
|
if self.options.validate_facets {
|
|
1112
1122
|
self.emit_struct_validator(out, s, ir, &field_names);
|
|
1113
1123
|
}
|
|
1114
1124
|
}
|
|
1115
1125
|
|
|
1126
|
+
fn emit_content_helpers(&self, out: &mut String) {
|
|
1127
|
+
out.push_str(r#"
|
|
1128
|
+
func polyxmlReadElement(d *xml.Decoder, start xml.StartElement) ([]byte,error) {
|
|
1129
|
+
var buffer bytes.Buffer; encoder:=xml.NewEncoder(&buffer);depth:=1
|
|
1130
|
+
token:=xml.Token(start)
|
|
1131
|
+
for {
|
|
1132
|
+
if element,ok:=token.(xml.StartElement);ok { attrs:=element.Attr[:0];for _,a:=range element.Attr {if a.Name.Space!="xmlns"&&a.Name.Local!="xmlns" {attrs=append(attrs,a)}};element.Attr=attrs;token=element }
|
|
1133
|
+
if err:=encoder.EncodeToken(token);err!=nil{return nil,err}
|
|
1134
|
+
if depth==0{break}
|
|
1135
|
+
next,err:=d.Token();if err!=nil{return nil,err};token=next
|
|
1136
|
+
switch token.(type){case xml.StartElement:depth++;case xml.EndElement:depth--}
|
|
1137
|
+
}
|
|
1138
|
+
if err:=encoder.Flush();err!=nil{return nil,err};return buffer.Bytes(),nil
|
|
1139
|
+
}
|
|
1140
|
+
func polyxmlValidateContent(data []byte,pattern string) error {
|
|
1141
|
+
decoder:=xml.NewDecoder(bytes.NewReader(data));depth:=0;tokens:=""
|
|
1142
|
+
for {token,err:=decoder.Token();if err==io.EOF{break};if err!=nil{return err};switch v:=token.(type){case xml.StartElement:depth++;if depth==2{if v.Name.Space!=""{tokens+="{"+v.Name.Space+"}"};tokens+=v.Name.Local+";"};case xml.EndElement:depth--}}
|
|
1143
|
+
matched,err:=regexp.MatchString(pattern,tokens);if err!=nil{return err};if !matched{return fmt.Errorf("content model constraint violated")};return nil
|
|
1144
|
+
}
|
|
1145
|
+
"#);
|
|
1146
|
+
}
|
|
1147
|
+
fn emit_content_codec(&self, out: &mut String, structure: &StructDef, pattern: &str) {
|
|
1148
|
+
let name = type_ident(&structure.qname);
|
|
1149
|
+
writeln!(out,"func (v *{name}) UnmarshalXML(d *xml.Decoder,start xml.StartElement) error {{
|
|
1150
|
+
data,err:=polyxmlReadElement(d,start);if err!=nil{{return err}};if err:=polyxmlValidateContent(data,{pattern:?});err!=nil{{return err}}
|
|
1151
|
+
type Alias {name};return xml.Unmarshal(data,(*Alias)(v))
|
|
1152
|
+
}}
|
|
1153
|
+
func (v {name}) MarshalXML(e *xml.Encoder,start xml.StartElement) error {{
|
|
1154
|
+
type Alias {name};var buffer bytes.Buffer;encoder:=xml.NewEncoder(&buffer);if err:=encoder.EncodeElement(Alias(v),start);err!=nil{{return err}};if err:=polyxmlValidateContent(buffer.Bytes(),{pattern:?});err!=nil{{return err}}
|
|
1155
|
+
decoder:=xml.NewDecoder(&buffer);for {{token,err:=decoder.Token();if err==io.EOF{{return nil}};if err!=nil{{return err}};if err:=e.EncodeToken(token);err!=nil{{return err}}}}
|
|
1156
|
+
}}
|
|
1157
|
+
").unwrap();
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1116
1160
|
fn emit_mixed_struct_xml(
|
|
1117
1161
|
&self,
|
|
1118
1162
|
out: &mut String,
|
|
@@ -645,6 +645,14 @@ impl PythonCodegen {
|
|
|
645
645
|
}
|
|
646
646
|
let _ = writeln!(out, " name = \"{}\"", s.qname.local);
|
|
647
647
|
out.push_str(" strict_root = False\n");
|
|
648
|
+
if let Some(model) = ir.content_models.get(&s.qname) {
|
|
649
|
+
writeln!(
|
|
650
|
+
out,
|
|
651
|
+
" content_pattern = {:?}",
|
|
652
|
+
format!("^(?:{})$", model.pattern())
|
|
653
|
+
)
|
|
654
|
+
.unwrap();
|
|
655
|
+
}
|
|
648
656
|
if let Some(ref ns) = s.qname.namespace {
|
|
649
657
|
let _ = writeln!(out, " namespace = \"{}\"", ns);
|
|
650
658
|
}
|
|
@@ -993,6 +1001,9 @@ impl PythonCodegen {
|
|
|
993
1001
|
parts.push(format!("\"namespace\": \"{}\"", ns));
|
|
994
1002
|
}
|
|
995
1003
|
|
|
1004
|
+
if let Some(ref fixed) = field.fixed_value {
|
|
1005
|
+
parts.push(format!("\"fixed\": {fixed:?}"));
|
|
1006
|
+
}
|
|
996
1007
|
if let Some(ref default) = field.default_value {
|
|
997
1008
|
parts.push(format!("\"default\": {default:?}"));
|
|
998
1009
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
pub mod chunker;
|
|
2
|
+
pub mod particle;
|
|
3
|
+
pub use particle::Particle;
|
|
2
4
|
pub mod tarjan;
|
|
3
5
|
|
|
4
6
|
pub use chunker::{partition_topological_chunks, ChunkPlan, TypeChunk};
|
|
@@ -467,6 +469,8 @@ pub struct SchemaIR {
|
|
|
467
469
|
/// repeated particle or substitution-member order during serialization.
|
|
468
470
|
#[serde(default)]
|
|
469
471
|
pub ordered_types: std::collections::BTreeSet<QName>,
|
|
472
|
+
#[serde(default)]
|
|
473
|
+
pub content_models: BTreeMap<QName, Particle>,
|
|
470
474
|
/// Type owners outside the module currently being emitted. Definitions
|
|
471
475
|
/// remain in `types` so generators can resolve their field semantics.
|
|
472
476
|
#[serde(default)]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
use super::QName;
|
|
2
|
+
use serde::{Deserialize, Serialize};
|
|
3
|
+
|
|
4
|
+
/// Source particle boundaries and occurrence constraints, independent of field layout.
|
|
5
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
6
|
+
pub enum Particle {
|
|
7
|
+
Element(QName),
|
|
8
|
+
Sequence(Vec<Particle>),
|
|
9
|
+
Choice(Vec<Particle>),
|
|
10
|
+
Repeat {
|
|
11
|
+
particle: Box<Particle>,
|
|
12
|
+
min: usize,
|
|
13
|
+
max: Option<usize>,
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
impl Particle {
|
|
17
|
+
pub fn has_choice(&self) -> bool {
|
|
18
|
+
match self {
|
|
19
|
+
Self::Choice(_) => true,
|
|
20
|
+
Self::Sequence(items) => items.iter().any(Self::has_choice),
|
|
21
|
+
Self::Repeat { particle, .. } => particle.has_choice(),
|
|
22
|
+
_ => false,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
pub fn adopt_namespace(&mut self, namespace: &str) {
|
|
26
|
+
match self {
|
|
27
|
+
Self::Element(name) => {
|
|
28
|
+
if name.namespace.is_none() {
|
|
29
|
+
name.namespace = Some(namespace.into());
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
Self::Sequence(items) | Self::Choice(items) => {
|
|
33
|
+
for item in items {
|
|
34
|
+
item.adopt_namespace(namespace);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
Self::Repeat { particle, .. } => particle.adopt_namespace(namespace),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
pub fn pattern(&self) -> String {
|
|
41
|
+
match self {
|
|
42
|
+
Self::Element(name) => regex::escape(&format!("{};", name)),
|
|
43
|
+
Self::Sequence(items) => items.iter().map(Self::pattern).collect(),
|
|
44
|
+
Self::Choice(items) => format!(
|
|
45
|
+
"(?:{})",
|
|
46
|
+
items
|
|
47
|
+
.iter()
|
|
48
|
+
.map(Self::pattern)
|
|
49
|
+
.collect::<Vec<_>>()
|
|
50
|
+
.join("|")
|
|
51
|
+
),
|
|
52
|
+
Self::Repeat { particle, min, max } => format!(
|
|
53
|
+
"(?:{}){{{},{}}}",
|
|
54
|
+
particle.pattern(),
|
|
55
|
+
min,
|
|
56
|
+
max.map(|v| v.to_string()).unwrap_or_default()
|
|
57
|
+
),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -18,6 +18,7 @@ pub(crate) struct StackFrame {
|
|
|
18
18
|
list_values: SmallVec<[Option<Vec<PolyValue>>; 4]>,
|
|
19
19
|
frame_text_buf: Option<Vec<u8>>,
|
|
20
20
|
mixed_parent_kind: Option<String>,
|
|
21
|
+
content_tokens: String,
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
impl StackFrame {
|
|
@@ -34,6 +35,7 @@ impl StackFrame {
|
|
|
34
35
|
list_values: SmallVec::new(),
|
|
35
36
|
frame_text_buf,
|
|
36
37
|
mixed_parent_kind: None,
|
|
38
|
+
content_tokens: String::new(),
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -71,11 +73,13 @@ impl StackFrame {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
fn finish(mut self) -> Result<PolyValue> {
|
|
76
|
+
crate::schema::validate_content(&self.schema, &self.content_tokens)?;
|
|
74
77
|
for (index, field) in self.schema.fields.iter().enumerate() {
|
|
75
78
|
if field.kind == FieldKind::Attribute && self.values[index].is_none() {
|
|
76
|
-
if let (Some(default), ValueType::Scalar(scalar)) =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
if let (Some(default), ValueType::Scalar(scalar)) = (
|
|
80
|
+
&field.default_value.as_ref().or(field.fixed_value.as_ref()),
|
|
81
|
+
&field.val_type,
|
|
82
|
+
) {
|
|
79
83
|
self.values[index] = Some(ValueConverter::parse_scalar(
|
|
80
84
|
scalar,
|
|
81
85
|
default.as_bytes(),
|
|
@@ -113,6 +117,11 @@ impl StackFrame {
|
|
|
113
117
|
}
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
for (index, field) in self.schema.fields.iter().enumerate() {
|
|
121
|
+
if let Some(value) = self.values[index].as_ref() {
|
|
122
|
+
crate::schema::validate_fixed(field, value)?;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
116
125
|
Ok(PolyValue::Record {
|
|
117
126
|
schema: self.schema,
|
|
118
127
|
values: self.values.into_vec().into_boxed_slice(),
|
|
@@ -129,6 +138,7 @@ fn parse_field_scalar(
|
|
|
129
138
|
field
|
|
130
139
|
.default_value
|
|
131
140
|
.as_ref()
|
|
141
|
+
.or(field.fixed_value.as_ref())
|
|
132
142
|
.map_or(text, |value| value.as_bytes())
|
|
133
143
|
} else {
|
|
134
144
|
text
|
|
@@ -422,6 +432,13 @@ impl XmlDeserializer {
|
|
|
422
432
|
unknown_depth += 1;
|
|
423
433
|
continue;
|
|
424
434
|
}
|
|
435
|
+
if active_scalar_field.is_none()
|
|
436
|
+
&& active_mixed_scalar.is_none()
|
|
437
|
+
&& any_stack.is_empty()
|
|
438
|
+
{
|
|
439
|
+
let scope = namespace_stack.last().unwrap();
|
|
440
|
+
record_content_token(stack.last_mut().unwrap(), e, scope);
|
|
441
|
+
}
|
|
425
442
|
if active_scalar_field.is_some() || active_mixed_scalar.is_some() {
|
|
426
443
|
unknown_depth = 1;
|
|
427
444
|
continue;
|
|
@@ -598,6 +615,10 @@ impl XmlDeserializer {
|
|
|
598
615
|
{
|
|
599
616
|
continue;
|
|
600
617
|
}
|
|
618
|
+
if any_stack.is_empty() {
|
|
619
|
+
let scope = namespace_scope(namespace_stack.last().unwrap(), e);
|
|
620
|
+
record_content_token(stack.last_mut().unwrap(), e, &scope);
|
|
621
|
+
}
|
|
601
622
|
if !any_stack.is_empty() {
|
|
602
623
|
let scope = namespace_scope(namespace_stack.last().unwrap(), e);
|
|
603
624
|
let (ns, local) = resolve_element_qname(e, &scope);
|
|
@@ -1068,3 +1089,18 @@ impl<R: std::io::BufRead> XmlItemStream<R> {
|
|
|
1068
1089
|
}
|
|
1069
1090
|
}
|
|
1070
1091
|
}
|
|
1092
|
+
|
|
1093
|
+
fn record_content_token(
|
|
1094
|
+
frame: &mut StackFrame,
|
|
1095
|
+
element: &BytesStart,
|
|
1096
|
+
scope: &HashMap<String, String>,
|
|
1097
|
+
) {
|
|
1098
|
+
if frame.schema.content_pattern.is_some() {
|
|
1099
|
+
let (ns, local) = resolve_element_qname(element, scope);
|
|
1100
|
+
if let Some(ns) = ns {
|
|
1101
|
+
frame.content_tokens.push_str(&format!("{{{ns}}}"));
|
|
1102
|
+
}
|
|
1103
|
+
frame.content_tokens.push_str(&local);
|
|
1104
|
+
frame.content_tokens.push(';');
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
@@ -66,6 +66,7 @@ pub struct FieldSchema {
|
|
|
66
66
|
pub val_type: ValueType,
|
|
67
67
|
pub required: bool,
|
|
68
68
|
pub default_value: Option<String>,
|
|
69
|
+
pub fixed_value: Option<String>,
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
impl FieldSchema {
|
|
@@ -83,6 +84,7 @@ impl FieldSchema {
|
|
|
83
84
|
val_type,
|
|
84
85
|
required: false,
|
|
85
86
|
default_value: None,
|
|
87
|
+
fixed_value: None,
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -117,6 +119,7 @@ pub struct ModelSchema {
|
|
|
117
119
|
pub is_abstract: bool,
|
|
118
120
|
/// Require the document element to match this schema's expanded QName.
|
|
119
121
|
pub strict_root: bool,
|
|
122
|
+
pub content_pattern: Option<regex::Regex>,
|
|
120
123
|
/// Concrete derivations eligible for `xsi:type` dispatch. Python may
|
|
121
124
|
/// refresh this registry when subclasses are defined after first use.
|
|
122
125
|
variants: Arc<RwLock<Vec<Arc<ModelSchema>>>>,
|
|
@@ -354,6 +357,7 @@ impl ModelSchema {
|
|
|
354
357
|
|
|
355
358
|
let mut field_schema = FieldSchema::new(&f.name, f.xml_name.as_bytes(), kind, val_type);
|
|
356
359
|
field_schema.default_value = f.default_value.clone();
|
|
360
|
+
field_schema.fixed_value = f.fixed_value.clone();
|
|
357
361
|
if let Some(ref ns) = f.namespace {
|
|
358
362
|
field_schema = field_schema.namespace(ns);
|
|
359
363
|
}
|
|
@@ -396,6 +400,12 @@ impl ModelSchema {
|
|
|
396
400
|
builder = builder.namespace(ns);
|
|
397
401
|
}
|
|
398
402
|
builder = builder.is_abstract(s.is_abstract);
|
|
403
|
+
if let Some(model) = ir.content_models.get(&s.qname) {
|
|
404
|
+
builder = builder.content_pattern(
|
|
405
|
+
regex::Regex::new(&format!("^(?:{})$", model.pattern()))
|
|
406
|
+
.expect("validated content pattern"),
|
|
407
|
+
);
|
|
408
|
+
}
|
|
399
409
|
|
|
400
410
|
// Flatten the xs:extension content model: base-chain fields
|
|
401
411
|
// precede the struct's own fields (mirrors the Java codegen).
|
|
@@ -534,6 +544,7 @@ pub struct ModelSchemaBuilder {
|
|
|
534
544
|
fields: Vec<FieldSchema>,
|
|
535
545
|
is_abstract: bool,
|
|
536
546
|
strict_root: bool,
|
|
547
|
+
content_pattern: Option<regex::Regex>,
|
|
537
548
|
}
|
|
538
549
|
|
|
539
550
|
impl ModelSchemaBuilder {
|
|
@@ -545,6 +556,7 @@ impl ModelSchemaBuilder {
|
|
|
545
556
|
fields: Vec::new(),
|
|
546
557
|
is_abstract: false,
|
|
547
558
|
strict_root: false,
|
|
559
|
+
content_pattern: None,
|
|
548
560
|
}
|
|
549
561
|
}
|
|
550
562
|
|
|
@@ -559,6 +571,11 @@ impl ModelSchemaBuilder {
|
|
|
559
571
|
self
|
|
560
572
|
}
|
|
561
573
|
|
|
574
|
+
pub fn content_pattern(mut self, pattern: regex::Regex) -> Self {
|
|
575
|
+
self.content_pattern = Some(pattern);
|
|
576
|
+
self
|
|
577
|
+
}
|
|
578
|
+
|
|
562
579
|
pub fn strict_root(mut self, strict_root: bool) -> Self {
|
|
563
580
|
self.strict_root = strict_root;
|
|
564
581
|
self
|
|
@@ -629,7 +646,67 @@ impl ModelSchemaBuilder {
|
|
|
629
646
|
mixed_content: None,
|
|
630
647
|
is_abstract: self.is_abstract,
|
|
631
648
|
strict_root: self.strict_root,
|
|
649
|
+
content_pattern: self.content_pattern,
|
|
632
650
|
variants: Arc::new(RwLock::new(Vec::new())),
|
|
633
651
|
})
|
|
634
652
|
}
|
|
635
653
|
}
|
|
654
|
+
|
|
655
|
+
/// Validate fixed constraints in value space rather than comparing lexical spellings.
|
|
656
|
+
pub(crate) fn validate_fixed(
|
|
657
|
+
field: &FieldSchema,
|
|
658
|
+
value: &crate::value::PolyValue,
|
|
659
|
+
) -> crate::error::Result<()> {
|
|
660
|
+
use crate::value::PolyValue;
|
|
661
|
+
let Some(fixed) = &field.fixed_value else {
|
|
662
|
+
return Ok(());
|
|
663
|
+
};
|
|
664
|
+
fn check(
|
|
665
|
+
ty: &ValueType,
|
|
666
|
+
value: &PolyValue,
|
|
667
|
+
fixed: &str,
|
|
668
|
+
name: &str,
|
|
669
|
+
) -> crate::error::Result<()> {
|
|
670
|
+
if matches!(value, PolyValue::Null) {
|
|
671
|
+
return Ok(());
|
|
672
|
+
}
|
|
673
|
+
match (ty, value) {
|
|
674
|
+
(ValueType::Scalar(scalar), value) => {
|
|
675
|
+
let expected = crate::converters::ValueConverter::parse_scalar(
|
|
676
|
+
scalar,
|
|
677
|
+
fixed.as_bytes(),
|
|
678
|
+
name,
|
|
679
|
+
)?;
|
|
680
|
+
if *value != expected {
|
|
681
|
+
return Err(crate::error::PolyXmlError::SchemaError(format!(
|
|
682
|
+
"Fixed value constraint violated for {name}"
|
|
683
|
+
)));
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
(ValueType::List(inner), PolyValue::List(items)) => {
|
|
687
|
+
for item in items {
|
|
688
|
+
check(inner, item, fixed, name)?;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
_ => {}
|
|
692
|
+
}
|
|
693
|
+
Ok(())
|
|
694
|
+
}
|
|
695
|
+
check(&field.val_type, value, fixed, &field.name)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
pub(crate) fn validate_content(schema: &ModelSchema, tokens: &str) -> crate::error::Result<()> {
|
|
699
|
+
if let Some(pattern) = &schema.content_pattern {
|
|
700
|
+
if !pattern.is_match(tokens) {
|
|
701
|
+
return Err(crate::error::PolyXmlError::SchemaError(format!(
|
|
702
|
+
"Content model constraint violated for {}",
|
|
703
|
+
schema.name
|
|
704
|
+
)));
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
Ok(())
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
pub fn compile_content_pattern(pattern: &str) -> std::result::Result<regex::Regex, regex::Error> {
|
|
711
|
+
regex::Regex::new(pattern)
|
|
712
|
+
}
|