From dbb9f0de4bed31a13fd91fda1d143d7292b518a8 Mon Sep 17 00:00:00 2001 From: Taku Fukada Date: Sun, 31 Dec 2023 11:57:54 +0900 Subject: [PATCH] citygml namespaces: add test, etc. --- nusamai-citygml/src/namespace.rs | 93 ++++++++++++++++++++++++++++- nusamai-citygml/src/parser.rs | 59 +++++++++--------- nusamai-plateau/src/codelist/xml.rs | 17 +++--- 3 files changed, 128 insertions(+), 41 deletions(-) diff --git a/nusamai-citygml/src/namespace.rs b/nusamai-citygml/src/namespace.rs index 0d358c51b..071b46477 100644 --- a/nusamai-citygml/src/namespace.rs +++ b/nusamai-citygml/src/namespace.rs @@ -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/"; @@ -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:", @@ -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:" } else { // PLATEAU 1.x match *name { @@ -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#" + + + + + + + + + + + + + + + + + + + + + + + + + + + + "#; + + 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), + } + } + } +} diff --git a/nusamai-citygml/src/parser.rs b/nusamai-citygml/src/parser.rs index 94f61a686..95b31fc0f 100644 --- a/nusamai-citygml/src/parser.rs +++ b/nusamai-citygml/src/parser.rs @@ -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 { @@ -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 { @@ -172,7 +171,7 @@ impl 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 @@ -215,7 +214,7 @@ impl 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" @@ -325,7 +324,7 @@ impl 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)?; } @@ -345,7 +344,7 @@ impl 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)?; } @@ -374,22 +373,22 @@ impl 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") => { 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!(), _ => { return Err(ParseError::SchemaViolation(format!( "Unexpected element <{}>", @@ -433,10 +432,10 @@ impl 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") => { self.parse_triangulated_surface()? } - (Bound(GML_NS), b"Tin") => self.parse_triangulated_surface()?, + (Bound(GML31_NS), b"Tin") => self.parse_triangulated_surface()?, _ => { return Err(ParseError::SchemaViolation(format!( "Unexpected element <{}>", @@ -469,7 +468,7 @@ impl 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)?; } @@ -480,7 +479,7 @@ impl SubTreeReader<'_, '_, R> { if expect_start( self.reader, &mut self.state.buf1, - GML_NS, + GML31_NS, b"trianglePatches", )? { self.parse_triangle_patch_array()?; @@ -495,7 +494,7 @@ impl 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()?, _ => { return Err(ParseError::SchemaViolation(format!( "Unexpected element <{}>", @@ -522,7 +521,7 @@ impl 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())), } } @@ -544,7 +543,7 @@ impl 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 <{}>", @@ -571,9 +570,9 @@ impl 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 @@ -607,7 +606,7 @@ impl 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") => { diff --git a/nusamai-plateau/src/codelist/xml.rs b/nusamai-plateau/src/codelist/xml.rs index 79a5367c1..7044f4fec 100644 --- a/nusamai-plateau/src/codelist/xml.rs +++ b/nusamai-plateau/src/codelist/xml.rs @@ -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 { @@ -66,11 +65,11 @@ fn parse_definition( 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; } @@ -122,20 +121,20 @@ pub fn parse_dictionary( 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!( " 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; }