use regex::Regex;
use serde::{
de::{self, MapAccess, Visitor},
ser::{SerializeMap, SerializeSeq, SerializeStruct},
Deserialize, Deserializer, Serializer,
};
use std::{
cmp::Ordering,
collections::BTreeMap,
convert::{Infallible, TryFrom, TryInto},
fmt::{self, Debug},
marker::Copy,
ops::Deref,
str::FromStr,
};
pub use url::Url;
pub mod temporal;
#[cfg(test)]
mod test;
pub type NodeList = Vec<PropertyValue>;
pub type Properties = std::collections::BTreeMap<String, NodeList>;
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "kebab-case")]
pub enum KnownClass {
#[serde(alias = "h-entry")]
Entry,
#[serde(alias = "h-cite")]
Cite,
#[serde(alias = "h-card")]
Card,
#[serde(alias = "h-feed")]
Feed,
#[serde(alias = "h-event")]
Event,
#[serde(alias = "h-product")]
Product,
#[serde(alias = "h-adr")]
Adr,
#[serde(alias = "h-geo")]
Geo,
#[serde(alias = "h-resume")]
Resume,
#[serde(alias = "h-review")]
Review,
#[serde(alias = "h-recipe")]
Recipe,
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("The type {0:?} is not a known Microformats class supported by this library.")]
NotKnownClass(String),
#[error("JSON: {0}")]
JSON(#[from] serde_json::Error),
#[error("The provided JSON value was not an object.")]
NotAnObject,
#[error("Missing property {0:?} when converting from JSON.")]
JsonObjectMissingProperty(String),
#[error(transparent)]
Temporal(#[from] temporal::Error),
}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
self.to_string().eq(&other.to_string())
}
}
impl Eq for Error {}
impl FromStr for KnownClass {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"h-entry" | "entry" => Ok(Self::Entry),
"h-cite" | "cite" => Ok(Self::Cite),
"h-card" | "card" => Ok(Self::Card),
"h-event" | "event" => Ok(Self::Event),
"h-product" | "product" => Ok(Self::Product),
"h-feed" | "feed" => Ok(Self::Feed),
"h-geo" | "geo" => Ok(Self::Geo),
"h-adr" | "adr" => Ok(Self::Adr),
"h-recipe" | "recipe" => Ok(Self::Recipe),
_ => Err(Error::NotKnownClass(s.to_string())),
}
}
}
impl std::fmt::Display for KnownClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
KnownClass::Entry => "h-entry",
KnownClass::Cite => "h-cite",
KnownClass::Card => "h-card",
KnownClass::Feed => "h-feed",
KnownClass::Event => "h-event",
KnownClass::Product => "h-product",
KnownClass::Adr => "h-adr",
KnownClass::Geo => "h-geo",
KnownClass::Resume => "h-resume",
KnownClass::Review => "h-review",
KnownClass::Recipe => "h-recipe",
})
}
}
#[derive(Debug, Clone, Eq)]
pub enum Class {
Known(KnownClass),
Custom(String),
}
impl PartialOrd for Class {
fn partial_cmp(&self, other: &Self) -> std::option::Option<std::cmp::Ordering> {
self.to_string().partial_cmp(&other.to_string())
}
}
impl PartialEq for Class {
fn eq(&self, other: &Self) -> bool {
self.to_string().eq(&other.to_string())
}
}
impl FromStr for Class {
type Err = Infallible;
fn from_str(class_str: &str) -> Result<Self, Self::Err> {
KnownClass::from_str(class_str)
.or_else(|_| KnownClass::from_str(&class_str.replace("h-", "")))
.map(Class::Known)
.or_else(|_| Ok(Self::Custom(class_str.trim_start_matches("h-").to_string())))
}
}
impl std::fmt::Display for Class {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Known(class) => f.write_fmt(format_args!("{}", class)),
Self::Custom(class) => f.write_fmt(format_args!("h-{}", class)),
}
}
}
impl serde::Serialize for Class {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
impl Class {
pub fn is_recognized(&self) -> bool {
!matches!(self, Self::Custom(_))
}
}
struct ClassVisitor;
impl<'de> Visitor<'de> for ClassVisitor {
type Value = Class;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a string that follows Microformats class conventions")
}
fn visit_str<E>(self, class_str: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Class::from_str(class_str).map_err(|e| E::custom(e.to_string()))
}
}
impl<'de> serde::Deserialize<'de> for Class {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_string(ClassVisitor)
}
}
impl From<KnownClass> for Class {
fn from(kc: KnownClass) -> Self {
Self::Known(kc)
}
}
fn short_circuit_url_deserialization<'de, D>(d: D) -> Result<Url, D::Error>
where
D: serde::Deserializer<'de>,
{
let string_form = String::deserialize(d)?;
let url_form = Url::parse(&string_form).map_err(serde::de::Error::custom)?;
if url_form.as_str() != string_form {
Err(serde::de::Error::custom(
"This string doesn't represent a valid URL due looking like one.",
))
} else {
Ok(url_form)
}
}
fn short_circuit_plain_text_deserialization<'de, D>(d: D) -> Result<String, D::Error>
where
D: serde::Deserializer<'de>,
{
let string_form = String::deserialize(d)?;
Url::from_str(&string_form)
.map_err(serde::de::Error::custom)
.map(|u| u.as_str().to_string())
.and_then(|u| {
if u == string_form && !u.contains(|c: char| c.is_whitespace()) && !u.contains('\n') {
Err(serde::de::Error::invalid_type(
de::Unexpected::Other("URL"),
&"plain 'ol string",
))
} else {
Ok(string_form.clone())
}
})
.or_else(|r: D::Error| {
if r.to_string().starts_with("invalid type: URL") {
Err(r)
} else {
temporal::Value::from_str(&string_form)
.map_err(serde::de::Error::custom)
.map(|u| u.to_string())
.and_then(|u| {
if u == string_form {
Err(serde::de::Error::invalid_type(
de::Unexpected::Str("temporal data"),
&"plain 'ol string",
))
} else {
Ok(string_form.clone())
}
})
}
})
.or_else(|r: D::Error| {
if r.to_string().starts_with("invalid type: URL")
|| r.to_string().contains("temporal data")
{
Err(r)
} else {
Ok(string_form)
}
})
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(untagged, rename_all = "kebab-case")]
pub enum PropertyValue {
#[serde(deserialize_with = "short_circuit_plain_text_deserialization")]
Plain(String),
#[serde(deserialize_with = "short_circuit_url_deserialization")]
Url(Url),
Temporal(temporal::Value),
Fragment(Fragment),
#[serde(with = "referenced_item")]
Item(Item),
Image(Image),
}
impl From<Item> for PropertyValue {
fn from(item: Item) -> Self {
PropertyValue::Item(item)
}
}
#[derive(
Debug, Clone, PartialEq, Eq, serde::Serialize, Default, serde::Deserialize, PartialOrd, Ord,
)]
#[serde(rename_all = "kebab-case")]
pub struct Fragment {
#[serde(skip_serializing_if = "String::is_empty")]
pub html: String,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lang: Option<String>,
#[serde(skip)]
pub links: Vec<String>,
}
impl Fragment {
pub fn is_empty(&self) -> bool {
self.value.is_empty()
}
pub fn links(&self) -> &[String] {
&self.links
}
}
#[derive(Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, PartialOrd, Ord)]
#[serde(rename_all = "kebab-case")]
pub struct Image {
pub value: Url,
#[serde(default)]
pub alt: Option<String>,
}
impl std::fmt::Debug for Image {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Image")
.field("value", &self.value.to_string())
.field("alt", &self.alt)
.finish()
}
}
mod referenced_item {
use super::*;
type Value = Item;
struct ItemVisitor;
#[derive(serde::Deserialize, Debug)]
#[serde(field_identifier, rename_all = "kebab-case")]
enum ItemDeserializationFields {
Children,
Value,
Id,
Properties,
r#Type,
}
impl<'de> Visitor<'de> for ItemVisitor {
type Value = Value;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("expecting null or an map representing an item")
}
fn visit_map<A>(self, mut item_map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut children: Items = Default::default();
let mut value: Option<ValueKind> = Default::default();
let mut id: Option<String> = Default::default();
let mut types = Vec::new();
let mut properties = Properties::default();
while let Some(property) = item_map.next_key()? {
match property {
ItemDeserializationFields::Children => {
let new_items = item_map.next_value::<Vec<Item>>()?.into_iter();
if children.is_empty() && new_items.len() > 0 {
children = new_items.collect::<Vec<Item>>().into();
} else {
children.extend(new_items);
}
}
ItemDeserializationFields::Value => {
if value.is_none() {
value = item_map.next_value::<Option<ValueKind>>()?;
}
}
ItemDeserializationFields::Id => {
if id.is_none() {
id = item_map.next_value::<Option<String>>()?;
}
}
ItemDeserializationFields::Type => {
types.extend(item_map.next_value::<Vec<Class>>()?);
}
ItemDeserializationFields::Properties => {
properties.extend(item_map.next_value::<BTreeMap<String, _>>()?);
}
}
}
let item = Item {
r#type: types,
properties,
id,
value,
children,
..Default::default()
};
Ok(item)
}
}
pub fn serialize<S>(item: &Value, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_some(&Some(item))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Value, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_struct(
"Item",
&["type", "properties", "id", "value", "children"],
ItemVisitor,
)
}
}
impl PropertyValue {
pub fn is_empty(&self) -> bool {
match self {
Self::Temporal(_) | Self::Url(_) | Self::Image(_) => false,
Self::Plain(s) => s.is_empty(),
Self::Fragment(f) => f.is_empty(),
Self::Item(i) => i.is_empty(),
}
}
}
impl From<Url> for PropertyValue {
fn from(u: Url) -> Self {
Self::Url(u)
}
}
impl From<temporal::Stamp> for PropertyValue {
fn from(t: temporal::Stamp) -> Self {
Self::Temporal(temporal::Value::Timestamp(t))
}
}
impl From<temporal::Duration> for PropertyValue {
fn from(t: temporal::Duration) -> Self {
Self::Temporal(temporal::Value::Duration(t))
}
}
#[derive(serde::Serialize, serde::Deserialize, Default, PartialEq, Eq, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Item {
pub r#type: Vec<Class>,
#[serde(with = "referenced_properties")]
pub properties: Properties,
#[serde(
default,
with = "referenced_children",
skip_serializing_if = "referenced_children::is_empty"
)]
pub children: Items,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lang: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<ValueKind>,
}
impl std::fmt::Debug for Item {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Item")
.field("type", &self.r#type)
.field("id", &self.id)
.field("value", &self.value)
.field("lang", &self.value)
.finish()
}
}
impl TryFrom<serde_json::Map<String, serde_json::Value>> for Item {
type Error = crate::Error;
fn try_from(obj: serde_json::Map<String, serde_json::Value>) -> Result<Self, Self::Error> {
if !obj.contains_key("type") {
return Err(Self::Error::JsonObjectMissingProperty("type".to_string()));
}
if !obj.contains_key("properties") {
return Err(Self::Error::JsonObjectMissingProperty(
"properties".to_string(),
));
}
serde_json::from_value(serde_json::Value::Object(obj)).map_err(Self::Error::JSON)
}
}
impl TryFrom<serde_json::Value> for Item {
type Error = crate::Error;
fn try_from(v: serde_json::Value) -> Result<Self, Self::Error> {
if let serde_json::Value::Object(o) = v {
Self::try_from(o)
} else {
Err(Self::Error::NotAnObject)
}
}
}
impl TryInto<serde_json::Value> for Item {
type Error = crate::Error;
fn try_into(self) -> Result<serde_json::Value, Self::Error> {
serde_json::to_value(self).map_err(crate::Error::JSON)
}
}
impl IntoIterator for Item {
type Item = Item;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let mut items = self
.children
.iter()
.flat_map(|i| i.clone().into_iter())
.collect::<Vec<Self::Item>>();
items.push(self);
items.into_iter()
}
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[serde(untagged, rename_all = "kebab-case")]
pub enum ValueKind {
Url(Url),
Plain(String),
}
impl Default for ValueKind {
fn default() -> Self {
Self::Plain(String::default())
}
}
mod referenced_properties {
use super::*;
type Value = Properties;
struct PropertyVisitor;
#[derive(serde::Deserialize, Debug)]
#[serde(untagged)]
enum PotentialValues {
List(NodeList),
Value(PropertyValue),
}
impl<'de> Visitor<'de> for PropertyVisitor {
type Value = Value;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a map of properties with values that could be null, a string, a list of either strings, maps or both")
}
fn visit_map<A>(self, mut map_visitor: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut property_map = Properties::default();
while let Some(key) = map_visitor.next_key()? {
let concrete_value: NodeList = match map_visitor.next_value::<PotentialValues>()? {
PotentialValues::List(values) => values,
PotentialValues::Value(node) => vec![node],
};
property_map.insert(key, concrete_value);
}
Ok(property_map)
}
}
pub fn serialize<S>(properties: &Value, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let mut properties_seq = serializer.serialize_map(Some(properties.len()))?;
for (key, value) in properties.iter() {
properties_seq.serialize_entry(key, value)?;
}
properties_seq.end()
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Value, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_map(PropertyVisitor)
}
}
mod referenced_children {
use super::*;
type Value = Items;
struct ChildrenVisitor;
impl<'de> Visitor<'de> for ChildrenVisitor {
type Value = Value;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("expecting a list of children nodes, an empty list or null")
}
fn visit_seq<ChildrenSequenceAccessor>(
self,
mut seq: ChildrenSequenceAccessor,
) -> Result<Self::Value, ChildrenSequenceAccessor::Error>
where
ChildrenSequenceAccessor: de::SeqAccess<'de>,
{
let size_hint = seq.size_hint().unwrap_or(0);
let mut children: Items = Items::with_capacity(size_hint);
while let Some(item) = seq.next_element()? {
children.push(item);
}
Ok(children)
}
}
#[allow(clippy::ptr_arg)]
pub fn serialize<S>(children: &Value, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
let mut seq = serializer.serialize_seq(Some(children.deref().len()))?;
let safe_items = children
.iter()
.filter(|item| !item.is_empty())
.cloned()
.collect::<Vec<_>>();
for concrete_item in safe_items {
seq.serialize_element(&concrete_item)?;
}
seq.end()
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Value, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_seq(ChildrenVisitor)
}
pub fn is_empty(items: &Items) -> bool {
items.is_empty()
}
}
impl Item {
pub fn new(types: Vec<Class>) -> Self {
Item {
r#type: types,
..Default::default()
}
}
pub fn is_empty(&self) -> bool {
self.children.is_empty() && self.r#type.is_empty()
}
pub fn remove_whole_property(&mut self, property_name: &str) {
self.properties.remove(property_name);
}
pub fn content(&self) -> Option<Vec<PropertyValue>> {
self.properties.get("content").cloned()
}
pub fn append_property(&mut self, property_name: &str, property_value: PropertyValue) {
let mut new_values = if let Some(values) = self.properties.get(property_name) {
values.to_vec()
} else {
Vec::default()
};
new_values.push(property_value);
self.properties.insert(property_name.to_owned(), new_values);
}
pub fn has_nested_microformats(&self) -> bool {
let has_nested_value_microformats = self
.properties
.values()
.flatten()
.any(|v| matches!(v, PropertyValue::Item(_)));
has_nested_value_microformats || !self.children.is_empty()
}
pub fn nested_children(&self) -> Vec<Item> {
self.properties
.values()
.flatten()
.filter_map(|value| {
if let PropertyValue::Item(item) = value {
Some(item)
} else {
None
}
})
.cloned()
.collect::<Vec<_>>()
}
pub fn get_property(&self, arg: &str) -> Option<Vec<PropertyValue>> {
self.properties.get(arg).cloned()
}
}
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, serde::Serialize, serde::Deserialize,
)]
pub struct Relation {
pub rels: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hreflang: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub media: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
}
impl Relation {
pub fn merge_with(&mut self, other: Self) {
self.rels.extend_from_slice(&other.rels);
self.rels.sort();
self.rels.dedup();
if self.hreflang.is_none() {
self.hreflang = other.hreflang;
}
if self.media.is_none() {
self.media = other.media;
}
if self.title.is_none() {
self.title = other.title;
}
if self.r#type.is_none() {
self.r#type = other.r#type;
}
if self.text.is_none() {
self.text = other.text;
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Default, serde::Deserialize, serde::Serialize)]
pub struct Relations {
#[serde(flatten)]
pub items: BTreeMap<Url, Relation>,
}
impl Relations {
pub fn by_rels(&self) -> BTreeMap<String, Vec<Url>> {
let mut rels: BTreeMap<String, Vec<Url>> = BTreeMap::default();
self.items
.iter()
.flat_map(|(u, rel)| {
rel.rels
.iter()
.map(move |rel_name| (rel_name.to_owned(), u.to_owned()))
})
.for_each(|(rel_name, url)| {
if let Some(rel_urls) = rels.get_mut(&rel_name) {
rel_urls.push(url);
} else {
rels.insert(rel_name, vec![url]);
}
});
rels.iter_mut().for_each(|(_, urls)| {
urls.dedup();
urls.sort()
});
rels
}
}
#[derive(Default, Debug, PartialEq, Eq, Clone)]
pub struct Items(Vec<Item>);
impl From<Vec<Item>> for Items {
fn from(value: Vec<Item>) -> Self {
Self(value)
}
}
impl Items {
pub fn create_child_item(&mut self, types: &[Class]) -> Item {
let item = Item::new(types.to_vec());
self.0.push(item.to_owned());
item
}
pub fn get_by_id(&self, id: &str) -> Option<Item> {
self.iter()
.flat_map(|item| item.clone().into_iter())
.find(|item| item.id == Some(id.to_string()))
.clone()
}
pub fn get_by_url(&self, url: &Url) -> Option<Item> {
self.iter()
.flat_map(|item| item.clone().into_iter())
.find(|item| item.value == Some(ValueKind::Url(url.to_owned())))
.clone()
}
pub fn with_capacity(size_hint: usize) -> Items {
Items(Vec::with_capacity(size_hint))
}
}
impl std::ops::DerefMut for Items {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Deref for Items {
type Target = Vec<Item>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Default, Eq)]
pub struct Document {
pub items: Vec<Item>,
pub url: Option<url::Url>,
pub rels: Relations,
pub lang: Option<String>,
}
impl Document {
pub fn new(url: Option<Url>) -> Self {
Self {
url,
..Default::default()
}
}
pub fn add_relation(&mut self, url: Url, relation: Relation) {
if let Some(rel) = self.rels.items.get_mut(&url) {
rel.merge_with(relation);
} else {
self.rels.items.insert(url.to_owned(), relation);
}
}
}
impl serde::Serialize for Document {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut s = serializer.serialize_struct("Document", 4)?;
s.serialize_field("items", &self.items)?;
s.serialize_field("rel-urls", &self.rels.items)?;
s.serialize_field("rels", &self.rels.by_rels())?;
if let Some(lang) = &self.lang {
s.serialize_field("lang", lang)?;
} else {
s.skip_field("lang")?;
}
s.end()
}
}
#[derive(serde::Deserialize, Debug)]
#[serde(field_identifier, rename_all = "kebab-case")]
enum DocumentDeserializationFields {
Items,
RelUrls,
Rels,
Url,
Lang,
}
impl<'de> serde::Deserialize<'de> for Document {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct DocumentVisitor;
impl<'de> Visitor<'de> for DocumentVisitor {
type Value = Document;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a Microformat document represented with the expected fields")
}
fn visit_map<V>(self, mut map: V) -> Result<Document, V::Error>
where
V: de::MapAccess<'de>,
{
let mut document = Document::default();
let mut rel_urls: Option<Relations> = None;
while let Ok(Some(key)) = map.next_key() {
match key {
DocumentDeserializationFields::Items => {
let raw_items = map.next_value::<Vec<Item>>()?;
document.items.extend(raw_items);
}
DocumentDeserializationFields::Url => {
if document.url.is_some() {
return Err(de::Error::duplicate_field("url"));
}
document.url = map.next_value()?;
}
DocumentDeserializationFields::RelUrls => {
if rel_urls.is_some() {
return Err(de::Error::duplicate_field("rel-urls"));
}
rel_urls = map.next_value()?;
}
DocumentDeserializationFields::Lang => {
if document.lang.is_some() {
return Err(de::Error::duplicate_field("lang"));
}
document.lang = map.next_value()?;
}
DocumentDeserializationFields::Rels => {
map.next_value::<BTreeMap<String, Vec<String>>>()?;
}
}
}
document.rels = rel_urls.unwrap_or_default();
Ok(document)
}
}
deserializer.deserialize_struct(
"Document",
&["items", "rel-urls", "url", "lang"],
DocumentVisitor,
)
}
}
impl IntoIterator for Document {
type Item = Item;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.items
.into_iter()
.flat_map(|i| i.into_iter())
.collect::<Vec<Item>>()
.into_iter()
}
}
pub trait FindItemByProperty {
fn find_items_with_matching_property_value_by<F>(&self, predicate: F) -> Vec<(String, Item)>
where
F: Fn(String, PropertyValue) -> bool + Copy;
fn find_items_with_matching_property_value(
&self,
needle: PropertyValue,
) -> Vec<(String, Item)> {
self.find_items_with_matching_property_value_by(|_name, property_value| {
property_value == needle
})
}
}
impl FindItemByProperty for Item {
fn find_items_with_matching_property_value_by<F>(&self, predicate: F) -> Vec<(String, Item)>
where
F: Fn(String, PropertyValue) -> bool + Copy,
{
let mut values = self
.properties
.iter()
.filter_map(|(name, values)| {
if values
.iter()
.any(|value| predicate(name.to_owned(), value.to_owned()))
{
Some((name.to_owned(), self.to_owned()))
} else {
None
}
})
.collect::<Vec<_>>();
self.children.iter().for_each(|child| {
values.extend(child.find_items_with_matching_property_value_by(predicate));
});
values
}
}
impl FindItemByProperty for Document {
fn find_items_with_matching_property_value_by<F>(&self, predicate: F) -> Vec<(String, Item)>
where
F: Fn(String, PropertyValue) -> bool + std::marker::Copy,
{
self.items
.iter()
.flat_map(|item| item.find_items_with_matching_property_value_by(predicate))
.collect()
}
}
pub trait FindItemByUrl: FindItemByProperty {
fn find_item_by_url(&self, expected_url: Url) -> Option<Item> {
let url_property_value = PropertyValue::Url(expected_url.to_owned());
self.find_items_with_matching_property_value(url_property_value)
.first()
.map(|(_name, value)| value.to_owned())
}
}
impl FindItemByUrl for Document {}
impl FindItemByUrl for Item {}
pub trait FindItemById {
fn find_item_by_id(&self, expected_id: &str) -> Option<Item>;
}
impl FindItemById for Item {
fn find_item_by_id(&self, expected_id: &str) -> Option<Item> {
if self.id == Some(expected_id.to_string()) {
Some(self.to_owned())
} else {
self.children
.iter()
.find_map(|item| item.find_item_by_id(expected_id))
}
}
}
impl FindItemById for Document {
fn find_item_by_id(&self, expected_id: &str) -> Option<Item> {
self.items
.iter()
.find_map(|item| item.find_item_by_id(expected_id))
}
}