1use crate::{Bindings, Diagnostic, Document, Node, Position, Report, TokenCounter};
2use std::{collections::BTreeMap, ops::Range};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
7#[cfg_attr(feature = "json", derive(serde::Serialize))]
8pub struct TextMeasurement {
9 pub tokenizer: String,
10 pub tokens: u64,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15#[cfg_attr(feature = "json", derive(serde::Serialize))]
16pub struct ElementOrigin {
17 pub name: String,
18 pub path: Vec<usize>,
19 pub id: Option<String>,
20 pub position: Position,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
27#[cfg_attr(feature = "json", derive(serde::Serialize))]
28pub struct RenderedSpan {
29 pub range: Range<usize>,
30 pub elements: Vec<ElementOrigin>,
31 pub variable: Option<String>,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
37#[cfg_attr(feature = "json", derive(serde::Serialize))]
38pub struct RenderedElement {
39 pub origin: ElementOrigin,
40 pub range: Range<usize>,
41}
42
43#[derive(Debug, Clone)]
47pub struct RenderedDocument {
48 text: String,
49 measurement: TextMeasurement,
50 report: Report,
51 spans: Vec<RenderedSpan>,
52 by_id: BTreeMap<String, Range<usize>>,
53 elements: Vec<RenderedElement>,
54}
55impl RenderedDocument {
56 pub fn text(&self) -> &str {
57 &self.text
58 }
59 pub fn measurement(&self) -> &TextMeasurement {
60 &self.measurement
61 }
62 pub fn report(&self) -> &Report {
63 &self.report
64 }
65 pub fn spans(&self) -> &[RenderedSpan] {
66 &self.spans
67 }
68 pub fn element(&self, id: &str) -> Option<&str> {
71 self.by_id.get(id).map(|range| &self.text[range.clone()])
72 }
73 pub fn elements(&self) -> &[RenderedElement] {
74 &self.elements
75 }
76 pub fn elements_by_name(&self, name: &str) -> Vec<&str> {
77 self.elements
78 .iter()
79 .filter(|e| e.origin.name == name)
80 .map(|e| &self.text[e.range.clone()])
81 .collect()
82 }
83 pub fn into_text(self) -> String {
84 self.text
85 }
86}
87
88pub fn render_checked(
91 doc: &Document,
92 bindings: &Bindings,
93 counter: &impl TokenCounter,
94) -> Result<RenderedDocument, Vec<Diagnostic>> {
95 let (text, report) = crate::check::render_with_report(doc, bindings, counter)?;
96 let mut output = RenderedDocument {
97 measurement: TextMeasurement {
98 tokenizer: counter.name().into(),
99 tokens: report
100 .measurements
101 .last()
102 .and_then(|m| m.tokens)
103 .expect("render counts root"),
104 },
105 text,
106 report,
107 spans: Vec::new(),
108 by_id: BTreeMap::new(),
109 elements: Vec::new(),
110 };
111 fn walk(
112 nodes: &[Node],
113 bindings: &Bindings,
114 ancestry: &mut Vec<ElementOrigin>,
115 path: &mut Vec<usize>,
116 offset: &mut usize,
117 out: &mut RenderedDocument,
118 ) {
119 for (index, node) in nodes.iter().enumerate() {
120 let (length, variable) = match node {
121 Node::Element(section) => {
122 let start = *offset;
123 path.push(index);
124 let origin = ElementOrigin {
125 name: section.name.clone(),
126 id: section.id.clone(),
127 position: section.position,
128 path: path.clone(),
129 };
130 let item = out.elements.len();
131 out.elements.push(RenderedElement {
132 origin: origin.clone(),
133 range: start..start,
134 });
135 ancestry.push(origin);
136 walk(§ion.children, bindings, ancestry, path, offset, out);
137 ancestry.pop();
138 path.pop();
139 out.elements[item].range.end = *offset;
140 if let Some(id) = §ion.id {
141 out.by_id.insert(id.clone(), start..*offset);
142 }
143 continue;
144 }
145 Node::Text { value } => (value.len(), None),
146 Node::Variable(variable) => {
147 (bindings[&variable.id].len(), Some(variable.id.clone()))
148 }
149 };
150 if length > 0 {
151 out.spans.push(RenderedSpan {
152 range: *offset..*offset + length,
153 elements: ancestry.clone(),
154 variable,
155 });
156 }
157 *offset += length;
158 }
159 }
160 walk(
161 &doc.children,
162 bindings,
163 &mut Vec::new(),
164 &mut Vec::new(),
165 &mut 0,
166 &mut output,
167 );
168 Ok(output)
169}