diff --git a/.gitignore b/.gitignore index 4fffb2f..3a03231 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /target /Cargo.lock + +*.html +*.js diff --git a/Cargo.toml b/Cargo.toml index 0a11c9d..b220799 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,7 @@ license = "Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lints.clippy] +nursery = "warn" + [dependencies] diff --git a/src/attributes.rs b/src/attributes.rs new file mode 100644 index 0000000..1ac4c06 --- /dev/null +++ b/src/attributes.rs @@ -0,0 +1,75 @@ +/// Direction for [`Attribute::Dir`]. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Dir { + RightToLeft, + LeftToRight, +} + +/// ScriptLevel for [`Attribute::ScriptLevel`]. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum ScriptLevel { + Add(usize), + Sub(usize), + Num(usize), +} + +/// Attribute of a MathML element. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum Attribute { + /// Class of the element, same as in HTML. + Class(String), + + /// Data attribute with custom name and value (e.g. `data-name="value"`). + Data { + /// Name of the data attribute, the part after `data-` prefix. e.g. `data-abc="2"` has the + /// name "abc". + name: String, + + /// The value of the data attribute. e.g. `data-name="test"` has value "test". + value: String, + }, + + /// Direction of the math element. Must be either `rtl` (`RightToLeft`) or `ltr` + /// (`LeftToRight`). + Dir(Dir), + + /// Display style of the element, `true` maps to `normal` and `false` to `compact`. + DisplayStyle(bool), + + /// Id of the element, same as in HTML. + Id(String), + + /// Presentational hint for the background color of the element. Must be a value that is + /// [color](https://www.w3.org/TR/css-color-4/#propdef-color) + MathBackground(String), + + /// Presentational hint for the color of the element. Must be a value that is + /// [color](https://www.w3.org/TR/css-color-4/#propdef-color). + MathColor(String), + + /// Presentational hint for the font size of the element. Must be a value that is + /// [length-percentage](https://www.w3.org/TR/css-values-4/#typedef-length-percentage). + MathSize(String), + + /// The `nonce` attribute, same as in HTML. + Nonce(String), + + /// Presentational hint for setting the element's math-depth property to the corresponding + /// value. Can be `+` ([`ScriptLevel::Add`]), `-` ([`ScriptLevel::Sub`]) or `` + /// ([`ScriptLevel::Num`]) where `` is a [`usize`]. + ScriptLevel(ScriptLevel), + + /// The `style` attribute of the element, same as in HTML. + Style(String), + + /// The `tabindex` attribute, same as in HTML. + TabIndex(i16), + + OnHandler { + /// Name of the event. + name: String, + + /// Handler function for the event. + handler: String, + }, +} diff --git a/src/elements.rs b/src/elements.rs new file mode 100644 index 0000000..cdc447f --- /dev/null +++ b/src/elements.rs @@ -0,0 +1,57 @@ +//! Elements found in the MathML Core Specification. These elements are called MathML Core +//! elements. + +mod maction; +mod math; +mod merror; +mod mmultiscripts; +mod mphantom; +mod mrow; +mod mstyle; + +pub mod grouping { + pub use super::maction::*; + pub use super::math::*; + pub use super::merror::*; + pub use super::mmultiscripts::Prescripts; + pub use super::mphantom::*; + pub use super::mrow::*; + pub use super::mstyle::*; +} + +mod mroot; + +pub mod radicals { + pub use super::mroot::*; +} + +mod msubsup; +mod munderover; + +pub mod scripted { + pub use super::mmultiscripts::Multiscripts; + pub use super::msubsup::*; + pub use super::munderover::*; +} + +mod annotation; +mod mfrac; +mod mi; +mod mn; +mod mo; +mod mpadded; +mod ms; +mod mspace; +mod mtable; +mod mtext; + +pub use annotation::*; +pub use mfrac::*; +pub use mi::*; +pub use mn::*; +pub use mo::*; +pub use mpadded::*; +pub use ms::*; +pub use mspace::*; +pub use mtable::*; +pub use mtext::*; diff --git a/src/elements/annotation.rs b/src/elements/annotation.rs new file mode 100644 index 0000000..2895508 --- /dev/null +++ b/src/elements/annotation.rs @@ -0,0 +1,168 @@ +use std::marker::PhantomData; + +use crate::{ + attributes::Attribute, + markers::{Init, Uninit}, + MathMl, +}; + +/// The content of `annotation` element, either text or MathML. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum AnnotationContent { + Text(String), + MathMl(MathMl), +} + +impl From for AnnotationContent { + fn from(value: String) -> Self { + Self::Text(value) + } +} + +impl From for AnnotationContent +where + T: Into, +{ + fn from(value: T) -> Self { + Self::MathMl(value.into()) + } +} + +/// An attribute of `annotation` element. Either one of the global [`Attribute`]s, or `encode` +/// attribute. +/// +/// [`Attribute`]: crate::attributes::Attribute +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum AnnotationAttr { + Global(Attribute), + Encoding(String), +} + +impl From for AnnotationAttr { + fn from(value: Attribute) -> Self { + Self::Global(value) + } +} + +/// The `annotation` (and `annotation-xml`) element. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Annotation { + content: AnnotationContent, + attributes: Vec, +} + +impl Annotation { + pub fn builder() -> AnnotationBuilder { + AnnotationBuilder::default() + } +} + +impl From for Annotation +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + content: value.into(), + attributes: Default::default(), + } + } +} + +crate::tag_from_type!(Annotation => Annotation); + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct AnnotationBuilder { + content: Option, + attr: Vec, + + _marker: PhantomData<(T,)>, +} + +impl AnnotationBuilder { + pub fn content(self, content: impl Into) -> AnnotationBuilder { + AnnotationBuilder { + content: Some(content.into()), + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn attr(mut self, attr: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + self + } +} + +impl AnnotationBuilder { + pub fn build(self) -> Annotation { + Annotation { + content: self + .content + .expect("Content is guaranteed to be initialized at compile time."), + attributes: self.attr, + } + } +} + +/// The `semantics` element is the container element that associates annotations with a MathML +/// expression. Typically, the `semantics` element has as its first child element a MathML +/// expression to be annotated while subsequent child elements represent text annotations within an +/// `annotation` element, or more complex markup annotations within an `annotation-xml` element. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Semantics { + /// Children of the `semantics` element. Rendering is same as `mrow`. + children: MathMl, + + /// The `semantics` element accepts the global [`Attribute`]s. + attr: Vec, +} + +impl Semantics { + pub fn builder() -> SemanticsBuilder { + SemanticsBuilder::default() + } +} + +crate::tag_from_type!(Semantics => Semantics); + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct SemanticsBuilder { + content: Option, + attr: Vec, + + _marker: PhantomData<(T,)>, +} + +impl SemanticsBuilder { + pub fn content(self, content: impl Into) -> SemanticsBuilder { + SemanticsBuilder { + content: Some(content.into()), + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn attr(mut self, attr: A) -> Self + where + A: IntoIterator, + { + self.attr.extend(attr); + self + } +} + +impl SemanticsBuilder { + pub fn build(self) -> Semantics { + Semantics { + children: self + .content + .expect("Content is guaranteed to be initialized at compile time."), + attr: self.attr, + } + } +} diff --git a/src/elements/maction.rs b/src/elements/maction.rs new file mode 100644 index 0000000..d313265 --- /dev/null +++ b/src/elements/maction.rs @@ -0,0 +1,113 @@ +use std::marker::PhantomData; + +use crate::{ + attributes::Attribute, + markers::{Init, Uninit}, + MathMl, +}; + +/// The `maction` element accepts the global [`Attribute`]s as well as `selection` and +/// `actiontype`. +/// +/// [`Attribute`]: crate::attributes::Attribute +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum ActionAttr { + Global(Attribute), + /// The child element currently visible, only taken into account for `actiontype="toggle"` or + /// non-standard actiontype values. The default value is `1`, which is the first child element. + Selection(String), + + /// The action which specifies what happens for this element. Special behavior for the + /// following values were implemented by some browsers: + /// + /// * `statusline`: If there is a click on the expression or the reader moves the pointer over + /// it, the message is sent to the browser's status line. The syntax is: + /// + /// ```html + /// expression message . + /// ``` + /// + /// * `toggle`: When there is a click on the subexpression, the rendering alternates the + /// display of selected subexpressions. Therefore each click increments the selection value. + /// The syntax is: + /// + /// ```html + /// + /// expression1 expression2 expressionN + /// . + /// ``` + ActionType(String), +} + +/// Historically, the `maction` element provides a mechanism for binding actions to expressions. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Action { + content: MathMl, + attributes: Vec, +} + +impl Action { + /// Create new `maction` element. + pub fn with_mathml(math: impl Into) -> Self { + Self { + content: math.into(), + attributes: Default::default(), + } + } + + pub fn builder() -> ActionBuilder { + ActionBuilder::default() + } +} + +impl From for Action +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + content: value.into(), + attributes: Default::default(), + } + } +} + +crate::tag_from_type!(Action => Action); + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct ActionBuilder { + content: Option, + attributes: Vec, + + _marker: PhantomData<(T,)>, +} + +impl ActionBuilder { + pub fn content(self, content: impl Into) -> ActionBuilder { + ActionBuilder { + content: Some(content.into()), + attributes: self.attributes, + _marker: PhantomData, + } + } + + pub fn attr(mut self, attributes: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.attributes + .extend(attributes.into_iter().map(Into::into)); + + self + } +} + +impl ActionBuilder { + pub fn build(self) -> Action { + Action { + content: self.content.expect("Content is guaranteed to be init."), + attributes: self.attributes, + } + } +} diff --git a/src/elements/math.rs b/src/elements/math.rs new file mode 100644 index 0000000..59ccad4 --- /dev/null +++ b/src/elements/math.rs @@ -0,0 +1,102 @@ +use std::marker::PhantomData; + +use crate::{ + attributes::Attribute, + markers::{Init, Uninit}, + MathMl, +}; + +/// The display attribute, if present, must be an ASCII case-insensitive match to `block` or +/// `inline`. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum DisplayAttr { + /// `block` display attribute. + Block, + + /// `inline` display attribute. + Inline, +} + +/// An attribute of `math` element. Either one of the global [`Attribute`]s, `display` or `alttext` +/// attribute. +/// +/// [`Attribute`]: crate::attributes::Attribute +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum MathAttr { + Global(Attribute), + Display(DisplayAttr), + AltText(String), +} + +// TODO: figure out if this element is actually needed. +/// MathML specifies a single top-level or root math element, which encapsulates each instance of +/// MathML markup within a document. All other MathML content must be contained in a `` +/// element. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Math { + content: MathMl, + attributes: Vec, +} + +impl Math { + /// Create new `math` element. + pub fn with_mathml(math: impl Into) -> Self { + Self { + content: math.into(), + attributes: Default::default(), + } + } + + pub fn builder() -> MathBuilder { + MathBuilder::default() + } +} + +impl From for Math +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + content: value.into(), + attributes: Default::default(), + } + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct MathBuilder { + content: Option, + attributes: Vec, + + _marker: PhantomData<(T,)>, +} + +impl MathBuilder { + pub fn content(self, content: impl Into) -> MathBuilder { + MathBuilder { + content: Some(content.into()), + attributes: self.attributes, + + _marker: PhantomData, + } + } + + pub fn attr(mut self, attr: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.attributes.extend(attr.into_iter().map(Into::into)); + self + } +} + +impl MathBuilder { + pub fn build(self) -> Math { + Math { + content: self.content.expect("Content is guaranteed to be init."), + attributes: self.attributes, + } + } +} diff --git a/src/elements/merror.rs b/src/elements/merror.rs new file mode 100644 index 0000000..ba58639 --- /dev/null +++ b/src/elements/merror.rs @@ -0,0 +1,81 @@ +use std::marker::PhantomData; + +use crate::{ + attributes::Attribute, + markers::{Init, Uninit}, + MathMl, +}; + +/// The merror element displays its contents as an ”error message”. The intent of this element is +/// to provide a standard way for programs that generate MathML from other input to report syntax +/// errors in their input. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Error { + content: MathMl, + attributes: Vec, +} + +impl Error { + /// Create new `merror` element. + pub fn with_mathml(math: impl Into) -> Self { + Self { + content: math.into(), + attributes: Default::default(), + } + } + + pub fn builder() -> ErrorBuilder { + ErrorBuilder::default() + } +} + +impl From for Error +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + content: value.into(), + attributes: Default::default(), + } + } +} + +crate::tag_from_type!(Error => Error); + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct ErrorBuilder { + content: Option, + attributes: Vec, + + _marker: PhantomData<(T,)>, +} + +impl ErrorBuilder { + pub fn content(self, content: impl Into) -> ErrorBuilder { + ErrorBuilder { + content: Some(content.into()), + attributes: self.attributes, + + _marker: PhantomData, + } + } + + pub fn attr(mut self, attr: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.attributes.extend(attr.into_iter().map(Into::into)); + self + } +} + +impl ErrorBuilder { + pub fn build(self) -> Error { + Error { + content: self.content.expect("Content is guaranteed to be init."), + attributes: self.attributes, + } + } +} diff --git a/src/elements/mfrac.rs b/src/elements/mfrac.rs new file mode 100644 index 0000000..664923c --- /dev/null +++ b/src/elements/mfrac.rs @@ -0,0 +1,131 @@ +use std::marker::PhantomData; + +use crate::markers::{Init, Uninit}; +use crate::{attributes::Attribute, MathMl}; + +/// An attribute of `mfrac` element. Either one of the global [`Attribute`]s, or `linethickness` +/// attribute. +/// +/// [`Attribute`]: crate::attributes::Attribute +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum FracAttr { + /// Global attribute. + Global(Attribute), + + /// The linethickness attribute indicates the fraction line thickness to use for the fraction + /// bar. + /// It must have a value that is a valid + /// [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage). + LineThickness(String), +} + +/// The merror element displays its contents as an ”error message”. The intent of this element is +/// to provide a standard way for programs that generate MathML from other input to report syntax +/// errors in their input. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Frac { + // regular comment + num: MathMl, + denom: MathMl, + attributes: Vec, +} + +impl Frac { + pub fn builder() -> FracBuilder { + FracBuilder::default() + } +} + +impl From<(N, D)> for Frac +where + N: Into, + D: Into, +{ + fn from((num, denom): (N, D)) -> Self { + let mut i = 0; + let _ = 'some_loop: loop { + if i == 5 { + break 'some_loop i; + } + i += 1; + }; + + Self { + num: num.into(), + denom: denom.into(), + attributes: Default::default(), + } + } +} + +crate::tag_from_type!(Frac => Frac); + +/// Builder for [`Frac`]. It uses static type checking to ensure that all required fields have been +/// initialized. Only then is the `build` function available. +/// +/// # Example +/// +/// ```rust +/// use alemat::elements::Frac; +/// +/// let frac = Frac::builder().num("1").denom("2").build(); +/// ``` +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct FracBuilder { + num: Option, + denom: Option, + attr: Vec, + + _marker: PhantomData<(N, D)>, +} + +impl FracBuilder { + /// Add or overwrite the numerator to the `mfrac` element. + pub fn num(self, num: impl Into) -> FracBuilder { + FracBuilder { + num: Some(num.into()), + denom: self.denom, + attr: self.attr, + _marker: PhantomData, + } + } + + /// Add or overwrite the denominator to the `mfrac` element. + pub fn denom(self, denom: impl Into) -> FracBuilder { + FracBuilder { + num: self.num, + denom: Some(denom.into()), + attr: self.attr, + _marker: PhantomData, + } + } + + /// Add attributes to the `mfrac` element. Previous attributes will not be overwritten. + pub fn attr(mut self, attr: A) -> Self + where + A: IntoIterator, + { + self.attr.extend(attr); + self + } +} + +impl FracBuilder { + pub fn build(self) -> Frac { + let num = self + .num + .expect("Numerator is guaranteed to be initialized."); + + let denom = self + .denom + .expect("Denominator is guaranteed to be initialized."); + + let attr = self.attr; + + Frac { + num, + denom, + attributes: attr, + } + } +} diff --git a/src/elements/mi.rs b/src/elements/mi.rs new file mode 100644 index 0000000..8fc4b2c --- /dev/null +++ b/src/elements/mi.rs @@ -0,0 +1,94 @@ +use std::marker::PhantomData; + +use crate::{ + attributes::Attribute, + markers::{Init, Uninit}, + MathMl, Tag, +}; + +/// An attribute of `mi` element. Either one of the global [`Attribute`]s, or `mathvariant` +/// attribute. +/// +/// [`Attribute`]: crate::attributes::Attribute +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum IdentAttr { + /// Global attribute. + Global(Attribute), + + /// The linethickness attribute indicates the fraction line thickness to use for the fraction + /// bar. + /// It must have a value that is a valid + /// [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage). + MathVariant(String), +} + +/// The `mi` (`Ident`) element represents a symbolic name or arbitrary text that should be rendered +/// as an identifier. Identifiers can include variables, function names, and symbolic constants. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Ident { + ident: String, + attributes: Vec, +} + +impl Ident { + pub fn builder() -> IdentBuilder { + IdentBuilder::default() + } +} + +impl From for Ident +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + ident: value.into(), + attributes: Default::default(), + } + } +} + +impl From for MathMl { + fn from(value: Ident) -> Self { + Self { + content: vec![Tag::Ident(value)], + } + } +} + +crate::tag_from_type!(Ident => Ident); + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct IdentBuilder { + ident: Option, + attributes: Vec, + _marker: PhantomData<(T,)>, +} + +impl IdentBuilder { + pub fn ident(self, ident: impl Into) -> IdentBuilder { + IdentBuilder { + ident: Some(ident.into()), + attributes: self.attributes, + _marker: PhantomData, + } + } + + pub fn attr(mut self, attr: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.attributes.extend(attr.into_iter().map(Into::into)); + self + } +} + +impl IdentBuilder { + pub fn build(self) -> Ident { + Ident { + ident: self.ident.expect("Content is guaranteed to be init."), + attributes: self.attributes, + } + } +} diff --git a/src/elements/mmultiscripts.rs b/src/elements/mmultiscripts.rs new file mode 100644 index 0000000..72e1cf2 --- /dev/null +++ b/src/elements/mmultiscripts.rs @@ -0,0 +1,91 @@ +use std::marker::PhantomData; + +use crate::{ + attributes::Attribute, + markers::{Init, Uninit}, + MathMl, +}; + +/// Presubscripts and tensor notations are represented by the `mmultiscripts` element. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Multiscripts { + content: MathMl, + /// Accepts the global [`Attribute`]s. + attributes: Vec, +} + +impl From for Multiscripts +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + content: value.into(), + attributes: Default::default(), + } + } +} + +impl Multiscripts { + pub fn builder() -> MultiscriptsBuilder { + MultiscriptsBuilder::default() + } +} + +crate::tag_from_type!(Multiscripts => Multiscripts); + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct MultiscriptsBuilder { + content: Option, + attr: Vec, + + _marker: PhantomData<(T,)>, +} + +impl MultiscriptsBuilder { + pub fn content(self, content: impl Into) -> MultiscriptsBuilder { + MultiscriptsBuilder { + content: Some(content.into()), + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn attr(mut self, attr: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + self + } +} + +impl MultiscriptsBuilder { + pub fn build(self) -> Multiscripts { + Multiscripts { + content: self.content.expect("Guaranteed to be init"), + attributes: self.attr, + } + } +} + +/// Presubscripts and tensor notations are represented by the `mmultiscripts` element. The +/// `mprescripts` element is used as a separator between the `postscripts` and `prescripts`. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Prescripts { + /// Accepts the global [`Attribute`]s. + attr: Vec, +} + +impl Prescripts { + pub fn with_attr(attr: I) -> Self + where + I: IntoIterator, + A: Into, + { + Self { + attr: attr.into_iter().map(Into::into).collect(), + } + } +} diff --git a/src/elements/mn.rs b/src/elements/mn.rs new file mode 100644 index 0000000..625234c --- /dev/null +++ b/src/elements/mn.rs @@ -0,0 +1,45 @@ +use crate::{attributes::Attribute, MathMl, Tag}; + +/// The `mn` element represents a "numeric literal" or other data that should be rendered as a +/// numeric literal. Generally speaking, a numeric literal is a sequence of digits, perhaps +/// including a decimal point, representing an unsigned integer or real number. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Num { + num: String, + attributes: Vec, +} + +impl<'a> From<&'a str> for Num { + fn from(value: &'a str) -> Self { + Self { + num: String::from(value), + attributes: Default::default(), + } + } +} + +crate::from_types!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize => Num; + |val| Num { num: format!("{}", val), attributes: Default::default() }); + +impl From for MathMl +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + content: vec![Tag::Num(value.into())], + } + } +} + +impl Num { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attributes.extend(attr.into_iter().map(Into::into)); + } +} + +crate::tag_from_type!(Num => Num); diff --git a/src/elements/mo.rs b/src/elements/mo.rs new file mode 100644 index 0000000..48d550a --- /dev/null +++ b/src/elements/mo.rs @@ -0,0 +1,130 @@ +use std::marker::PhantomData; + +use crate::{ + attributes::Attribute, + markers::{Init, Uninit}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum OpForm { + Infix, + Prefix, + Postfix, +} + +/// Attribute for the `mo` (`Operator`) element. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum OperatorAttr { + /// One of the global attributes. + Global(Attribute), + + /// Either `infix`, `prefix` or `postfix`. + Form(OpForm), + + /// The specification does not define any observable behavior that is specific to the fence + /// attribute. + Fence, + + /// The specification does not define any observable behavior that is specific to the separator + /// attribute. + Separator, + + /// Must be a + /// [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage). + LeftSpace(String), + + /// Must be a + /// [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage). + RightSpace(String), + + /// Must be a + /// [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage). + MaxSize(String), + + /// Must be a + /// [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage). + MinSize(String), + + /// Either `true` or `false`. In this implementation, the attribute is `true` if present. + Stretchy, + + /// Either `true` or `false`. In this implementation, the attribute is `true` if present. + Symmetric, + + /// Either `true` or `false`. In this implementation, the attribute is `true` if present. + LargeOp, + + /// Either `true` or `false`. In this implementation, the attribute is `true` if present. + MovableLimits, +} + +impl From for OperatorAttr { + fn from(value: Attribute) -> Self { + Self::Global(value) + } +} + +/// The `mo` element represents an operator or anything that should be rendered as an operator. In +/// general, the notational conventions for mathematical operators are quite complicated, and +/// therefore MathML provides a relatively sophisticated mechanism for specifying the rendering +/// behavior of an `` element. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Operator { + op: String, + attributes: Vec, +} + +impl From for Operator +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + op: value.into(), + attributes: Default::default(), + } + } +} + +impl Operator { + pub fn builder() -> OperatorBuilder { + OperatorBuilder::default() + } +} + +crate::tag_from_type!(Operator => Operator); + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct OperatorBuilder { + op: Option, + attr: Vec, + _marker: PhantomData<(T,)>, +} + +impl OperatorBuilder { + pub fn op(self, op: impl Into) -> OperatorBuilder { + OperatorBuilder { + op: Some(op.into()), + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn attr(mut self, attr: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + self + } +} + +impl OperatorBuilder { + pub fn build(self) -> Operator { + Operator { + op: self.op.expect("Op is guaranteed to be init."), + attributes: self.attr, + } + } +} diff --git a/src/elements/mpadded.rs b/src/elements/mpadded.rs new file mode 100644 index 0000000..6258d18 --- /dev/null +++ b/src/elements/mpadded.rs @@ -0,0 +1,55 @@ +use crate::{attributes::Attribute, MathMl}; + +/// The `mpadded` element accepts global attributes as well as the `width`, `height`, `depth`, +/// `lspace` and `voffset` +/// +/// The `width`, `height`, `depth`, `lspace` and `voffset` if present, must have a value that is a +/// valid [``](https://www.w3.org/TR/css-values-4/#typedef-length-percentage). +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PaddedAttr { + Width(String), + Height(String), + Depth(String), + LeftSpace(String), + VerticalOffset(String), + + Global(Attribute), +} + +impl From for PaddedAttr { + fn from(value: Attribute) -> Self { + Self::Global(value) + } +} + +/// The `mpadded` element renders the same as its in-flow child content, but with the size and +/// relative positioning point of its content modified according to `mpadded`’s attributes. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Padded { + children: MathMl, + attributes: Vec, +} + +impl From for Padded +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + children: value.into(), + attributes: Default::default(), + } + } +} + +impl Padded { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attributes.extend(attr.into_iter().map(Into::into)); + } +} + +crate::tag_from_type!(Padded => Padded); diff --git a/src/elements/mphantom.rs b/src/elements/mphantom.rs new file mode 100644 index 0000000..a39124c --- /dev/null +++ b/src/elements/mphantom.rs @@ -0,0 +1,42 @@ +use crate::{attributes::Attribute, MathMl}; + +/// The `mphantom` element was introduced to render its content invisibly, but with the same +/// metrics size and other dimensions, including alphabetic baseline position that its contents +/// would have if they were rendered normally. +/// +/// The user agent stylesheet must contain the following rule in order to hide the content: +/// +/// ```css +/// mphantom { +/// visibility: hidden; +/// } +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Phantom { + children: MathMl, + attributes: Vec, +} + +impl From for Phantom +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + children: value.into(), + attributes: Default::default(), + } + } +} + +impl Phantom { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attributes.extend(attr.into_iter().map(Into::into)); + } +} + +crate::tag_from_type!(Phantom => Phantom); diff --git a/src/elements/mroot.rs b/src/elements/mroot.rs new file mode 100644 index 0000000..d68bd17 --- /dev/null +++ b/src/elements/mroot.rs @@ -0,0 +1,76 @@ +use std::marker::PhantomData; + +use crate::{ + attributes::Attribute, + markers::{Init, Uninit}, + MathMl, +}; + +/// The radical elements construct an expression with a root symbol `√` with a line over the content. +/// The msqrt element is used for square roots, while the mroot element is used to draw radicals +/// with indices, e.g. a cube root. +/// +/// The `msqrt` and `mroot` elements accept the global [`Attribute`]s. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Radical { + /// The index of the radical, e.g. the 3 in `∛`. + index: String, + content: MathMl, + attributes: Vec, +} + +impl Radical { + pub fn builder() -> RadicalsBuilder { + RadicalsBuilder::default() + } +} + +crate::tag_from_type!(Radical => Radical); + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct RadicalsBuilder { + index: Option, + content: Option, + attr: Vec, + + _marker: PhantomData<(T1, T2)>, +} + +impl RadicalsBuilder { + pub fn index(self, index: impl Into) -> RadicalsBuilder { + RadicalsBuilder { + index: Some(index.into()), + content: self.content, + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn content(self, content: impl Into) -> RadicalsBuilder { + RadicalsBuilder { + index: self.index, + content: Some(content.into()), + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn attr(mut self, attr: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + self + } +} + +impl RadicalsBuilder { + pub fn build(self) -> Radical { + Radical { + index: self.index.expect("Index is guaranteed to be init."), + content: self.content.expect("Content is guaranteed to be init."), + attributes: self.attr, + } + } +} diff --git a/src/elements/mrow.rs b/src/elements/mrow.rs new file mode 100644 index 0000000..cbc88b8 --- /dev/null +++ b/src/elements/mrow.rs @@ -0,0 +1,34 @@ +use crate::{attributes::Attribute, MathMl}; + +/// The `mrow` element is used to group together any number of sub-expressions, usually consisting +/// of one or more `mo` elements acting as "operators" on one or more other expressions that are +/// their "operands". +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Row { + children: MathMl, + attr: Vec, +} + +impl From for Row +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + children: value.into(), + attr: Default::default(), + } + } +} + +impl Row { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + } +} + +crate::tag_from_type!(Row => Row); diff --git a/src/elements/ms.rs b/src/elements/ms.rs new file mode 100644 index 0000000..ce4cbd9 --- /dev/null +++ b/src/elements/ms.rs @@ -0,0 +1,33 @@ +use crate::attributes::Attribute; + +/// `ms` element is used to represent "string literals" in expressions meant to be interpreted by +/// computer algebra systems or other systems containing "programming languages". +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct StrLiteral { + literal: String, + attr: Vec, +} + +impl From for StrLiteral +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + literal: value.into(), + attr: Default::default(), + } + } +} + +impl StrLiteral { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + } +} + +crate::tag_from_type!(StrLiteral => StrLiteral); diff --git a/src/elements/mspace.rs b/src/elements/mspace.rs new file mode 100644 index 0000000..0940d67 --- /dev/null +++ b/src/elements/mspace.rs @@ -0,0 +1,55 @@ +use crate::attributes::Attribute; + +/// The `mspace` element accepts the global [`Attribute`]s as well as `width`, `height` and `depth`. +/// +/// The `width`, `height`, `depth`, if present, must have a value that is a valid +/// [length-percentage](https://www.w3.org/TR/css-values-4/#typedef-length-percentage). +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum SpaceAttr { + /// If present, valid and not a percentage then it is used as a presentational hint setting the + /// element's width property to the corresponding value. + Width(String), + + /// If absent, invalid or a percentage then the requested line-ascent is 0. Otherwise the + /// requested line-ascent is the resolved value of the height attribute, clamping negative + /// values to 0. + Height(String), + + /// If both the height and depth attributes are present, valid and not a percentage then they + /// are used as a presentational hint setting the element's height property to the + /// concatenation of the strings "calc(", the height attribute value, " + ", the depth + /// attribute value, and ")". + /// + /// If only one of these attributes is present, valid and not a + /// percentage then it is treated as a presentational hint setting the element's height + /// property to the corresponding value. + Depth(String), + + /// One of the global [`Attribute`]s. + Global(Attribute), +} + +impl From for SpaceAttr { + fn from(value: Attribute) -> Self { + Self::Global(value) + } +} + +/// The mspace empty element represents a blank space of any desired size, as set by its +/// attributes. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Space { + attr: Vec, +} + +impl Space { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + } +} + +crate::tag_from_type!(Space => Space); diff --git a/src/elements/mstyle.rs b/src/elements/mstyle.rs new file mode 100644 index 0000000..1c7af5e --- /dev/null +++ b/src/elements/mstyle.rs @@ -0,0 +1,34 @@ +use crate::{attributes::Attribute, MathMl}; + +/// `mstyle` element was introduced to make style changes that affect the rendering of its contents. +/// +/// The `mstyle` element accepts the global [`Attribute`]s. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Style { + children: MathMl, + attr: Vec, +} + +impl From for Style +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + children: value.into(), + attr: Default::default(), + } + } +} + +impl Style { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + } +} + +crate::tag_from_type!(Style => Style); diff --git a/src/elements/msubsup.rs b/src/elements/msubsup.rs new file mode 100644 index 0000000..73e4f9a --- /dev/null +++ b/src/elements/msubsup.rs @@ -0,0 +1,91 @@ +use std::marker::PhantomData; + +use crate::{ + attributes::Attribute, + markers::{Init, Uninit}, + MathMl, +}; + +/// The `msub`, `msup` and `msubsup` elements are used to attach subscript and superscript to a MathML +/// expression. They accept the global [`Attribute`]s. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct SubSup { + base: MathMl, + sub: Option, + sup: Option, + attributes: Vec, +} + +crate::tag_from_type!(SubSup => SubSup); + +impl SubSup { + pub fn builder() -> SubSupBuilder { + SubSupBuilder::default() + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct SubSupBuilder { + base: Option, + sub: Option, + sup: Option, + attr: Vec, + _marker: PhantomData<(T1, T2)>, +} + +impl SubSupBuilder { + pub fn base(self, base: impl Into) -> SubSupBuilder { + SubSupBuilder { + base: Some(base.into()), + sub: self.sub, + sup: self.sup, + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn subscript(self, sub: impl Into) -> SubSupBuilder { + SubSupBuilder { + base: self.base, + sub: Some(sub.into()), + sup: self.sup, + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn supscript(self, sub: impl Into) -> SubSupBuilder { + SubSupBuilder { + base: self.base, + sub: self.sup, + sup: Some(sub.into()), + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn attr(mut self, attr: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + self + } +} + +impl SubSupBuilder { + pub fn build(self) -> SubSup { + debug_assert!( + self.sub.is_some() || self.sup.is_some(), + "SubSup element must have at least one of sub or sup." + ); + + SubSup { + base: self.base.expect("Base is guaranteed to be init."), + sub: self.sub, + sup: self.sup, + attributes: self.attr, + } + } +} diff --git a/src/elements/mtable.rs b/src/elements/mtable.rs new file mode 100644 index 0000000..a519cf7 --- /dev/null +++ b/src/elements/mtable.rs @@ -0,0 +1,233 @@ +use crate::{attributes::Attribute, MathMl}; + +pub enum ColumnLine { + /// No line is drawn. + None, + + /// A solid line is drawn. + Solid, + + /// a dashed line is drawn. + Dashed, +} + +/// The `mtable` accepts the global [`Attribute`]s as well as `columnlines` that can be used to +/// render augmented matrix. +pub enum TableAttr { + /// The `columnlines` attribute is a space-separated list of values, one for each column. + ColumnLines(Vec), + + /// One of the global [`Attribute`]s. + Global(Attribute), +} + +/// The `mtable` is laid out as an inline-table and sets displaystyle to false. The user agent +/// stylesheet must contain the following rules in order to implement these properties: +/// +/// The `mtable` accepts the global [`Attribute`]s. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Table { + rows: Vec, + /// The `mtable` accepts the global [`Attribute`]s. + attributes: Vec, +} + +crate::tag_from_type!(Table => Table); + +impl FromIterator for Table +where + R: Into, +{ + fn from_iter>(iter: T) -> Self { + Self { + rows: iter.into_iter().map(Into::into).collect(), + attributes: Default::default(), + } + } +} + +impl From for Table +where + I: IntoIterator, + C: Into, +{ + fn from(value: I) -> Self { + Self::from_iter(value) + } +} + +impl Table { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attributes.extend(attr.into_iter().map(Into::into)); + } +} + +/// Create a [`Table`] easily using this macro. +/// +/// # Example +/// +/// ```rust +/// use alemat::table; +/// use alemat::elements::{Ident, Num}; +/// +/// let table = table![ +/// [Ident::from("x"), Num::from(41)], +/// [Ident::from("x"), Num::from(41)] +/// ]; +/// ``` +#[macro_export] +macro_rules! table { + ($([$($cell:expr),*]),*) => { + $crate::elements::Table::from([ + $( + $crate::row![$($cell),*], + )* + ]) + } +} + +/// The `mtr` is laid out as `table-row`. The user agent stylesheet must contain the following +/// rules in order to implement that behavior: +/// +/// ```css +/// mtr { +/// display: table-row; +/// } +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct TableRow { + /// Table cells (`mtd`) of this table row. + cells: Vec, + /// The `mtr` accepts the global [`Attribute`]s. + attr: Vec, +} + +impl FromIterator for TableRow +where + C: Into, +{ + fn from_iter>(iter: T) -> Self { + Self { + cells: iter.into_iter().map(Into::into).collect(), + attr: Default::default(), + } + } +} + +impl TableRow { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + } +} + +impl From for TableRow +where + I: IntoIterator, + C: Into, +{ + fn from(value: I) -> Self { + Self { + cells: value.into_iter().map(Into::into).collect(), + attr: Default::default(), + } + } +} + +/// Create a row of [`TableCell`]s. To be used in [`Table`]. +/// +/// # Example: +/// +/// ```rust +/// use alemat::row; +/// use alemat::elements::{Ident, Num}; +/// let row = row![Ident::from("x"), Num::from(42)]; +/// +/// // create a table +/// use alemat::elements::Table; +/// let table = Table::from([ +/// row![Ident::from("x"), Num::from(42)], +/// row![Ident::from("y"), Num::from(43)], +/// ]); +/// ``` +#[macro_export] +macro_rules! row { + ($($cell:expr),* $(,)?) => { + [$($crate::elements::TableCell::from($cell)),*] + } +} + +pub use row; + +/// The `mtd` accepts the global [`Attribute`]s as well as `columnspan` and `rowspan`. +/// +/// The `columnspan` (respectively `rowspan`) attribute has the same syntax and semantics as the +/// colspan (respectively rowspan) attribute on the `` element from HTML. In particular, the +/// parsing of these attributes is handled as described in the algorithm for processing rows, +/// always reading `colspan` as `columnspan`. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum TableCellAttr { + /// Has the same syntax and semantics as the `colspan` attribute on the `` element from + /// HTML. + ColumnSpan(String), + + /// Has the same syntax and semantics as the `rowspan` attribute on the `` element from + /// HTML. + RowSpan(String), + + /// One of the global [`Attribute`]s. + Global(Attribute), +} + +impl From for TableCellAttr { + fn from(value: Attribute) -> Self { + Self::Global(value) + } +} + +/// The `mtd` is laid out as a `table-cell` with content centered in the cell and a default +/// padding. The user agent stylesheet must contain the following rules: +/// +/// ```css +/// mtd { +/// display: table-cell; +/// /* Centering inside table cells should rely on box alignment properties. +/// See https://github.com/w3c/mathml-core/issues/156 */ +/// text-align: center; +/// padding: 0.5ex 0.4em; +/// } +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct TableCell { + children: MathMl, + attr: Vec, +} + +impl From for TableCell +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + children: value.into(), + attr: Default::default(), + } + } +} + +impl TableCell { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + } +} diff --git a/src/elements/mtext.rs b/src/elements/mtext.rs new file mode 100644 index 0000000..e7a72a5 --- /dev/null +++ b/src/elements/mtext.rs @@ -0,0 +1,36 @@ +use crate::attributes::Attribute; + +/// The `mtext` element is used to represent arbitrary text that should be rendered as itself. In +/// general, the `mtext` element is intended to denote commentary text. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Text { + /// The text to be rendered. + text: String, + + /// The `mtext` element accepts the global [`Attribute`]s. + attr: Vec, +} + +impl From for Text +where + T: Into, +{ + fn from(value: T) -> Self { + Self { + text: value.into(), + attr: Default::default(), + } + } +} + +impl Text { + pub fn add_attr(&mut self, attr: I) + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + } +} + +crate::tag_from_type!(Text => Text); diff --git a/src/elements/munderover.rs b/src/elements/munderover.rs new file mode 100644 index 0000000..68ec7bd --- /dev/null +++ b/src/elements/munderover.rs @@ -0,0 +1,107 @@ +use std::marker::PhantomData; + +use crate::{ + attributes::Attribute, + markers::{Init, Uninit}, + MathMl, +}; + +/// The `munderover` element accepts global attributes as well as `accent` and `accentunder`. +/// +/// Similarly, the `mover` element (respectively `munder` element) accepts global attributes as +/// well as the `accent` attribute (respectively the `accentunder` attribute). +/// +/// `accent`, `accentunder` attributes, if present, must have values that are booleans. If these +/// attributes are absent or invalid, they are treated as equal to false. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum UnderOverAttr { + AccentUnder, + AccentOver, + Global(Attribute), +} + +/// The munder, mover and munderover elements are used to attach accents or limits placed under or +/// over a MathML expression. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct UnderOver { + expr: MathMl, + over: Option, + under: Option, + attributes: Vec, +} + +impl UnderOver { + pub fn builder() -> UnderOverBuilder { + UnderOverBuilder::default() + } +} + +crate::tag_from_type!(UnderOver => UnderOver); + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct UnderOverBuilder { + expr: Option, + under: Option, + over: Option, + attr: Vec, + + _marker: PhantomData<(T1, T2)>, +} + +impl UnderOverBuilder { + pub fn expr(self, expr: impl Into) -> UnderOverBuilder { + UnderOverBuilder { + expr: Some(expr.into()), + under: self.under, + over: self.over, + attr: self.attr, + + _marker: PhantomData, + } + } + + pub fn over(self, over: impl Into) -> UnderOverBuilder { + UnderOverBuilder { + expr: self.expr, + under: self.under, + over: Some(over.into()), + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn under(self, under: impl Into) -> UnderOverBuilder { + UnderOverBuilder { + expr: self.expr, + under: Some(under.into()), + over: self.over, + attr: self.attr, + _marker: PhantomData, + } + } + + pub fn attr(mut self, attr: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.attr.extend(attr.into_iter().map(Into::into)); + self + } +} + +impl UnderOverBuilder { + pub fn build(self) -> UnderOver { + debug_assert!( + self.over.is_some() || self.under.is_some(), + "At least one of 'over' or 'under' must be initialized." + ); + + UnderOver { + expr: self.expr.expect("Expr is guaranteed to be init."), + over: self.over, + under: self.under, + attributes: self.attr, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index dd79d8c..6be5336 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,67 @@ //! Library for type-safe building of MathML. +pub mod attributes; +pub mod elements; +pub mod markers; + +use elements::{ + grouping::{Action, Error, Phantom, Row, Style}, + radicals::Radical, + scripted::{Multiscripts, SubSup, UnderOver}, + Annotation, Frac, Ident, Num, Operator, Padded, Semantics, Space, StrLiteral, Table, Text, +}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) enum Tag { + Action(Action), + Annotation(Annotation), + Error(Error), + Frac(Frac), + Ident(Ident), + Multiscripts(Multiscripts), + Num(Num), + Operator(Operator), + Padded(Padded), + Phantom(Phantom), + Radical(Radical), + Row(Row), + Semantics(Semantics), + Space(Space), + StrLiteral(StrLiteral), + Style(Style), + SubSup(SubSup), + Table(Table), + Text(Text), + UnderOver(UnderOver), +} + +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct MathMl { + content: Vec, + // TODO: decide what fields should go inside +} + +macro_rules! from_types { + ($($type:path),* $(,)? => $for_type:path; $func:expr) => { + $( + impl From<$type> for $for_type { + fn from(value: $type) -> Self { + let from_value = $func; + from_value(value) + } + } + )* + }; +} + +macro_rules! tag_from_type { + ($variant:ident => $type:path) => { + impl From<$type> for crate::Tag { + fn from(value: $type) -> Self { + Self::$variant(value) + } + } + }; +} + +pub(crate) use {from_types, tag_from_type}; diff --git a/src/markers.rs b/src/markers.rs new file mode 100644 index 0000000..c5c2283 --- /dev/null +++ b/src/markers.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[doc(hidden)] +pub struct Init; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[doc(hidden)] +pub struct Uninit; diff --git a/tests/mod.rs b/tests/mod.rs new file mode 100644 index 0000000..a24c677 --- /dev/null +++ b/tests/mod.rs @@ -0,0 +1,34 @@ +use alemat::{ + attributes::Attribute, + elements::{Annotation, Ident, Num, Table}, +}; +use alemat::{row, table}; + +#[test] +fn test_annotation_api() { + let _annotation = Annotation::builder() + .content(Num::from(42)) + .attr([Attribute::Id("Some id".into())]) + .build(); +} + +#[test] +fn test_table_api() { + let _table = Table::from([ + // this should be a row + [Num::from(40), Num::from(41)], + [Num::from(42), Num::from(43)], + [Num::from(44), Num::from(45)], + ]); + + let _table = Table::from([ + row![Ident::from("x"), Num::from(41)], + row![Num::from(42), Num::from(43)], + row![Num::from(44), Num::from(45)], + ]); + + let _table = table![ + [Ident::from("x"), Num::from(41)], + [Ident::from("x"), Num::from(41)] + ]; +}