1use colored::*;
2use indexmap::map::Iter;
3use indexmap::IndexMap;
4use iri_s::*;
5use serde::{Deserialize, Serialize};
6
7use crate::{IriRef, PrefixMapError};
8use std::str::FromStr;
9use std::{collections::HashMap, fmt};
10
11#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Default)]
13#[serde(transparent)]
14pub struct PrefixMap {
15 pub map: IndexMap<String, IriS>,
17
18 #[serde(skip)]
20 qualify_prefix_color: Option<Color>,
21
22 #[serde(skip)]
24 qualify_localname_color: Option<Color>,
25
26 #[serde(skip)]
28 qualify_semicolon_color: Option<Color>,
29
30 #[serde(skip)]
32 hyperlink: bool,
33}
34
35fn split(str: &str) -> Option<(&str, &str)> {
36 str.rsplit_once(':')
37}
38
39impl PrefixMap {
40 pub fn new() -> PrefixMap {
42 PrefixMap::default()
43 }
44
45 pub fn with_qualify_prefix_color(mut self, color: Option<Color>) -> Self {
47 self.qualify_prefix_color = color;
48 self
49 }
50
51 pub fn with_qualify_localname_color(mut self, color: Option<Color>) -> Self {
53 self.qualify_localname_color = color;
54 self
55 }
56
57 pub fn with_qualify_semicolon_color(mut self, color: Option<Color>) -> Self {
59 self.qualify_semicolon_color = color;
60 self
61 }
62
63 pub fn without_rich_qualifying(self) -> Self {
64 self.with_hyperlink(false)
65 .with_qualify_localname_color(None)
66 .with_qualify_prefix_color(None)
67 .with_qualify_semicolon_color(None)
68 }
69
70 pub fn insert(&mut self, alias: &str, iri: &IriS) -> Result<(), PrefixMapError> {
72 match self.map.entry(alias.to_string()) {
73 indexmap::map::Entry::Occupied(mut e) => {
74 e.insert(iri.to_owned());
76 }
77 indexmap::map::Entry::Vacant(v) => {
78 v.insert(iri.to_owned());
79 }
80 };
81 Ok(())
82 }
83
84 pub fn find(&self, str: &str) -> Option<&IriS> {
85 self.map.get(str)
86 }
87
88 pub fn from_hashmap(hm: &HashMap<&str, &str>) -> Result<PrefixMap, PrefixMapError> {
89 let mut pm = PrefixMap::new();
90 for (a, s) in hm.iter() {
91 let iri = IriS::from_str(s)?;
92 pm.insert(a, &iri)?;
93 }
94 Ok(pm)
95 }
96
97 pub fn iter(&self) -> Iter<String, IriS> {
99 self.map.iter()
100 }
101
102 pub fn resolve(&self, str: &str) -> Result<IriS, PrefixMapError> {
129 match split(str) {
130 Some((prefix, local)) => {
131 let iri = self.resolve_prefix_local(prefix, local)?;
132 Ok(iri)
133 }
134 None => {
135 let iri = IriS::from_str(str)?;
136 Ok(iri)
137 }
138 }
139 }
140
141 pub fn resolve_iriref(&self, iri_ref: &IriRef) -> Result<IriS, PrefixMapError> {
143 match iri_ref {
144 IriRef::Prefixed { prefix, local } => {
145 let iri = self.resolve_prefix_local(prefix, local)?;
146 Ok(iri)
147 }
148 IriRef::Iri(iri) => Ok(iri.clone()),
149 }
150 }
151
152 pub fn resolve_prefix_local(&self, prefix: &str, local: &str) -> Result<IriS, PrefixMapError> {
182 match self.find(prefix) {
183 Some(iri) => {
184 let new_iri = iri.extend(local)?;
185 Ok(new_iri)
186 }
187 None => Err(PrefixMapError::PrefixNotFound {
188 prefix: prefix.to_string(),
189 prefixmap: self.clone(),
190 }),
191 }
192 }
193
194 pub fn qualify(&self, iri: &IriS) -> String {
219 if let Some(qualified) = self.qualify_optional(iri) {
220 qualified
221 } else {
222 format!("<{iri}>")
223 }
224 }
225
226 pub fn qualify_optional(&self, iri: &IriS) -> Option<String> {
252 let mut founds: Vec<_> = self
253 .map
254 .iter()
255 .filter_map(|(alias, pm_iri)| {
256 iri.as_str()
257 .strip_prefix(pm_iri.as_str())
258 .map(|rest| (alias, rest))
259 })
260 .collect();
261 founds.sort_by_key(|(_, iri)| iri.len());
262 let str = if let Some((alias, rest)) = founds.first() {
263 let prefix_colored = match self.qualify_prefix_color {
264 Some(color) => alias.color(color),
265 None => ColoredString::from(alias.as_str()),
266 };
267 let rest_colored = match self.qualify_localname_color {
268 Some(color) => rest.color(color),
269 None => ColoredString::from(*rest),
270 };
271 let semicolon_colored = match self.qualify_semicolon_color {
272 Some(color) => ":".color(color),
273 None => ColoredString::from(":"),
274 };
275 Some(format!(
276 "{}{}{}",
277 prefix_colored, semicolon_colored, rest_colored
278 ))
279 } else {
280 None
281 };
282 if self.hyperlink {
283 str.map(|s| format!("\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\", s.as_str(), s))
284 } else {
285 str
286 }
287 }
288
289 pub fn qualify_and_length(&self, iri: &IriS) -> (String, usize) {
312 let mut founds: Vec<_> = self
313 .map
314 .iter()
315 .filter_map(|(alias, pm_iri)| {
316 iri.as_str()
317 .strip_prefix(pm_iri.as_str())
318 .map(|rest| (alias, rest))
319 })
320 .collect();
321 founds.sort_by_key(|(_, iri)| iri.len());
322 let (str, length) = if let Some((alias, rest)) = founds.first() {
323 let prefix_colored = match self.qualify_prefix_color {
324 Some(color) => alias.color(color),
325 None => ColoredString::from(alias.as_str()),
326 };
327 let rest_colored = match self.qualify_localname_color {
328 Some(color) => rest.color(color),
329 None => ColoredString::from(*rest),
330 };
331 let semicolon_colored = match self.qualify_semicolon_color {
332 Some(color) => ":".color(color),
333 None => ColoredString::from(":"),
334 };
335 let length = prefix_colored.len() + 1 + rest_colored.len();
336 (
337 format!("{}{}{}", prefix_colored, semicolon_colored, rest_colored),
338 length,
339 )
340 } else {
341 let length = format!("{iri}").len();
342 (format!("<{iri}>"), length)
343 };
344 if self.hyperlink {
345 (
346 format!(
347 "\u{1b}]8;;{}\u{1b}\\{}\u{1b}]8;;\u{1b}\\",
348 iri.as_str(),
349 str
350 ),
351 length,
352 )
353 } else {
354 (str, length)
355 }
356 }
357
358 pub fn qualify_local(&self, iri: &IriS) -> Option<String> {
381 let mut founds: Vec<_> = self
382 .map
383 .iter()
384 .filter_map(|(alias, pm_iri)| {
385 iri.as_str()
386 .strip_prefix(pm_iri.as_str())
387 .map(|rest| (alias, rest))
388 })
389 .collect();
390 founds.sort_by_key(|(_, iri)| iri.len());
391 if let Some((_alias, rest)) = founds.first() {
392 Some(rest.to_string())
393 } else {
394 None
395 }
396 }
397
398 pub fn basic() -> PrefixMap {
400 PrefixMap::from_hashmap(&HashMap::from([
401 ("", "http://example.org/"),
402 ("dc", "http://purl.org/dc/elements/1.1/"),
403 ("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
404 ("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
405 ("sh", "http://www.w3.org/ns/shacl#"),
406 ("xsd", "http://www.w3.org/2001/XMLSchema#"),
407 ]))
408 .unwrap()
409 }
410
411 pub fn wikidata() -> PrefixMap {
414 PrefixMap::from_hashmap(&HashMap::from([
415 ("bd", "http://www.bigdata.com/rdf#"),
416 ("cc", "http://creativecommons.org/ns#"),
417 ("dct", "http://purl.org/dc/terms/"),
418 ("geo", "http://www.opengis.net/ont/geosparql#"),
419 ("hint", "http://www.bigdata.com/queryHints#"),
420 ("ontolex", "http://www.w3.org/ns/lemon/ontolex#"),
421 ("owl", "http://www.w3.org/2002/07/owl#"),
422 ("prov", "http://www.w3.org/ns/prov#"),
423 ("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
424 ("rdfs", "http://www.w3.org/2000/01/rdf-schema#"),
425 ("schema", "http://schema.org/"),
426 ("skos", "http://www.w3.org/2004/02/skos/core#"),
427 ("xsd", "http://www.w3.org/2001/XMLSchema#"),
428 ("p", "http://www.wikidata.org/prop/"),
429 ("pq", "http://www.wikidata.org/prop/qualifier/"),
430 (
431 "pqn",
432 "http://www.wikidata.org/prop/qualifier/value-normalized/",
433 ),
434 ("pqv", "http://www.wikidata.org/prop/qualifier/value/"),
435 ("pr", "http://www.wikidata.org/prop/reference/"),
436 (
437 "prn",
438 "http://www.wikidata.org/prop/reference/value-normalized/",
439 ),
440 ("prv", "http://www.wikidata.org/prop/reference/value/"),
441 ("psv", "http://www.wikidata.org/prop/statement/value/"),
442 ("ps", "http://www.wikidata.org/prop/statement/"),
443 (
444 "psn",
445 "http://www.wikidata.org/prop/statement/value-normalized/",
446 ),
447 ("wd", "http://www.wikidata.org/entity/"),
448 ("wdata", "http://www.wikidata.org/wiki/Special:EntityData/"),
449 ("wdno", "http://www.wikidata.org/prop/novalue/"),
450 ("wdref", "http://www.wikidata.org/reference/"),
451 ("wds", "http://www.wikidata.org/entity/statement/"),
452 ("wdt", "http://www.wikidata.org/prop/direct/"),
453 ("wdtn", "http://www.wikidata.org/prop/direct-normalized/"),
454 ("wdv", "http://www.wikidata.org/value/"),
455 ("wikibase", "http://wikiba.se/ontology#"),
456 ]))
457 .unwrap()
458 .without_default_colors()
459 .with_hyperlink(true)
460 }
461
462 pub fn without_colors(mut self) -> Self {
463 self.qualify_localname_color = None;
464 self.qualify_prefix_color = None;
465 self.qualify_semicolon_color = None;
466 self
467 }
468
469 pub fn without_default_colors(mut self) -> Self {
470 self.qualify_localname_color = Some(Color::Black);
471 self.qualify_prefix_color = Some(Color::Blue);
472 self.qualify_semicolon_color = Some(Color::Red);
473 self
474 }
475
476 pub fn with_hyperlink(mut self, hyperlink: bool) -> Self {
477 self.hyperlink = hyperlink;
478 self
479 }
480
481 pub fn merge(&mut self, other: PrefixMap) -> Result<(), PrefixMapError> {
482 for (alias, iri) in other.iter() {
483 self.insert(alias, iri)?
484 }
485 Ok(())
486 }
487}
488
489impl fmt::Display for PrefixMap {
490 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491 for (alias, iri) in self.map.iter() {
492 writeln!(f, "prefix {}: <{}>", &alias, &iri)?
493 }
494 Ok(())
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501
502 #[test]
503 fn split_ex_name() {
504 assert_eq!(split("ex:name"), Some(("ex", "name")))
505 }
506
507 #[test]
508 fn prefix_map1() {
509 let mut pm = PrefixMap::new();
510 let binding = IriS::from_str("http://example.org/").unwrap();
511 pm.insert("ex", &binding).unwrap();
512 let expected = IriS::from_str("http://example.org/name").unwrap();
513 assert_eq!(pm.resolve("ex:name").unwrap(), expected);
514 }
515
516 #[test]
517 fn prefixmap_display() {
518 let mut pm = PrefixMap::new();
519 let ex_iri = IriS::from_str("http://example.org/").unwrap();
520 pm.insert("ex", &ex_iri).unwrap();
521 let ex_rdf = IriS::from_str("http://www.w3.org/1999/02/22-rdf-syntax-ns#").unwrap();
522 pm.insert("rdf", &ex_rdf).unwrap();
523 assert_eq!(
524 pm.to_string(),
525 "prefix ex: <http://example.org/>\nprefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
526 );
527 }
528
529 #[test]
530 fn prefixmap_resolve() {
531 let mut pm = PrefixMap::new();
532 let ex_iri = IriS::from_str("http://example.org/").unwrap();
533 pm.insert("ex", &ex_iri).unwrap();
534 assert_eq!(
535 pm.resolve("ex:pepe").unwrap(),
536 IriS::from_str("http://example.org/pepe").unwrap()
537 );
538 }
539
540 #[test]
541 fn prefixmap_resolve_xsd() {
542 let mut pm = PrefixMap::new();
543 let ex_iri = IriS::from_str("http://www.w3.org/2001/XMLSchema#").unwrap();
544 pm.insert("xsd", &ex_iri).unwrap();
545 assert_eq!(
546 pm.resolve_prefix_local("xsd", "string").unwrap(),
547 IriS::from_str("http://www.w3.org/2001/XMLSchema#string").unwrap()
548 );
549 }
550
551 #[test]
552 fn qualify() {
553 let mut pm = PrefixMap::new();
554 pm.insert("", &IriS::from_str("http://example.org/").unwrap())
555 .unwrap();
556 pm.insert(
557 "shapes",
558 &IriS::from_str("http://example.org/shapes/").unwrap(),
559 )
560 .unwrap();
561 assert_eq!(
562 pm.qualify(&IriS::from_str("http://example.org/alice").unwrap()),
563 ":alice"
564 );
565 assert_eq!(
566 pm.qualify(&IriS::from_str("http://example.org/shapes/User").unwrap()),
567 "shapes:User"
568 );
569 }
570}