use fastxml::schema::types::TypeDef;
use fastxml::schema::{CompiledSchema, ComplexType, ContentModel, ElementDef};
#[test]
fn test_namespace_qualified_type_resolution() {
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(),
ElementDef::new("function")
.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 gml_type = schema.get_type("gml:TrackType");
assert!(gml_type.is_some(), "gml:TrackType should be found");
if let Some(TypeDef::Complex(complex)) = gml_type {
if let ContentModel::Sequence(elements) = &complex.content {
assert_eq!(elements.len(), 1);
assert_eq!(elements[0].name, "MovingObjectStatus");
} else {
panic!("expected sequence content for gml:TrackType");
}
}
let tran_type = schema.get_type("tran:TrackType");
assert!(tran_type.is_some(), "tran:TrackType should be found");
if let Some(TypeDef::Complex(complex)) = tran_type {
if let ContentModel::Sequence(elements) = &complex.content {
assert_eq!(elements.len(), 2);
assert_eq!(elements[0].name, "class");
assert_eq!(elements[1].name, "function");
} else {
panic!("expected sequence content for tran:TrackType");
}
}
assert_ne!(
schema.get_type("gml:TrackType").map(|t| format!("{:?}", t)),
schema
.get_type("tran:TrackType")
.map(|t| format!("{:?}", t)),
"gml:TrackType and tran:TrackType should be different"
);
}
#[test]
fn test_type_fallback_with_same_local_name() {
let mut schema = CompiledSchema::new();
let gml_track_type = ComplexType::sequence(
"TrackType", vec![ElementDef::new("MovingObjectStatus").with_type("MovingObjectStatusType")],
);
let tran_track_type = ComplexType::sequence(
"TrackType", vec![ElementDef::new("class").with_type("CodeType").optional()],
);
schema
.types
.insert("TrackType".to_string(), TypeDef::Complex(gml_track_type));
schema
.types
.insert("TrackType".to_string(), TypeDef::Complex(tran_track_type));
let found = schema.get_type("TrackType");
assert!(found.is_some());
if let Some(TypeDef::Complex(complex)) = found
&& let ContentModel::Sequence(elements) = &complex.content
{
assert_eq!(elements[0].name, "class");
}
}
#[test]
fn test_xsd_compiler_namespace_qualified_types() {
use fastxml::schema::xsd::parse_xsd_multiple;
let gml_schema = 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="TrackType">
<xs:sequence>
<xs:element name="MovingObjectStatus" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:element name="track" type="gml:TrackType"/>
</xs:schema>"#;
let tran_schema = 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">
<xs:import namespace="http://www.opengis.net/gml"/>
<xs:complexType name="TrackType">
<xs:sequence>
<xs:element name="class" type="xs:string" minOccurs="0"/>
<xs:element name="function" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:element name="Track" type="tran:TrackType"/>
</xs:schema>"#;
let schema = parse_xsd_multiple(&[
("http://www.opengis.net/gml/gml.xsd", gml_schema.as_bytes()),
(
"http://www.opengis.net/citygml/transportation/2.0/transportation.xsd",
tran_schema.as_bytes(),
),
])
.expect("Failed to compile schemas");
let gml_type = schema.get_type("gml:TrackType");
let tran_type = schema.get_type("tran:TrackType");
assert!(
gml_type.is_some(),
"gml:TrackType should be found in compiled schema"
);
assert!(
tran_type.is_some(),
"tran:TrackType should be found in compiled schema"
);
if let Some(TypeDef::Complex(complex)) = gml_type {
if let ContentModel::Sequence(elements) = &complex.content {
assert!(
elements.iter().any(|e| e.name == "MovingObjectStatus"),
"gml:TrackType should have MovingObjectStatus child, got: {:?}",
elements.iter().map(|e| &e.name).collect::<Vec<_>>()
);
} else {
panic!(
"gml:TrackType should have sequence content, got: {:?}",
complex.content
);
}
} else {
panic!(
"gml:TrackType should be a complex type, got: {:?}",
gml_type
);
}
if let Some(TypeDef::Complex(complex)) = tran_type {
if let ContentModel::Sequence(elements) = &complex.content {
assert!(
elements.iter().any(|e| e.name == "class"),
"tran:TrackType should have class child, got: {:?}",
elements.iter().map(|e| &e.name).collect::<Vec<_>>()
);
} else {
panic!(
"tran:TrackType should have sequence content, got: {:?}",
complex.content
);
}
} else {
panic!(
"tran:TrackType should be a complex type, got: {:?}",
tran_type
);
}
assert_ne!(
format!("{:?}", gml_type),
format!("{:?}", tran_type),
"gml:TrackType and tran:TrackType should be different types"
);
}
#[test]
fn test_substitution_group_elements_from_imported_schema() {
use fastxml::schema::xsd::parse_xsd_multiple;
let core_schema = 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">
<!-- Abstract element that can be substituted -->
<xs:element name="_GenericApplicationPropertyOfCityObject" abstract="true"/>
<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:element ref="core:_GenericApplicationPropertyOfCityObject" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:schema>"#;
let tran_schema = 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"/>
<!-- Elements with substitutionGroup - these should be stored as tran:class, etc. -->
<xs:element name="class" type="xs:string"
substitutionGroup="core:_GenericApplicationPropertyOfCityObject"/>
<xs:element name="function" type="xs:string"
substitutionGroup="core:_GenericApplicationPropertyOfCityObject"/>
<xs:element name="lod1MultiSurface" type="xs:string"
substitutionGroup="core:_GenericApplicationPropertyOfCityObject"/>
</xs:schema>"#;
let schema = parse_xsd_multiple(&[
(
"http://www.opengis.net/citygml/2.0/core.xsd",
core_schema.as_bytes(),
),
(
"http://www.opengis.net/citygml/transportation/2.0/transportation.xsd",
tran_schema.as_bytes(),
),
])
.expect("Failed to compile schemas");
assert!(
schema
.get_element("core:_GenericApplicationPropertyOfCityObject")
.is_some(),
"core:_GenericApplicationPropertyOfCityObject should be found. Available elements: {:?}",
schema.elements.keys().collect::<Vec<_>>()
);
assert!(
schema.get_element("tran:class").is_some(),
"tran:class should be found. Available elements: {:?}",
schema.elements.keys().collect::<Vec<_>>()
);
assert!(
schema.get_element("tran:function").is_some(),
"tran:function should be found. Available elements: {:?}",
schema.elements.keys().collect::<Vec<_>>()
);
assert!(
schema.get_element("tran:lod1MultiSurface").is_some(),
"tran:lod1MultiSurface should be found. Available elements: {:?}",
schema.elements.keys().collect::<Vec<_>>()
);
let tran_class = schema.get_element("tran:class").unwrap();
assert_eq!(
tran_class.substitution_group.as_deref(),
Some("core:_GenericApplicationPropertyOfCityObject"),
"tran:class should have substitutionGroup=core:_GenericApplicationPropertyOfCityObject"
);
}
#[test]
fn test_elements_without_explicit_target_namespace_prefix() {
use fastxml::schema::xsd::parse_xsd_multiple;
let core_schema = 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:element name="_GenericApplicationPropertyOfCityObject" abstract="true"/>
</xs:schema>"#;
let tran_schema = 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/transportation/2.0"
elementFormDefault="qualified">
<xs:import namespace="http://www.opengis.net/citygml/2.0"/>
<xs:element name="class" type="xs:string"
substitutionGroup="core:_GenericApplicationPropertyOfCityObject"/>
<xs:element name="function" type="xs:string"
substitutionGroup="core:_GenericApplicationPropertyOfCityObject"/>
</xs:schema>"#;
let schema = parse_xsd_multiple(&[
(
"http://www.opengis.net/citygml/2.0/core.xsd",
core_schema.as_bytes(),
),
(
"http://www.opengis.net/citygml/transportation/2.0/transportation.xsd",
tran_schema.as_bytes(),
),
])
.expect("Failed to compile schemas");
eprintln!(
"Available elements: {:?}",
schema.elements.keys().collect::<Vec<_>>()
);
assert!(
schema.elements.contains_key("class"),
"class element is stored without prefix"
);
assert!(
schema.elements.contains_key("function"),
"function element is stored without prefix"
);
let tran_class_found = schema.get_element("tran:class").is_some();
eprintln!("get_element('tran:class') found: {}", tran_class_found);
assert!(
tran_class_found,
"tran:class should be found via local name fallback (stored as 'class')"
);
let by_ns = schema
.get_element_by_ns("http://www.opengis.net/citygml/transportation/2.0", "class")
.is_some();
assert!(
by_ns,
"get_element_by_ns should find via local name fallback"
);
}
#[test]
fn test_validator_finds_element_with_namespace_mismatch() {
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_schema = 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:element name="_GenericApplicationPropertyOfCityObject" abstract="true"/>
</xs:schema>"#;
let tran_schema = 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/transportation/2.0"
elementFormDefault="qualified">
<xs:import namespace="http://www.opengis.net/citygml/2.0"/>
<xs:element name="Road">
<xs:complexType>
<xs:sequence>
<xs:element name="class" type="xs:string" minOccurs="0"/>
<xs:element name="function" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="class" type="xs:string"
substitutionGroup="core:_GenericApplicationPropertyOfCityObject"/>
<xs:element name="function" type="xs:string"
substitutionGroup="core:_GenericApplicationPropertyOfCityObject"/>
</xs:schema>"#;
let schema = parse_xsd_multiple(&[
(
"http://www.opengis.net/citygml/2.0/core.xsd",
core_schema.as_bytes(),
),
(
"http://www.opengis.net/citygml/transportation/2.0/transportation.xsd",
tran_schema.as_bytes(),
),
])
.expect("Failed to compile schemas");
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("main_road".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"))
.collect();
eprintln!("Validation errors: {:?}", errors);
if !errors.is_empty() {
eprintln!(
"BUG CONFIRMED: {} 'not declared' errors found",
errors.len()
);
eprintln!("This confirms the namespace prefix mismatch issue");
}
}
#[test]
fn test_validator_fails_with_different_prefix_same_namespace() {
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_schema = 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:element name="_GenericApplicationPropertyOfCityObject" abstract="true"/>
</xs:schema>"#;
let tran_schema = 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"/>
<xs:element name="Road">
<xs:complexType>
<xs:sequence>
<xs:element name="class" type="xs:string" minOccurs="0"/>
<xs:element name="function" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="class" type="xs:string"
substitutionGroup="core:_GenericApplicationPropertyOfCityObject"/>
</xs:schema>"#;
let schema = parse_xsd_multiple(&[
(
"http://www.opengis.net/citygml/2.0/core.xsd",
core_schema.as_bytes(),
),
(
"http://www.opengis.net/citygml/transportation/2.0/transportation.xsd",
tran_schema.as_bytes(),
),
])
.expect("Failed to compile schemas");
eprintln!(
"Schema elements: {:?}",
schema.elements.keys().collect::<Vec<_>>()
);
assert!(
schema.elements.contains_key("tran:Road"),
"Road should be stored as tran:Road"
);
assert!(
schema.elements.contains_key("tran:class"),
"class should be stored as tran:class"
);
let mut validator = OnePassSchemaValidator::new(Arc::new(schema));
validator
.handle(&XmlEvent::StartElement {
name: "Road".into(),
prefix: Some("tr".into()), namespace: Some("http://www.opengis.net/citygml/transportation/2.0".into()),
attributes: vec![],
namespace_decls: vec![Namespace::new(
"tr", "http://www.opengis.net/citygml/transportation/2.0",
)],
line: Some(1),
column: Some(1),
})
.unwrap();
validator
.handle(&XmlEvent::StartElement {
name: "class".into(),
prefix: Some("tr".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("main_road".into()))
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "class".into(),
prefix: Some("tr".into()),
})
.unwrap();
validator
.handle(&XmlEvent::EndElement {
name: "Road".into(),
prefix: Some("tr".into()),
})
.unwrap();
validator.handle(&XmlEvent::Eof).unwrap();
validator.finish().unwrap();
let errors: Vec<_> = validator
.errors()
.iter()
.filter(|e| e.message.contains("not declared"))
.collect();
eprintln!("Validation errors: {:?}", errors);
assert!(
errors.is_empty(),
"Validator should match by namespace URI, not prefix. Errors: {:?}",
errors
);
}