Skip to main content

microformats_types/
document.rs

1//! Parsed document container.
2
3use crate::{Item, LanguageFilter, Relation, Relations};
4use serde::ser::SerializeStruct;
5use serde::{
6    Deserialize, Deserializer, Serialize, Serializer,
7    de::{self, Visitor},
8};
9use std::collections::{BTreeMap, HashSet};
10
11/// A parsed Microformats2 document.
12#[derive(Clone, Debug, PartialEq, Default, Eq)]
13pub struct Document {
14    /// The top-level items in the document.
15    pub items: Vec<Item>,
16    /// The URL of the document being parsed.
17    pub url: Option<url::Url>,
18    /// The rel-values defined in the document.
19    pub rels: Relations,
20    /// The language of the document.
21    pub lang: Option<String>,
22    #[cfg(feature = "metaformats")]
23    pub meta_item: Option<Item>,
24    #[cfg(feature = "debug_flow")]
25    pub _debug_context: Option<crate::DebugContext>,
26}
27
28impl Document {
29    /// Creates a new document with the given URL.
30    pub fn new(url: Option<url::Url>) -> Self {
31        Self {
32            url,
33            ..Default::default()
34        }
35    }
36
37    /// Adds a relation to the document, merging with existing relations for the same URL.
38    pub fn add_relation(&mut self, url: url::Url, relation: Relation) {
39        if let Some(rel) = self.rels.items.get_mut(&url) {
40            rel.merge_with(relation);
41        } else {
42            self.rels.items.insert(url.to_owned(), relation);
43        }
44    }
45}
46
47impl Serialize for Document {
48    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
49    where
50        S: Serializer,
51    {
52        #[cfg(all(feature = "metaformats", feature = "debug_flow"))]
53        let field_count = 6;
54        #[cfg(all(not(feature = "metaformats"), feature = "debug_flow"))]
55        let field_count = 5;
56        #[cfg(all(feature = "metaformats", not(feature = "debug_flow")))]
57        let field_count = 5;
58        #[cfg(all(not(feature = "metaformats"), not(feature = "debug_flow")))]
59        let field_count = 4;
60
61        let mut s = serializer.serialize_struct("Document", field_count)?;
62
63        s.serialize_field("items", &self.items)?;
64        s.serialize_field("rel-urls", &self.rels.items)?;
65        s.serialize_field("rels", &self.rels.by_rels())?;
66        if let Some(lang) = &self.lang {
67            s.serialize_field("lang", lang)?;
68        } else {
69            s.skip_field("lang")?;
70        }
71        #[cfg(feature = "metaformats")]
72        if let Some(meta_item) = &self.meta_item {
73            s.serialize_field("meta-item", meta_item)?;
74        } else {
75            s.skip_field("meta-item")?;
76        }
77        s.end()
78    }
79}
80
81#[derive(serde::Deserialize, Debug)]
82#[serde(field_identifier, rename_all = "kebab-case")]
83enum DocumentDeserializationFields {
84    Items,
85    RelUrls,
86    Rels,
87    Url,
88    Lang,
89    #[cfg(feature = "metaformats")]
90    MetaItem,
91}
92
93impl<'de> Deserialize<'de> for Document {
94    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95    where
96        D: Deserializer<'de>,
97    {
98        struct DocumentVisitor;
99
100        impl<'de> Visitor<'de> for DocumentVisitor {
101            type Value = Document;
102
103            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104                formatter.write_str("a Microformat document represented with the expected fields")
105            }
106
107            fn visit_map<V>(self, mut map: V) -> Result<Document, V::Error>
108            where
109                V: de::MapAccess<'de>,
110            {
111                let mut document = Document::default();
112                let mut rel_urls: Option<Relations> = None;
113
114                while let Ok(Some(key)) = map.next_key() {
115                    match key {
116                        DocumentDeserializationFields::Items => {
117                            let raw_items = map.next_value::<Vec<Item>>()?;
118                            document.items.extend(raw_items);
119                        }
120                        DocumentDeserializationFields::Url => {
121                            if document.url.is_some() {
122                                return Err(de::Error::duplicate_field("url"));
123                            }
124
125                            document.url = map.next_value()?;
126                        }
127                        DocumentDeserializationFields::RelUrls => {
128                            if rel_urls.is_some() {
129                                return Err(de::Error::duplicate_field("rel-urls"));
130                            }
131
132                            rel_urls = map.next_value()?;
133                        }
134                        DocumentDeserializationFields::Lang => {
135                            if document.lang.is_some() {
136                                return Err(de::Error::duplicate_field("lang"));
137                            }
138
139                            document.lang = map.next_value()?;
140                        }
141                        DocumentDeserializationFields::Rels => {
142                            map.next_value::<BTreeMap<String, Vec<String>>>()?;
143                        }
144                        #[cfg(feature = "metaformats")]
145                        DocumentDeserializationFields::MetaItem => {
146                            if document.meta_item.is_some() {
147                                return Err(de::Error::duplicate_field("meta-item"));
148                            }
149
150                            document.meta_item = map.next_value()?;
151                        }
152                    }
153                }
154
155                document.rels = rel_urls.unwrap_or_default();
156
157                Ok(document)
158            }
159        }
160
161        #[cfg(feature = "metaformats")]
162        let expected_fields = &["items", "rel-urls", "url", "lang", "meta-item"];
163        #[cfg(not(feature = "metaformats"))]
164        let expected_fields = &["items", "rel-urls", "url", "lang"];
165
166        deserializer.deserialize_struct("Document", expected_fields, DocumentVisitor)
167    }
168}
169
170impl IntoIterator for Document {
171    type Item = Item;
172    type IntoIter = std::vec::IntoIter<Self::Item>;
173
174    fn into_iter(self) -> Self::IntoIter {
175        self.items
176            .into_iter()
177            .flat_map(|i| i.into_iter())
178            .collect::<Vec<Item>>()
179            .into_iter()
180    }
181}
182
183impl LanguageFilter for Document {
184    fn matches_languages(&self, languages: &HashSet<&str>) -> bool {
185        self.items
186            .iter()
187            .any(|item| item.matches_languages(languages))
188    }
189
190    fn filter_by_languages_set(&self, languages: &HashSet<&str>) -> Option<Self> {
191        let filtered_items: Vec<Item> = self
192            .items
193            .iter()
194            .filter_map(|item| item.filter_by_languages_set(languages))
195            .collect();
196
197        if filtered_items.is_empty() {
198            return None;
199        }
200
201        Some(Self {
202            items: filtered_items,
203            url: self.url.clone(),
204            rels: self.rels.clone(),
205            lang: self.lang.clone(),
206            #[cfg(feature = "metaformats")]
207            meta_item: self
208                .meta_item
209                .as_ref()
210                .and_then(|item| item.filter_by_languages_set(languages)),
211            #[cfg(feature = "debug_flow")]
212            _debug_context: self._debug_context.clone(),
213        })
214    }
215}