Skip to content

Commit

Permalink
citygml namespaces: add test, etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
ciscorn committed Dec 31, 2023
1 parent b8e2f3d commit dbb9f0d
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 41 deletions.
93 changes: 91 additions & 2 deletions nusamai-citygml/src/namespace.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use quick_xml::name::{Namespace, ResolveResult};

/// Normalize a XML namaespace URI to a well-known prefix.
pub const GML31_NS: Namespace = Namespace(b"http://www.opengis.net/gml");

/// Normalizes `quick_xml::name::ResolveResult` to the well-known prefix.
///
/// e.g. `"http://www.opengis.net/citygml/2.0"` -> `"core:"`
#[inline]
pub fn normalize_ns_prefix<'a>(ns: &ResolveResult<'a>) -> &'a [u8] {
pub fn wellknown_prefix_from_nsres<'a>(ns: &ResolveResult<'a>) -> &'a [u8] {
match ns {
ResolveResult::Bound(Namespace(name)) => {
const OPENGIS_PREFIX: &[u8] = b"http://www.opengis.net/";
Expand All @@ -27,6 +29,7 @@ pub fn normalize_ns_prefix<'a>(ns: &ResolveResult<'a>) -> &'a [u8] {
b"cityobjectgroup/2.0" => b"grp:",
b"landuse/2.0" => b"luse:",
b"transportation/2.0" => b"tran:",
b"texturedsurface/2.0" => b"tex:", // deprecated
b"vegetation/2.0" => b"veg:",
b"waterbody/2.0" => b"wtr:",
b"tunnel/2.0" => b"tun:",
Expand All @@ -46,6 +49,12 @@ pub fn normalize_ns_prefix<'a>(ns: &ResolveResult<'a>) -> &'a [u8] {
b"urf/2.0" => b"urf:",
_ => b"unsupported:",
}
} else if name == b"urn:oasis:names:tc:ciq:xsdschema:xAL:2.0" {
// OASIS xAL 2.0
b"xAL:"
} else if name == b"http://www.w3.org/1999/xlink" {
// xlink
b"xlink:"

Check warning on line 57 in nusamai-citygml/src/namespace.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/src/namespace.rs#L57

Added line #L57 was not covered by tests
} else {
// PLATEAU 1.x
match *name {
Expand All @@ -65,3 +74,83 @@ pub fn normalize_ns_prefix<'a>(ns: &ResolveResult<'a>) -> &'a [u8] {
ResolveResult::Unknown(_name) => b"unknown:",
}
}

#[cfg(test)]
mod tests {
#[test]
fn normalized_prefix() {
use super::*;
use quick_xml::{events::Event, NsReader};

let data = r#"
<?xml version="1.0" encoding="UTF-8"?>
<core2ns:core
xmlns:gml31ns="http://www.opengis.net/gml"
xmlns:core2ns="http://www.opengis.net/citygml/2.0"
xmlns:grp2ns="http://www.opengis.net/citygml/cityobjectgroup/2.0"
xmlns:bldg2ns="http://www.opengis.net/citygml/building/2.0"
xmlns:brid2ns="http://www.opengis.net/citygml/bridge/2.0"
xmlns:tran2ns="http://www.opengis.net/citygml/transportation/2.0"
xmlns:frn2ns="http://www.opengis.net/citygml/cityfurniture/2.0"
xmlns:wtr2ns="http://www.opengis.net/citygml/waterbody/2.0"
xmlns:veg2ns="http://www.opengis.net/citygml/vegetation/2.0"
xmlns:tun2ns="http://www.opengis.net/citygml/tunnel/2.0"
xmlns:tex2ns="http://www.opengis.net/citygml/texturedsurface/2.0"
xmlns:app2ns="http://www.opengis.net/citygml/appearance/2.0"
xmlns:gen2ns="http://www.opengis.net/citygml/generics/2.0"
xmlns:dem2ns="http://www.opengis.net/citygml/relief/2.0"
xmlns:luse2ns="http://www.opengis.net/citygml/landuse/2.0"
xmlns:uro3ns="https://www.geospatial.jp/iur/uro/3.0"
xmlns:urf3ns="https://www.geospatial.jp/iur/urf/3.0"
xmlns:uro2ns="https://www.geospatial.jp/iur/uro/2.0"
xmlns:urf2ns="https://www.geospatial.jp/iur/urf/3.0"
xmlns:uro15ns="https://www.chisou.go.jp/tiiki/toshisaisei/itoshisaisei/iur/uro/1.5"
xmlns:urf15ns="https://www.chisou.go.jp/tiiki/toshisaisei/itoshisaisei/iur/urf/1.5"
xmlns:xAL2ns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0"
xmlns:xlinkns="http://www.w3.org/1999/xlink"
>
<!-- namespace_prefix:wellknown_prefix -->
<foobar:unknown />
<gml31ns:gml />
<core2ns:core />
<grp2ns:grp />
<bldg2ns:bldg />
<brid2ns:brid />
<tran2ns:tran />
<frn2ns:frn />
<wtr2ns:wtr />
<veg2ns:veg />
<tun2ns:tun />
<tex2ns:tex />
<app2ns:app />
<gen2ns:gen />
<dem2ns:dem />
<luse2ns:luse />
<uro3ns:uro />
<urf3ns:urf />
<uro2ns:uro />
<urf2ns:urf />
<uro15ns:uro />
<urf15ns:urf />
<xAL2ns:xAL />
</core2ns:core>
"#;

let mut reader = NsReader::from_str(data);
reader.trim_text(true);
reader.expand_empty_elements(true);
loop {
match reader.read_resolved_event() {
Ok((ns, Event::Start(ref e))) => {
let wellknown = std::str::from_utf8(wellknown_prefix_from_nsres(&ns)).unwrap();
let localname = e.local_name();
let expected = String::from_utf8_lossy(localname.as_ref()) + ":";
assert_eq!(wellknown, expected);
}
Ok((_, Event::Eof)) => break,
Ok(_) => {}
Err(e) => panic!("{:?}", e),

Check warning on line 152 in nusamai-citygml/src/namespace.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/src/namespace.rs#L152

Added line #L152 was not covered by tests
}
}
}
}
59 changes: 29 additions & 30 deletions nusamai-citygml/src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
use crate::codelist::{self, CodeResolver};
use std::io::BufRead;
use std::str;
use url::Url;

use crate::geometry::{
Geometries, GeometryCollector, GeometryParseType, GeometryRef, GeometryRefEntry, GeometryType,
};
use crate::namespace::normalize_ns_prefix;
use quick_xml::events::{BytesStart, Event};
use quick_xml::name::Namespace;
use quick_xml::name::ResolveResult::Bound;
use quick_xml::NsReader;
use thiserror::Error;
use url::Url;

const GML_NS: Namespace = Namespace(b"http://www.opengis.net/gml");
use crate::codelist::{self, CodeResolver};
use crate::geometry::{
Geometries, GeometryCollector, GeometryParseType, GeometryRef, GeometryRefEntry, GeometryType,
};
use crate::namespace::{wellknown_prefix_from_nsres, GML31_NS};

#[derive(Error, Debug)]
pub enum ParseError {
Expand Down Expand Up @@ -122,7 +121,7 @@ impl<'a> CityGMLReader<'a> {
let (nsres, localname) = reader.resolve_element(start.name());
state.path_stack_indices.push(state.path_buf.len());
state.path_buf.push(b'/');
state.path_buf.extend(normalize_ns_prefix(&nsres));
state.path_buf.extend(wellknown_prefix_from_nsres(&nsres));
state.path_buf.extend(localname.as_ref());
state.current_start = Some(start.into_owned());
return Ok(SubTreeReader {
Expand Down Expand Up @@ -172,7 +171,7 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
match self.reader.read_event_into(&mut self.state.buf1) {
Ok(Event::Start(start)) => {
let (nsres, localname) = self.reader.resolve_element(start.name());
let ns = normalize_ns_prefix(&nsres);
let ns = wellknown_prefix_from_nsres(&nsres);

// Append "/{ns_prefix}:{localname}" to the path stack
self.state
Expand Down Expand Up @@ -215,7 +214,7 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
self.state.buf1.push(b'@');
for attr in start.attributes().flatten() {
let (nsres, localname) = self.reader.resolve_attribute(attr.key);
self.state.buf1.extend(normalize_ns_prefix(&nsres));
self.state.buf1.extend(wellknown_prefix_from_nsres(&nsres));
self.state.buf1.extend(localname.as_ref());
logic(
self.state.buf1.as_ref(), // attribute path "@nsprefix:name"
Expand Down Expand Up @@ -325,7 +324,7 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
) -> Result<(), ParseError> {
let poly_begin = self.state.geometry_collector.multipolygon.len();

if expect_start(self.reader, &mut self.state.buf1, GML_NS, b"MultiSurface")? {
if expect_start(self.reader, &mut self.state.buf1, GML31_NS, b"MultiSurface")? {
self.parse_multi_surface()?;
expect_end(self.reader, &mut self.state.buf1)?;
}
Expand All @@ -345,7 +344,7 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
fn parse_solid_prop(&mut self, geomrefs: &mut GeometryRef, lod: u8) -> Result<(), ParseError> {
let poly_begin = self.state.geometry_collector.multipolygon.len();

if expect_start(self.reader, &mut self.state.buf1, GML_NS, b"Solid")? {
if expect_start(self.reader, &mut self.state.buf1, GML31_NS, b"Solid")? {
self.parse_solid()?;
expect_end(self.reader, &mut self.state.buf1)?;
}
Expand Down Expand Up @@ -374,22 +373,22 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
let poly_begin = self.state.geometry_collector.multipolygon.len();

let geomtype = match (nsres, localname.as_ref()) {
(Bound(GML_NS), b"Solid") => {
(Bound(GML31_NS), b"Solid") => {
self.parse_solid()?;
GeometryType::Solid
}
(Bound(GML_NS), b"MultiSurface") => {
(Bound(GML31_NS), b"MultiSurface") => {
self.parse_multi_surface()?;
GeometryType::Surface
}
(Bound(GML_NS), b"CompositeSurface") => {
(Bound(GML31_NS), b"CompositeSurface") => {

Check warning on line 384 in nusamai-citygml/src/parser.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/src/parser.rs#L384

Added line #L384 was not covered by tests
self.parse_composite_surface()?;
GeometryType::Surface
}
(Bound(GML_NS), b"OrientableSurface") => todo!(),
(Bound(GML_NS), b"Polygon") => todo!(),
(Bound(GML_NS), b"TriangulatedSurface") => todo!(),
(Bound(GML_NS), b"Tin") => todo!(),
(Bound(GML31_NS), b"OrientableSurface") => todo!(),
(Bound(GML31_NS), b"Polygon") => todo!(),
(Bound(GML31_NS), b"TriangulatedSurface") => todo!(),
(Bound(GML31_NS), b"Tin") => todo!(),

Check warning on line 391 in nusamai-citygml/src/parser.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/src/parser.rs#L388-L391

Added lines #L388 - L391 were not covered by tests
_ => {
return Err(ParseError::SchemaViolation(format!(
"Unexpected element <{}>",
Expand Down Expand Up @@ -433,10 +432,10 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
Ok(Event::Start(start)) => {
let (nsres, localname) = self.reader.resolve_element(start.name());
match (nsres, localname.as_ref()) {
(Bound(GML_NS), b"TriangulatedSurface") => {
(Bound(GML31_NS), b"TriangulatedSurface") => {

Check warning on line 435 in nusamai-citygml/src/parser.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/src/parser.rs#L435

Added line #L435 was not covered by tests
self.parse_triangulated_surface()?
}
(Bound(GML_NS), b"Tin") => self.parse_triangulated_surface()?,
(Bound(GML31_NS), b"Tin") => self.parse_triangulated_surface()?,

Check warning on line 438 in nusamai-citygml/src/parser.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/src/parser.rs#L438

Added line #L438 was not covered by tests
_ => {
return Err(ParseError::SchemaViolation(format!(
"Unexpected element <{}>",
Expand Down Expand Up @@ -469,7 +468,7 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
}

fn parse_solid(&mut self) -> Result<(), ParseError> {
if expect_start(self.reader, &mut self.state.buf1, GML_NS, b"exterior")? {
if expect_start(self.reader, &mut self.state.buf1, GML31_NS, b"exterior")? {
self.parse_surface_prop()?;
expect_end(self.reader, &mut self.state.buf1)?;
}
Expand All @@ -480,7 +479,7 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
if expect_start(
self.reader,
&mut self.state.buf1,
GML_NS,
GML31_NS,

Check warning on line 482 in nusamai-citygml/src/parser.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/src/parser.rs#L482

Added line #L482 was not covered by tests
b"trianglePatches",
)? {
self.parse_triangle_patch_array()?;
Expand All @@ -495,7 +494,7 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
Ok(Event::Start(start)) => {
let (nsres, localname) = self.reader.resolve_element(start.name());
match (nsres, localname.as_ref()) {
(Bound(GML_NS), b"Triangle") => self.parse_polygon()?,
(Bound(GML31_NS), b"Triangle") => self.parse_polygon()?,

Check warning on line 497 in nusamai-citygml/src/parser.rs

View check run for this annotation

Codecov / codecov/patch

nusamai-citygml/src/parser.rs#L497

Added line #L497 was not covered by tests
_ => {
return Err(ParseError::SchemaViolation(format!(
"Unexpected element <{}>",
Expand All @@ -522,7 +521,7 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
Ok(Event::Start(start)) => {
let (nsres, localname) = self.reader.resolve_element(start.name());
match (nsres, localname.as_ref()) {
(Bound(GML_NS), b"surfaceMember") => self.parse_surface_prop()?,
(Bound(GML31_NS), b"surfaceMember") => self.parse_surface_prop()?,
_ => return Err(ParseError::SchemaViolation("Unexpected element".into())),
}
}
Expand All @@ -544,7 +543,7 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
Ok(Event::Start(start)) => {
let (nsres, localname) = self.reader.resolve_element(start.name());
match (nsres, localname.as_ref()) {
(Bound(GML_NS), b"surfaceMember") => self.parse_surface_prop()?,
(Bound(GML31_NS), b"surfaceMember") => self.parse_surface_prop()?,
_ => {
return Err(ParseError::SchemaViolation(format!(
"Unexpected element <{}>",
Expand All @@ -571,9 +570,9 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
Ok(Event::Start(start)) => {
let (nsres, localname) = self.reader.resolve_element(start.name());
match (nsres, localname.as_ref()) {
(Bound(GML_NS), b"Polygon") => self.parse_polygon()?,
(Bound(GML_NS), b"CompositeSurface") => self.parse_composite_surface()?,
(Bound(GML_NS), b"OrientableSurface") => {
(Bound(GML31_NS), b"Polygon") => self.parse_polygon()?,
(Bound(GML31_NS), b"CompositeSurface") => self.parse_composite_surface()?,
(Bound(GML31_NS), b"OrientableSurface") => {
// TODO: OrientableSurface
println!("OrientableSurface is not supported");
self.reader
Expand Down Expand Up @@ -607,7 +606,7 @@ impl<R: BufRead> SubTreeReader<'_, '_, R> {
let mut is_exterior = true;
loop {
match self.reader.read_resolved_event_into(&mut self.state.buf1) {
Ok((Bound(GML_NS), Event::Start(start))) => {
Ok((Bound(GML31_NS), Event::Start(start))) => {
depth += 1;
match (depth, start.local_name().as_ref()) {
(2, b"exterior") => {
Expand Down
17 changes: 8 additions & 9 deletions nusamai-plateau/src/codelist/xml.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use std::collections::HashMap;
use std::io::BufRead;

use nusamai_citygml::ParseError;
use quick_xml::events::Event;
use quick_xml::name::Namespace;
use quick_xml::name::ResolveResult::Bound;

const GML_NS: Namespace = Namespace(b"http://www.opengis.net/gml");
use nusamai_citygml::namespace::GML31_NS;
use nusamai_citygml::ParseError;

#[derive(Debug)]
pub struct Definition {
Expand Down Expand Up @@ -66,11 +65,11 @@ fn parse_definition<R: BufRead>(
depth += 1;
let (nsres, localname) = reader.resolve_element(start.name());
match (depth, nsres, localname.as_ref()) {
(2, Bound(GML_NS), b"name") => {
(2, Bound(GML31_NS), b"name") => {
identifier = Some(expect_text(reader, buf)?);
depth -= 1;
}
(2, Bound(GML_NS), b"description") => {
(2, Bound(GML31_NS), b"description") => {
value = Some(expect_text(reader, buf)?);
depth -= 1;
}
Expand Down Expand Up @@ -122,20 +121,20 @@ pub fn parse_dictionary<R: BufRead>(
depth += 1;
let (nsres, localname) = reader.resolve_element(start.name());
match (depth, nsres, localname.as_ref()) {
(1, Bound(GML_NS), b"Dictionary") => {}
(1, Bound(GML31_NS), b"Dictionary") => {}
(1, _, _) => {
return Err(ParseError::SchemaViolation(format!(
"<Dictionary> is expected, but found {}",
String::from_utf8_lossy(localname.as_ref())
)))
}
(2, Bound(GML_NS), b"name") => {
(2, Bound(GML31_NS), b"name") => {
// Just ignore it for now.
let _name = expect_text(&mut reader, &mut buf)?;
depth -= 1;
}
(2, Bound(GML_NS), b"dictionaryEntry") => {}
(3, Bound(GML_NS), b"Definition") => {
(2, Bound(GML31_NS), b"dictionaryEntry") => {}
(3, Bound(GML31_NS), b"Definition") => {
parse_definition(&mut reader, &mut definitions, &mut buf, &mut buf2)?;
depth -= 1;
}
Expand Down

0 comments on commit dbb9f0d

Please sign in to comment.