Skip to main content

json_ld/algorithms/context_processing/
iri.rs

1use rdf_syntax::BlankId;
2use rdf_syntax::{Iri, IriBuf, IriRef};
3
4use crate::{
5	JsonLdBacktraceBuilder, JsonLdLenient, JsonLdLocatedError, JsonLdNullable, JsonLdTerm,
6	JsonLdWarning,
7	algorithms::{
8		AsyncJsonLdEnvironment,
9		context_processing::{ContextProcessor, TargetProcessedContext, merged::Merged},
10	},
11	context::RawJsonLdProcessedContext,
12	syntax::{
13		JsonLdCompactIri, JsonLdExpandableRef,
14		context::{JsonLdContextTerm, JsonLdKeyOrKeywordRef},
15		is_keyword_like,
16	},
17};
18
19/// Resolve `iri_ref` against the given base IRI.
20pub fn resolve_iri(iri_ref: &IriRef, base_iri: Option<&Iri>) -> Option<IriBuf> {
21	match base_iri {
22		Some(base_iri) => Some(iri_ref.resolved(base_iri)),
23		None => iri_ref.as_iri().map(ToOwned::to_owned),
24	}
25}
26
27/// Result of the [`expand_iri_with`] function.
28pub type ExpandIriResult = Result<JsonLdTerm, JsonLdLocatedError>;
29
30impl<'a> ContextProcessor<'a> {
31	/// `context_loc` has the same meaning as in `define` and is only ever
32	/// forwarded, unnarrowed, to `define`.
33	pub async fn expand_iri_recursive(
34		&self,
35		env: &impl AsyncJsonLdEnvironment,
36		result: &mut TargetProcessedContext<'_>,
37		local_context: &Merged<'_>,
38		value: JsonLdNullable<JsonLdExpandableRef<'_>>,
39		context_loc: JsonLdBacktraceBuilder<'_>,
40	) -> ExpandIriResult {
41		match value {
42			JsonLdNullable::Null => Ok(JsonLdTerm::Null),
43			JsonLdNullable::Some(JsonLdExpandableRef::Keyword(k)) => Ok(JsonLdTerm::Keyword(k)),
44			JsonLdNullable::Some(JsonLdExpandableRef::String(value)) => {
45				if is_keyword_like(value) {
46					return Ok(JsonLdTerm::Null);
47				}
48
49				// If `local_context` is not null, it contains an entry with a key that equals value, and the
50				// value of the entry for value in defined is not true, invoke the Create Term Definition
51				// algorithm, passing active context, local context, value as term, and defined. This will
52				// ensure that a term definition is created for value in active context during Context
53				// Processing.
54				Box::pin(self.for_recursive_definition().define(
55					env,
56					result,
57					local_context,
58					value.into(),
59					false,
60					context_loc,
61				))
62				.await?;
63
64				if let Some(term_definition) = result.value.get(value) {
65					// If active context has a term definition for value, and the associated IRI mapping
66					// is a keyword, return that keyword.
67					if let Some(value) = term_definition.value()
68						&& value.is_keyword()
69					{
70						return Ok(value.clone());
71					}
72
73					// Return the associated IRI mapping.
74					return match term_definition.value() {
75						Some(value) => Ok(value.clone()),
76						None => Ok(JsonLdTerm::Null),
77					};
78				}
79
80				if value.find(':').map(|i| i > 0).unwrap_or(false) {
81					if let Ok(blank_id) = BlankId::new(value) {
82						return Ok(JsonLdTerm::Id(JsonLdLenient::blank(blank_id.to_owned())));
83					}
84
85					if value == "_:" {
86						return Ok(JsonLdTerm::Id(JsonLdLenient::Invalid("_:".to_string())));
87					}
88
89					if let Ok(compact_iri) = JsonLdCompactIri::new(value) {
90						// If local context is not null, it contains a `prefix` entry, and the value of the
91						// prefix entry in defined is not true, invoke the Create Term Definition
92						// algorithm, passing active context, local context, prefix as term, and defined.
93						// This will ensure that a term definition is created for prefix in active context
94						// during Context Processing.
95						Box::pin(self.for_recursive_definition().define(
96							env,
97							result,
98							local_context,
99							JsonLdKeyOrKeywordRef::Key(compact_iri.prefix().into()),
100							false,
101							context_loc,
102						))
103						.await?;
104
105						// If active context contains a term definition for prefix having a non-null IRI
106						// mapping and the prefix flag of the term definition is true, return the result
107						// of concatenating the IRI mapping associated with prefix and suffix.
108						let prefix_key = JsonLdContextTerm::from(compact_iri.prefix().to_string());
109						if let Some(term_definition) = result.value.get_normal(&prefix_key)
110							&& term_definition.prefix
111							&& let Some(mapping) = &term_definition.value
112						{
113							let mut result = mapping.as_str().to_owned();
114							result.push_str(compact_iri.suffix());
115
116							return Ok(JsonLdTerm::Id(JsonLdLenient::from_string(result).0));
117						}
118					}
119
120					if let Ok(iri) = Iri::new(value) {
121						return Ok(JsonLdTerm::Id(JsonLdLenient::iri(iri.to_owned())));
122					}
123				}
124
125				// If active context has a vocabulary mapping, return the result of
126				// concatenating the vocabulary mapping with value.
127				match result.value.vocabulary() {
128					Some(JsonLdTerm::Id(mapping)) => {
129						let mut result = mapping.as_str().to_owned();
130						result.push_str(value);
131
132						return Ok(JsonLdTerm::Id(JsonLdLenient::from_string(result).0));
133					}
134					Some(_) => return Ok(invalid_iri(value.to_owned(), |w| env.warn(w))),
135					None => (),
136				}
137
138				// Return value as is.
139				Ok(invalid_iri(value.to_owned(), |w| env.warn(w)))
140			}
141		}
142	}
143}
144
145impl RawJsonLdProcessedContext {
146	pub fn expand_iri(
147		&self,
148		value: JsonLdNullable<JsonLdExpandableRef<'_>>,
149		document_relative: bool,
150		vocab: bool,
151	) -> JsonLdTerm {
152		self.expand_iri_with(value, document_relative, vocab, |_| {})
153	}
154
155	pub fn expand_iri_with(
156		&self,
157		value: JsonLdNullable<JsonLdExpandableRef<'_>>,
158		document_relative: bool,
159		vocab: bool,
160		on_warning: impl FnOnce(JsonLdWarning),
161	) -> JsonLdTerm {
162		match value {
163			JsonLdNullable::Null => JsonLdTerm::Null,
164			JsonLdNullable::Some(JsonLdExpandableRef::Keyword(k)) => JsonLdTerm::Keyword(k),
165			JsonLdNullable::Some(JsonLdExpandableRef::String(value)) => {
166				if is_keyword_like(value) {
167					return JsonLdTerm::Null;
168				}
169
170				if let Some(term_definition) = self.get(value) {
171					// If active context has a term definition for value, and the associated IRI mapping
172					// is a keyword, return that keyword.
173					if let Some(value) = term_definition.value()
174						&& value.is_keyword()
175					{
176						return value.clone();
177					}
178
179					// If vocab is true and the active context has a term definition for value, return the
180					// associated IRI mapping.
181					if vocab {
182						return match term_definition.value() {
183							Some(value) => value.clone(),
184							None => JsonLdTerm::Null,
185						};
186					}
187				}
188
189				if value.find(':').map(|i| i > 0).unwrap_or(false) {
190					if let Ok(blank_id) = BlankId::new(value) {
191						return JsonLdTerm::Id(JsonLdLenient::blank(blank_id.to_owned()));
192					}
193
194					if value == "_:" {
195						return JsonLdTerm::Id(JsonLdLenient::Invalid("_:".to_string()));
196					}
197
198					if let Ok(compact_iri) = JsonLdCompactIri::new(value) {
199						// If active context contains a term definition for prefix having a non-null IRI
200						// mapping and the prefix flag of the term definition is true, return the result
201						// of concatenating the IRI mapping associated with prefix and suffix.
202						let prefix_key = JsonLdContextTerm::from(compact_iri.prefix().to_string());
203						if let Some(term_definition) = self.get_normal(&prefix_key)
204							&& term_definition.prefix
205							&& let Some(mapping) = &term_definition.value
206						{
207							let mut result = mapping.as_str().to_owned();
208							result.push_str(compact_iri.suffix());
209							return JsonLdTerm::Id(JsonLdLenient::from_string(result).0);
210						}
211					}
212
213					if let Ok(iri) = Iri::new(value) {
214						return JsonLdTerm::Id(JsonLdLenient::iri(iri.to_owned()));
215					}
216				}
217
218				// If vocab is true, and active context has a vocabulary mapping, return the result of
219				// concatenating the vocabulary mapping with value.
220				if vocab {
221					match self.vocabulary() {
222						Some(JsonLdTerm::Id(mapping)) => {
223							let mut result = mapping.as_str().to_owned();
224							result.push_str(value);
225
226							return JsonLdTerm::Id(JsonLdLenient::from_string(result).0);
227						}
228						Some(_) => return invalid_iri(value.to_string(), on_warning),
229						None => (),
230					}
231				}
232
233				// Otherwise, if document relative is true set value to the result of resolving value
234				// against the base IRI from active context. Only the basic algorithm in section 5.2 of
235				// [RFC3986] is used; neither Syntax-Based Normalization nor Scheme-Based Normalization
236				// are performed. Characters additionally allowed in IRI references are treated in the
237				// same way that unreserved characters are treated in URI references, per section 6.5 of
238				// [RFC3987].
239				if document_relative
240					&& let Ok(iri_ref) = IriRef::new(value)
241					&& let Some(iri) = resolve_iri(iri_ref, self.base_iri())
242				{
243					return JsonLdTerm::from(iri);
244				}
245
246				// Return value as is.
247				invalid_iri(value.to_string(), on_warning)
248			}
249		}
250	}
251}
252
253fn invalid_iri(value: String, on_warning: impl FnOnce(JsonLdWarning)) -> JsonLdTerm {
254	(on_warning)(JsonLdWarning::MalformedIri(value.clone()));
255	JsonLdTerm::Id(JsonLdLenient::Invalid(value))
256}