microformats_types/
lib.rs

1use regex::Regex;
2use serde::{
3    de::{self, MapAccess, Visitor},
4    ser::{SerializeMap, SerializeSeq, SerializeStruct},
5    Deserialize, Deserializer, Serializer,
6};
7use std::{
8    cmp::Ordering,
9    collections::BTreeMap,
10    convert::{Infallible, TryFrom, TryInto},
11    fmt::{self, Debug},
12    marker::Copy,
13    ops::Deref,
14    str::FromStr,
15};
16pub use url::Url;
17pub mod temporal;
18
19#[cfg(test)]
20mod test;
21
22/// A helper type for representing a list of [property values][PropertyValue].
23pub type NodeList = Vec<PropertyValue>;
24
25/// A helper type for representing a map of named [property values][PropertyValue].
26pub type Properties = std::collections::BTreeMap<String, NodeList>;
27
28/// A concrete reference of the supported Microformats class by this library.
29#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
30#[serde(rename_all = "kebab-case")]
31pub enum KnownClass {
32    /// Represents a general container for content.
33    /// https://microformats.org/wiki/h-entry
34    #[serde(alias = "h-entry")]
35    Entry,
36
37    /// Represents a rewference to content at a different location.
38    /// https://microformats.org/wiki/h-cite
39    #[serde(alias = "h-cite")]
40    Cite,
41
42    /// Represents a contact card or vCard.
43    /// https://microformats.org/wiki/h-card
44    #[serde(alias = "h-card")]
45    Card,
46
47    /// https://microformats.org/wiki/h-feed
48    #[serde(alias = "h-feed")]
49    Feed,
50
51    /// https://microformats.org/wiki/h-event
52    #[serde(alias = "h-event")]
53    Event,
54
55    /// https://microformats.org/wiki/h-product
56    #[serde(alias = "h-product")]
57    Product,
58
59    /// https://microformats.org/wiki/h-adr
60    #[serde(alias = "h-adr")]
61    Adr,
62
63    /// https://microformats.org/wiki/h-geo
64    #[serde(alias = "h-geo")]
65    Geo,
66
67    /// https://microformats.org/wiki/h-resume
68    #[serde(alias = "h-resume")]
69    Resume,
70
71    /// https://microformats.org/wiki/h-review
72    #[serde(alias = "h-review")]
73    Review,
74
75    /// https://microformats.org/wiki/h-recipe
76    #[serde(alias = "h-recipe")]
77    Recipe,
78}
79
80#[derive(thiserror::Error, Debug)]
81pub enum Error {
82    #[error("The type {0:?} is not a known Microformats class supported by this library.")]
83    NotKnownClass(String),
84
85    #[error("JSON: {0}")]
86    JSON(#[from] serde_json::Error),
87
88    #[error("The provided JSON value was not an object.")]
89    NotAnObject,
90
91    #[error("Missing property {0:?} when converting from JSON.")]
92    JsonObjectMissingProperty(String),
93
94    #[error(transparent)]
95    Temporal(#[from] temporal::Error),
96}
97
98impl PartialEq for Error {
99    fn eq(&self, other: &Self) -> bool {
100        self.to_string().eq(&other.to_string())
101    }
102}
103
104impl Eq for Error {}
105
106impl FromStr for KnownClass {
107    type Err = Error;
108
109    /// Converts this concrete known class into a string.
110    fn from_str(s: &str) -> Result<Self, Self::Err> {
111        match s.to_ascii_lowercase().as_str() {
112            "h-entry" | "entry" => Ok(Self::Entry),
113            "h-cite" | "cite" => Ok(Self::Cite),
114            "h-card" | "card" => Ok(Self::Card),
115            "h-event" | "event" => Ok(Self::Event),
116            "h-product" | "product" => Ok(Self::Product),
117            "h-feed" | "feed" => Ok(Self::Feed),
118            "h-geo" | "geo" => Ok(Self::Geo),
119            "h-adr" | "adr" => Ok(Self::Adr),
120            "h-recipe" | "recipe" => Ok(Self::Recipe),
121            _ => Err(Error::NotKnownClass(s.to_string())),
122        }
123    }
124}
125
126impl std::fmt::Display for KnownClass {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        f.write_str(match self {
129            KnownClass::Entry => "h-entry",
130            KnownClass::Cite => "h-cite",
131            KnownClass::Card => "h-card",
132            KnownClass::Feed => "h-feed",
133            KnownClass::Event => "h-event",
134            KnownClass::Product => "h-product",
135            KnownClass::Adr => "h-adr",
136            KnownClass::Geo => "h-geo",
137            KnownClass::Resume => "h-resume",
138            KnownClass::Review => "h-review",
139            KnownClass::Recipe => "h-recipe",
140        })
141    }
142}
143
144/// Represents a Microformat class.
145/// https://microformats.org/wiki/Category:Draft_Specifications
146#[derive(Debug, Clone, Eq)]
147pub enum Class {
148    /// Represents a known Microformat class (h-entry, h-card, etc).
149    Known(KnownClass),
150
151    /// Represents a class that's not spec-compliant (h-cookies, h-monster, etc).
152    Custom(String),
153}
154
155impl PartialOrd for Class {
156    fn partial_cmp(&self, other: &Self) -> std::option::Option<std::cmp::Ordering> {
157        self.to_string().partial_cmp(&other.to_string())
158    }
159}
160
161impl PartialEq for Class {
162    fn eq(&self, other: &Self) -> bool {
163        self.to_string().eq(&other.to_string())
164    }
165}
166
167impl FromStr for Class {
168    type Err = Infallible;
169
170    /// Parses a string as a Microformat class.
171    ///
172    /// # Examples
173    /// ```
174    /// # use std::str::FromStr;
175    /// # use microformats_types::{Class, KnownClass};
176    ///
177    /// assert_eq!(Class::from_str("entry"), Ok(Class::Known(KnownClass::Entry)));
178    /// assert_eq!(Class::from_str("h-card"), Ok(Class::Known(KnownClass::Card)));
179    /// assert_eq!(Class::from_str("x-plane"), Ok(Class::Custom("x-plane".to_string())));
180    /// ```
181    fn from_str(class_str: &str) -> Result<Self, Self::Err> {
182        KnownClass::from_str(class_str)
183            .or_else(|_| KnownClass::from_str(&class_str.replace("h-", "")))
184            .map(Class::Known)
185            .or_else(|_| Ok(Self::Custom(class_str.trim_start_matches("h-").to_string())))
186    }
187}
188
189impl std::fmt::Display for Class {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        match self {
192            Self::Known(class) => f.write_fmt(format_args!("{}", class)),
193            Self::Custom(class) => f.write_fmt(format_args!("h-{}", class)),
194        }
195    }
196}
197
198impl serde::Serialize for Class {
199    /// Serializes this class reference into a string.
200    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
201    where
202        S: serde::Serializer,
203    {
204        serializer.serialize_str(self.to_string().as_str())
205    }
206}
207
208impl Class {
209    /// Checks if this class is a recognized one by the Microformats spec.
210    pub fn is_recognized(&self) -> bool {
211        !matches!(self, Self::Custom(_))
212    }
213}
214
215struct ClassVisitor;
216
217impl Visitor<'_> for ClassVisitor {
218    type Value = Class;
219
220    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
221        formatter.write_str(
222            "a string that follows Microformats class conventions of being prefixed by 'h-'",
223        )
224    }
225
226    fn visit_str<E>(self, class_str: &str) -> Result<Self::Value, E>
227    where
228        E: serde::de::Error,
229    {
230        Class::from_str(class_str).map_err(|e| E::custom(e.to_string()))
231    }
232}
233
234impl<'de> serde::Deserialize<'de> for Class {
235    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
236    where
237        D: serde::Deserializer<'de>,
238    {
239        deserializer.deserialize_string(ClassVisitor)
240    }
241}
242
243impl From<KnownClass> for Class {
244    fn from(kc: KnownClass) -> Self {
245        Self::Known(kc)
246    }
247}
248
249fn short_circuit_url_deserialization<'de, D>(d: D) -> Result<Url, D::Error>
250where
251    D: serde::Deserializer<'de>,
252{
253    let string_form = String::deserialize(d)?;
254    let url_form = Url::parse(&string_form).map_err(serde::de::Error::custom)?;
255
256    if url_form.as_str() != string_form {
257        // This is called in the event a string happens to match the parsing of a URL but doesn't
258        // convert back into one.
259        Err(serde::de::Error::custom(
260            "This string doesn't represent a valid URL despite looking like one.",
261        ))
262    } else {
263        Ok(url_form)
264    }
265}
266
267fn short_circuit_plain_text_deserialization<'de, D>(d: D) -> Result<String, D::Error>
268where
269    D: serde::Deserializer<'de>,
270{
271    let string_form = String::deserialize(d)?;
272
273    Url::from_str(&string_form)
274        .map_err(serde::de::Error::custom)
275        .map(|u| u.as_str().to_string())
276        .and_then(|u| {
277            if u == string_form && !u.contains(|c: char| c.is_whitespace()) && !u.contains('\n') {
278                Err(serde::de::Error::invalid_type(
279                    de::Unexpected::Other("URL"),
280                    &"plain 'ol string",
281                ))
282            } else {
283                Ok(string_form.clone())
284            }
285        })
286        .or_else(|r: D::Error| {
287            if r.to_string().starts_with("invalid type: URL") {
288                Err(r)
289            } else {
290                temporal::Value::from_str(&string_form)
291                    .map_err(serde::de::Error::custom)
292                    .map(|u| u.to_string())
293                    .and_then(|u| {
294                        if u == string_form {
295                            Err(serde::de::Error::invalid_type(
296                                de::Unexpected::Str("temporal data"),
297                                &"plain 'ol string",
298                            ))
299                        } else {
300                            Ok(string_form.clone())
301                        }
302                    })
303            }
304        })
305        .or_else(|r: D::Error| {
306            if r.to_string().starts_with("invalid type: URL")
307                || r.to_string().contains("temporal data")
308            {
309                Err(r)
310            } else {
311                Ok(string_form)
312            }
313        })
314}
315
316/// Represents the multiple forms in which a property's value is represented.
317#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
318#[serde(untagged, rename_all = "kebab-case")]
319pub enum PropertyValue {
320    /// Represents plain-text, usually stored in properties like "p-name". It can be
321    /// displayed directly to the user agent.
322    /// <https://microformats.org/wiki/microformats2-parsing#parsing_a_p-_property>
323    #[serde(deserialize_with = "short_circuit_plain_text_deserialization")]
324    Plain(String),
325
326    /// Represents a linked value.
327    /// It's a companion of `Plain`; meant to make detecting of absolute URLs easier.
328    #[serde(deserialize_with = "short_circuit_url_deserialization")]
329    Url(Url),
330
331    /// Represents a compatible datetime parser defined by <https://microformats.org/wiki/value-class-pattern#Date_and_time_parsing>
332    /// <https://microformats.org/wiki/microformats2-parsing#parsing_a_dt-_property>
333    Temporal(temporal::Value),
334
335    Fragment(Fragment),
336
337    /// Represents a structured form of information presented by Microformats as an
338    /// `Item`. This will usually require a bit more processing before showing it.
339    /// <https://microformats.org/wiki/microformats2-parsing#parsing_a_u-_property>
340    #[serde(with = "referenced_item")]
341    Item(Item),
342
343    Image(Image),
344}
345
346impl From<Item> for PropertyValue {
347    fn from(item: Item) -> Self {
348        PropertyValue::Item(item)
349    }
350}
351
352/// Represents markup and the plain text representation accompanying it.
353/// <https://microformats.org/wiki/microformats2-parsing#parsing_a_e-_property>
354#[derive(
355    Debug, Clone, PartialEq, Eq, serde::Serialize, Default, serde::Deserialize, PartialOrd, Ord,
356)]
357#[serde(rename_all = "kebab-case")]
358pub struct Fragment {
359    /// Provides the HTML representation of this fragment.
360    #[serde(skip_serializing_if = "String::is_empty")]
361    pub html: String,
362
363    /// Provides the plain-text form of the HTML.
364    #[serde(default, skip_serializing_if = "String::is_empty")]
365    pub value: String,
366
367    /// Provides the language that this fragment is represented in.
368    #[serde(default, skip_serializing_if = "Option::is_none")]
369    pub lang: Option<String>,
370
371    /// A list of direct links extracted from the HTML.
372    #[serde(skip)]
373    pub links: Vec<String>,
374}
375
376impl Fragment {
377    /// Determines if this fragment has any information represented in it.
378    ///
379    /// The presence of HTML does not determine if this fragment is empty; a
380    /// fragment can be defined with no HTML (meaning the HTML would implictly look the same).
381    pub fn is_empty(&self) -> bool {
382        self.value.is_empty()
383    }
384
385    pub fn links(&self) -> &[String] {
386        &self.links
387    }
388}
389
390/// Represents the structured form of an image.
391/// <https://microformats.org/wiki/microformats2-parsing#parse_an_img_element_for_src_and_alt>
392#[derive(Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, PartialOrd, Ord)]
393#[serde(rename_all = "kebab-case")]
394pub struct Image {
395    pub value: Url,
396
397    #[serde(default)]
398    pub alt: Option<String>,
399}
400
401impl std::fmt::Debug for Image {
402    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403        f.debug_struct("Image")
404            .field("value", &self.value.to_string())
405            .field("alt", &self.alt)
406            .finish()
407    }
408}
409
410mod referenced_item {
411
412    use super::*;
413
414    type Value = Item;
415
416    struct ItemVisitor;
417
418    #[derive(serde::Deserialize, Debug)]
419    #[serde(field_identifier, rename_all = "kebab-case")]
420    enum ItemDeserializationFields {
421        Children,
422        Value,
423        Id,
424        Properties,
425        r#Type,
426    }
427
428    impl<'de> Visitor<'de> for ItemVisitor {
429        type Value = Value;
430        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
431            formatter.write_str("expecting null or an map representing an item")
432        }
433
434        fn visit_map<A>(self, mut item_map: A) -> Result<Self::Value, A::Error>
435        where
436            A: MapAccess<'de>,
437        {
438            let mut children: Items = Default::default();
439            let mut value: Option<ValueKind> = Default::default();
440            let mut id: Option<String> = Default::default();
441            let mut types = Vec::new();
442            let mut properties = Properties::default();
443
444            while let Some(property) = item_map.next_key()? {
445                match property {
446                    ItemDeserializationFields::Children => {
447                        let new_items = item_map.next_value::<Vec<Item>>()?.into_iter();
448
449                        if children.is_empty() && new_items.len() > 0 {
450                            children = new_items.collect::<Vec<Item>>().into();
451                        } else {
452                            children.extend(new_items);
453                        }
454                    }
455                    ItemDeserializationFields::Value => {
456                        if value.is_none() {
457                            value = item_map.next_value::<Option<ValueKind>>()?;
458                        }
459                    }
460                    ItemDeserializationFields::Id => {
461                        if id.is_none() {
462                            id = item_map.next_value::<Option<String>>()?;
463                        }
464                    }
465                    ItemDeserializationFields::Type => {
466                        types.extend(item_map.next_value::<Vec<Class>>()?);
467                    }
468                    ItemDeserializationFields::Properties => {
469                        properties.extend(item_map.next_value::<BTreeMap<String, _>>()?);
470                    }
471                }
472            }
473
474            let item = Item {
475                r#type: types,
476                properties,
477                id,
478                value,
479                children,
480                ..Default::default()
481            };
482
483            Ok(item)
484        }
485    }
486
487    pub fn serialize<S>(item: &Value, serializer: S) -> Result<S::Ok, S::Error>
488    where
489        S: Serializer,
490    {
491        serializer.serialize_some(&Some(item))
492    }
493
494    pub fn deserialize<'de, D>(deserializer: D) -> Result<Value, D::Error>
495    where
496        D: Deserializer<'de>,
497    {
498        // TODO: Confirm that these are all of the top-level fields for an item.
499        deserializer.deserialize_struct(
500            "Item",
501            &["type", "properties", "id", "value", "children"],
502            ItemVisitor,
503        )
504    }
505}
506
507impl PropertyValue {
508    /// Determines if this node's internal value is empty
509    pub fn is_empty(&self) -> bool {
510        match self {
511            Self::Temporal(_) | Self::Url(_) | Self::Image(_) => false,
512            Self::Plain(s) => s.is_empty(),
513            Self::Fragment(f) => f.is_empty(),
514            Self::Item(i) => i.is_empty(),
515        }
516    }
517}
518
519impl From<Url> for PropertyValue {
520    fn from(u: Url) -> Self {
521        Self::Url(u)
522    }
523}
524
525impl From<temporal::Stamp> for PropertyValue {
526    fn from(t: temporal::Stamp) -> Self {
527        Self::Temporal(temporal::Value::Timestamp(t))
528    }
529}
530
531impl From<temporal::Duration> for PropertyValue {
532    fn from(t: temporal::Duration) -> Self {
533        Self::Temporal(temporal::Value::Duration(t))
534    }
535}
536
537/// Represents the structured form of an 'object' in Microformats.
538#[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Eq, Clone)]
539#[serde(rename_all = "kebab-case")]
540pub struct Item {
541    pub r#type: Vec<Class>,
542
543    /// Represents the directly associated attributes for this item.
544    #[serde(default, with = "referenced_properties")]
545    pub properties: Properties,
546
547    /// Represents a list of children for this item.
548    #[serde(
549        default,
550        with = "referenced_children",
551        skip_serializing_if = "referenced_children::is_empty"
552    )]
553    pub children: Items,
554
555    /// The ID string of this item, if any is resolved.
556    #[serde(default, skip_serializing_if = "Option::is_none")]
557    pub id: Option<String>,
558
559    /// The language of this item, if any is resolved.
560    #[serde(default, skip_serializing_if = "Option::is_none")]
561    pub lang: Option<String>,
562
563    /// Represents the precise value of this item (if it's defined as a property to another).
564    #[serde(default, skip_serializing_if = "Option::is_none")]
565    pub value: Option<ValueKind>,
566}
567
568impl std::fmt::Debug for Item {
569    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
570        f.debug_struct("Item")
571            .field("type", &self.r#type)
572            .field("id", &self.id)
573            .field("value", &self.value)
574            .field("lang", &self.lang)
575            .finish()
576    }
577}
578
579impl TryFrom<serde_json::Map<String, serde_json::Value>> for Item {
580    type Error = crate::Error;
581
582    fn try_from(obj: serde_json::Map<String, serde_json::Value>) -> Result<Self, Self::Error> {
583        if !obj.contains_key("type") {
584            return Err(Self::Error::JsonObjectMissingProperty("type".to_string()));
585        }
586        if !obj.contains_key("properties") {
587            return Err(Self::Error::JsonObjectMissingProperty(
588                "properties".to_string(),
589            ));
590        }
591
592        serde_json::from_value(serde_json::Value::Object(obj)).map_err(Self::Error::JSON)
593    }
594}
595
596impl TryFrom<serde_json::Value> for Item {
597    type Error = crate::Error;
598
599    fn try_from(v: serde_json::Value) -> Result<Self, Self::Error> {
600        if let serde_json::Value::Object(o) = v {
601            Self::try_from(o)
602        } else {
603            Err(Self::Error::NotAnObject)
604        }
605    }
606}
607
608impl TryInto<serde_json::Value> for Item {
609    type Error = crate::Error;
610
611    fn try_into(self) -> Result<serde_json::Value, Self::Error> {
612        serde_json::to_value(self).map_err(crate::Error::JSON)
613    }
614}
615
616impl IntoIterator for Item {
617    type Item = Item;
618    type IntoIter = std::vec::IntoIter<Self::Item>;
619
620    fn into_iter(self) -> Self::IntoIter {
621        let mut items = self
622            .children
623            .iter()
624            .flat_map(|i| i.clone().into_iter())
625            .collect::<Vec<Self::Item>>();
626        items.push(self);
627        items.into_iter()
628    }
629}
630
631#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
632#[serde(untagged, rename_all = "kebab-case")]
633pub enum ValueKind {
634    Url(Url),
635    Plain(String),
636}
637
638impl Default for ValueKind {
639    fn default() -> Self {
640        Self::Plain(String::default())
641    }
642}
643
644mod referenced_properties {
645    use super::*;
646    type Value = Properties;
647
648    struct PropertyVisitor;
649
650    #[derive(serde::Deserialize, Debug)]
651    #[serde(untagged)]
652    enum PotentialPropertyValue {
653        List(NodeList),
654        Value(PropertyValue),
655    }
656
657    impl<'de> Visitor<'de> for PropertyVisitor {
658        type Value = Value;
659
660        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
661            formatter.write_str("a map of properties with values that could be null, a string, a list of either strings, maps or both")
662        }
663
664        fn visit_map<A>(self, mut map_visitor: A) -> Result<Self::Value, A::Error>
665        where
666            A: de::MapAccess<'de>,
667        {
668            let mut property_map = Properties::default();
669
670            while let Some(key) = map_visitor.next_key()? {
671                // NOTE: This is a lossy operation.
672                let concrete_value: NodeList =
673                    match map_visitor.next_value::<PotentialPropertyValue>() {
674                        Ok(PotentialPropertyValue::List(values)) => values,
675                        Ok(PotentialPropertyValue::Value(node)) => vec![node],
676                        Err(_) => vec![],
677                    };
678
679                if let Some(values) = property_map.get_mut(&key) {
680                    values.extend(concrete_value);
681                } else {
682                    property_map.insert(key, concrete_value);
683                }
684            }
685
686            Ok(property_map)
687        }
688    }
689
690    pub fn serialize<S>(properties: &Value, serializer: S) -> Result<S::Ok, S::Error>
691    where
692        S: serde::ser::Serializer,
693    {
694        let mut properties_seq = serializer.serialize_map(Some(properties.len()))?;
695
696        for (key, value) in properties.iter() {
697            properties_seq.serialize_entry(key, value)?;
698        }
699
700        properties_seq.end()
701    }
702
703    pub fn deserialize<'de, D>(deserializer: D) -> Result<Value, D::Error>
704    where
705        D: serde::Deserializer<'de>,
706    {
707        deserializer.deserialize_map(PropertyVisitor)
708    }
709}
710
711mod referenced_children {
712
713    use super::*;
714    type Value = Items;
715
716    struct ChildrenVisitor;
717
718    impl<'de> Visitor<'de> for ChildrenVisitor {
719        type Value = Value;
720        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
721            formatter.write_str("expecting a list of children nodes, an empty list or null")
722        }
723
724        fn visit_seq<ChildrenSequenceAccessor>(
725            self,
726            mut seq: ChildrenSequenceAccessor,
727        ) -> Result<Self::Value, ChildrenSequenceAccessor::Error>
728        where
729            ChildrenSequenceAccessor: de::SeqAccess<'de>,
730        {
731            let size_hint = seq.size_hint().unwrap_or(0);
732            let mut children: Items = Items::with_capacity(size_hint);
733
734            while let Some(item) = seq.next_element()? {
735                children.push(item);
736            }
737
738            Ok(children)
739        }
740    }
741
742    #[allow(clippy::ptr_arg)]
743    pub fn serialize<S>(children: &Value, serializer: S) -> Result<S::Ok, S::Error>
744    where
745        S: serde::ser::Serializer,
746    {
747        let mut seq = serializer.serialize_seq(Some(children.deref().len()))?;
748        let safe_items = children
749            .iter()
750            .filter(|item| !item.is_empty())
751            .cloned()
752            .collect::<Vec<_>>();
753        for concrete_item in safe_items {
754            seq.serialize_element(&concrete_item)?;
755        }
756        seq.end()
757    }
758
759    pub fn deserialize<'de, D>(deserializer: D) -> Result<Value, D::Error>
760    where
761        D: serde::Deserializer<'de>,
762    {
763        deserializer.deserialize_seq(ChildrenVisitor)
764    }
765
766    pub fn is_empty(items: &Items) -> bool {
767        items.is_empty()
768    }
769}
770
771impl Item {
772    /// Creates a new item with the provided `ItemParent` as its parent.
773    pub fn new(types: Vec<Class>) -> Self {
774        Item {
775            r#type: types,
776            ..Default::default()
777        }
778    }
779
780    /// Determines if this item is undefined - an empty one.
781    pub fn is_empty(&self) -> bool {
782        self.children.is_empty() && self.r#type.is_empty()
783    }
784
785    pub fn remove_whole_property(&mut self, property_name: &str) {
786        self.properties.remove(property_name);
787    }
788
789    pub fn content(&self) -> Option<Vec<PropertyValue>> {
790        self.properties.get("content").cloned()
791    }
792
793    pub fn append_property(&mut self, property_name: &str, property_value: PropertyValue) {
794        let mut new_values = if let Some(values) = self.properties.get(property_name) {
795            values.to_vec()
796        } else {
797            Vec::default()
798        };
799
800        new_values.push(property_value);
801        self.properties.insert(property_name.to_owned(), new_values);
802    }
803
804    /// Checks if this item has any properties with a nested item.
805    pub fn has_nested_microformats(&self) -> bool {
806        let has_nested_value_microformats = self
807            .properties
808            .values()
809            .flatten()
810            .any(|v| matches!(v, PropertyValue::Item(_)));
811
812        has_nested_value_microformats || !self.children.is_empty()
813    }
814
815    pub fn nested_children(&self) -> Vec<Item> {
816        self.properties
817            .values()
818            .flatten()
819            .filter_map(|value| {
820                if let PropertyValue::Item(item) = value {
821                    Some(item)
822                } else {
823                    None
824                }
825            })
826            .cloned()
827            .collect::<Vec<_>>()
828    }
829
830    /// Obtains a list of values for a property.
831    pub fn get_property(&self, arg: &str) -> Option<Vec<PropertyValue>> {
832        self.properties.get(arg).cloned()
833    }
834}
835
836#[derive(
837    Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, serde::Serialize, serde::Deserialize,
838)]
839pub struct Relation {
840    pub rels: Vec<String>,
841
842    #[serde(default, skip_serializing_if = "Option::is_none")]
843    pub hreflang: Option<String>,
844    #[serde(default, skip_serializing_if = "Option::is_none")]
845    pub media: Option<String>,
846    #[serde(default, skip_serializing_if = "Option::is_none")]
847    pub title: Option<String>,
848    #[serde(default, skip_serializing_if = "Option::is_none")]
849    pub r#type: Option<String>,
850    #[serde(default, skip_serializing_if = "Option::is_none")]
851    pub text: Option<String>,
852}
853
854fn expand_text<T: std::fmt::Display>(value: &Option<T>, attr: &str) -> String {
855    if let Some(value) = value {
856        format!(" {attr}=\"{value}\"")
857    } else {
858        Default::default()
859    }
860}
861
862impl Relation {
863    /// Fuses the values of the other relation with this one.
864    pub fn merge_with(&mut self, other: Self) {
865        self.rels.extend_from_slice(&other.rels);
866        self.rels.sort();
867        self.rels.dedup();
868
869        if self.hreflang.is_none() {
870            self.hreflang = other.hreflang;
871        }
872
873        if self.media.is_none() {
874            self.media = other.media;
875        }
876        if self.title.is_none() {
877            self.title = other.title;
878        }
879        if self.r#type.is_none() {
880            self.r#type = other.r#type;
881        }
882        if self.text.is_none() {
883            self.text = other.text;
884        }
885    }
886
887    /// Generates a value to be used in a header.
888    pub fn to_header_value(&self, base_url: &Url) -> String {
889        todo!("convert this relation to a header value with its URLs resolved to {base_url}")
890    }
891    /// Generates HTML to represent this relation.
892    pub fn to_html(&self, rel_url: &str) -> String {
893        format!(
894            "<link href=\"{rel_url}\"{hreflang}{rel}{type_}{title}{media} />",
895            hreflang = expand_text(&self.hreflang, "hreflang"),
896            rel = expand_text(&Some(self.rels.join(" ")), "rel"),
897            type_ = expand_text(&self.r#type, "type"),
898            title = expand_text(&self.title, "title"),
899            media = expand_text(&self.media, "media")
900        )
901    }
902}
903
904#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Deserialize, serde::Serialize)]
905pub struct Relations {
906    #[serde(flatten)]
907    pub items: BTreeMap<Url, Relation>,
908}
909
910impl Relations {
911    pub fn by_rels(&self) -> BTreeMap<String, Vec<Url>> {
912        let mut rels: BTreeMap<String, Vec<Url>> = BTreeMap::default();
913        self.items
914            .iter()
915            .flat_map(|(u, rel)| {
916                rel.rels
917                    .iter()
918                    .map(move |rel_name| (rel_name.to_owned(), u.to_owned()))
919            })
920            .for_each(|(rel_name, url)| {
921                if let Some(rel_urls) = rels.get_mut(&rel_name) {
922                    rel_urls.push(url);
923                } else {
924                    rels.insert(rel_name, vec![url]);
925                }
926            });
927
928        rels.iter_mut().for_each(|(_, urls)| {
929            urls.dedup();
930            urls.sort()
931        });
932
933        rels
934    }
935}
936
937#[derive(Default, Debug, PartialEq, Eq, Clone)]
938pub struct Items(Vec<Item>);
939
940impl From<Vec<Item>> for Items {
941    fn from(value: Vec<Item>) -> Self {
942        Self(value)
943    }
944}
945
946impl Items {
947    /// Creates a new child item for this list of children.
948    pub fn create_child_item(&mut self, types: &[Class]) -> Item {
949        let item = Item::new(types.to_vec());
950        self.0.push(item.to_owned());
951        item
952    }
953
954    pub fn get_by_id(&self, id: &str) -> Option<Item> {
955        self.iter()
956            .flat_map(|item| item.clone().into_iter())
957            .find(|item| item.id == Some(id.to_string()))
958            .clone()
959    }
960
961    pub fn get_by_url(&self, url: &Url) -> Option<Item> {
962        self.iter()
963            .flat_map(|item| item.clone().into_iter())
964            .find(|item| item.value == Some(ValueKind::Url(url.to_owned())))
965            .clone()
966    }
967
968    pub fn with_capacity(size_hint: usize) -> Items {
969        Items(Vec::with_capacity(size_hint))
970    }
971}
972
973impl std::ops::DerefMut for Items {
974    fn deref_mut(&mut self) -> &mut Self::Target {
975        &mut self.0
976    }
977}
978
979impl Deref for Items {
980    type Target = Vec<Item>;
981
982    fn deref(&self) -> &Self::Target {
983        &self.0
984    }
985}
986
987/// Represents a parsed document of Microformats items and its relating rel links.
988#[derive(Clone, Debug, PartialEq, Default, Eq)]
989pub struct Document {
990    pub items: Vec<Item>,
991    pub url: Option<url::Url>,
992    pub rels: Relations,
993    pub lang: Option<String>,
994}
995
996impl Document {
997    pub fn new(url: Option<Url>) -> Self {
998        Self {
999            url,
1000            ..Default::default()
1001        }
1002    }
1003
1004    /// Adds a new [relation][Relation] pointing to the [url][Url] provided.
1005    pub fn add_relation(&mut self, url: Url, relation: Relation) {
1006        if let Some(rel) = self.rels.items.get_mut(&url) {
1007            rel.merge_with(relation);
1008        } else {
1009            self.rels.items.insert(url.to_owned(), relation);
1010        }
1011    }
1012}
1013
1014impl serde::Serialize for Document {
1015    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1016    where
1017        S: serde::Serializer,
1018    {
1019        let mut s = serializer.serialize_struct("Document", 4)?;
1020
1021        s.serialize_field("items", &self.items)?;
1022        s.serialize_field("rel-urls", &self.rels.items)?;
1023        s.serialize_field("rels", &self.rels.by_rels())?;
1024        if let Some(lang) = &self.lang {
1025            s.serialize_field("lang", lang)?;
1026        } else {
1027            s.skip_field("lang")?;
1028        }
1029        s.end()
1030    }
1031}
1032
1033#[derive(serde::Deserialize, Debug)]
1034#[serde(field_identifier, rename_all = "kebab-case")]
1035enum DocumentDeserializationFields {
1036    Items,
1037    RelUrls,
1038    Rels,
1039    Url,
1040    Lang,
1041}
1042
1043impl<'de> serde::Deserialize<'de> for Document {
1044    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1045    where
1046        D: serde::Deserializer<'de>,
1047    {
1048        struct DocumentVisitor;
1049
1050        impl<'de> Visitor<'de> for DocumentVisitor {
1051            type Value = Document;
1052
1053            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1054                formatter.write_str("a Microformat document represented with the expected fields")
1055            }
1056
1057            fn visit_map<V>(self, mut map: V) -> Result<Document, V::Error>
1058            where
1059                V: de::MapAccess<'de>,
1060            {
1061                let mut document = Document::default();
1062                let mut rel_urls: Option<Relations> = None;
1063
1064                while let Ok(Some(key)) = map.next_key() {
1065                    match key {
1066                        DocumentDeserializationFields::Items => {
1067                            let raw_items = map.next_value::<Vec<Item>>()?;
1068                            document.items.extend(raw_items);
1069                        }
1070                        DocumentDeserializationFields::Url => {
1071                            if document.url.is_some() {
1072                                return Err(de::Error::duplicate_field("url"));
1073                            }
1074
1075                            document.url = map.next_value()?;
1076                        }
1077                        DocumentDeserializationFields::RelUrls => {
1078                            if rel_urls.is_some() {
1079                                return Err(de::Error::duplicate_field("rel-urls"));
1080                            }
1081
1082                            rel_urls = map.next_value()?;
1083                        }
1084                        DocumentDeserializationFields::Lang => {
1085                            if document.lang.is_some() {
1086                                return Err(de::Error::duplicate_field("lang"));
1087                            }
1088
1089                            document.lang = map.next_value()?;
1090                        }
1091                        DocumentDeserializationFields::Rels => {
1092                            map.next_value::<BTreeMap<String, Vec<String>>>()?;
1093                        }
1094                    }
1095                }
1096
1097                document.rels = rel_urls.unwrap_or_default();
1098
1099                Ok(document)
1100            }
1101        }
1102
1103        deserializer.deserialize_struct(
1104            "Document",
1105            &["items", "rel-urls", "url", "lang"],
1106            DocumentVisitor,
1107        )
1108    }
1109}
1110
1111impl IntoIterator for Document {
1112    type Item = Item;
1113    type IntoIter = std::vec::IntoIter<Self::Item>;
1114
1115    fn into_iter(self) -> Self::IntoIter {
1116        self.items
1117            .into_iter()
1118            .flat_map(|i| i.into_iter())
1119            .collect::<Vec<Item>>()
1120            .into_iter()
1121    }
1122}
1123
1124pub trait FindItemByProperty {
1125    fn find_items_with_matching_property_value_by<F>(&self, predicate: F) -> Vec<(String, Item)>
1126    where
1127        F: Fn(String, PropertyValue) -> bool + Copy;
1128
1129    fn find_items_with_matching_property_value(
1130        &self,
1131        needle: PropertyValue,
1132    ) -> Vec<(String, Item)> {
1133        self.find_items_with_matching_property_value_by(|_name, property_value| {
1134            property_value == needle
1135        })
1136    }
1137}
1138
1139impl FindItemByProperty for Item {
1140    fn find_items_with_matching_property_value_by<F>(&self, predicate: F) -> Vec<(String, Item)>
1141    where
1142        F: Fn(String, PropertyValue) -> bool + Copy,
1143    {
1144        let mut values = self
1145            .properties
1146            .iter()
1147            .filter_map(|(name, values)| {
1148                if values
1149                    .iter()
1150                    .any(|value| predicate(name.to_owned(), value.to_owned()))
1151                {
1152                    Some((name.to_owned(), self.to_owned()))
1153                } else {
1154                    None
1155                }
1156            })
1157            .collect::<Vec<_>>();
1158
1159        self.children.iter().for_each(|child| {
1160            values.extend(child.find_items_with_matching_property_value_by(predicate));
1161        });
1162
1163        values
1164    }
1165}
1166
1167impl FindItemByProperty for Document {
1168    fn find_items_with_matching_property_value_by<F>(&self, predicate: F) -> Vec<(String, Item)>
1169    where
1170        F: Fn(String, PropertyValue) -> bool + std::marker::Copy,
1171    {
1172        self.items
1173            .iter()
1174            .flat_map(|item| item.find_items_with_matching_property_value_by(predicate))
1175            .collect()
1176    }
1177}
1178
1179pub trait FindItemByUrl: FindItemByProperty {
1180    fn find_item_by_url(&self, expected_url: &Url) -> Option<Item> {
1181        let url_property_value = PropertyValue::Url(expected_url.to_owned());
1182        self.find_items_with_matching_property_value(url_property_value)
1183            .first()
1184            .map(|(_name, value)| value.to_owned())
1185    }
1186}
1187
1188impl FindItemByUrl for Document {}
1189
1190impl FindItemByUrl for Item {}
1191
1192pub trait FindItemById {
1193    fn find_item_by_id(&self, expected_id: &str) -> Option<Item>;
1194}
1195
1196impl FindItemById for Item {
1197    fn find_item_by_id(&self, expected_id: &str) -> Option<Item> {
1198        if self.id == Some(expected_id.to_string()) {
1199            Some(self.to_owned())
1200        } else {
1201            self.children
1202                .iter()
1203                .find_map(|item| item.find_item_by_id(expected_id))
1204        }
1205    }
1206}
1207
1208impl FindItemById for Document {
1209    fn find_item_by_id(&self, expected_id: &str) -> Option<Item> {
1210        self.items
1211            .iter()
1212            .find_map(|item| item.find_item_by_id(expected_id))
1213    }
1214}