openmath/
lib.rs

1#![allow(unexpected_cfgs)]
2#![cfg_attr(all(doc, CHANNEL_NIGHTLY), feature(doc_auto_cfg))]
3#![allow(clippy::doc_markdown)]
4#![doc = include_str!("../README.md")]
5/*! ## Features */
6#![cfg_attr(doc,doc = document_features::document_features!())]
7pub mod ser;
8
9use std::{borrow::Cow, convert::Infallible};
10
11pub use ser::OMSerializable;
12pub mod de;
13pub use de::{OM, OMDeserializable};
14pub mod base64;
15mod int;
16/// reexported for convenience
17pub use either;
18pub use int::Int;
19
20use crate::ser::AsOMS;
21
22/// The base URI of official OᴘᴇɴMᴀᴛʜ dictionaries (`http://www.openmath.org/cd`)
23pub const CD_BASE: &str = "http://www.openmath.org/cd";
24
25/// XML namespace for OpenMath elements
26pub const XML_NS: &str = "http://www.openmath.org/OpenMath";
27
28macro_rules! omkinds {
29    ($( $(#[$meta:meta])* $id:ident = $v:literal ),* $(,)?) => {
30        /// All <span style="font-variant:small-caps;">OpenMath</span> tags/kinds
31        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32        #[repr(u8)]
33        #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
34        pub enum OMKind {
35            $(
36                $(#[$meta])*
37                $id = $v
38            ),*
39        }
40        impl OMKind {
41            /// as static string
42            #[must_use]
43            pub const fn as_str(self) -> &'static str {
44                match self {$(
45                    Self::$id => stringify!($id)
46                ),*}
47            }
48            /// convert from a byte
49            #[must_use]
50            pub const fn from_u8(u:u8) -> Option<Self> {
51                match u {
52                    $( $v => Some(Self::$id) ),*,
53                    _ => None
54                }
55            }
56        }
57        impl std::fmt::Display for OMKind {
58            #[inline]
59            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60                f.write_str(self.as_str())
61            }
62        }
63    };
64}
65
66omkinds! {
67    /** <div class="openmath">
68    Integers in the mathematical sense, with no predefined range.
69    They are “infinite precision” integers (also called “bignums” in computer algebra).
70    </div> */
71    OMI = 0,
72
73    /** <div class="openmath">
74    Double precision floating-point numbers following the IEEE 754-1985 standard.
75    </div> */
76    OMF = 1,
77
78    /** <div class="openmath">
79    A Unicode Character string. This also corresponds to “characters” in XML.
80    </div> */
81    OMSTR = 2,
82
83    /** <div class="openmath">
84    A sequence of bytes.
85    </div> */
86    OMB = 3,
87
88    ///<div class="openmath">
89    ///
90    /// A Variable must have a name which is a sequence of characters matching a regular
91    /// expression, as described in [Section 2.3](https://openmath.org/standard/om20-2019-07-01/omstd20.html#sec_names).
92    ///
93    ///</div>
94    ///
95    ///(Note: We do not enforce that names are valid XML names;)
96    OMV = 4,
97
98    ///<div class="openmath">
99    ///
100    /// A Symbol encodes three fields of information, a symbol name, a Content Dictionary name,
101    /// and (optionally) a Content Dictionary base URI, The name of a symbol is a sequence of
102    /// characters matching the regular expression described in
103    /// [Section 2.3](https://openmath.org/standard/om20-2019-07-01/omstd20.html#sec_names).
104    /// The Content Dictionary is the location of the definition of the symbol, consisting of a
105    /// name (a sequence of characters matching the regular expression described in
106    /// [Section 2.3](https://openmath.org/standard/om20-2019-07-01/omstd20.html#sec_names))
107    /// and, optionally, a unique prefix called a cdbase which is used to disambiguate multiple
108    /// Content Dictionaries of the same name. There are other properties of the symbol that are
109    /// not explicit in these fields but whose values may be obtained by inspecting the Content
110    /// Dictionary specified. These include the symbol definition, formal properties and examples
111    /// and, optionally, a role which is a restriction on where the symbol may appear in an
112    /// <span style="font-variant:small-caps;">OpenMath</span> object. The possible roles are described in
113    /// [Section 2.1.4](https://openmath.org/standard/om20-2019-07-01/omstd20.html#sec_roles).
114    ///
115    ///</div>
116    OMS = 5,
117
118    /** <div class="openmath">
119    If $A_1,...,A_n\;(n>0)$ are <span style="font-variant:small-caps;">OpenMath</span> objects, then
120    $\mathrm{application}(A_1,...,A_n)$ is an <span style="font-variant:small-caps;">OpenMath</span> application object.
121    We call $A_1$ the function and $A_2$ to $A_n$ the arguments.
122    </div> */
123    OMA = 6,
124
125    /** <div class="openmath">
126    If $B$ and $C$ are <span style="font-variant:small-caps;">OpenMath</span> objects, and $v_1,...,v_n\;(n\geq0)$
127    are <span style="font-variant:small-caps;">OpenMath</span> variables or attributed variables, then
128    $\mathrm{binding}(B,v_1,...,v_n,C)$ is an <span style="font-variant:small-caps;">OpenMath</span> binding object.
129    $B$ is called the binder, $v_1,...,v_n$ are called variable bindings, and
130    $C$ is called the body of the binding object above.
131    </div> */
132    OMBIND = 7,
133
134    /** <div class="openmath">
135    If $S$ is an <span style="font-variant:small-caps;">OpenMath</span> symbol and $A_1,...,A_n\;(n\geq0)$ are <span style="font-variant:small-caps;">OpenMath</span> objects or
136    derived <span style="font-variant:small-caps;">OpenMath</span> objects, then $\mathrm{error}(S,A_1,...,A_n)$ is an <span style="font-variant:small-caps;">OpenMath</span> error object.
137    </div> */
138    OME = 8,
139
140    /** <div class="openmath">
141    If $S_1,...,S_n$ are <span style="font-variant:small-caps;">OpenMath</span> symbols, and $A$ is an <span style="font-variant:small-caps;">OpenMath</span> object, and
142    $A_1,...,A_n\;(n>0)$ are <span style="font-variant:small-caps;">OpenMath</span> objects or derived <span style="font-variant:small-caps;">OpenMath</span> objects, then
143    $\mathrm{attribution}(A,S_1\;A_1,...,S_n\;A_n)$ is an <span style="font-variant:small-caps;">OpenMath</span> attribution object. We call
144    $A$ the attributed object, the $S_i$ the keys, and the $A_i$ the attribute values.
145    </div> */
146    OMATTR = 9,
147
148    /** <div class="openmath">
149    If $A$ is not an <span style="font-variant:small-caps;">OpenMath</span> object, then $\mathrm{foreign}(A)$ is an <span style="font-variant:small-caps;">OpenMath</span> foreign object.
150    An <span style="font-variant:small-caps;">OpenMath</span> foreign object may optionally have an encoding field which describes how its
151    contents should be interpreted.
152    </div> */
153    OMFOREIGN = 10,
154
155    /** <div class="openmath">
156    <span style="font-variant:small-caps;">OpenMath</span> integers, symbols, variables, floating point numbers, character strings, bytearrays,
157    applications, binding, attributions, error, and foreign objects can also be encoded as an empty
158    OMR element with an href attribute whose value is the value of a URI referencing an id attribute of an
159    <span style="font-variant:small-caps;">OpenMath</span> object of that type. The <span style="font-variant:small-caps;">OpenMath</span> element represented by this OMR reference is a copy of the
160    <span style="font-variant:small-caps;">OpenMath</span> element referenced href attribute. Note that this copy is structurally equal, but not
161    identical to the element referenced. These URI references will often be relative, in which case they
162    are resolved using the base URI of the document containing the <span style="font-variant:small-caps;">OpenMath</span>.
163    </div> */
164    OMR = 11,
165}
166
167/// Enum representing all possible OᴘᴇɴMᴀᴛʜ objects.
168///
169/// This enum encompasses the complete OᴘᴇɴMᴀᴛʜ object model, providing variants
170/// for each type of mathematical object that can be represented in <span style="font-variant:small-caps;">OpenMath</span>.
171///
172/// Note that we add `attributes` to each variant rather than having a separate
173/// [`OMATTR`](OMKind::OMATTR) case; that is to avoid having to deal with nested
174/// `OMATTR(OMATTR(OMATTR(...` terms or having to make the grammar significantly
175/// more complicated.
176///
177///<div class="openmath">
178/// OᴘᴇɴMᴀᴛʜ objects are built recursively as follows.
179/// </div>
180#[derive(Debug, Clone, PartialEq, Eq, Hash)]
181#[repr(u8)]
182pub enum OpenMath<'om> {
183    /** <div class="openmath">
184    Integers in the mathematical sense, with no predefined range.
185    They are “infinite precision” integers (also called “bignums” in computer algebra).
186    </div> */
187    OMI {
188        int: Int<'om>,
189        attributes: Vec<Attr<'om, OMMaybeForeign<'om, Self>>>,
190    } = OMKind::OMI as _,
191
192    /** <div class="openmath">
193    Double precision floating-point numbers following the IEEE 754-1985 standard.
194    </div> */
195    OMF {
196        float: ordered_float::OrderedFloat<f64>,
197        attributes: Vec<Attr<'om, OMMaybeForeign<'om, Self>>>,
198    } = OMKind::OMF as _,
199
200    /** <div class="openmath">
201    A Unicode Character string. This also corresponds to “characters” in XML.
202    </div> */
203    OMSTR {
204        string: Cow<'om, str>,
205        attributes: Vec<Attr<'om, OMMaybeForeign<'om, Self>>>,
206    } = OMKind::OMSTR as _,
207
208    /** <div class="openmath">
209    A sequence of bytes.
210    </div> */
211    OMB {
212        bytes: Cow<'om, [u8]>,
213        attributes: Vec<Attr<'om, OMMaybeForeign<'om, Self>>>,
214    } = OMKind::OMB as _,
215
216    ///<div class="openmath">
217    ///
218    /// A Variable must have a name which is a sequence of characters matching a regular
219    /// expression, as described in [Section 2.3](https://openmath.org/standard/om20-2019-07-01/omstd20.html#sec_names).
220    ///
221    ///</div>
222    ///
223    ///(Note: We do not enforce that names are valid XML names;)
224    OMV {
225        name: Cow<'om, str>,
226        attributes: Vec<Attr<'om, OMMaybeForeign<'om, Self>>>,
227    } = OMKind::OMV as _,
228
229    /** <div class="openmath">
230    A Symbol encodes three fields of information, a symbol name, a Content Dictionary name,
231    and (optionally) a Content Dictionary base URI, The name of a symbol is a sequence of
232    characters matching the regular expression described in Section 2.3.
233    The Content Dictionary is the location of the definition of the symbol, consisting of a
234    name (a sequence of characters matching the regular expression described in Section 2.3)
235    and, optionally, a unique prefix called a cdbase which is used to disambiguate multiple
236    Content Dictionaries of the same name. There are other properties of the symbol that are
237    not explicit in these fields but whose values may be obtained by inspecting the Content
238    Dictionary specified. These include the symbol definition, formal properties and examples
239    and, optionally, a role which is a restriction on where the symbol may appear in an
240    <span style="font-variant:small-caps;">OpenMath</span> object. The possible roles are described in Section 2.1.4.
241    </div> */
242    OMS {
243        cd: Cow<'om, str>,
244        name: Cow<'om, str>,
245        cdbase: Option<Cow<'om, str>>,
246        attributes: Vec<Attr<'om, OMMaybeForeign<'om, Self>>>,
247    } = OMKind::OMS as _,
248
249    /** <div class="openmath">
250    If $A_1,...,A_n\;(n>0)$ are <span style="font-variant:small-caps;">OpenMath</span> objects, then
251    $\mathrm{application}(A_1,...,A_n)$ is an <span style="font-variant:small-caps;">OpenMath</span> application object.
252    We call $A_1$ the function and $A_2$ to $A_n$ the arguments.
253    </div> */
254    OMA {
255        applicant: Box<Self>,
256        arguments: Vec<Self>,
257        attributes: Vec<Attr<'om, OMMaybeForeign<'om, Self>>>,
258    } = OMKind::OMA as _,
259
260    /** <div class="openmath">
261    If $S$ is an <span style="font-variant:small-caps;">OpenMath</span> symbol and $A_1,...,A_n\;(n\geq0)$ are <span style="font-variant:small-caps;">OpenMath</span> objects or
262    derived <span style="font-variant:small-caps;">OpenMath</span> objects, then $\mathrm{error}(S,A_1,...,A_n)$ is an <span style="font-variant:small-caps;">OpenMath</span> error object.
263    </div> */
264    OME {
265        cd: Cow<'om, str>,
266        name: Cow<'om, str>,
267        cdbase: Option<Cow<'om, str>>,
268        arguments: Vec<OMMaybeForeign<'om, Self>>,
269        attributes: Vec<Attr<'om, OMMaybeForeign<'om, Self>>>,
270    } = OMKind::OME as _,
271
272    /** <div class="openmath">
273    If $B$ and $C$ are <span style="font-variant:small-caps;">OpenMath</span> objects, and $v_1,...,v_n\;(n\geq0)$
274    are <span style="font-variant:small-caps;">OpenMath</span> variables or attributed variables, then
275    $\mathrm{binding}(B,v_1,...,v_n,C)$ is an <span style="font-variant:small-caps;">OpenMath</span> binding object.
276    $B$ is called the binder, $v_1,...,v_n$ are called variable bindings, and
277    $C$ is called the body of the binding object above.
278    </div> */
279    OMBIND {
280        binder: Box<Self>,
281        variables: Vec<BoundVariable<'om>>,
282        object: Box<Self>,
283        attributes: Vec<Attr<'om, OMMaybeForeign<'om, Self>>>,
284    } = OMKind::OMBIND as _,
285}
286
287/// A bound variable in an [`OMBIND`](OpenMath::OMBIND)
288#[derive(Debug, Clone, PartialEq, Eq, Hash)]
289pub struct BoundVariable<'om> {
290    /// the name of the variable
291    pub name: Cow<'om, str>,
292    /// (optional) attributes of the variable;
293    /// this Vec being non-empty represents the case `OMATTR(...,OMV(name))`
294    pub attributes: Vec<Attr<'om, OMMaybeForeign<'om, OpenMath<'om>>>>,
295}
296impl ser::BindVar for &BoundVariable<'_> {
297    #[inline]
298    fn attrs(&self) -> impl ExactSizeIterator<Item: ser::OMAttr> {
299        self.attributes.iter()
300    }
301    #[inline]
302    fn name(&self) -> impl std::fmt::Display {
303        &*self.name
304    }
305}
306
307/// An attribute in an [`OMATTR`](OMKind::OMATTR)
308///
309/// Generic over the attribute value, so it can be used in [OpenMath] and [OM]
310#[derive(Debug, Clone, PartialEq, Eq, Hash)]
311pub struct Attr<'o, I> {
312    pub cdbase: Option<Cow<'o, str>>,
313    pub cd: Cow<'o, str>,
314    pub name: Cow<'o, str>,
315    pub value: I,
316}
317impl<I> ser::OMAttr for &Attr<'_, I>
318where
319    for<'a> &'a I: ser::OMOrForeign,
320{
321    #[inline]
322    fn symbol(&self) -> impl AsOMS {
323        ser::Uri {
324            cdbase: self.cdbase.as_deref(),
325            cd: &self.cd,
326            name: &self.name,
327        }
328    }
329    fn value(&self) -> impl ser::OMOrForeign {
330        &self.value
331    }
332}
333
334/// Either an [OpenMath Expression](OpenMath) or an [`OMFOREIGN`](OMKind::OMFOREIGN).
335///
336/// Generic over the non-OMFOREIGN-case, so it can be used in both [OpenMath] and [OM]
337#[derive(Debug, Clone, PartialEq, Eq, Hash)]
338pub enum OMMaybeForeign<'o, I> {
339    // An OMExpr
340    OM(I),
341
342    /** <div class="openmath">
343    If $A$ is not an OpenMath object, then $\mathrm{foreign}(A)$ is an OpenMath foreign object.
344    An OpenMath foreign object may optionally have an encoding field which describes how its
345    contents should be interpreted.
346    </div> */
347    Foreign {
348        encoding: Option<Cow<'o, str>>,
349        value: Cow<'o, str>,
350    },
351}
352
353impl<I: ser::OMSerializable> ser::OMOrForeign for &OMMaybeForeign<'_, I> {
354    /// converts this into an `Either`(crate::either::Either)
355    fn om_or_foreign(
356        self,
357    ) -> crate::either::Either<
358        impl OMSerializable,
359        (Option<impl std::fmt::Display>, impl std::fmt::Display),
360    > {
361        match self {
362            OMMaybeForeign::OM(i) => either::Either::Left(i),
363            OMMaybeForeign::Foreign { encoding, value } => {
364                either::Either::Right((encoding.as_deref(), &**value))
365            }
366        }
367    }
368}
369
370impl ser::OMSerializable for OpenMath<'_> {
371    fn as_openmath<'s, S: ser::OMSerializer<'s>>(&self, serializer: S) -> Result<S::Ok, S::Err> {
372        struct NoAttrs<'s, 'o>(&'s OpenMath<'o>);
373        impl ser::OMSerializable for NoAttrs<'_, '_> {
374            fn as_openmath<'s, S: ser::OMSerializer<'s>>(
375                &self,
376                serializer: S,
377            ) -> Result<S::Ok, S::Err> {
378                match self.0 {
379                    OpenMath::OMI { int, .. } => int.as_openmath(serializer),
380                    OpenMath::OMF { float, .. } => float.0.as_openmath(serializer),
381                    OpenMath::OMSTR { string, .. } => string.as_openmath(serializer),
382                    OpenMath::OMB { bytes, .. } => bytes.as_openmath(serializer),
383                    OpenMath::OMV { name, .. } => ser::Omv(name).as_openmath(serializer),
384                    OpenMath::OMS {
385                        cd, name, cdbase, ..
386                    } => ser::Uri {
387                        cdbase: cdbase.as_deref(),
388                        name,
389                        cd,
390                    }
391                    .as_oms()
392                    .as_openmath(serializer),
393                    OpenMath::OMA {
394                        applicant,
395                        arguments,
396                        ..
397                    } => serializer.oma(&**applicant, arguments.iter()),
398                    OpenMath::OME {
399                        cd,
400                        name,
401                        cdbase,
402                        arguments,
403                        ..
404                    } => serializer.ome(
405                        &ser::Uri {
406                            cdbase: cdbase.as_deref(),
407                            cd,
408                            name,
409                        },
410                        arguments.iter(),
411                    ),
412                    OpenMath::OMBIND {
413                        binder,
414                        variables,
415                        object,
416                        ..
417                    } => serializer.ombind(&**binder, variables.iter(), &**object),
418                }
419            }
420        }
421        match self {
422            Self::OMI { attributes, .. }
423            | Self::OMF { attributes, .. }
424            | Self::OMSTR { attributes, .. }
425            | Self::OMB { attributes, .. }
426            | Self::OMV { attributes, .. }
427            | Self::OMS { attributes, .. }
428            | Self::OMA { attributes, .. }
429            | Self::OME { attributes, .. }
430            | Self::OMBIND { attributes, .. }
431                if !attributes.is_empty() =>
432            {
433                serializer.omattr(attributes.iter(), NoAttrs(self))
434            }
435            _ => NoAttrs(self).as_openmath(serializer),
436        }
437    }
438}
439
440impl<'o> de::OMDeserializable<'o> for OpenMath<'o> {
441    type Ret = Self;
442    type Err = Infallible;
443    #[allow(clippy::too_many_lines)]
444    fn from_openmath(om: OM<'o, Self>, cdbase: &str) -> Result<Self, Self::Err>
445    where
446        Self: Sized,
447    {
448        /*fn do_attrs<'o>(
449            attrs: Vec<de::OMAttr<'o, OpenMath<'o>>>,
450        ) -> Vec<Attr<'o, OMMaybeForeign<'o, OpenMath<'o>>>> {
451            attrs
452                .into_iter()
453                .map(|a| Attr {
454                    cdbase: a.cdbase,
455                    cd: a.cd,
456                    name: a.name,
457                    value: match a.value {
458                        either::Either::Left(a) => OMMaybeForeign::OM(a),
459                        either::Either::Right(OMMaybeForeign::Foreign { encoding, value }) => {
460                            OMMaybeForeign::Foreign { encoding, value }
461                        }
462                        either::Either::Right(OMMaybeForeign::OM(_)) => {
463                            unreachable!("by construction")
464                        }
465                    },
466                })
467                .collect()
468        }*/
469        Ok(match om {
470            OM::OMI { int, attrs } => Self::OMI {
471                int,
472                attributes: attrs,
473            },
474            OM::OMF { float, attrs } => Self::OMF {
475                float: float.into(),
476                attributes: attrs,
477            },
478            OM::OMSTR { string, attrs } => Self::OMSTR {
479                string,
480                attributes: attrs,
481            },
482            OM::OMB { bytes, attrs } => Self::OMB {
483                bytes,
484                attributes: attrs,
485            },
486            OM::OMV { name, attrs } => Self::OMV {
487                name,
488                attributes: attrs,
489            },
490            OM::OMS { cd, name, attrs } => Self::OMS {
491                cd,
492                name,
493                cdbase: Some(Cow::Owned(cdbase.to_string())),
494                attributes: attrs,
495            },
496            OM::OMA {
497                applicant,
498                arguments,
499                attrs,
500            } => Self::OMA {
501                applicant: Box::new(applicant),
502                arguments: arguments.into_iter().collect(),
503                attributes: attrs,
504            },
505            OM::OMBIND {
506                binder,
507                variables,
508                object,
509                attrs,
510            } => Self::OMBIND {
511                binder: Box::new(binder),
512                variables: variables
513                    .into_iter()
514                    .map(|(name, a)| BoundVariable {
515                        name,
516                        attributes: a,
517                    })
518                    .collect(),
519                object: Box::new(object),
520                attributes: attrs,
521            },
522            OM::OME {
523                cdbase,
524                cd,
525                name,
526                arguments,
527                attrs,
528            } => Self::OME {
529                cd,
530                name,
531                cdbase,
532                arguments,
533                attributes: attrs,
534            },
535        })
536    }
537}
538
539#[cfg(all(test, feature = "xml", feature = "serde"))]
540#[test]
541#[allow(clippy::too_many_lines)]
542fn roundtrip() {
543    use OpenMath::*;
544    const XML: &str = r#"<OMOBJ version="2.0" xmlns="http://www.openmath.org/OpenMath">
545      <OMBIND>
546        <OMS cdbase="http://openmath.org/cd" cd="fns1" name="lambda"/>
547        <OMBVAR>
548          <OMV name="x"/>
549          <OMATTR>
550            <OMATP>
551              <OMS cdbase="http://openmath.org/cd" cd="nope" name="type"/>
552              <OMS cdbase="http://openmath.org/cd" cd="arith1" name="real"/>
553            </OMATP>
554          <OMV name="y"/>
555          </OMATTR>
556        </OMBVAR>
557        <OMA>
558          <OMS cdbase="http://my.namespace" cd="utils" name="either"/>
559          <OMA>
560            <OMS cdbase="http://openmath.org/cd" cd="arith1" name="plus"/>
561            <OMI>128</OMI>
562            <OMATTR>
563              <OMATP>
564                <OMS cdbase="http://openmath.org/cd" cd="nope" name="type"/>
565                <OMFOREIGN>
566                  <MOOT>this is an opaque OMFOREIGN</MOOT>
567                </OMFOREIGN>
568              </OMATP>
569            <OMI>-1234567898765432123456789</OMI>
570            </OMATTR>
571            <OMF dec="3.88988"/>
572            <OMSTR>some number</OMSTR>
573            <OMV name="x"/>
574          </OMA>
575          <OME>
576            <OMS cdbase="http://openmath.org" cd="error" name="unhandled_arithmetics"/>
577            <OMFOREIGN encoding="application/nonsense">
578              ERROAR CODE MOO
579            </OMFOREIGN>
580          </OME>
581        </OMA>
582      </OMBIND>
583    </OMOBJ>"#;
584    const JSON: &str = r#"{
585      "kind": "OMOBJ",
586      "openmath": "2.0",
587      "object": {
588        "kind": "OMBIND",
589        "binder": {
590          "kind": "OMS",
591          "cdbase": "http://openmath.org/cd",
592          "cd": "fns1",
593          "name": "lambda"
594        },
595        "variables": [
596          {
597            "kind": "OMV",
598            "name": "x"
599          },
600          {
601            "kind": "OMATTR",
602            "attributes": [
603              [
604                {
605                  "kind": "OMS",
606                  "cdbase": "http://openmath.org/cd",
607                  "cd": "nope",
608                  "name": "type"
609                },
610                {
611                  "kind": "OMS",
612                  "cdbase": "http://openmath.org/cd",
613                  "cd": "arith1",
614                  "name": "real"
615                }
616              ]
617            ],
618            "object": {
619              "kind": "OMV",
620              "name": "y"
621            }
622          }
623        ],
624        "object": {
625          "kind": "OMA",
626          "applicant": {
627            "kind": "OMS",
628            "cdbase": "http://my.namespace",
629            "cd": "utils",
630            "name": "either"
631          },
632          "arguments": [
633            {
634              "kind": "OMA",
635              "applicant": {
636                "kind": "OMS",
637                "cdbase": "http://openmath.org/cd",
638                "cd": "arith1",
639                "name": "plus"
640              },
641              "arguments": [
642                {
643                  "kind": "OMI",
644                  "integer": 128
645                },
646                {
647                  "kind": "OMATTR",
648                  "attributes": [
649                    [
650                      {
651                        "kind": "OMS",
652                        "cdbase": "http://openmath.org/cd",
653                        "cd": "nope",
654                        "name": "type"
655                      },
656                      {
657                        "kind": "OMFOREIGN",
658                        "foreign": "<MOOT>this is an opaque OMFOREIGN</MOOT>"
659                      }
660                    ]
661                  ],
662                  "object": {
663                    "kind": "OMI",
664                    "integer": -1234567898765432123456789
665                  }
666                },
667                {
668                  "kind": "OMF",
669                  "float": 3.88988
670                },
671                {
672                  "kind": "OMSTR",
673                  "string": "some number"
674                },
675                {
676                  "kind": "OMV",
677                  "name": "x"
678                }
679              ]
680            },
681            {
682              "kind": "OME",
683              "error": {
684                "kind": "OMS",
685                "cdbase": "http://openmath.org",
686                "cd": "error",
687                "name": "unhandled_arithmetics"
688              },
689              "arguments": [
690                {
691                  "kind": "OMFOREIGN",
692                  "foreign": "ERROAR CODE MOO",
693                  "encoding": "application/nonsense"
694                }
695              ]
696            }
697          ]
698        }
699      }
700    }"#;
701
702    let om = OMBIND {
703        binder: Box::new(OMS {
704            cdbase: Some(Cow::Borrowed("http://openmath.org/cd")),
705            cd: Cow::Borrowed("fns1"),
706            name: Cow::Borrowed("lambda"),
707            attributes: Vec::new(),
708        }),
709        variables: vec![
710            BoundVariable {
711                name: Cow::Borrowed("x"),
712                attributes: Vec::new(),
713            },
714            BoundVariable {
715                name: Cow::Borrowed("y"),
716                attributes: vec![Attr {
717                    cdbase: Some(Cow::Borrowed("http://openmath.org/cd")),
718                    cd: Cow::Borrowed("nope"),
719                    name: Cow::Borrowed("type"),
720                    value: OMMaybeForeign::OM(OMS {
721                        cdbase: Some(Cow::Borrowed("http://openmath.org/cd")),
722                        cd: Cow::Borrowed("arith1"),
723                        name: Cow::Borrowed("real"),
724                        attributes: Vec::new(),
725                    }),
726                }],
727            },
728        ],
729        object: Box::new(OMA {
730            applicant: Box::new(OMS {
731                cd: Cow::Borrowed("utils"),
732                name: Cow::Borrowed("either"),
733                cdbase: Some(Cow::Borrowed("http://my.namespace")),
734                attributes: Vec::new(),
735            }),
736            arguments: vec![
737                OMA {
738                    applicant: Box::new(OMS {
739                        cdbase: Some(Cow::Borrowed("http://openmath.org/cd")),
740                        cd: Cow::Borrowed("arith1"),
741                        name: Cow::Borrowed("plus"),
742                        attributes: Vec::new(),
743                    }),
744                    arguments: vec![
745                        OMI {
746                            int: 128.into(),
747                            attributes: Vec::new(),
748                        },
749                        OMI {
750                            int: Int::new("-1234567898765432123456789").expect("works"),
751                            attributes: vec![Attr {
752                                cdbase: Some(Cow::Borrowed("http://openmath.org/cd")),
753                                cd: Cow::Borrowed("nope"),
754                                name: Cow::Borrowed("type"),
755                                value: OMMaybeForeign::Foreign {
756                                    encoding: None,
757                                    value: Cow::Borrowed(
758                                        "<MOOT>this is an opaque OMFOREIGN</MOOT>",
759                                    ),
760                                },
761                            }],
762                        },
763                        OMF {
764                            float: 3.88988.into(),
765                            attributes: Vec::new(),
766                        },
767                        OMSTR {
768                            string: Cow::Borrowed("some number"),
769                            attributes: Vec::new(),
770                        },
771                        OMV {
772                            name: Cow::Borrowed("x"),
773                            attributes: Vec::new(),
774                        },
775                    ],
776                    attributes: Vec::new(),
777                },
778                OME {
779                    cdbase: Some(Cow::Borrowed("http://openmath.org")),
780                    cd: Cow::Borrowed("error"),
781                    name: Cow::Borrowed("unhandled_arithmetics"),
782                    arguments: vec![OMMaybeForeign::Foreign {
783                        encoding: Some(Cow::Borrowed("application/nonsense")),
784                        value: Cow::Borrowed("ERROAR CODE MOO"),
785                    }],
786                    attributes: Vec::new(),
787                },
788            ],
789            attributes: Vec::new(),
790        }),
791        attributes: Vec::new(),
792    };
793
794    let json = serde_json::to_string_pretty(&ser::OMObject(&om)).expect("works");
795    assert_eq!(
796        json.replace(|c: char| c.is_ascii_whitespace(), ""),
797        JSON.replace(|c: char| c.is_ascii_whitespace(), "")
798    );
799    let nom = serde_json::from_str::<'_, de::OMObject<OpenMath<'_>>>(&json)
800        .expect("works")
801        .into_inner();
802    assert_eq!(om, nom);
803    let xml = ser::OMObject(&nom).xml(true, true).to_string();
804    assert_eq!(
805        xml.replace(|c: char| c.is_ascii_whitespace(), ""),
806        XML.replace(|c: char| c.is_ascii_whitespace(), "")
807    );
808    let nom = de::OMObject::<OpenMath<'_>>::from_openmath_xml(&xml).expect("works");
809    assert_eq!(om, nom);
810}