1use super::fact::ItemFact;
4use crate::{ExpandedName, Label, PresentationArc, TaxonomySet};
5use std::{
6 cmp::Ordering,
7 collections::{HashMap, HashSet},
8};
9
10#[derive(Debug)]
12pub struct DocumentView<'a> {
13 pub sections: Vec<SectionView<'a>>,
15}
16
17impl<'a> DocumentView<'a> {
18 pub fn build(facts: &[&ItemFact], taxonomy: &'a TaxonomySet) -> Self {
24 build_view(facts, taxonomy)
25 }
26}
27
28#[derive(Debug)]
30pub struct SectionView<'a> {
31 pub role: &'a str,
33 pub nodes: Vec<TreeNode<'a>>,
35}
36
37#[derive(Debug)]
39pub struct TreeNode<'a> {
40 pub concept_name: &'a str,
42 pub labels: &'a [Label],
45 pub depth: usize,
47 pub fact_indices: Vec<usize>,
54 pub children: Vec<TreeNode<'a>>,
56}
57
58pub fn build_view<'a>(facts: &[&ItemFact], taxonomy: &'a TaxonomySet) -> DocumentView<'a> {
65 let mut fact_index: HashMap<ExpandedName, Vec<usize>> = HashMap::new();
67
68 for (i, fact) in facts.iter().enumerate() {
69 fact_index
70 .entry(fact.concept_name().clone())
71 .or_default()
72 .push(i);
73 }
74
75 let roles = taxonomy
76 .presentations()
77 .iter()
78 .map(|(role, arcs)| (role.as_str(), arcs))
79 .collect::<Vec<_>>();
80
81 let mut sections = Vec::with_capacity(roles.len());
82
83 for (role, arcs) in roles {
84 let mut arc_index: HashMap<&'a ExpandedName, Vec<&'a PresentationArc>> = HashMap::new();
85
86 for arc in arcs {
87 arc_index.entry(&arc.from).or_default().push(arc);
88 }
89
90 for children in arc_index.values_mut() {
93 children.sort_by(|a, b| match (a.order, b.order) {
94 (Some(x), Some(y)) => x.cmp(&y),
95 (Some(_), None) => Ordering::Less,
96 (None, Some(_)) => Ordering::Greater,
97 (None, None) => Ordering::Equal,
98 });
99 }
100
101 let roots = find_roots(arcs, &arc_index);
102 let mut visited: HashSet<&'a ExpandedName> = HashSet::new();
103 let nodes = roots
104 .iter()
105 .flat_map(|root_id| {
106 build_nodes(&arc_index, root_id, 0, taxonomy, &fact_index, &mut visited)
107 })
108 .collect();
109
110 sections.push(SectionView { role, nodes });
111 }
112
113 DocumentView { sections }
114}
115
116pub(super) fn find_roots<'a>(
118 arcs: &'a [PresentationArc],
119 arc_index: &HashMap<&'a ExpandedName, Vec<&'a PresentationArc>>,
120) -> Vec<&'a ExpandedName> {
121 let to_set: HashSet<&ExpandedName> = arcs.iter().map(|a| &a.to).collect();
122 let mut seen: HashSet<&ExpandedName> = HashSet::new();
123 let mut roots: Vec<&'a ExpandedName> = Vec::new();
124
125 for arc in arcs {
126 let from = &arc.from;
127
128 if !to_set.contains(from) && seen.insert(from) {
129 roots.push(from);
130 }
131 }
132
133 roots.sort_by(|a, b| {
135 let min_order = |id: &&ExpandedName| {
136 arc_index
137 .get(*id)
138 .and_then(|arcs| arcs.iter().filter_map(|a| a.order).min())
139 };
140 match (min_order(a), min_order(b)) {
141 (Some(x), Some(y)) => x.cmp(&y),
142 (Some(_), None) => Ordering::Less,
143 (None, Some(_)) => Ordering::Greater,
144 (None, None) => Ordering::Equal,
145 }
146 });
147
148 roots
149}
150
151fn build_nodes<'a>(
153 arc_index: &HashMap<&'a ExpandedName, Vec<&'a PresentationArc>>,
154 parent_id: &'a ExpandedName,
155 depth: usize,
156 taxonomy: &'a TaxonomySet,
157 fact_index: &HashMap<ExpandedName, Vec<usize>>,
158 visited: &mut HashSet<&'a ExpandedName>,
159) -> Vec<TreeNode<'a>> {
160 if !visited.insert(parent_id) {
161 return Vec::new();
163 }
164
165 let children_arcs = arc_index.get(parent_id).map(Vec::as_slice).unwrap_or(&[]);
167
168 let mut nodes = Vec::with_capacity(children_arcs.len());
169
170 for arc in children_arcs {
171 let child_id = &arc.to;
172 let labels = taxonomy.labels(child_id).unwrap_or_default();
173 let fact_indices = fact_index.get(child_id).cloned().unwrap_or_default();
174 let children = build_nodes(
175 arc_index,
176 child_id,
177 depth + 1,
178 taxonomy,
179 fact_index,
180 visited,
181 );
182
183 nodes.push(TreeNode {
184 concept_name: &child_id.local_name,
185 labels,
186 depth,
187 fact_indices,
188 children,
189 });
190 }
191
192 visited.remove(parent_id);
193
194 nodes
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::{ItemFact, taxonomy::TaxonomySet};
201 use rust_decimal::Decimal;
202
203 fn create_taxonomy(
204 arcs: Vec<(String, PresentationArc)>,
205 labels: Vec<(ExpandedName, Label)>,
206 ) -> TaxonomySet {
207 let mut taxonomy = TaxonomySet::default();
208 for (role, arc) in arcs {
209 taxonomy.add_presentation_arc(role, arc);
210 }
211 for (concept_name, label) in labels {
212 taxonomy.add_label(concept_name, label);
213 }
214 taxonomy
215 }
216
217 #[test]
218 fn build_view_empty_taxonomy() {
219 let taxonomy = TaxonomySet::default();
220 let view = build_view(&[], &taxonomy);
221 assert!(view.sections.is_empty());
222 }
223
224 #[test]
225 fn build_view_single_section_with_hierarchy() {
226 let role = "http://example.com/role/bs".to_string();
227 let arcs = vec![
228 (
229 role.clone(),
230 PresentationArc {
231 from: ExpandedName::new("http://example.com/namespace".into(), "root".into()),
232 to: ExpandedName::new("http://example.com/namespace".into(), "child_a".into()),
233 order: Some(Decimal::new(1, 0)),
234 preferred_label: None,
235 arcrole: "http://www.xbrl.org/2003/arcrole/parent-child".into(),
236 },
237 ),
238 (
239 role.clone(),
240 PresentationArc {
241 from: ExpandedName::new("http://example.com/namespace".into(), "root".into()),
242 to: ExpandedName::new("http://example.com/namespace".into(), "child_b".into()),
243 order: Some(Decimal::new(2, 0)),
244 preferred_label: None,
245 arcrole: "http://www.xbrl.org/2003/arcrole/parent-child".into(),
246 },
247 ),
248 (
249 role.clone(),
250 PresentationArc {
251 from: ExpandedName::new(
252 "http://example.com/namespace".into(),
253 "child_a".into(),
254 ),
255 to: ExpandedName::new(
256 "http://example.com/namespace".into(),
257 "grandchild".into(),
258 ),
259 order: Some(Decimal::new(1, 0)),
260 preferred_label: None,
261 arcrole: "http://www.xbrl.org/2003/arcrole/parent-child".into(),
262 },
263 ),
264 ];
265 let labels = vec![(
266 ExpandedName::new("http://example.com/namespace".into(), "child_a".into()),
267 Label {
268 role: "http://www.xbrl.org/2003/role/label".to_string(),
269 lang: "en".to_string(),
270 text: "Child A".to_string(),
271 },
272 )];
273 let taxonomy = create_taxonomy(arcs, labels);
274
275 let fact = ItemFact::new(
278 None,
279 ExpandedName::new("http://example.com/namespace".into(), "child_a".into()),
280 "ctx1".to_string(),
281 None,
282 "42".to_string(),
283 false,
284 None,
285 None,
286 );
287 let facts = vec![&fact];
288
289 let view = build_view(&facts, &taxonomy);
290
291 assert_eq!(view.sections.len(), 1);
292 let section = &view.sections[0];
293 assert_eq!(section.role, role);
294
295 assert_eq!(section.nodes.len(), 2);
297
298 let node_a = §ion.nodes[0];
299 assert_eq!(node_a.concept_name, "child_a");
300 assert_eq!(node_a.labels.len(), 1);
301 assert_eq!(node_a.labels[0].text, "Child A");
302 assert_eq!(node_a.labels[0].lang, "en");
303 assert_eq!(node_a.depth, 0);
304 assert_eq!(node_a.fact_indices.len(), 1);
305 assert_eq!(facts[node_a.fact_indices[0]].value(), "42");
306 assert_eq!(node_a.children.len(), 1);
307
308 let grandchild = &node_a.children[0];
309 assert_eq!(grandchild.concept_name, "grandchild");
310 assert_eq!(grandchild.depth, 1);
311 assert!(grandchild.labels.is_empty());
312
313 let node_b = §ion.nodes[1];
314 assert_eq!(node_b.concept_name, "child_b");
315 assert!(node_b.fact_indices.is_empty());
316 }
317
318 #[test]
319 fn build_view_cycle_protection() {
320 let role = "http://example.com/role/cycle".to_string();
321 let arcs = vec![
323 (
324 role.clone(),
325 PresentationArc {
326 from: ExpandedName::new("http://example.com/namespace".into(), "a".into()),
327 to: ExpandedName::new("http://example.com/namespace".into(), "b".into()),
328 order: Some(Decimal::new(1, 0)),
329 preferred_label: None,
330 arcrole: "http://www.xbrl.org/2003/arcrole/parent-child".into(),
331 },
332 ),
333 (
334 role.clone(),
335 PresentationArc {
336 from: ExpandedName::new("http://example.com/namespace".into(), "b".into()),
337 to: ExpandedName::new("http://example.com/namespace".into(), "a".into()),
338 order: Some(Decimal::new(1, 0)),
339 preferred_label: None,
340 arcrole: "http://www.xbrl.org/2003/arcrole/parent-child".into(),
341 },
342 ),
343 ];
344 let taxonomy = create_taxonomy(arcs, vec![]);
345 let view = build_view(&[], &taxonomy);
347 assert_eq!(view.sections.len(), 1);
348 }
349
350 #[test]
351 fn sibling_order_respected() {
352 let role = "http://example.com/role/order".to_string();
353 let arcs = vec![
354 (
355 role.clone(),
356 PresentationArc {
357 from: ExpandedName::new("http://example.com/namespace".into(), "root".into()),
358 to: ExpandedName::new("http://example.com/namespace".into(), "b".into()),
359 order: Some(Decimal::new(2, 0)),
360 preferred_label: None,
361 arcrole: "http://www.xbrl.org/2003/arcrole/parent-child".into(),
362 },
363 ),
364 (
365 role.clone(),
366 PresentationArc {
367 from: ExpandedName::new("http://example.com/namespace".into(), "root".into()),
368 to: ExpandedName::new("http://example.com/namespace".into(), "a".into()),
369 order: Some(Decimal::new(1, 0)),
370 preferred_label: None,
371 arcrole: "http://www.xbrl.org/2003/arcrole/parent-child".into(),
372 },
373 ),
374 ];
375 let taxonomy = create_taxonomy(arcs, vec![]);
376 let view = build_view(&[], &taxonomy);
377
378 let section = &view.sections[0];
379 assert_eq!(section.nodes[0].concept_name, "a");
380 assert_eq!(section.nodes[1].concept_name, "b");
381 }
382}