Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions xsd-parser-types/src/xml/any_simple_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ fn parse_base64_binary(bytes: &str) -> Result<Base64Binary, Error> {

#[inline]
#[cfg(not(feature = "base64"))]
#[allow(clippy::unnecessary_wraps)]
fn parse_base64_binary(s: &str) -> Result<Base64Binary, Error> {
Ok(s.to_string())
}
Expand Down
1 change: 1 addition & 0 deletions xsd-parser/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ This directory contains different examples that show some features of `xsd-parse
- `simple` - Probably the most simple example, at least, a starting point to understand `xsd-parser` or start more complex applications.
- `update_schema` - It demonstrates a more advanced use of the generator to create types from the schema with deserialization support enabled.
- `custom_names` - Short example that shows how to assign custom defined names to the generated types.
- `custom_naming` - Self-contained example that shows how to plug a custom `Naming` implementation into `Config::with_naming` to avoid name collisions.
- `custom_variants` - Similar to `custom_names`, this example shows how to assign custom names for enumeration variants.
- `custom_render_step` - Example that explains how to implement user defined render steps. This is useful if you want to generate additional structures from the already parsed and interpreted schema.
20 changes: 20 additions & 0 deletions xsd-parser/examples/custom-naming.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="String..1">
<xs:restriction base="xs:string">
<xs:maxLength value="1" />
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="String1">
<xs:restriction base="xs:string">
<xs:length value="1" />
</xs:restriction>
</xs:simpleType>

<xs:complexType name="Document">
<xs:sequence>
<xs:element name="type" type="String..1" />
<xs:element name="value" type="String1" />
</xs:sequence>
</xs:complexType>
</xs:schema>
105 changes: 105 additions & 0 deletions xsd-parser/examples/custom_naming.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
//! Example that shows how to implement a custom naming strategy using
//! [`Config::with_naming`] with a custom [`Naming`] implementation.
//!
//! The default naming strategy normalizes names into `PascalCase`, which means an
//! XSD type like `String..1` can collapse into the same Rust type name as
//! `String1`. This example keeps separators during `unify`, so both schema
//! names stay distinct while the final identifier formatting is still delegated
//! to the library helpers.

#![allow(missing_docs)]

use std::sync::{atomic::AtomicUsize, Arc};

use anyhow::{ensure, Error};
use inflector::Inflector;
use xsd_parser::{
config::Schema,
generate,
models::{
format_ident, format_unknown_variant, make_type_name, meta::MetaType,
NameBuilder as DefaultNameBuilder,
},
traits::{NameBuilder, Naming},
Config, Ident2, IdentType, Name, TypeIdent,
};

fn main() -> Result<(), Error> {
let mut config = Config::default();
config.parser.schemas = vec![Schema::named_schema(
"custom-naming.xsd",
include_str!("custom-naming.xsd"),
)];
config = config
.with_naming(PreserveSeparatorsNaming::default())
.with_generate([(IdentType::Type, "Document")]);

let code = generate(config)?.to_string();

ensure!(
code.contains("String__1Type"),
"expected generated code to keep `String..1` distinct"
);
ensure!(
code.contains("String1Type"),
"expected generated code to keep `String1` available as its own type"
);
ensure!(
code.contains("type_"),
"expected keyword field names to still be formatted into valid Rust identifiers"
);

println!("{code}");

Ok(())
}

#[derive(Debug, Clone, Default)]
struct PreserveSeparatorsNaming(Arc<AtomicUsize>);

impl Naming for PreserveSeparatorsNaming {
fn clone_boxed(&self) -> Box<dyn Naming> {
Box::new(self.clone())
}

fn builder(&self) -> Box<dyn NameBuilder> {
Box::new(DefaultNameBuilder::new(
self.0.clone(),
Box::new(self.clone()),
))
}

fn unify(&self, s: &str) -> String {
s.chars()
.map(|ch| if ch.is_alphanumeric() { ch } else { '_' })
.collect()
}

fn make_type_name(&self, postfixes: &[String], ty: &MetaType, ident: &TypeIdent) -> Name {
make_type_name(self, postfixes, ty, ident)
}

fn make_unknown_variant(&self, id: usize) -> Ident2 {
format_unknown_variant(id)
}

fn format_module_name(&self, s: &str) -> String {
format_ident(self.unify(s).to_snake_case())
}

fn format_type_name(&self, s: &str) -> String {
format_ident(self.unify(s))
}

fn format_field_name(&self, s: &str) -> String {
format_ident(self.unify(s).to_snake_case())
}

fn format_variant_name(&self, s: &str) -> String {
format_ident(self.unify(s))
}

fn format_constant_name(&self, s: &str) -> String {
format_ident(self.unify(s).to_screaming_snake_case())
}
}
4 changes: 2 additions & 2 deletions xsd-parser/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ pub use self::ident::{
pub use self::ident_cache::IdentCache;
pub use self::name::Name;
pub use self::naming::{
format_ident, format_unknown_variant, unify_string, ExplicitNameBuilder, ExplicitNaming,
NameBuilder, Naming,
format_ident, format_unknown_variant, make_type_name, unify_string, ExplicitNameBuilder,
ExplicitNaming, NameBuilder, Naming,
};
32 changes: 3 additions & 29 deletions xsd-parser/src/models/naming/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ use std::sync::{
use inflector::Inflector;
use proc_macro2::Ident as Ident2;

use crate::models::{
meta::{MetaType, MetaTypeVariant},
schema::MaxOccurs,
TypeIdent,
};
use crate::models::{meta::MetaType, TypeIdent};
use crate::traits::{NameBuilder as NameBuilderTrait, Naming as NamingTrait};

use super::Name;
Expand All @@ -29,37 +25,15 @@ impl NamingTrait for Naming {
}

fn builder(&self) -> Box<dyn NameBuilderTrait> {
Box::new(NameBuilder::new(self.0.clone(), self.clone_boxed()))
Box::new(NameBuilder::new(self.0.clone(), Box::new(self.clone())))
}

fn unify(&self, s: &str) -> String {
super::unify_string(s)
}

fn make_type_name(&self, postfixes: &[String], ty: &MetaType, ident: &TypeIdent) -> Name {
if let MetaTypeVariant::Reference(ti) = &ty.variant {
if ident.name.is_generated() && ti.type_.name.is_named() {
let s = self.format_type_name(ti.type_.name.as_str());

if ti.max_occurs > MaxOccurs::Bounded(1) {
return Name::new_generated(format!("{s}List"));
} else if ti.min_occurs == 0 {
return Name::new_generated(format!("{s}Opt"));
}
}
}

let postfix = postfixes
.get(ident.type_ as usize)
.map_or("", |s| s.as_str());

let s = self.format_type_name(ident.name.as_str());

if s.ends_with(postfix) {
ident.name.clone()
} else {
Name::new_generated(format!("{s}{postfix}"))
}
super::make_type_name(self, postfixes, ty, ident)
}

fn make_unknown_variant(&self, id: usize) -> Ident2 {
Expand Down
35 changes: 35 additions & 0 deletions xsd-parser/src/models/naming/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,46 @@ use inflector::Inflector;
use proc_macro2::Ident as Ident2;
use quote::format_ident;

use crate::config::MetaType;
use crate::models::meta::MetaTypeVariant;
use crate::models::schema::MaxOccurs;
use crate::TypeIdent;

use super::Name;

pub use self::default::{NameBuilder, Naming};
pub use self::explicit::{ExplicitNameBuilder, ExplicitNaming};

/// Make a type name based on the passed `ident` and `ty` and the provided `postfixes`.
pub fn make_type_name<X>(naming: &X, postfixes: &[String], ty: &MetaType, ident: &TypeIdent) -> Name
where
X: crate::traits::Naming,
{
if let MetaTypeVariant::Reference(ti) = &ty.variant {
if ident.name.is_generated() && ti.type_.name.is_named() {
let s = naming.format_type_name(ti.type_.name.as_str());

if ti.max_occurs > MaxOccurs::Bounded(1) {
return Name::new_generated(format!("{s}List"));
} else if ti.min_occurs == 0 {
return Name::new_generated(format!("{s}Opt"));
}
}
}

let postfix = postfixes
.get(ident.type_ as usize)
.map_or("", |s| s.as_str());

let s = naming.format_type_name(ident.name.as_str());

if s.ends_with(postfix) {
ident.name.clone()
} else {
Name::new_generated(format!("{s}{postfix}"))
}
}

/// Unify the passed string `s` into a standard format.
#[must_use]
pub fn unify_string(s: &str) -> String {
Expand Down
Loading