Skip to main content

jsonld_core/flattening/
mod.rs

1//! Flattening algorithm and related types.
2use crate::{ExpandedDocument, FlattenedDocument, IndexedNode, IndexedObject, Object, flattened::UnorderedFlattenedDocument};
3use contextual::WithContext;
4use rdfx::{
5    LocalGenerator,
6    vocabulary::{Vocabulary, VocabularyMut},
7};
8use std::{collections::HashSet, hash::Hash};
9
10mod environment;
11mod node_map;
12
13pub use environment::Environment;
14pub use node_map::*;
15
16/// Result of flattening a document into an ordered list of nodes.
17pub type FlattenResult<I, B> = Result<FlattenedDocument<I, B>, NodeMapError<I, B>>;
18
19/// Result of flattening a document into an unordered set of nodes.
20pub type FlattenUnorderedResult<I, B> = Result<UnorderedFlattenedDocument<I, B>, NodeMapError<I, B>>;
21
22/// Documents that can be flattened into node objects.
23pub trait Flatten<I, B> {
24    /// Flattens this document using the given vocabulary and blank node
25    /// generator.
26    ///
27    /// # Errors
28    ///
29    /// Returns an error when the generator runs out of identifiers, or when two nodes disagree on an `@index`.
30    fn flatten_with<V, G: LocalGenerator>(self, vocabulary: &mut V, generator: G, ordered: bool) -> FlattenResult<I, B>
31    where
32        V: Vocabulary<Iri = I, BlankId = B> + VocabularyMut;
33
34    /// Flattens this document into an unordered set, using the given
35    /// vocabulary and generator.
36    ///
37    /// # Errors
38    ///
39    /// Returns an error when the generator runs out of identifiers, or when two nodes disagree on an `@index`.
40    fn flatten_unordered_with<V, G: LocalGenerator>(self, vocabulary: &mut V, generator: G) -> FlattenUnorderedResult<I, B>
41    where
42        V: Vocabulary<Iri = I, BlankId = B> + VocabularyMut;
43
44    /// Flattens this document, sorting the result when `ordered` is set.
45    ///
46    /// # Errors
47    ///
48    /// Returns an error when the generator runs out of identifiers, or when two nodes disagree on an `@index`.
49    fn flatten<G: LocalGenerator>(self, generator: G, ordered: bool) -> FlattenResult<I, B>
50    where
51        (): Vocabulary<Iri = I, BlankId = B>,
52        Self: Sized,
53    {
54        self.flatten_with(rdfx::vocabulary::no_vocabulary_mut(), generator, ordered)
55    }
56
57    /// Flattens this document into an unordered set.
58    ///
59    /// # Errors
60    ///
61    /// Returns an error when the generator runs out of identifiers, or when two nodes disagree on an `@index`.
62    fn flatten_unordered<G: LocalGenerator>(self, generator: G) -> FlattenUnorderedResult<I, B>
63    where
64        (): Vocabulary<Iri = I, BlankId = B>,
65        Self: Sized,
66    {
67        self.flatten_unordered_with(rdfx::vocabulary::no_vocabulary_mut(), generator)
68    }
69}
70
71impl<I: Clone + Eq + Hash, B: Clone + Eq + Hash> Flatten<I, B> for ExpandedDocument<I, B> {
72    fn flatten_with<V, G: LocalGenerator>(self, vocabulary: &mut V, generator: G, ordered: bool) -> FlattenResult<I, B>
73    where
74        V: Vocabulary<Iri = I, BlankId = B> + VocabularyMut,
75    {
76        Ok(self.generate_node_map_with(vocabulary, generator)?.flatten_with(vocabulary, ordered))
77    }
78
79    fn flatten_unordered_with<V, G: LocalGenerator>(self, vocabulary: &mut V, generator: G) -> FlattenUnorderedResult<I, B>
80    where
81        V: Vocabulary<Iri = I, BlankId = B> + VocabularyMut,
82    {
83        Ok(self.generate_node_map_with(vocabulary, generator)?.flatten_unordered())
84    }
85}
86
87fn filter_graph<T, B>(node: IndexedNode<T, B>) -> Option<IndexedNode<T, B>> {
88    if node.index().is_none() && node.is_empty() { None } else { Some(node) }
89}
90
91fn filter_sub_graph<T, B>(mut node: IndexedNode<T, B>) -> Option<IndexedObject<T, B>> {
92    if node.index().is_none() && node.properties().is_empty() {
93        None
94    } else {
95        node.set_graph_entry(None);
96        node.set_included(None);
97        node.set_reverse_properties(None);
98        Some(node.map_inner(Object::node))
99    }
100}
101
102impl<T: Clone + Eq + Hash, B: Clone + Eq + Hash> NodeMap<T, B> {
103    /// Flattens this node map into a list of nodes, sorted when `ordered` is
104    /// set.
105    #[must_use]
106    pub fn flatten(self, ordered: bool) -> FlattenedDocument<T, B>
107    where
108        (): Vocabulary<Iri = T, BlankId = B>,
109    {
110        self.flatten_with(&(), ordered)
111    }
112
113    /// Flattens this node map into a list of nodes, sorting through the given
114    /// vocabulary when `ordered` is set.
115    pub fn flatten_with<V>(self, vocabulary: &V, ordered: bool) -> FlattenedDocument<T, B>
116    where
117        V: Vocabulary<Iri = T, BlankId = B>,
118    {
119        let (mut default_graph, named_graphs) = self.into_parts();
120
121        let named_graphs: Vec<_> = if ordered {
122            let mut decorated: Vec<_> = named_graphs
123                .into_iter()
124                .map(|entry| (entry.0.with(vocabulary).as_str().to_string(), entry))
125                .collect();
126            decorated.sort_by(|a, b| a.0.cmp(&b.0));
127            decorated.into_iter().map(|(_, entry)| entry).collect()
128        } else {
129            named_graphs.into_iter().collect()
130        };
131
132        for (graph_id, graph) in named_graphs {
133            // `declare_node` with no index is infallible (only a conflicting
134            // index can fail it), so the skip is unreachable.
135            let Ok(entry) = default_graph.declare_node(graph_id, None) else {
136                continue;
137            };
138            let nodes: Vec<_> = if ordered {
139                let mut decorated: Vec<_> = graph
140                    .into_nodes()
141                    .map(|n| {
142                        // Every node in a node-map graph has an `id`, so the
143                        // empty-key fallback is unreachable.
144                        let key = n.id.as_ref().map(|id| id.with(vocabulary).as_str().to_string()).unwrap_or_default();
145                        (key, n)
146                    })
147                    .collect();
148                decorated.sort_by(|a, b| a.0.cmp(&b.0));
149                decorated.into_iter().map(|(_, n)| n).collect()
150            } else {
151                graph.into_nodes().collect()
152            };
153            entry.set_graph_entry(Some(nodes.into_iter().filter_map(filter_sub_graph).collect()));
154        }
155
156        let nodes: Vec<_> = if ordered {
157            let mut decorated: Vec<_> = default_graph
158                .into_nodes()
159                .filter_map(filter_graph)
160                .map(|n| {
161                    // Every node in a node-map graph has an `id`, so the
162                    // empty-key fallback is unreachable.
163                    let key = n.id.as_ref().map(|id| id.with(vocabulary).as_str().to_string()).unwrap_or_default();
164                    (key, n)
165                })
166                .collect();
167            decorated.sort_by(|a, b| a.0.cmp(&b.0));
168            decorated.into_iter().map(|(_, n)| n).collect()
169        } else {
170            default_graph.into_nodes().filter_map(filter_graph).collect()
171        };
172
173        nodes
174    }
175
176    /// Flattens this node map into an unordered set of nodes.
177    pub fn flatten_unordered(self) -> HashSet<IndexedNode<T, B>> {
178        let (mut default_graph, named_graphs) = self.into_parts();
179
180        for (graph_id, graph) in named_graphs {
181            // `declare_node` with no index is infallible (only a conflicting
182            // index can fail it), so the skip is unreachable.
183            let Ok(entry) = default_graph.declare_node(graph_id, None) else {
184                continue;
185            };
186            entry.set_graph_entry(Some(graph.into_nodes().filter_map(filter_sub_graph).collect()));
187        }
188
189        default_graph.into_nodes().filter_map(filter_graph).collect()
190    }
191}