1
use crate::server::{
2
    dataset::{Dataset, PredicateObject},
3
    DatasetSummary, DatasetVersionSummary,
4
};
5
use askama::Template;
6
use sophia::api::{prefix::PrefixMapPair, term::IriRef, MownStr};
7
use std::collections::BTreeSet;
8

            
9
2
#[derive(Template)]
10
2
#[template(path = "index.html")]
11
pub(crate) struct IndexTemplate<'a> {
12
    pub datasets: &'a [DatasetSummary<'a>],
13
}
14

            
15
1
#[derive(Template)]
16
1
#[template(path = "dataset.html")]
17
pub(crate) struct DatasetTemplate<'a> {
18
    pub dataset: &'a Dataset,
19
    pub versions: &'a [DatasetVersionSummary],
20
}
21

            
22
7
#[derive(Template)]
23
1
#[template(path = "version.html")]
24
pub(crate) struct VersionTemplate<'a> {
25
    pub dataset: &'a DatasetSummary<'a>,
26
    pub version: &'a DatasetVersionSummary,
27
    pub subjects: BTreeSet<&'a IriRef<MownStr<'a>>>,
28
    pub datasets: &'a Vec<Dataset>,
29
    pub prefixes: &'a Vec<PrefixMapPair>,
30
}
31

            
32
6
#[derive(Template)]
33
1
#[template(path = "subject.html")]
34
pub(crate) struct SubjectTemplate<'a> {
35
    pub dataset: &'a DatasetSummary<'a>,
36
    pub subject: String,
37
    pub version: &'a DatasetVersionSummary,
38
    pub pos: Vec<PredicateObject<'a>>,
39
    pub datasets: &'a Vec<Dataset>,
40
    pub prefixes: &'a Vec<PrefixMapPair>,
41
}
42

            
43
mod filters {
44
    use std::{collections::BTreeSet, fmt};
45

            
46
    use askama::Error::Fmt;
47
    use chrono::{DateTime, Utc};
48
    use sophia::{
49
        api::{
50
            prefix::{PrefixMap, PrefixMapPair},
51
            term::IriRef,
52
            MownStr,
53
        },
54
        iri::Iri,
55
    };
56

            
57
    use crate::{
58
        constants::LABEL_RDFS_IRI,
59
        server::{dataset::Dataset, get_dataset_version},
60
    };
61

            
62
    /// Filter to replace the prefix in a resource IRI
63
122
    pub(crate) fn replace_prefix<T: std::fmt::Display>(
64
122
        s: T,
65
122
        prefixes: &Vec<PrefixMapPair>,
66
122
    ) -> ::askama::Result<String> {
67
122
        let prefix_found = prefixes.get_prefixed_pair(Iri::new_unchecked(s.to_string()));
68

            
69
122
        let out = if let Some(ps) = prefix_found {
70
122
            format!("{}:{}", ps.0.as_str(), ps.1)
71
        } else {
72
            s.to_string()
73
        };
74

            
75
122
        Ok(out)
76
122
    }
77

            
78
    /// Filter to find and display the rdfs:label resource for a subject
79
121
    pub(crate) fn rdfslabel<T: std::fmt::Display>(
80
121
        s: T,
81
121
        datasets: &[Dataset],
82
121
        id: &&str,
83
121
        version: &DateTime<Utc>,
84
121
    ) -> ::askama::Result<String> {
85
121
        let subject = Iri::new_unchecked(s.to_string());
86
121
        let Ok((_, dataset_version)) = get_dataset_version(datasets, id, *version) else {
87
            return Err(Fmt(fmt::Error));
88
        };
89
121
        let pos = dataset_version.pos(&subject);
90
121

            
91
121
        let find_target_po = pos
92
121
            .into_iter()
93
365
            .find(|po| po.predicate.as_str() == LABEL_RDFS_IRI);
94
121
        let Some(target_po) = find_target_po else {
95
5
            return Ok("".to_string());
96
        };
97
116
        let Some(target_object) = target_po.object_literal else {
98
            return Ok("".to_string());
99
        };
100

            
101
116
        Ok(target_object.to_string())
102
121
    }
103

            
104
    /// Tuple with a prefix and subjects that match that prefix
105
    type PrefixWithMatchingSubjects<'a> = (&'a str, Vec<&'a IriRef<MownStr<'a>>>);
106

            
107
    /// Filter to sort subjects by prefix
108
1
    pub(crate) fn categorize_subjects<'a>(
109
1
        subjects: &BTreeSet<&'a IriRef<MownStr<'a>>>,
110
1
        prefixes: &'a Vec<PrefixMapPair>,
111
1
    ) -> ::askama::Result<Vec<PrefixWithMatchingSubjects<'a>>> {
112
1
        // Define the special categories to be shown first
113
1
        let mut special_categories: Vec<&str> = vec!["proposal", "nlnet"];
114
1

            
115
1
        // Find the non-special categories
116
1
        let mut other_categories: Vec<&str> = prefixes
117
1
            .iter()
118
7
            .map(|(p, _)| p.as_str())
119
7
            .filter(|cat| !special_categories.contains(cat))
120
1
            .collect();
121
1

            
122
1
        // Merge categories
123
1
        let mut categories = vec![];
124
1
        categories.append(&mut special_categories);
125
1
        categories.append(&mut other_categories);
126
1

            
127
1
        // Find prefix for each subject
128
1
        let subs_with_prefix: Vec<(&IriRef<MownStr<'a>>, Option<String>)> = subjects
129
1
            .iter()
130
118
            .map(|subject| {
131
118
                (
132
118
                    *subject,
133
118
                    prefixes
134
118
                        .get_prefixed_pair(Iri::new_unchecked(subject.to_string()))
135
118
                        .map(|prefix| (prefix.0.to_string())),
136
118
                )
137
118
            })
138
1
            .collect();
139
1

            
140
1
        // Loop over each category and find the subjects with the matching prefix
141
1
        Ok(categories
142
1
            .iter()
143
9
            .map(|cat| {
144
9
                let subs: Vec<&IriRef<MownStr<'a>>> = subs_with_prefix
145
9
                    .iter()
146
9
                    .filter_map(|(sub, prefix)| {
147
1062
                        if let Some(prefix) = prefix {
148
1044
                            match prefix == cat {
149
116
                                true => Some(*sub),
150
928
                                false => None,
151
                            }
152
                        } else {
153
18
                            None
154
                        }
155
1062
                    })
156
9
                    .collect();
157
9
                (*cat, subs)
158
9
            })
159
1
            .collect())
160
1
    }
161
}