use fastxml::schema::types::TypeDef;
use fastxml::schema::{CompiledSchema, ComplexType, ContentModel, ElementDef};
#[test]
fn test_element_type_ref_with_namespace() {
let mut schema = CompiledSchema::new();
let gml_track_type = ComplexType::sequence(
"gml:TrackType",
vec![ElementDef::new("MovingObjectStatus").with_type("gml:MovingObjectStatusType")],
);
let tran_track_type = ComplexType::sequence(
"tran:TrackType",
vec![
ElementDef::new("class")
.with_type("gml:CodeType")
.optional(),
],
);
schema.types.insert(
"gml:TrackType".to_string(),
TypeDef::Complex(gml_track_type),
);
schema.types.insert(
"tran:TrackType".to_string(),
TypeDef::Complex(tran_track_type),
);
let track_element = ElementDef::new("Track").with_type("tran:TrackType");
schema
.elements
.insert("tran:Track".to_string(), track_element);
let elem = schema.get_element("tran:Track");
assert!(elem.is_some(), "tran:Track element should be found");
let type_ref = elem.unwrap().type_ref.as_ref().unwrap();
assert_eq!(type_ref, "tran:TrackType");
let resolved_type = schema.get_type(type_ref);
assert!(resolved_type.is_some(), "tran:TrackType should be resolved");
if let Some(TypeDef::Complex(complex)) = resolved_type {
if let ContentModel::Sequence(elements) = &complex.content {
assert_eq!(
elements[0].name, "class",
"tran:Track should use tran:TrackType (with 'class'), not gml:TrackType (with 'MovingObjectStatus')"
);
} else {
panic!("expected sequence content");
}
}
}
#[test]
fn test_inherited_elements_from_base_type_extension() {
use fastxml::Namespace;
use fastxml::event::{XmlEvent, XmlEventHandler};
use fastxml::schema::validator::OnePassSchemaValidator;
use fastxml::schema::xsd::parse_xsd_multiple;
use std::sync::Arc;
let schema_xsd = r#"<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tran="http://www.opengis.net/citygml/transportation/2.0"
xmlns:gml="http://www.opengis.net/gml"
targetNamespace="http://www.opengis.net/citygml/transportation/2.0"
elementFormDefault="qualified">
<!-- Base type with some elements -->
<xs:complexType name="AbstractTransportationObjectType" abstract="true">
<xs:sequence>
<xs:element name="description" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<!-- TransportationComplexType extends base and adds class, function, lod1MultiSurface -->
<xs:complexType name="TransportationComplexType">
<xs:complexContent>
<xs:extension base="tran:AbstractTransportationObjectType">
<xs:sequence>
<xs:element name="class" type="xs:string" minOccurs="0"/>
<xs:element name="function" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="lod1MultiSurface" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- RoadType extends TransportationComplexType -->
<xs:complexType name="RoadType">
<xs:complexContent>
<xs:extension base="tran:TransportationComplexType">
<xs:sequence>
<xs:element name="trafficArea" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- Road element uses RoadType -->
<xs:element name="Road" type="tran:RoadType"/>
</xs:schema>"#;
let schema = parse_xsd_multiple(&[(
"http://www.opengis.net/citygml/transportation/2.0/transportation.xsd",
schema_xsd.as_bytes(),
)])
.expect("Failed to compile schema");
if let Some(TypeDef::Complex(road_type)) = schema.get_type("tran:RoadType") {
eprintln!("RoadType content: {:?}", road_type.content);
}
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "Road".into(),
prefix: Some("tran".into()),
namespace: Some("http://www.opengis.net/citygml/transportation/2.0".into()),
attributes: vec![],
namespace_decls: vec![Namespace::new(
"tran",
"http://www.opengis.net/citygml/transportation/2.0",
)],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "class".into(),
prefix: Some("tran".into()),
namespace: Some("http://www.opengis.net/citygml/transportation/2.0".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(2),
column: Some(1),
})
.unwrap();
validator.handle(&XmlEvent::Text("道路".into())).unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "class".into(),
prefix: Some("tran".into()),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "Road".into(),
prefix: Some("tran".into()),
})
.unwrap();
validator.handle(&XmlEvent::Eof).unwrap();
validator.finish().unwrap();
let errors: Vec<_> = validator
.errors()
.iter()
.filter(|e| e.message.contains("not declared") || e.message.contains("not expected"))
.collect();
eprintln!("Validation errors: {:?}", errors);
assert!(
errors.is_empty(),
"Elements from base type (class) should be visible in derived type (RoadType). Errors: {:?}",
errors
);
}
#[test]
fn test_inherited_elements_across_namespaces_via_import() {
use fastxml::Namespace;
use fastxml::event::{XmlEvent, XmlEventHandler};
use fastxml::schema::validator::OnePassSchemaValidator;
use fastxml::schema::xsd::parse_xsd_multiple;
use std::sync::Arc;
let core_xsd = r#"<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:core="http://www.opengis.net/citygml/2.0"
targetNamespace="http://www.opengis.net/citygml/2.0"
elementFormDefault="qualified">
<xs:complexType name="AbstractCityObjectType" abstract="true">
<xs:sequence>
<xs:element name="creationDate" type="xs:date" minOccurs="0"/>
<xs:element name="terminationDate" type="xs:date" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>"#;
let tran_xsd = r#"<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tran="http://www.opengis.net/citygml/transportation/2.0"
xmlns:core="http://www.opengis.net/citygml/2.0"
targetNamespace="http://www.opengis.net/citygml/transportation/2.0"
elementFormDefault="qualified">
<xs:import namespace="http://www.opengis.net/citygml/2.0"
schemaLocation="http://www.opengis.net/citygml/2.0/cityGMLBase.xsd"/>
<!-- Extends core:AbstractCityObjectType to inherit creationDate -->
<xs:complexType name="TransportationComplexType">
<xs:complexContent>
<xs:extension base="core:AbstractCityObjectType">
<xs:sequence>
<xs:element name="class" type="xs:string" minOccurs="0"/>
<xs:element name="function" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="lod1MultiSurface" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- RoadType extends TransportationComplexType -->
<xs:complexType name="RoadType">
<xs:complexContent>
<xs:extension base="tran:TransportationComplexType">
<xs:sequence>
<xs:element name="trafficArea" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- Road element uses RoadType -->
<xs:element name="Road" type="tran:RoadType"/>
</xs:schema>"#;
let schema = parse_xsd_multiple(&[
(
"http://www.opengis.net/citygml/2.0/cityGMLBase.xsd",
core_xsd.as_bytes(),
),
(
"http://www.opengis.net/citygml/transportation/2.0/transportation.xsd",
tran_xsd.as_bytes(),
),
])
.expect("Failed to compile schema");
eprintln!("=== Type children cache contents ===");
for (type_name, flattened) in &schema.type_children_cache {
if type_name.contains("Road") || type_name.contains("Transportation") {
eprintln!(
"{}: {:?}",
type_name,
flattened.constraints.keys().collect::<Vec<_>>()
);
}
}
if let Some(fastxml::schema::types::TypeDef::Complex(road_type)) = schema.get_type("RoadType") {
eprintln!("RoadType content: {:?}", road_type.content);
}
if let Some(fastxml::schema::types::TypeDef::Complex(trans_type)) =
schema.get_type("TransportationComplexType")
{
eprintln!(
"TransportationComplexType content: {:?}",
trans_type.content
);
}
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "Road".into(),
prefix: Some("tran".into()),
namespace: Some("http://www.opengis.net/citygml/transportation/2.0".into()),
attributes: vec![],
namespace_decls: vec![
Namespace::new("tran", "http://www.opengis.net/citygml/transportation/2.0"),
Namespace::new("core", "http://www.opengis.net/citygml/2.0"),
],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "creationDate".into(),
prefix: Some("core".into()),
namespace: Some("http://www.opengis.net/citygml/2.0".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(2),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::Text("2024-01-01".into()))
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "creationDate".into(),
prefix: Some("core".into()),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "class".into(),
prefix: Some("tran".into()),
namespace: Some("http://www.opengis.net/citygml/transportation/2.0".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(3),
column: Some(1),
})
.unwrap();
validator.handle(&XmlEvent::Text("9999".into())).unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "class".into(),
prefix: Some("tran".into()),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "Road".into(),
prefix: Some("tran".into()),
})
.unwrap();
let errors: Vec<_> = validator
.errors()
.iter()
.filter(|e| e.message.contains("not declared"))
.collect();
eprintln!("Validation errors: {:?}", errors);
assert!(
errors.is_empty(),
"Elements inherited across namespaces should be visible. Errors: {:?}",
errors
);
}
#[test]
fn test_deep_inheritance_chain_across_namespaces() {
use fastxml::Namespace;
use fastxml::event::{XmlEvent, XmlEventHandler};
use fastxml::schema::validator::OnePassSchemaValidator;
use fastxml::schema::xsd::parse_xsd_multiple;
use std::sync::Arc;
let gml_xsd = r#"<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:gml="http://www.opengis.net/gml"
targetNamespace="http://www.opengis.net/gml"
elementFormDefault="qualified">
<xs:complexType name="AbstractGMLType" abstract="true">
<xs:sequence>
<xs:element name="description" type="xs:string" minOccurs="0"/>
<xs:element name="name" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute ref="gml:id"/>
</xs:complexType>
<xs:attribute name="id" type="xs:ID"/>
<xs:complexType name="AbstractFeatureType" abstract="true">
<xs:complexContent>
<xs:extension base="gml:AbstractGMLType">
<xs:sequence>
<xs:element name="boundedBy" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>"#;
let core_xsd = r#"<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:core="http://www.opengis.net/citygml/2.0"
xmlns:gml="http://www.opengis.net/gml"
targetNamespace="http://www.opengis.net/citygml/2.0"
elementFormDefault="qualified">
<xs:import namespace="http://www.opengis.net/gml"
schemaLocation="http://schemas.opengis.net/gml/3.1.1/base/gml.xsd"/>
<xs:complexType name="AbstractCityObjectType" abstract="true">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="creationDate" type="xs:date" minOccurs="0"/>
<xs:element name="terminationDate" type="xs:date" minOccurs="0"/>
<xs:element name="externalReference" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="cityObjectMember" type="xs:string"/>
</xs:schema>"#;
let tran_xsd = r#"<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tran="http://www.opengis.net/citygml/transportation/2.0"
xmlns:core="http://www.opengis.net/citygml/2.0"
xmlns:gml="http://www.opengis.net/gml"
targetNamespace="http://www.opengis.net/citygml/transportation/2.0"
elementFormDefault="qualified">
<xs:import namespace="http://www.opengis.net/citygml/2.0"
schemaLocation="http://www.opengis.net/citygml/2.0/cityGMLBase.xsd"/>
<xs:import namespace="http://www.opengis.net/gml"
schemaLocation="http://schemas.opengis.net/gml/3.1.1/base/gml.xsd"/>
<!-- Intermediate abstract type -->
<xs:complexType name="AbstractTransportationObjectType" abstract="true">
<xs:complexContent>
<xs:extension base="core:AbstractCityObjectType">
<xs:sequence>
<!-- No additional elements here -->
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- TransportationComplexType adds class, function, etc. -->
<xs:complexType name="TransportationComplexType">
<xs:complexContent>
<xs:extension base="tran:AbstractTransportationObjectType">
<xs:sequence>
<xs:element name="class" type="xs:string" minOccurs="0"/>
<xs:element name="function" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="usage" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="lod1MultiSurface" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- RoadType extends TransportationComplexType -->
<xs:complexType name="RoadType">
<xs:complexContent>
<xs:extension base="tran:TransportationComplexType">
<xs:sequence>
<xs:element name="trafficArea" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="auxiliaryTrafficArea" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="Road" type="tran:RoadType" substitutionGroup="core:cityObjectMember"/>
</xs:schema>"#;
let schema = parse_xsd_multiple(&[
(
"http://schemas.opengis.net/gml/3.1.1/base/gml.xsd",
gml_xsd.as_bytes(),
),
(
"http://www.opengis.net/citygml/2.0/cityGMLBase.xsd",
core_xsd.as_bytes(),
),
(
"http://www.opengis.net/citygml/transportation/2.0/transportation.xsd",
tran_xsd.as_bytes(),
),
])
.expect("Failed to compile schema");
eprintln!("=== Deep inheritance test: type_children_cache ===");
if let Some(flattened) = schema.type_children_cache.get("RoadType") {
eprintln!(
"RoadType children: {:?}",
flattened.constraints.keys().collect::<Vec<_>>()
);
} else {
eprintln!("RoadType not found in cache!");
}
if let Some(flattened) = schema.type_children_cache.get("tran:RoadType") {
eprintln!(
"tran:RoadType children: {:?}",
flattened.constraints.keys().collect::<Vec<_>>()
);
}
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "Road".into(),
prefix: Some("tran".into()),
namespace: Some("http://www.opengis.net/citygml/transportation/2.0".into()),
attributes: vec![("gml:id".into(), "road_1".into())],
namespace_decls: vec![
Namespace::new("tran", "http://www.opengis.net/citygml/transportation/2.0"),
Namespace::new("core", "http://www.opengis.net/citygml/2.0"),
Namespace::new("gml", "http://www.opengis.net/gml"),
],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "boundedBy".into(),
prefix: Some("gml".into()),
namespace: Some("http://www.opengis.net/gml".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(2),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::Text("envelope".into()))
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "boundedBy".into(),
prefix: Some("gml".into()),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "creationDate".into(),
prefix: Some("core".into()),
namespace: Some("http://www.opengis.net/citygml/2.0".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(3),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::Text("2024-01-01".into()))
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "creationDate".into(),
prefix: Some("core".into()),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "class".into(),
prefix: Some("tran".into()),
namespace: Some("http://www.opengis.net/citygml/transportation/2.0".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(4),
column: Some(1),
})
.unwrap();
validator.handle(&XmlEvent::Text("9999".into())).unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "class".into(),
prefix: Some("tran".into()),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "function".into(),
prefix: Some("tran".into()),
namespace: Some("http://www.opengis.net/citygml/transportation/2.0".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(5),
column: Some(1),
})
.unwrap();
validator.handle(&XmlEvent::Text("9020".into())).unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "function".into(),
prefix: Some("tran".into()),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "lod1MultiSurface".into(),
prefix: Some("tran".into()),
namespace: Some("http://www.opengis.net/citygml/transportation/2.0".into()),
attributes: vec![],
namespace_decls: vec![],
line: Some(6),
column: Some(1),
})
.unwrap();
validator.handle(&XmlEvent::Text("surface".into())).unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "lod1MultiSurface".into(),
prefix: Some("tran".into()),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "Road".into(),
prefix: Some("tran".into()),
})
.unwrap();
let errors: Vec<_> = validator
.errors()
.iter()
.filter(|e| e.message.contains("not declared"))
.collect();
eprintln!("Validation errors: {:?}", errors);
assert!(
errors.is_empty(),
"Elements from deep inheritance chain should be visible. Errors: {:?}",
errors
);
}
#[test]
fn test_same_namespace_inheritance_without_prefix() {
use fastxml::schema::xsd::{compile_schemas, parser::parse_xsd_ast, register_builtin_types};
let tran_xsd = r#"<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tran="http://www.opengis.net/citygml/transportation/2.0"
targetNamespace="http://www.opengis.net/citygml/transportation/2.0"
elementFormDefault="qualified">
<!-- Base type with elements -->
<xs:complexType name="TransportationComplexType">
<xs:sequence>
<xs:element name="class" type="xs:string" minOccurs="0"/>
<xs:element name="function" type="xs:string" minOccurs="0"/>
<xs:element name="usage" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<!-- Derived type extends base WITHOUT prefix (same namespace) -->
<xs:complexType name="RoadType">
<xs:complexContent>
<xs:extension base="TransportationComplexType">
<xs:sequence>
<xs:element name="trafficArea" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="Road" type="tran:RoadType"/>
</xs:schema>"#;
let tran_ast = parse_xsd_ast(tran_xsd.as_bytes()).unwrap();
let mut compiled = compile_schemas(vec![tran_ast]).expect("Failed to compile schemas");
register_builtin_types(&mut compiled);
eprintln!("=== Same-namespace inheritance test ===");
eprintln!(
"Types in schema: {:?}",
compiled.types.keys().collect::<Vec<_>>()
);
let road_type_cache = compiled.type_children_cache.get("RoadType");
eprintln!(
"RoadType cache: {:?}",
road_type_cache.map(|f| f.constraints.keys().collect::<Vec<_>>())
);
let road_type_cache = road_type_cache.expect("RoadType should be in cache");
assert!(
road_type_cache.constraints.contains_key("trafficArea"),
"RoadType should have trafficArea (own element)"
);
assert!(
road_type_cache.constraints.contains_key("class"),
"RoadType should have class (inherited from TransportationComplexType)"
);
assert!(
road_type_cache.constraints.contains_key("function"),
"RoadType should have function (inherited from TransportationComplexType)"
);
assert!(
road_type_cache.constraints.contains_key("usage"),
"RoadType should have usage (inherited from TransportationComplexType)"
);
}