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
22pub type NodeList = Vec<PropertyValue>;
24
25pub type Properties = std::collections::BTreeMap<String, NodeList>;
27
28#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
30#[serde(rename_all = "kebab-case")]
31pub enum KnownClass {
32 #[serde(alias = "h-entry")]
35 Entry,
36
37 #[serde(alias = "h-cite")]
40 Cite,
41
42 #[serde(alias = "h-card")]
45 Card,
46
47 #[serde(alias = "h-feed")]
49 Feed,
50
51 #[serde(alias = "h-event")]
53 Event,
54
55 #[serde(alias = "h-product")]
57 Product,
58
59 #[serde(alias = "h-adr")]
61 Adr,
62
63 #[serde(alias = "h-geo")]
65 Geo,
66
67 #[serde(alias = "h-resume")]
69 Resume,
70
71 #[serde(alias = "h-review")]
73 Review,
74
75 #[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 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#[derive(Debug, Clone, Eq)]
147pub enum Class {
148 Known(KnownClass),
150
151 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 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 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 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 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#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
318#[serde(untagged, rename_all = "kebab-case")]
319pub enum PropertyValue {
320 #[serde(deserialize_with = "short_circuit_plain_text_deserialization")]
324 Plain(String),
325
326 #[serde(deserialize_with = "short_circuit_url_deserialization")]
329 Url(Url),
330
331 Temporal(temporal::Value),
334
335 Fragment(Fragment),
336
337 #[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#[derive(
355 Debug, Clone, PartialEq, Eq, serde::Serialize, Default, serde::Deserialize, PartialOrd, Ord,
356)]
357#[serde(rename_all = "kebab-case")]
358pub struct Fragment {
359 #[serde(skip_serializing_if = "String::is_empty")]
361 pub html: String,
362
363 #[serde(default, skip_serializing_if = "String::is_empty")]
365 pub value: String,
366
367 #[serde(default, skip_serializing_if = "Option::is_none")]
369 pub lang: Option<String>,
370
371 #[serde(skip)]
373 pub links: Vec<String>,
374}
375
376impl Fragment {
377 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#[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 deserializer.deserialize_struct(
500 "Item",
501 &["type", "properties", "id", "value", "children"],
502 ItemVisitor,
503 )
504 }
505}
506
507impl PropertyValue {
508 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#[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 #[serde(default, with = "referenced_properties")]
545 pub properties: Properties,
546
547 #[serde(
549 default,
550 with = "referenced_children",
551 skip_serializing_if = "referenced_children::is_empty"
552 )]
553 pub children: Items,
554
555 #[serde(default, skip_serializing_if = "Option::is_none")]
557 pub id: Option<String>,
558
559 #[serde(default, skip_serializing_if = "Option::is_none")]
561 pub lang: Option<String>,
562
563 #[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 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 pub fn new(types: Vec<Class>) -> Self {
774 Item {
775 r#type: types,
776 ..Default::default()
777 }
778 }
779
780 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 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 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 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 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 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 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#[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 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}