trinja 0.7.1

HTML templating / SSG for RDF(S) resources
Documentation
//! HTML mapping for RDF resources
//!
//! ## Template context
//!
//! The template context used to render resource templates is extended
//! with some Trinja and RDF specific globals and functions.
//!
//! * `this` – always refers to the current resource's [GraphObject]
//!   * `this["<https://example.com/some/predicate>"]` – retrieves the object pointed
//!     to by this predicate
//!   * `this.subject` provides the resource's subject
//! * `rdf_get("<https://example.com/some/subject>")` – retrieves the resource with this
//!   subject as a [GraphObject]
//! * `rdf_subjects("rdf:type", "<https://example.com/some/Thing>")` - iterate over all
//!   resources having this predicate and object

use std::sync::Arc;

use iref::{IriBuf, IriRefBuf};
use list::GraphList;
use minijinja::Value;
use taganak_core::{
    graphs::{Graph, GraphView},
    terms::{Term, TermError},
};
use taganak_orm::re::PrefixMap;
use tokio::runtime::Handle;
use tracing::trace;

pub(crate) mod list;
pub(crate) mod object;

pub use object::GraphObject;

use crate::{Params, ParamsWithEnv};

/// Create a [minijinja::Value] from an RDF term
///
/// This function resolves the term as subject on the graph and determines
/// its type to provide a matching [minijinja::Value].
pub(crate) fn term_to_value<G>(params: ParamsWithEnv<'static, G>, term: &Arc<Term>) -> Option<Value>
where
    G: Graph + std::fmt::Debug + Sync + 'static,
{
    trace!(?term, "Resolving term to value");

    match &*term.clone() {
        Term::Literal(literal) => {
            trace!(?term, "is a literal");
            Some(Value::from(literal.lexical()))
        }
        node @ Term::NamedNode(_) | node @ Term::BlankNode(_) => {
            trace!(?term, "is a reference");

            let handle = Handle::current();
            if handle
                .block_on(async {
                    let graph = params.graph.read().unwrap();
                    graph
                        .view()
                        .await
                        .object(
                            Some(node),
                            Some(
                                &"<http://www.w3.org/1999/02/22-rdf-syntax-ns#first>"
                                    .try_into()
                                    .expect("static IRI"),
                            ),
                        )
                        .await
                })
                .ok()?
                .is_some()
            {
                trace!(?term, "points to a list");
                Some(Value::from_object(GraphList::new(
                    params.base(Some(term.clone())),
                )))
            } else {
                trace!(?term, "points to an object");
                Some(Value::from_object(GraphObject::new(
                    params.base(Some(term.clone())).to_params(),
                )))
            }
        }
        _ => None,
    }
}

/// Creates an RDF term from a string reference in a template
///
/// As prefixes and base resolution are not available inside templates:
///
/// * resolution of relative IRI references needs to be done manually here
/// * only pre-defined prefixes are recognized
pub(crate) fn str_to_term<G>(params: Params<'_, G>, value: &str) -> Result<Term, TermError> {
    if value.starts_with('<') && value.ends_with('>') {
        let mut chars = value.chars();
        chars.next();
        chars.next_back();

        if let Some(base) = params.base {
            let iri_ref = IriRefBuf::new(chars.as_str().to_string()).unwrap();
            Ok(Term::NamedNode(
                iri_ref.into_resolved(&base.to_iri().unwrap()),
            ))
        } else {
            Ok(Term::NamedNode(
                IriBuf::new(chars.as_str().to_string())
                    .map_err(|_| TermError::UnexpectedValue(value.to_string()))?,
            ))
        }
    } else if value.contains(':') && !value.starts_with('_') {
        Ok(params.prefix_map.resolve(value).unwrap().as_ref().clone())
    } else {
        value.try_into()
    }
}