sophia_api/prefix/_prefix_map.rs
1use super::{AsPrefix, IsPrefix, Prefix};
2/// Define the [`PrefixMap`] trait with default implementation.
3use mownstr::MownStr;
4use sophia_iri::{AsIri, Iri, IsIri};
5
6/// A prefix map associates prefixes to namespaces.
7pub trait PrefixMap {
8 /// Return the IRI associated to this prefix, if any.
9 fn get_namespace<'s>(&'s self, prefix: &str) -> Option<Iri<&'s str>>;
10 /// Return a prefix-suffix pair describing the given IRI, if any.
11 fn get_prefixed_pair<'s, T: IsIri + 's>(
12 &'s self,
13 iri: T,
14 ) -> Option<(Prefix<&'s str>, MownStr<'s>)> {
15 self.get_checked_prefixed_pair(iri, |_| true)
16 }
17 /// Return a prefix-suffix pair describing the given IRI, if any,
18 /// guaranteeing that the suffix will satisfy the given predicate.
19 fn get_checked_prefixed_pair<'s, T, F>(
20 &'s self,
21 iri: T,
22 suffix_check: F,
23 ) -> Option<(Prefix<&'s str>, MownStr<'s>)>
24 where
25 T: IsIri + 's,
26 F: Fn(&str) -> bool;
27 /// Iterate over (prefix, IRI) pairs.
28 fn iter<'s>(&'s self) -> Box<dyn Iterator<Item = (Prefix<&'s str>, Iri<&'s str>)> + 's>;
29 /// Copies this prefix map as a self-sufficient vector
30 #[allow(clippy::type_complexity)]
31 fn to_vec(&self) -> Vec<PrefixMapPair> {
32 self.iter()
33 .map(|(prefix, ns)| (prefix.map_unchecked(Box::from), ns.map_unchecked(Box::from)))
34 .collect()
35 }
36}
37
38/// Type alias for the return type of [`PrefixMap::to_vec`].
39pub type PrefixMapPair = (Prefix<Box<str>>, Iri<Box<str>>);
40
41impl<P, N> PrefixMap for [(P, N)]
42where
43 P: IsPrefix,
44 N: IsIri,
45{
46 fn get_namespace<'s>(&'s self, prefix: &str) -> Option<Iri<&'s str>> {
47 for (p, n) in self {
48 if p.borrow() == prefix {
49 return Some(n.as_iri());
50 }
51 }
52 None
53 }
54 fn get_checked_prefixed_pair<'s, T, F>(
55 &'s self,
56 iri: T,
57 suffix_check: F,
58 ) -> Option<(Prefix<&'s str>, MownStr<'s>)>
59 where
60 T: IsIri + 's,
61 F: Fn(&str) -> bool,
62 {
63 let iri_str = iri.borrow();
64 let mut matched = 0;
65 let mut found = None;
66 for (p, n) in self {
67 let n_str = n.borrow();
68 if iri_str.starts_with(n_str) && n_str.len() > matched {
69 let maybe_matched = n_str.len();
70 let suffix = &iri_str[maybe_matched..];
71 if suffix_check(suffix) {
72 matched = maybe_matched;
73 found = Some((p.as_prefix(), suffix));
74 }
75 }
76 }
77 found.map(|(p, s)| (p, s.to_owned().into()))
78 }
79 fn iter<'s>(&'s self) -> Box<dyn Iterator<Item = (Prefix<&'s str>, Iri<&'s str>)> + 's> {
80 Box::new(<[(P, N)]>::iter(self).map(|(prefix, ns)| (prefix.as_prefix(), ns.as_iri())))
81 }
82}
83
84/*
85#[cfg(test)]
86#[allow(clippy::bool_assert_comparison, clippy::unused_unit)] // test_case! generated warnings
87mod test {
88 use super::super::Prefix;
89 use super::*;
90 use crate::term::SimpleIri;
91 use sophia_iri::Iri;
92 use test_case::test_case;
93
94 fn make_map() -> Vec<(Prefix<'static>, Iri<'static>)> {
95 vec![
96 (
97 Prefix::new_unchecked("s"),
98 Iri::new_unchecked("http://schema.org/"),
99 ),
100 (
101 Prefix::new_unchecked("a"),
102 Iri::new_unchecked("http://example.org/a/"),
103 ),
104 (
105 Prefix::new_unchecked("ab"),
106 Iri::new_unchecked("http://example.org/a/b#"),
107 ),
108 (
109 Prefix::new_unchecked(""),
110 Iri::new_unchecked("http://example.org/"),
111 ),
112 ]
113 }
114
115 #[test_case("s", Some("http://schema.org/"); "s")]
116 #[test_case("a", Some("http://example.org/a/"); "a")]
117 #[test_case("ab", Some("http://example.org/a/b#"); "ab")]
118 #[test_case("", Some("http://example.org/"); "empty")]
119 #[test_case("sa", None; "sa")]
120 fn get_namespace(prefix: &str, expected: Option<&str>) {
121 let expected = expected.map(Iri::new_unchecked);
122 let map = make_map();
123 let got = map.get_namespace(prefix);
124 assert_eq!(got, expected);
125 }
126
127 #[test_case("http://something.else.com/", None, None; "something else")]
128 #[test_case("http://schema.org/Person", None, Some(("s", "Person")); "s:Person")]
129 #[test_case("http://example.org/", None, Some(("", "")); "single colon")]
130 #[test_case("http://example.org/", Some("a/c"), Some(("a", "c")); "a:c")]
131 #[test_case("http://example.org/", Some("a/b#c"), Some(("ab", "c")); "b:c")]
132 #[test_case("http://example.org/a#", Some("c"), Some(("", "a#c")); ":a#c")]
133 fn get_prefixed_pair(ns: &str, sf: Option<&str>, expected: Option<(&str, &str)>) {
134 let expected = expected.map(|(pf, sf)| (Prefix::new_unchecked(pf), MownStr::from(sf)));
135 let map = make_map();
136 let iri = SimpleIri::new_unchecked(ns, sf);
137 let got = map.get_prefixed_pair(&iri);
138 assert_eq!(got, expected);
139 }
140
141 #[test_case("http://something.else.com/", None, None; "something else")]
142 #[test_case("http://schema.org/Person", None, Some(("s", "Person")); "s:Person")]
143 #[test_case("http://example.org/", None, Some(("", "")); "single colon")]
144 #[test_case("http://example.org/", Some("a/c"), Some(("a", "c")); "a:c")]
145 #[test_case("http://example.org/", Some("a/b#c"), Some(("ab", "c")); "b:c")]
146 #[test_case("http://example.org/a#", Some("c"), None; ":a#c")]
147 fn get_checked_prefixed_pair(ns: &str, sf: Option<&str>, expected: Option<(&str, &str)>) {
148 let expected = expected.map(|(pf, sf)| (Prefix::new_unchecked(pf), MownStr::from(sf)));
149 let map = make_map();
150 let iri = SimpleIri::new_unchecked(ns, sf);
151 let suffix_check = |txt: &str| txt.chars().all(|c| c.is_alphabetic());
152 let got = map.get_checked_prefixed_pair(&iri, suffix_check);
153 assert_eq!(got, expected);
154 }
155}
156 */