1use super::context::{compact_iri, find_term, is_keyword};
12use super::{
13 CompactionError, CompactionOptions, ContainerType, JsonLdContext, JsonLdValue, TermDefinition,
14};
15use indexmap::IndexMap;
16
17pub fn compact_value(
42 active_ctx: &JsonLdContext,
43 active_property: Option<&str>,
44 value: &JsonLdValue,
45) -> Result<JsonLdValue, CompactionError> {
46 let obj = match value {
47 JsonLdValue::Object(m) => m,
48 other => return Ok(other.clone()),
49 };
50
51 let term_def = active_property.and_then(|p| active_ctx.terms.get(p));
53
54 let val = obj.get("@value");
55 let typ = obj.get("@type");
56 let lang = obj.get("@language");
57
58 if matches!(typ, Some(JsonLdValue::Str(t)) if t == "@json") {
60 return Ok(value.clone());
61 }
62
63 if let Some(JsonLdValue::Str(type_iri)) = typ {
65 if let Some(def) = term_def {
66 if def.type_mapping.as_deref() == Some(type_iri.as_str()) {
67 return Ok(val.cloned().unwrap_or(JsonLdValue::Null));
69 }
70 if def.type_mapping.as_deref() == Some("@id") {
71 if let Some(JsonLdValue::Str(v)) = val {
73 let compacted = compact_iri(active_ctx, v, None, false, false);
74 return Ok(JsonLdValue::Str(compacted));
75 }
76 }
77 }
78 let compact_type = compact_iri(active_ctx, type_iri, None, true, false);
80 let mut out: IndexMap<String, JsonLdValue> = IndexMap::new();
81 out.insert(
82 "@value".to_string(),
83 val.cloned().unwrap_or(JsonLdValue::Null),
84 );
85 out.insert("@type".to_string(), JsonLdValue::Str(compact_type));
86 return Ok(JsonLdValue::Object(out));
87 }
88
89 if let Some(JsonLdValue::Str(lang_tag)) = lang {
91 if let Some(def) = term_def {
92 if def.language.as_deref() == Some(lang_tag.as_str()) {
93 return Ok(val.cloned().unwrap_or(JsonLdValue::Null));
95 }
96 }
97 let mut out: IndexMap<String, JsonLdValue> = IndexMap::new();
99 if let Some(v) = val {
100 out.insert("@value".to_string(), v.clone());
101 }
102 out.insert("@language".to_string(), JsonLdValue::Str(lang_tag.clone()));
103 return Ok(JsonLdValue::Object(out));
104 }
105
106 if let Some(JsonLdValue::Str(s)) = val {
108 if typ.is_none() && lang.is_none() {
109 return Ok(JsonLdValue::Str(s.clone()));
110 }
111 }
112
113 if let Some(JsonLdValue::Number(n)) = val {
115 if typ.is_none() && lang.is_none() {
116 return Ok(JsonLdValue::Number(*n));
117 }
118 }
119
120 if let Some(JsonLdValue::Bool(b)) = val {
122 if typ.is_none() && lang.is_none() {
123 return Ok(JsonLdValue::Bool(*b));
124 }
125 }
126
127 let mut out: IndexMap<String, JsonLdValue> = IndexMap::new();
129 if let Some(v) = val {
130 out.insert("@value".to_string(), v.clone());
131 }
132 if let Some(t) = typ {
133 out.insert("@type".to_string(), t.clone());
134 }
135 if let Some(l) = lang {
136 out.insert("@language".to_string(), l.clone());
137 }
138 Ok(JsonLdValue::Object(out))
139}
140
141pub fn compact_node(
165 active_ctx: &JsonLdContext,
166 _type_scoped_ctx: &JsonLdContext,
167 _active_property: Option<&str>,
168 node: &IndexMap<String, JsonLdValue>,
169 options: &CompactionOptions,
170) -> Result<JsonLdValue, CompactionError> {
171 let mut result: IndexMap<String, JsonLdValue> = IndexMap::new();
172
173 if let Some(id_val) = node.get("@id") {
175 if let Some(id_str) = id_val.as_str() {
176 let compact = compact_iri(active_ctx, id_str, None, false, false);
177 result.insert("@id".to_string(), JsonLdValue::Str(compact));
178 }
179 }
180
181 if let Some(type_val) = node.get("@type") {
183 let types: Vec<&JsonLdValue> = match type_val {
184 JsonLdValue::Array(a) => a.iter().collect(),
185 single => vec![single],
186 };
187 let mut compact_types: Vec<JsonLdValue> = Vec::new();
188 for t in types {
189 if let Some(type_iri) = t.as_str() {
190 let compacted = compact_iri(active_ctx, type_iri, None, true, false);
191 compact_types.push(JsonLdValue::Str(compacted));
192 }
193 }
194 let mut type_iter = compact_types.into_iter();
195 match (options.compact_arrays, type_iter.next()) {
196 (true, Some(single)) if type_iter.len() == 0 => {
197 result.insert("@type".to_string(), single);
198 }
199 (_, first) => {
200 let values: Vec<JsonLdValue> = first.into_iter().chain(type_iter).collect();
201 result.insert("@type".to_string(), JsonLdValue::Array(values));
202 }
203 }
204 }
205
206 if let Some(graph_val) = node.get("@graph") {
208 let compact_key = compact_iri(active_ctx, "@graph", None, true, false);
209 let compacted = compact_element_dispatch(graph_val, active_ctx, Some("@graph"), options)?;
210 result.insert(compact_key, compacted);
211 }
212
213 if let Some(rev_val) = node.get("@reverse") {
215 if let Some(rev_obj) = rev_val.as_object() {
216 let mut rev_result: IndexMap<String, JsonLdValue> = IndexMap::new();
217 for (prop, val) in rev_obj {
218 let compact_prop = compact_iri(active_ctx, prop, Some(val), true, true);
219 let rev_term = find_term(active_ctx, prop, Some(val), &[], "@type", "");
221 let compact_val = compact_element_dispatch(val, active_ctx, Some(prop), options)?;
222 if let Some(_term) = rev_term {
223 result.insert(compact_prop.clone(), compact_val);
225 } else {
226 rev_result.insert(compact_prop, compact_val);
227 }
228 }
229 if !rev_result.is_empty() {
230 result.insert("@reverse".to_string(), JsonLdValue::Object(rev_result));
231 }
232 }
233 }
234
235 for (expanded_prop, value) in node {
237 if matches!(
239 expanded_prop.as_str(),
240 "@id" | "@type" | "@graph" | "@reverse" | "@context"
241 ) {
242 continue;
243 }
244
245 if is_keyword(expanded_prop) {
246 let compact_key = compact_iri(active_ctx, expanded_prop, None, true, false);
248 let compact_val =
249 compact_element_dispatch(value, active_ctx, Some(expanded_prop), options)?;
250 result.insert(compact_key, compact_val);
251 continue;
252 }
253
254 let compact_prop = compact_iri(active_ctx, expanded_prop, Some(value), true, false);
256
257 let term_def = active_ctx.terms.get(&compact_prop);
259
260 let values: &[JsonLdValue] = match value {
262 JsonLdValue::Array(a) => a.as_slice(),
263 single => std::slice::from_ref(single),
264 };
265
266 if let Some(def) = term_def {
267 if def.container.contains(&ContainerType::Language) {
269 let lang_map = build_language_map(values, active_ctx, expanded_prop, options)?;
270 result.insert(compact_prop, lang_map);
271 continue;
272 }
273
274 if def.container.contains(&ContainerType::Type) {
276 let type_map = build_type_map(values, active_ctx, expanded_prop, options)?;
277 result.insert(compact_prop, type_map);
278 continue;
279 }
280
281 if def.container.contains(&ContainerType::Index) {
283 let idx_map = build_index_map(values, active_ctx, expanded_prop, options)?;
284 result.insert(compact_prop, idx_map);
285 continue;
286 }
287
288 if def.container.contains(&ContainerType::List) {
290 let list_items = collect_list_items(values);
292 let mut compact_items: Vec<JsonLdValue> = Vec::new();
293 for item in &list_items {
294 let c =
295 compact_element_dispatch(item, active_ctx, Some(expanded_prop), options)?;
296 compact_items.push(c);
297 }
298 result.insert(compact_prop, JsonLdValue::Array(compact_items));
299 continue;
300 }
301
302 if def.container.contains(&ContainerType::Set) {
304 let compact_arr = compact_array(active_ctx, expanded_prop, values, options)?;
305 let arr = match compact_arr {
307 JsonLdValue::Array(a) => JsonLdValue::Array(a),
308 single => JsonLdValue::Array(vec![single]),
309 };
310 result.insert(compact_prop, arr);
311 continue;
312 }
313 }
314
315 let compact_val = if values.len() == 1 && options.compact_arrays {
317 let v = compact_element_dispatch(&values[0], active_ctx, Some(expanded_prop), options)?;
319 let needs_array = term_def
321 .map(|d| {
322 d.container.contains(&ContainerType::Set)
323 || d.container.contains(&ContainerType::List)
324 })
325 .unwrap_or(false);
326 if needs_array {
327 JsonLdValue::Array(vec![v])
328 } else {
329 v
330 }
331 } else {
332 compact_array(active_ctx, expanded_prop, values, options)?
333 };
334
335 insert_or_merge(&mut result, compact_prop, compact_val);
337 }
338
339 if options.ordered {
341 result.sort_keys();
342 }
343
344 Ok(JsonLdValue::Object(result))
345}
346
347pub fn compact_array(
364 active_ctx: &JsonLdContext,
365 active_property: &str,
366 array: &[JsonLdValue],
367 options: &CompactionOptions,
368) -> Result<JsonLdValue, CompactionError> {
369 let mut out: Vec<JsonLdValue> = Vec::with_capacity(array.len());
370
371 for item in array {
372 let compacted = compact_element_dispatch(item, active_ctx, Some(active_property), options)?;
373 out.push(compacted);
374 }
375
376 let term_def = active_ctx.terms.get(active_property);
378 let requires_array = term_def
379 .map(|d| {
380 d.container.contains(&ContainerType::Set) || d.container.contains(&ContainerType::List)
381 })
382 .unwrap_or(false);
383
384 if options.compact_arrays && out.len() == 1 && !requires_array {
385 Ok(out.into_iter().next().unwrap_or(JsonLdValue::Null))
386 } else {
387 Ok(JsonLdValue::Array(out))
388 }
389}
390
391fn compact_element_dispatch(
397 value: &JsonLdValue,
398 ctx: &JsonLdContext,
399 active_property: Option<&str>,
400 options: &CompactionOptions,
401) -> Result<JsonLdValue, CompactionError> {
402 match value {
403 JsonLdValue::Object(map) => {
404 if map.contains_key("@value") {
405 compact_value(ctx, active_property, value)
406 } else if map.contains_key("@list") {
407 let items = match map.get("@list") {
408 Some(JsonLdValue::Array(a)) => a.as_slice(),
409 _ => &[],
410 };
411 compact_array(ctx, active_property.unwrap_or("@list"), items, options)
412 } else {
413 compact_node(ctx, ctx, active_property, map, options)
414 }
415 }
416 JsonLdValue::Array(items) => {
417 compact_array(ctx, active_property.unwrap_or("@graph"), items, options)
418 }
419 other => Ok(other.clone()),
420 }
421}
422
423fn build_language_map(
428 values: &[JsonLdValue],
429 _ctx: &JsonLdContext,
430 _active_property: &str,
431 options: &CompactionOptions,
432) -> Result<JsonLdValue, CompactionError> {
433 let mut lang_map: IndexMap<String, JsonLdValue> = IndexMap::new();
434 for val in values {
435 let (lang_key, compacted) = match val {
438 JsonLdValue::Object(obj) => {
439 let lang = obj
440 .get("@language")
441 .and_then(|l| l.as_str())
442 .unwrap_or("@none")
443 .to_string();
444 let v = obj.get("@value").cloned().unwrap_or(JsonLdValue::Null);
446 (lang, v)
447 }
448 other => ("@none".to_string(), other.clone()),
449 };
450 insert_or_merge(&mut lang_map, lang_key, compacted);
451 }
452 if options.ordered {
454 lang_map.sort_keys();
455 }
456 Ok(JsonLdValue::Object(lang_map))
457}
458
459fn build_type_map(
461 values: &[JsonLdValue],
462 ctx: &JsonLdContext,
463 active_property: &str,
464 options: &CompactionOptions,
465) -> Result<JsonLdValue, CompactionError> {
466 let mut type_map: IndexMap<String, JsonLdValue> = IndexMap::new();
467 for val in values {
468 let compacted = compact_value(ctx, Some(active_property), val)?;
469 let type_key = match val {
470 JsonLdValue::Object(obj) => obj
471 .get("@type")
472 .and_then(|t| t.as_str())
473 .map(|t| compact_iri(ctx, t, None, true, false))
474 .unwrap_or_else(|| "@none".to_string()),
475 _ => "@none".to_string(),
476 };
477 insert_or_merge(&mut type_map, type_key, compacted);
478 }
479 if options.ordered {
480 type_map.sort_keys();
481 }
482 Ok(JsonLdValue::Object(type_map))
483}
484
485fn build_index_map(
487 values: &[JsonLdValue],
488 ctx: &JsonLdContext,
489 active_property: &str,
490 options: &CompactionOptions,
491) -> Result<JsonLdValue, CompactionError> {
492 let mut idx_map: IndexMap<String, JsonLdValue> = IndexMap::new();
493 for val in values {
494 let idx_key = match val {
495 JsonLdValue::Object(obj) => obj
496 .get("@index")
497 .and_then(|i| i.as_str())
498 .unwrap_or("@none")
499 .to_string(),
500 _ => "@none".to_string(),
501 };
502 let compacted = compact_element_dispatch(val, ctx, Some(active_property), options)?;
503 insert_or_merge(&mut idx_map, idx_key, compacted);
504 }
505 if options.ordered {
506 idx_map.sort_keys();
507 }
508 Ok(JsonLdValue::Object(idx_map))
509}
510
511fn collect_list_items(values: &[JsonLdValue]) -> Vec<&JsonLdValue> {
513 let mut items: Vec<&JsonLdValue> = Vec::new();
514 for val in values {
515 match val {
516 JsonLdValue::Object(obj) => {
517 if let Some(JsonLdValue::Array(list)) = obj.get("@list") {
518 items.extend(list.iter());
519 } else {
520 items.push(val);
521 }
522 }
523 _ => items.push(val),
524 }
525 }
526 items
527}
528
529fn insert_or_merge(map: &mut IndexMap<String, JsonLdValue>, key: String, value: JsonLdValue) {
531 if let Some(existing) = map.get_mut(&key) {
532 if let JsonLdValue::Array(arr) = existing {
534 arr.push(value);
535 return;
536 }
537 let prev = std::mem::replace(existing, JsonLdValue::Null);
539 *existing = JsonLdValue::Array(vec![prev, value]);
540 } else {
541 map.insert(key, value);
542 }
543}
544
545pub fn add_prefix_term(ctx: &mut JsonLdContext, prefix: impl Into<String>, iri: impl Into<String>) {
547 ctx.terms
548 .insert(prefix.into(), TermDefinition::prefix(iri.into()));
549}