Skip to content

Commit

Permalink
refactor!: remove DynElement
Browse files Browse the repository at this point in the history
  • Loading branch information
decahedron1 committed Nov 10, 2024
1 parent 88e9252 commit 1a38b32
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 269 deletions.
4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ edition = "2021"
repository = "https://github.com/pykeio/ssml"

[dependencies]
dyn-clone = "1.0"
serde = { version = "1.0", optional = true, default-features = false, features = [ "alloc", "derive" ] }
erased-serde = { version = "0.4", optional = true, default-features = false, features = [ "alloc" ] }

[features]
default = [ "std" ]
std = []
serde = [ "dep:serde", "dep:erased-serde" ]
serde = [ "dep:serde" ]
300 changes: 65 additions & 235 deletions src/element.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
use alloc::{borrow::Cow, string::ToString, vec::Vec};
use core::fmt::{Debug, Write};

use dyn_clone::DynClone;

use crate::{Audio, Break, Emphasis, Mark, Meta, Serialize, SerializeOptions, Text, Voice, XmlWriter};
use crate::{Audio, Break, Emphasis, Mark, Meta, Serialize, SerializeOptions, Text, Voice, XmlWriter, util};

macro_rules! el {
(
Expand Down Expand Up @@ -43,7 +41,7 @@ pub(crate) use el;
el! {
/// Represents all SSML elements.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Element<'s> {
Text(Text<'s>),
Expand All @@ -54,9 +52,7 @@ el! {
Emphasis(Emphasis<'s>),
Mark(Mark<'s>),
FlavorMSTTS(crate::mstts::Element<'s>),
/// A dyn element can be used to implement your own custom elements outside of the `ssml` crate. See
/// [`DynElement`] for more information and examples.
Dyn(Box<dyn DynElement>)
Custom(CustomElement<'s>)
// Lang(LangElement),
// Paragraph(ParagraphElement),
// Phoneme(PhonemeElement),
Expand All @@ -82,254 +78,88 @@ impl<'s> Element<'s> {
Self::Break(el) => Element::Break(el),
Self::Emphasis(el) => Element::Emphasis(el.into_owned()),
Self::Mark(el) => Element::Mark(el.into_owned()),
Self::Custom(el) => Element::Custom(el.into_owned()),
_ => panic!()
}
}
}

#[cfg(feature = "serde")]
impl<'s, 'a> serde::Deserialize<'a> for Element<'s> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'a>
{
use alloc::string::String;
use core::{fmt, marker::PhantomData};

#[allow(non_camel_case_types)]
enum ElementField {
Text,
Audio,
Voice,
Meta,
Break,
Emphasis,
Mark
}

struct ElementFieldVisitor;

impl<'de> serde::de::Visitor<'de> for ElementFieldVisitor {
type Value = ElementField;

fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("variant identifier")
}

fn visit_u64<__E>(self, val: u64) -> serde::__private::Result<Self::Value, __E>
where
__E: serde::de::Error
{
match val {
0u64 => Ok(ElementField::Text),
1u64 => Ok(ElementField::Audio),
2u64 => Ok(ElementField::Voice),
3u64 => Ok(ElementField::Meta),
4u64 => Ok(ElementField::Break),
5u64 => Ok(ElementField::Emphasis),
6u64 => Ok(ElementField::Mark),
7u64 => Err(serde::de::Error::custom("DynElements cannot be deserialized")),
_ => Err(serde::de::Error::invalid_value(serde::de::Unexpected::Unsigned(val), &"variant index 0 <= i < 8"))
}
}

fn visit_str<E>(self, val: &str) -> Result<Self::Value, E>
where
E: serde::de::Error
{
match val {
"Text" => Ok(ElementField::Text),
"Audio" => Ok(ElementField::Audio),
"Voice" => Ok(ElementField::Voice),
"Meta" => Ok(ElementField::Meta),
"Break" => Ok(ElementField::Break),
"Emphasis" => Ok(ElementField::Emphasis),
"Mark" => Ok(ElementField::Mark),
"Dyn" => Err(serde::de::Error::custom("DynElements cannot be deserialized")),
_ => Err(serde::de::Error::unknown_variant(val, VARIANTS))
}
}

fn visit_bytes<E>(self, val: &[u8]) -> serde::__private::Result<Self::Value, E>
where
E: serde::de::Error
{
match val {
b"Text" => Ok(ElementField::Text),
b"Audio" => Ok(ElementField::Audio),
b"Voice" => Ok(ElementField::Voice),
b"Meta" => Ok(ElementField::Meta),
b"Break" => Ok(ElementField::Break),
b"Emphasis" => Ok(ElementField::Emphasis),
b"Mark" => Ok(ElementField::Mark),
b"Dyn" => Err(serde::de::Error::custom("DynElements cannot be deserialized")),
_ => {
let __value = &String::from_utf8_lossy(val);
Err(serde::de::Error::unknown_variant(__value, VARIANTS))
}
}
}
}

impl<'de> serde::Deserialize<'de> for ElementField {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>
{
serde::Deserializer::deserialize_identifier(deserializer, ElementFieldVisitor)
}
}

#[doc(hidden)]
struct Visitor<'s, 'de> {
marker: PhantomData<Element<'s>>,
lifetime: PhantomData<&'de ()>
}
impl<'s, 'de> serde::de::Visitor<'de> for Visitor<'s, 'de> {
type Value = Element<'s>;

fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("enum Element")
}

fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
where
A: serde::de::EnumAccess<'de>
{
match serde::de::EnumAccess::variant(data)? {
(ElementField::Text, variant) => serde::de::VariantAccess::newtype_variant::<Text>(variant).map(Element::Text),
(ElementField::Audio, variant) => serde::de::VariantAccess::newtype_variant::<Audio>(variant).map(Element::Audio),
(ElementField::Voice, variant) => serde::de::VariantAccess::newtype_variant::<Voice>(variant).map(Element::Voice),
(ElementField::Meta, variant) => serde::de::VariantAccess::newtype_variant::<Meta>(variant).map(Element::Meta),
(ElementField::Break, variant) => serde::de::VariantAccess::newtype_variant::<Break>(variant).map(Element::Break),
(ElementField::Emphasis, variant) => serde::de::VariantAccess::newtype_variant::<Emphasis>(variant).map(Element::Emphasis),
(ElementField::Mark, variant) => serde::de::VariantAccess::newtype_variant::<Mark>(variant).map(Element::Mark)
}
}
}

#[doc(hidden)]
const VARIANTS: &[&str] = &["Text", "Audio", "Voice", "Meta", "Break", "Emphasis", "Mark"];
serde::Deserializer::deserialize_enum(deserializer, "Element", VARIANTS, Visitor {
marker: serde::__private::PhantomData::<Element>,
lifetime: serde::__private::PhantomData
})
}
}

impl<'s, T: Into<Cow<'s, str>>> From<T> for Element<'s> {
fn from(value: T) -> Self {
Element::Text(Text::from(value))
}
}

/// A dynamic element which can be used to implement non-standard SSML elements outside of the `ssml` crate.
///
/// ```
/// use std::fmt;
///
/// use ssml::{DynElement, Element, Serialize, SerializeOptions, XmlWriter};
///
/// #[derive(Debug, Clone)]
/// #[cfg_attr(feature = "serde", derive(serde::Serialize))]
/// pub struct TomfooleryElement {
/// value: f32,
/// children: Vec<Element<'static>>
/// }
///
/// impl TomfooleryElement {
/// // Increase the tomfoolery level of a section of elements.
/// // ...
/// pub fn new<'s, S: Into<Element<'s>>, I: IntoIterator<Item = S>>(value: f32, elements: I) -> Self {
/// Self {
/// value,
/// children: elements.into_iter().map(|f| f.into().into_owned()).collect()
/// }
/// }
///
/// // not required, but makes your code much cleaner!
/// pub fn into_dyn(self) -> Element<'static> {
/// Element::Dyn(Box::new(self))
/// }
/// }
///
/// impl DynElement for TomfooleryElement {
/// fn serialize_xml(
/// &self,
/// writer: &mut XmlWriter<&mut dyn fmt::Write>,
/// options: &SerializeOptions
/// ) -> ssml::Result<()> {
/// writer.element("tomfoolery", |writer| {
/// writer.attr("influence", self.value)?;
/// ssml::util::serialize_elements(writer, &self.children, options)
/// })
/// }
/// }
///
/// # fn main() -> ssml::Result<()> {
/// let doc = ssml::speak(Some("en-US"), [TomfooleryElement::new(2.0, [
/// "Approaching dangerous levels of tomfoolery!"
/// ])
/// .into_dyn()]);
/// let str = doc.serialize_to_string(&ssml::SerializeOptions::default().pretty())?;
/// assert_eq!(
/// str,
/// r#"<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
/// <tomfoolery influence="2">
/// Approaching dangerous levels of tomfoolery!
/// </tomfoolery>
/// </speak>"#
/// );
/// # Ok(())
/// # }
/// ```
#[allow(private_bounds)]
pub trait DynElement: Debug + DynClone + Send + OptionalErasedSerialize {
/// Serialize this dynamic element into an [`XmlWriter`].
///
/// See [`Serialize::serialize_xml`] for more information.
fn serialize_xml(&self, writer: &mut XmlWriter<&mut dyn Write>, options: &SerializeOptions) -> crate::Result<()>;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CustomElement<'s> {
tag: Cow<'s, str>,
attrs: Vec<(Cow<'s, str>, Cow<'s, str>)>,
children: Vec<Element<'s>>
}

/// An optional tag representing this dynamic element.
fn tag_name(&self) -> Option<&str> {
None
impl<'s> CustomElement<'s> {
pub fn new(tag: impl Into<Cow<'s, str>>) -> Self {
Self {
tag: tag.into(),
attrs: Vec::new(),
children: Vec::new()
}
}

/// If this element has children, returns a reference to the vector containing the element's children.
fn children(&self) -> Option<&Vec<Element>> {
None
pub fn with_attr(mut self, name: impl Into<Cow<'s, str>>, value: impl Into<Cow<'s, str>>) -> Self {
self.attrs.push((name.into(), value.into()));
self
}

/// If this element has children, returns a mutable reference to the vector containing the element's children.
fn children_mut(&mut self) -> Option<&mut Vec<Element>> {
None
pub fn with_child(mut self, element: impl Into<Element<'s>>) -> Self {
self.children.push(element.into());
self
}
}

#[cfg(feature = "serde")]
erased_serde::serialize_trait_object!(DynElement);

#[cfg(feature = "serde")]
trait OptionalErasedSerialize: erased_serde::Serialize {}
#[cfg(feature = "serde")]
impl<T: serde::Serialize> OptionalErasedSerialize for T {}
pub fn with_children<S: Into<Element<'s>>, I: IntoIterator<Item = S>>(mut self, elements: I) -> Self {
self.children.extend(elements.into_iter().map(|f| f.into()));
self
}

#[cfg(not(feature = "serde"))]
trait OptionalErasedSerialize {}
#[cfg(not(feature = "serde"))]
impl<T> OptionalErasedSerialize for T {}
pub fn to_owned(&self) -> CustomElement<'static> {
self.clone().into_owned()
}

dyn_clone::clone_trait_object!(DynElement);
pub fn into_owned(self) -> CustomElement<'static> {
CustomElement {
tag: match self.tag {
Cow::Borrowed(b) => Cow::Owned(b.to_string()),
Cow::Owned(b) => Cow::Owned(b)
},
attrs: self
.attrs
.into_iter()
.map(|(k, v)| {
(
match k {
Cow::Borrowed(b) => Cow::Owned(b.to_string()),
Cow::Owned(b) => Cow::Owned(b)
},
match v {
Cow::Borrowed(b) => Cow::Owned(b.to_string()),
Cow::Owned(b) => Cow::Owned(b)
}
)
})
.collect(),
children: self.children.into_iter().map(Element::into_owned).collect()
}
}
}

impl Serialize for Box<dyn DynElement> {
impl<'s> Serialize for CustomElement<'s> {
fn serialize_xml<W: Write>(&self, writer: &mut XmlWriter<W>, options: &SerializeOptions) -> crate::Result<()> {
let state = {
let mut as_dyn = writer.to_dyn();
DynElement::serialize_xml(self.as_ref(), &mut as_dyn, options)?;
as_dyn.into_state()
};
writer.synchronize_state(state);
Ok(())
writer.element(&self.tag, |writer| {
for (name, value) in &self.attrs {
writer.attr(name, value.as_ref())?;
}
util::serialize_elements(writer, &self.children, options)
})
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub(crate) use self::error::error;
pub use self::{
audio::{Audio, AudioRepeat, audio},
r#break::{Break, BreakStrength, breaks},
element::{DynElement, Element},
element::{CustomElement, Element},
emphasis::{Emphasis, EmphasisLevel, emphasis},
error::{Error, Result},
mark::{Mark, mark},
Expand Down
Loading

0 comments on commit 1a38b32

Please sign in to comment.