Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

citygml: 名前空間周りの修正、テスト追加など #141

Merged
merged 8 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 81 additions & 26 deletions nusamai-citygml/macros/src/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DataEnum, DataStruct, DeriveInput, Error, LitByteStr, LitStr};

use crate::ElementType;
use crate::StereoType;

const CITYGML_ATTR_IDENT: &str = "citygml";

Expand Down Expand Up @@ -38,7 +38,8 @@ fn generate_citygml_impl_for_struct(
let mut id_value = quote!(None);
let struct_ident = &derive_input.ident;
let mut typename = String::from(stringify!(derive_input.ident));
let mut ty = ElementType::Feature;
let mut ty = StereoType::Feature;
let mut allow_extra = false;

for attr in &derive_input.attrs {
if !attr.path().is_ident(CITYGML_ATTR_IDENT) {
Expand All @@ -48,23 +49,30 @@ fn generate_citygml_impl_for_struct(
if meta.path.is_ident("name") {
let name: LitStr = meta.value()?.parse()?;
typename = name.value();
}
if meta.path.is_ident("type") {
Ok(())
} else if meta.path.is_ident("type") {
let ty_ident: Ident = meta.value()?.parse()?;
ty = match ty_ident.to_string().as_str() {
"feature" => ElementType::Feature,
"data" => ElementType::Data,
"feature" => StereoType::Feature,
"data" => StereoType::Data,
_ => {
return Err(meta.error("feature or data expected"));
}
};
Ok(())
} else if meta.path.is_ident("allow_extra") {
allow_extra = true;
Ok(())
} else {
Ok(())
}
Ok(())
})?;
}

// Scan struct fields
for field in &struct_data.fields {
let mut into_obj_generated = false;

let Some(field_ident) = &field.ident else {
continue;
};
Expand All @@ -75,9 +83,22 @@ fn generate_citygml_impl_for_struct(
continue;
}
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("path") {
if meta.path.is_ident("required") {
// TODO: required
Ok(())
}
else if meta.path.is_ident("codelist") {
// TODO: codelist
let _codelist: LitStr = meta.value()?.parse()?;
Ok(())
}
else if meta.path.is_ident("path") {
let path: LitByteStr = meta.value()?.parse()?;

if path.value().iter().filter(|c| c == &&b'/').count() > 1 {
return Err(meta.error("path must not contain more than one '/'"));
}

if path.value().starts_with(b"@") {
// XML attributes (e.g. @gml:id)
attribute_arms.push(quote! {
Expand All @@ -103,25 +124,39 @@ fn generate_citygml_impl_for_struct(
}
} else {
// XML child elements (e.g. bldg:measuredHeight)

// if the path contains '/', add the first path as a 'noop' arm.
if let Some(pos) = path.value().iter().position(|&x| x == b'/') {
let prefix = LitByteStr::new(&path.value()[..pos], path.span());
chlid_arms.push(
quote! {
#prefix => Ok(()),
}
);
};

chlid_arms.push(
quote! {
#path => <#field_ty as CityGMLElement>::parse(&mut self.#field_ident, st),
}
);

// Use the first path component as the attribute name
// e.g. "bldg:interiorRoom/bldg:Room" -> "bldg:interiorRoom"
let path_value = path.value();
let pos_slash = path_value.iter().position(|&x| x == b'/').unwrap_or(path_value.len());
let name = std::str::from_utf8(&path_value[..pos_slash]).unwrap();
if !into_obj_generated {
// Use the first path component as the attribute name
// e.g. "bldg:interiorRoom/bldg:Room" -> "bldg:interiorRoom"
let path_value = path.value();
let pos_slash = path_value.iter().position(|&x| x == b'/').unwrap_or(path_value.len());
let name = std::str::from_utf8(&path_value[..pos_slash]).unwrap();

into_object_stmts.push(
quote! {
if let Some(v) = self.#field_ident.into_object() {
attributes.insert(#name.into(), v);
into_object_stmts.push(
quote! {
if let Some(v) = self.#field_ident.into_object() {
attributes.insert(#name.into(), v);
}
}
}
)
);
into_obj_generated = true;
}
}
Ok(())
}
Expand Down Expand Up @@ -161,15 +196,26 @@ fn generate_citygml_impl_for_struct(
add_arm(2, b"lod2Geometry", "Geometry"); // only in CityGML 2.0
add_arm(3, b"lod3Geometry", "Geometry"); // only in CityGML 2.0
add_arm(4, b"lod4Geometry", "Geometry"); // only in CityGML 2.0
add_arm(1, b"tin", "Triangulated");
add_arm(0, b"tin", "Triangulated");

geom_into_object_expr = quote! {
Some(self.#field_ident)
};

Ok(())
} else if meta.path.is_ident("generics") {
chlid_arms.push(quote! {
b"gen:dateAttribute" => st.skip_current_element(),
b"gen:doubleAttribute" => st.skip_current_element(),
b"gen:genericAttributeSet" => st.skip_current_element(),
b"gen:intAttribute" => st.skip_current_element(),
b"gen:measureAttribute" => st.skip_current_element(),
b"gen:stringAttribute" => st.skip_current_element(),
b"gen:uriAttribute" => st.skip_current_element(),
});
Ok(())
} else {
Err(meta.error("unrecognized attribute"))
Err(meta.error("unrecognized argument"))
}
})?;
}
Expand All @@ -187,7 +233,7 @@ fn generate_citygml_impl_for_struct(
});

let into_object_impl = match ty {
ElementType::Feature => {
StereoType::Feature => {
quote! {
Some(::nusamai_citygml::object::Value::Feature(
::nusamai_citygml::object::Feature {
Expand All @@ -203,7 +249,7 @@ fn generate_citygml_impl_for_struct(
))
}
}
ElementType::Data => {
StereoType::Data => {
quote! {
Some(::nusamai_citygml::object::Value::Data(
::nusamai_citygml::object::Data {
Expand All @@ -221,11 +267,20 @@ fn generate_citygml_impl_for_struct(
};

let element_type = match ty {
ElementType::Feature => quote! { ::nusamai_citygml::ElementType::FeatureType },
ElementType::Data => quote! { ::nusamai_citygml::ElementType::DataType },
StereoType::Feature => quote! { ::nusamai_citygml::ElementType::FeatureType },
StereoType::Data => quote! { ::nusamai_citygml::ElementType::DataType },
_ => unreachable!(),
};

let extra_arm = match allow_extra {
true => quote! { Ok(()) },
false => quote! {
Err(::nusamai_citygml::ParseError::SchemaViolation(
format!("unexpected element: {}", String::from_utf8_lossy(st.current_path())),
))
},
};

Ok(quote! {
impl #impl_generics ::nusamai_citygml::CityGMLElement for #struct_ident #ty_generics #where_clause {
const ELEMENT_TYPE: ::nusamai_citygml::ElementType = #element_type;
Expand All @@ -236,7 +291,7 @@ fn generate_citygml_impl_for_struct(
st.parse_children(|st| {
match st.current_path() {
#(#chlid_arms)*
_ => Ok(()),
_ => #extra_arm,
}
})
}
Expand Down
10 changes: 6 additions & 4 deletions nusamai-citygml/macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Parsing utilities for CityGML 2.0 (and possibly 3.0)

extern crate proc_macro;

mod derive;
Expand All @@ -12,21 +14,21 @@ pub fn derive_citygml_element(token: TokenStream) -> TokenStream {

#[proc_macro_attribute]
pub fn citygml_feature(args: TokenStream, input: TokenStream) -> TokenStream {
type_attrs::citygml_type(ElementType::Feature, args, input)
type_attrs::citygml_type(StereoType::Feature, args, input)
}

#[proc_macro_attribute]
pub fn citygml_data(args: TokenStream, input: TokenStream) -> TokenStream {
type_attrs::citygml_type(ElementType::Data, args, input)
type_attrs::citygml_type(StereoType::Data, args, input)
}

#[proc_macro_attribute]
pub fn citygml_property(args: TokenStream, input: TokenStream) -> TokenStream {
type_attrs::citygml_type(ElementType::Property, args, input)
type_attrs::citygml_type(StereoType::Property, args, input)
}

#[derive(Clone, Copy)]
pub(crate) enum ElementType {
pub(crate) enum StereoType {
Feature,
Data,
Property,
Expand Down
48 changes: 30 additions & 18 deletions nusamai-citygml/macros/src/type_attrs.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::ElementType;
use crate::StereoType;
use proc_macro2::TokenStream;
use quote::quote;
use syn::meta::ParseNestedMeta;
Expand All @@ -17,7 +17,11 @@ impl FeatureArgs {
if meta.path.is_ident("name") {
let s: LitStr = meta.value()?.parse()?;
self.prefix = Some(LitByteStr::new(
s.value().split_once(':').unwrap().0.as_bytes(),
s.value()
.split_once(':')
.ok_or_else(|| meta.error("ns prefix is missing"))?
.0
.as_bytes(),
s.span(),
));
self.name = Some(s);
Expand All @@ -29,7 +33,7 @@ impl FeatureArgs {
}

pub(crate) fn citygml_type(
ty: ElementType,
ty: StereoType,
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
Expand Down Expand Up @@ -57,7 +61,7 @@ fn add_named_field(fields: &mut syn::FieldsNamed, body: TokenStream) {
.push(syn::Field::parse_named.parse2(body).unwrap())
}

fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Result<(), Error> {
fn modify(ty: &StereoType, args: &FeatureArgs, input: &mut DeriveInput) -> Result<(), Error> {
match &args.name {
Some(name) => {
input.attrs.push(syn::parse_quote! {
Expand All @@ -68,13 +72,13 @@ fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Resu
};

input.attrs.push(match &ty {
ElementType::Feature => {
StereoType::Feature => {
syn::parse_quote! { #[citygml(type = feature)] }
}
ElementType::Data => {
StereoType::Data => {
syn::parse_quote! { #[citygml(type = data)] }
}
ElementType::Property => {
StereoType::Property => {
syn::parse_quote! { #[citygml(type = property)] }
}
});
Expand All @@ -84,12 +88,12 @@ fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Resu
// for #[citygml_feature] and #[citygml_data]

match ty {
ElementType::Feature | ElementType::Data => {}
StereoType::Feature | StereoType::Data => {}
_ => return Err(Error::new_spanned(input, "target must be struct")),
}

if let syn::Fields::Named(ref mut fields) = data.fields {
if let ElementType::Feature = ty {
if let StereoType::Feature = ty {
// for #[citygml_feature]

let prefix = args.prefix.as_ref().unwrap();
Expand Down Expand Up @@ -118,50 +122,58 @@ fn modify(ty: &ElementType, args: &FeatureArgs, input: &mut DeriveInput) -> Resu
add_named_field(
fields,
quote! {
#[citygml(path = b"gml:name")]
pub name: Vec<String>
#[citygml(path = b"gml:description")]
pub description: Option<String>
},
);
add_named_field(
fields,
quote! {
#[citygml(path = b"gml:description")]
pub description: Option<String>
#[citygml(path = b"gml:name")]
pub name: Vec<String>
},
);
add_named_field(
fields,
quote! {
#[citygml(path = b"gml:creationDate")]
#[citygml(path = b"core:creationDate")]
pub creation_date: Option<nusamai_citygml::Date> // TODO: DateTime (CityGML 3.0)
},
);
add_named_field(
fields,
quote! {
#[citygml(path = b"gml:terminationDate")]
#[citygml(path = b"core:terminationDate")]
pub termination_date: Option<nusamai_citygml::Date> // TODO: DateTime (CityGML 3.0)
},
);
add_named_field(
fields,
quote! {
#[citygml(path = b"gml:validFrom")]
#[citygml(path = b"core:validFrom")]
pub valid_from: Option<nusamai_citygml::Date> // TODO: DateTime (CityGML 3.0)
},
);
add_named_field(
fields,
quote! {
#[citygml(path = b"gml:validTo")]
#[citygml(path = b"core:validTo")]
pub valid_to: Option<nusamai_citygml::Date> // TODO: DateTime (CityGML 3.0)
},
);
// TODO: not implemented yet
add_named_field(
fields,
quote! {
#[citygml(generics)]
pub generic_attribute: Option<i32> // FIXME:
},
);
}
}
}
Data::Enum(_data) => match ty {
ElementType::Property => {
StereoType::Property => {
// for #[citygml_property]
_data.variants.push(parse_quote! {
#[default]
Expand Down
19 changes: 19 additions & 0 deletions nusamai-citygml/src/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::parser::ParseError;

pub trait CityGMLAttribute: Sized {
fn parse_attr_value(value: &str) -> Result<Self, ParseError>;
}

impl CityGMLAttribute for String {
#[inline]
fn parse_attr_value(value: &str) -> Result<Self, ParseError> {
Ok(value.to_string())
}
}

impl<T: CityGMLAttribute> CityGMLAttribute for Option<T> {
#[inline]
fn parse_attr_value(value: &str) -> Result<Self, ParseError> {
Ok(Some(<T as CityGMLAttribute>::parse_attr_value(value)?))
}
}
Loading