1use serde_json::{json, Map, Value};
12
13use crate::document::{CaptionParent, ContentLayer, DoclingDocument, Node, Table};
14
15const SCHEMA_VERSION: &str = "1.10.0";
16
17const CODE_LANGUAGES: &[&str] = &[
20 "Ada",
21 "Awk",
22 "Bash",
23 "bc",
24 "C",
25 "C#",
26 "C++",
27 "CMake",
28 "COBOL",
29 "CSS",
30 "Ceylon",
31 "Clojure",
32 "Crystal",
33 "Cuda",
34 "Cython",
35 "D",
36 "Dart",
37 "dc",
38 "Dockerfile",
39 "DocLang",
40 "Elixir",
41 "Erlang",
42 "FORTRAN",
43 "Forth",
44 "Go",
45 "HTML",
46 "Haskell",
47 "Haxe",
48 "Java",
49 "JavaScript",
50 "JSON",
51 "Julia",
52 "Kotlin",
53 "Latex",
54 "Lisp",
55 "Lua",
56 "Matlab",
57 "MoonScript",
58 "Nim",
59 "OCaml",
60 "ObjectiveC",
61 "Octave",
62 "PHP",
63 "Pascal",
64 "Perl",
65 "Prolog",
66 "Python",
67 "Racket",
68 "Ruby",
69 "Rust",
70 "SML",
71 "SQL",
72 "Scala",
73 "Scheme",
74 "Swift",
75 "Tikz",
76 "TypeScript",
77 "VisualBasic",
78 "XML",
79 "YAML",
80];
81
82pub fn code_language_label(lang: &str) -> &'static str {
88 code_language(Some(lang))
89}
90
91pub(crate) fn code_language(lang: Option<&str>) -> &'static str {
92 match lang {
93 Some(l) => CODE_LANGUAGES
94 .iter()
95 .find(|c| c.eq_ignore_ascii_case(l))
96 .copied()
97 .unwrap_or("unknown"),
98 None => "unknown",
99 }
100}
101
102#[derive(Clone, Copy)]
106struct ExactProv {
107 page_no: usize,
108 bbox: [f64; 4],
109 bottom_left: bool,
110 charspan: [usize; 2],
111}
112
113impl From<&crate::tree::TreeProv> for ExactProv {
114 fn from(p: &crate::tree::TreeProv) -> Self {
115 ExactProv {
116 page_no: p.page_no,
117 bbox: p.bbox,
118 bottom_left: p.bottom_left,
119 charspan: p.charspan,
120 }
121 }
122}
123
124type PageSizes = std::collections::HashMap<usize, (f64, f64)>;
126
127fn page_sizes(pages: &[(usize, f64, f64)]) -> PageSizes {
128 let mut sizes = PageSizes::with_capacity(pages.len());
129 for &(p, w, h) in pages {
130 sizes.entry(p).or_insert((w, h));
131 }
132 sizes
133}
134
135fn clamp_boxes_to_pages(b: &mut Builder) {
142 if b.pages.is_empty() {
143 return;
144 }
145 let sizes = page_sizes(&b.pages);
147 for item in &mut b.texts {
148 match item {
149 Item::Text(t) => t.prov.clamp(&sizes),
150 Item::Json(v) => {
151 clamp_item(v, &sizes);
152 }
153 }
154 }
155 for item in b.pictures.iter_mut().chain(b.key_value_items.iter_mut()) {
156 clamp_item(item, &sizes);
157 }
158 for t in &mut b.tables {
159 if let Some(size) = clamp_item(&mut t.item, &sizes) {
160 t.data.clamp(size);
161 }
162 }
163}
164
165fn clamp_item(item: &mut Value, sizes: &PageSizes) -> Option<(f64, f64)> {
168 let size = |page_no: &Value| -> Option<(f64, f64)> {
169 let n = page_no.as_u64()? as usize;
170 sizes.get(&n).copied()
171 };
172 let clamp_bbox = |bbox: &mut Value, (w, h): (f64, f64)| {
173 for (key, hi) in [("l", w), ("r", w), ("t", h), ("b", h)] {
174 if let Some(v) = bbox.get(key).and_then(Value::as_f64) {
175 bbox[key] = json!(round2(v.clamp(0.0, hi.max(0.0))));
176 }
177 }
178 };
179 let mut table_page: Option<Option<(f64, f64)>> = None;
180 if let Some(provs) = item.get_mut("prov").and_then(Value::as_array_mut) {
181 let mut page_nos: Vec<u64> = Vec::new();
182 for prov in provs.iter_mut() {
183 if let Some(n) = prov.get("page_no").and_then(Value::as_u64) {
184 page_nos.push(n);
185 }
186 let Some(sz) = prov.get("page_no").and_then(size) else {
187 continue;
188 };
189 if let Some(bbox) = prov.get_mut("bbox") {
190 clamp_bbox(bbox, sz);
191 }
192 }
193 page_nos.sort_unstable();
194 page_nos.dedup();
195 if let [only] = page_nos[..] {
196 table_page = Some(size(&json!(only)));
197 }
198 }
199 table_page.flatten()
200}
201
202fn formatting_json(f: &crate::tree::Formatting) -> Value {
204 json!({
205 "bold": f.bold,
206 "italic": f.italic,
207 "underline": f.underline,
208 "strikethrough": f.strikethrough,
209 "script": match f.script {
210 crate::Script::Baseline => "baseline",
211 crate::Script::Sub => "sub",
212 crate::Script::Super => "super",
213 },
214 })
215}
216
217fn track_json(t: &crate::tree::TreeTrack) -> Value {
220 let mut m = serde_json::Map::new();
221 m.insert("kind".into(), json!("track"));
222 m.insert("start_time".into(), json!(t.start_time));
223 m.insert("end_time".into(), json!(t.end_time));
224 if let Some(id) = &t.identifier {
225 m.insert("identifier".into(), json!(id));
226 }
227 if let Some(v) = &t.voice {
228 m.insert("voice".into(), json!(v));
229 }
230 Value::Object(m)
231}
232
233fn image_ref_json(img: &crate::PictureImage) -> Value {
237 let (mimetype, uri) = crate::pixel_digest::docling_data_uri(img);
238 json!({
239 "mimetype": mimetype,
240 "dpi": img.dpi,
241 "size": { "width": img.width as f64, "height": img.height as f64 },
242 "uri": uri,
243 })
244}
245
246pub fn to_json(doc: &DoclingDocument) -> Value {
248 build_parts(doc, false).into_value(None)
249}
250
251pub(crate) const TYPED_TABLE: &str = "_typed_table";
254
255pub(crate) fn to_json_typed_tables(
263 doc: &DoclingDocument,
264 note_keys: bool,
265) -> (Value, Vec<TableData>) {
266 let mut tables = Vec::new();
267 let value = build_parts(doc, note_keys).into_value(Some(&mut tables));
268 (value, tables)
269}
270
271pub(crate) fn to_json_with_notes(doc: &DoclingDocument) -> (Value, Vec<TableData>) {
284 to_json_typed_tables(doc, true)
285}
286
287fn build_parts(doc: &DoclingDocument, note_keys: bool) -> Parts {
288 let mut b = Builder {
289 note_keys,
290 ..Builder::default()
291 };
292 let body = match &doc.tree {
296 Some(tree) => {
297 for n in &doc.nodes {
300 if let Node::PageInfo {
301 page_no,
302 width,
303 height,
304 } = n
305 {
306 if *page_no > 0 {
307 b.pages.push((*page_no, *width as f64, *height as f64));
308 }
309 }
310 }
311 let mut tree = tree.clone();
315 tree.wrap_misplaced_list_items();
316 b.write_tree(&tree).into_iter().map(Child::json).collect()
317 }
318 None => b.walk_into(&doc.nodes, "#/body"),
319 };
320 b.link_comments();
321
322 clamp_boxes_to_pages(&mut b);
331
332 let pages = b
333 .pages
334 .iter()
335 .map(|(n, w, h)| {
336 let r2 = |v: f64| (v * 100.0).round() / 100.0;
337 let mut page = serde_json::Map::new();
340 page.insert("size".into(), json!({ "width": r2(*w), "height": r2(*h) }));
341 if let Some(img) = doc.page_images.get(n) {
342 page.insert("image".into(), image_ref_json(img));
343 }
344 page.insert("page_no".into(), json!(n));
345 (n.to_string(), Value::Object(page))
346 })
347 .collect::<serde_json::Map<String, Value>>();
348 let fields = (!b.field_regions.is_empty()).then_some((b.field_regions, b.field_items));
351 Parts {
352 name: doc.name.clone(),
353 metadata: (doc.metadata_output.in_json() || note_keys)
356 .then(|| doc.metadata.to_json())
357 .filter(|m| m.as_object().is_some_and(|o| !o.is_empty())),
358 origin: json!({
359 "mimetype": "text/plain",
360 "binary_hash": fnv1a(&doc.name),
361 "filename": doc.name,
362 }),
363 furniture: json!({
364 "self_ref": "#/furniture",
365 "children": [],
366 "content_layer": "furniture",
367 "name": "_root_",
368 "label": "unspecified",
369 }),
370 body,
371 groups: b.groups,
372 texts: b.texts,
373 pictures: b.pictures,
374 tables: b.tables,
375 key_value_items: b.key_value_items,
376 fields,
377 pages,
378 }
379}
380
381struct Parts {
386 name: String,
387 metadata: Option<Value>,
390 origin: Value,
391 furniture: Value,
392 body: Vec<Child>,
394 groups: Vec<Value>,
395 texts: Vec<Item>,
396 pictures: Vec<Value>,
397 tables: Vec<TableItem>,
398 key_value_items: Vec<Value>,
399 fields: Option<(Vec<Value>, Vec<Value>)>,
402 pages: serde_json::Map<String, Value>,
403}
404
405impl Parts {
406 fn into_value(self, mut typed: Option<&mut Vec<TableData>>) -> Value {
409 let mut out = serde_json::Map::with_capacity(15);
411 out.insert("schema_name".into(), "DoclingDocument".into());
412 out.insert("version".into(), SCHEMA_VERSION.into());
413 out.insert("name".into(), Value::String(self.name));
414 out.insert("origin".into(), self.origin);
415 if let Some(m) = self.metadata {
416 out.insert("metadata".into(), m);
417 }
418 out.insert("furniture".into(), self.furniture);
419 out.insert(
420 "body".into(),
421 object([
422 ("self_ref", "#/body".into()),
423 (
424 "children",
425 Value::Array(self.body.into_iter().map(Child::into_value).collect()),
426 ),
427 ("content_layer", "body".into()),
428 ("name", "_root_".into()),
429 ("label", "unspecified".into()),
430 ]),
431 );
432 out.insert("groups".into(), Value::Array(self.groups));
433 out.insert(
434 "texts".into(),
435 Value::Array(
436 self.texts
437 .into_iter()
438 .enumerate()
439 .map(|(i, item)| item.into_value(i))
440 .collect(),
441 ),
442 );
443 out.insert("pictures".into(), Value::Array(self.pictures));
444 out.insert(
445 "tables".into(),
446 Value::Array(
447 self.tables
448 .into_iter()
449 .map(|t| match typed.as_deref_mut() {
450 Some(store) => {
451 let TableItem { mut item, data } = t;
452 if let Some(slot) = item.get_mut("data") {
453 *slot = object([(TYPED_TABLE, store.len().into())]);
454 }
455 store.push(data);
456 item
457 }
458 None => t.into_value(),
459 })
460 .collect(),
461 ),
462 );
463 out.insert("key_value_items".into(), Value::Array(self.key_value_items));
464 out.insert("form_items".into(), Value::Array(Vec::new()));
465 if let Some((regions, items)) = self.fields {
466 out.insert("field_regions".into(), Value::Array(regions));
467 out.insert("field_items".into(), Value::Array(items));
468 }
469 out.insert("pages".into(), Value::Object(self.pages));
470 Value::Object(out)
471 }
472}
473
474struct PartsWith<'a>(&'a Parts, &'a [(&'a str, &'a Value)]);
477
478impl serde::Serialize for PartsWith<'_> {
479 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
480 use serde::ser::SerializeMap;
481 let PartsWith(parts, extras) = *self;
482 let mut m = s.serialize_map(Some(
483 13 + 2 * usize::from(parts.fields.is_some())
484 + usize::from(parts.metadata.is_some())
485 + extras.len(),
486 ))?;
487 m.serialize_entry("schema_name", "DoclingDocument")?;
488 m.serialize_entry("version", SCHEMA_VERSION)?;
489 m.serialize_entry("name", &parts.name)?;
490 m.serialize_entry("origin", &parts.origin)?;
491 if let Some(meta) = &parts.metadata {
492 m.serialize_entry("metadata", meta)?;
493 }
494 m.serialize_entry("furniture", &parts.furniture)?;
495 m.serialize_entry("body", &Body(&parts.body))?;
496 m.serialize_entry("groups", &parts.groups)?;
497 m.serialize_entry("texts", &Texts(&parts.texts))?;
498 m.serialize_entry("pictures", &parts.pictures)?;
499 m.serialize_entry("tables", &parts.tables)?;
500 m.serialize_entry("key_value_items", &parts.key_value_items)?;
501 m.serialize_entry("form_items", &[(); 0])?;
502 if let Some((regions, items)) = &parts.fields {
503 m.serialize_entry("field_regions", regions)?;
504 m.serialize_entry("field_items", items)?;
505 }
506 m.serialize_entry("pages", &parts.pages)?;
507 for (k, v) in extras {
508 m.serialize_entry(k, v)?;
509 }
510 m.end()
511 }
512}
513
514struct Body<'a>(&'a [Child]);
516
517impl serde::Serialize for Body<'_> {
518 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
519 use serde::ser::SerializeMap;
520 let mut m = s.serialize_map(Some(5))?;
521 m.serialize_entry("self_ref", "#/body")?;
522 m.serialize_entry("children", self.0)?;
523 m.serialize_entry("content_layer", "body")?;
524 m.serialize_entry("name", "_root_")?;
525 m.serialize_entry("label", "unspecified")?;
526 m.end()
527 }
528}
529
530pub fn write_json<W: std::io::Write>(
537 doc: &DoclingDocument,
538 w: W,
539 pretty: bool,
540) -> serde_json::Result<()> {
541 write_json_with(doc, w, pretty, &[])
542}
543
544pub fn write_json_with<W: std::io::Write>(
548 doc: &DoclingDocument,
549 w: W,
550 pretty: bool,
551 extras: &[(&str, &Value)],
552) -> serde_json::Result<()> {
553 use serde::Serialize;
554 let parts = build_parts(doc, false);
555 let parts = PartsWith(&parts, extras);
556 if pretty {
557 parts.serialize(&mut serde_json::Serializer::pretty(w))
558 } else {
559 parts.serialize(&mut serde_json::Serializer::new(w))
560 }
561}
562
563fn classification_meta(classes: &[crate::PictureClass]) -> Value {
570 json!({
571 "classification": {
572 "predictions": classes.iter().map(|c| json!({
573 "confidence": c.confidence as f64,
574 "created_by": "DocumentPictureClassifier",
575 "class_name": c.class_name,
576 })).collect::<Vec<_>>(),
577 },
578 "annotations": [{
579 "kind": "classification",
580 "provenance": "DocumentPictureClassifier",
581 "predicted_classes": classes.iter().map(|c| json!({
582 "class_name": c.class_name,
583 "confidence": c.confidence as f64,
584 })).collect::<Vec<_>>(),
585 }],
586 })
587}
588
589fn description_meta(desc: &crate::PictureDescription) -> Value {
597 json!({
598 "description": {
599 "created_by": desc.provenance,
600 "text": desc.text,
601 },
602 "annotations": [{
603 "kind": "description",
604 "text": desc.text,
605 "provenance": desc.provenance,
606 }],
607 })
608}
609
610fn picture_meta(
616 classes: Option<&[crate::PictureClass]>,
617 desc: Option<&crate::PictureDescription>,
618) -> Option<Value> {
619 let mut meta = Map::new();
620 let mut annotations = Vec::new();
621 if let Some(desc) = desc {
622 let mut m = description_meta(desc);
623 annotations.push(m["annotations"][0].take());
624 meta.insert("description".into(), m["description"].take());
625 }
626 if let Some(classes) = classes {
627 let mut m = classification_meta(classes);
628 annotations.insert(0, m["annotations"][0].take());
629 meta.insert("classification".into(), m["classification"].take());
630 }
631 if meta.is_empty() {
632 return None;
633 }
634 meta.insert("annotations".into(), Value::Array(annotations));
635 Some(Value::Object(meta))
636}
637
638#[derive(Clone)]
644pub(crate) struct GridCell {
645 row_span: usize,
646 col_span: usize,
647 start_row: usize,
648 end_row: usize,
649 start_col: usize,
650 end_col: usize,
651 text: Box<str>,
652 column_header: bool,
653 row_header: bool,
654 row_section: bool,
655 bbox: Option<[f64; 4]>,
658 cell_ref: Option<Box<Value>>,
661}
662
663impl GridCell {
664 fn filler(r: usize, c: usize) -> Self {
666 GridCell {
667 row_span: 1,
668 col_span: 1,
669 start_row: r,
670 end_row: r + 1,
671 start_col: c,
672 end_col: c + 1,
673 text: "".into(),
674 column_header: false,
675 row_header: false,
676 row_section: false,
677 bbox: None,
678 cell_ref: None,
679 }
680 }
681
682 fn to_value(&self, with_ref: bool) -> Value {
687 let mut m = serde_json::Map::with_capacity(13);
688 m.insert("row_span".into(), self.row_span.into());
689 m.insert("col_span".into(), self.col_span.into());
690 m.insert("start_row_offset_idx".into(), self.start_row.into());
691 m.insert("end_row_offset_idx".into(), self.end_row.into());
692 m.insert("start_col_offset_idx".into(), self.start_col.into());
693 m.insert("end_col_offset_idx".into(), self.end_col.into());
694 m.insert("text".into(), Value::String(self.text.to_string()));
695 m.insert("column_header".into(), self.column_header.into());
696 m.insert("row_header".into(), self.row_header.into());
697 m.insert("row_section".into(), self.row_section.into());
698 m.insert("fillable".into(), false.into());
699 if let Some([l, t, r, b]) = self.bbox {
700 m.insert(
701 "bbox".into(),
702 object([
703 ("l", l.into()),
704 ("t", t.into()),
705 ("r", r.into()),
706 ("b", b.into()),
707 ("coord_origin", "TOPLEFT".into()),
708 ]),
709 );
710 }
711 if let Some(r) = self.cell_ref.as_deref().filter(|_| with_ref) {
712 m.insert("ref".into(), r.clone());
713 }
714 Value::Object(m)
715 }
716}
717
718struct CellOut<'a>(&'a GridCell, bool);
720
721impl serde::Serialize for CellOut<'_> {
722 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
723 use serde::ser::SerializeMap;
724 let CellOut(c, with_ref) = *self;
725 let cell_ref = c.cell_ref.as_deref().filter(|_| with_ref);
726 let mut m = s.serialize_map(Some(
727 11 + usize::from(c.bbox.is_some()) + usize::from(cell_ref.is_some()),
728 ))?;
729 m.serialize_entry("row_span", &c.row_span)?;
730 m.serialize_entry("col_span", &c.col_span)?;
731 m.serialize_entry("start_row_offset_idx", &c.start_row)?;
732 m.serialize_entry("end_row_offset_idx", &c.end_row)?;
733 m.serialize_entry("start_col_offset_idx", &c.start_col)?;
734 m.serialize_entry("end_col_offset_idx", &c.end_col)?;
735 m.serialize_entry("text", &c.text)?;
736 m.serialize_entry("column_header", &c.column_header)?;
737 m.serialize_entry("row_header", &c.row_header)?;
738 m.serialize_entry("row_section", &c.row_section)?;
739 m.serialize_entry("fillable", &false)?;
740 if let Some(b) = c.bbox {
741 struct Bbox([f64; 4]);
742 impl serde::Serialize for Bbox {
743 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
744 let mut m = s.serialize_map(Some(5))?;
745 for (k, v) in ["l", "t", "r", "b"].iter().zip(self.0) {
746 m.serialize_entry(k, &v)?;
747 }
748 m.serialize_entry("coord_origin", "TOPLEFT")?;
749 m.end()
750 }
751 }
752 m.serialize_entry("bbox", &Bbox(b))?;
753 }
754 if let Some(r) = cell_ref {
755 m.serialize_entry("ref", r)?;
756 }
757 m.end()
758 }
759}
760
761#[derive(Clone, Copy)]
765enum GridSlot {
766 Cell(usize),
767 Filler,
768 Null,
769}
770
771pub(crate) struct TableData {
777 cells: Vec<GridCell>,
778 num_rows: usize,
779 num_cols: usize,
780 grid: Vec<GridSlot>,
782}
783
784pub(crate) struct GridPos<'a> {
789 pub(crate) start_row: usize,
790 pub(crate) start_col: usize,
791 pub(crate) text: &'a str,
792 pub(crate) column_header: bool,
793 pub(crate) row_header: bool,
794 pub(crate) row_section: bool,
795}
796
797pub(crate) fn raw_table_data(t: &Table) -> TableData {
799 table_data_with(t, true)
800}
801
802impl TableData {
803 pub(crate) fn dims(&self) -> (usize, usize) {
805 (self.num_rows, self.num_cols)
806 }
807
808 pub(crate) fn has_cells(&self) -> bool {
810 !self.cells.is_empty()
811 }
812
813 pub(crate) fn at(&self, r: usize, c: usize) -> GridPos<'_> {
815 match self.grid[r * self.num_cols + c] {
816 GridSlot::Cell(i) => {
817 let cell = &self.cells[i];
818 GridPos {
819 start_row: cell.start_row,
820 start_col: cell.start_col,
821 text: &cell.text,
822 column_header: cell.column_header,
823 row_header: cell.row_header,
824 row_section: cell.row_section,
825 }
826 }
827 GridSlot::Filler | GridSlot::Null => GridPos {
828 start_row: r,
829 start_col: c,
830 text: "",
831 column_header: false,
832 row_header: false,
833 row_section: false,
834 },
835 }
836 }
837}
838
839impl TableData {
840 fn slot_cell(
841 &self,
842 slot: GridSlot,
843 r: usize,
844 c: usize,
845 ) -> Option<std::borrow::Cow<'_, GridCell>> {
846 match slot {
847 GridSlot::Cell(i) => Some(std::borrow::Cow::Borrowed(&self.cells[i])),
848 GridSlot::Filler => Some(std::borrow::Cow::Owned(GridCell::filler(r, c))),
849 GridSlot::Null => None,
850 }
851 }
852
853 fn into_value(self) -> Value {
854 let cells = Value::Array(self.cells.iter().map(|c| c.to_value(true)).collect());
855 let mut data = serde_json::Map::with_capacity(5);
856 data.insert("table_cells".into(), cells);
857 data.insert("num_rows".into(), self.num_rows.into());
858 data.insert("num_cols".into(), self.num_cols.into());
859 data.insert("orientation".into(), "rot_0".into());
860 let mut rows = Vec::with_capacity(self.num_rows);
861 for r in 0..self.num_rows {
862 let mut row = Vec::with_capacity(self.num_cols);
863 for c in 0..self.num_cols {
864 let slot = self.grid[r * self.num_cols + c];
865 row.push(match self.slot_cell(slot, r, c) {
866 Some(cell) => cell.to_value(false),
867 None => Value::Null,
868 });
869 }
870 rows.push(Value::Array(row));
871 }
872 data.insert("grid".into(), Value::Array(rows));
873 Value::Object(data)
874 }
875
876 fn clamp(&mut self, (w, h): (f64, f64)) {
879 for bbox in self.cells.iter_mut().filter_map(|c| c.bbox.as_mut()) {
880 for (v, hi) in bbox.iter_mut().zip([w, h, w, h]) {
881 if v.is_finite() {
884 *v = round2(v.clamp(0.0, hi.max(0.0)));
885 }
886 }
887 }
888 }
889}
890
891impl serde::Serialize for TableData {
892 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
893 use serde::ser::{SerializeMap, SerializeSeq};
894 struct Cells<'a>(&'a [GridCell]);
895 impl serde::Serialize for Cells<'_> {
896 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
897 let mut seq = s.serialize_seq(Some(self.0.len()))?;
898 for c in self.0 {
899 seq.serialize_element(&CellOut(c, true))?;
900 }
901 seq.end()
902 }
903 }
904 struct Row<'a>(&'a TableData, usize);
905 impl serde::Serialize for Row<'_> {
906 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
907 let Row(t, r) = *self;
908 let mut seq = s.serialize_seq(Some(t.num_cols))?;
909 for c in 0..t.num_cols {
910 match t.slot_cell(t.grid[r * t.num_cols + c], r, c) {
911 Some(cell) => seq.serialize_element(&CellOut(&cell, false))?,
912 None => seq.serialize_element(&())?,
913 }
914 }
915 seq.end()
916 }
917 }
918 struct Grid<'a>(&'a TableData);
919 impl serde::Serialize for Grid<'_> {
920 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
921 let mut seq = s.serialize_seq(Some(self.0.num_rows))?;
922 for r in 0..self.0.num_rows {
923 seq.serialize_element(&Row(self.0, r))?;
924 }
925 seq.end()
926 }
927 }
928 let mut m = s.serialize_map(Some(5))?;
929 m.serialize_entry("table_cells", &Cells(&self.cells))?;
930 m.serialize_entry("num_rows", &self.num_rows)?;
931 m.serialize_entry("num_cols", &self.num_cols)?;
932 m.serialize_entry("orientation", "rot_0")?;
933 m.serialize_entry("grid", &Grid(self))?;
934 m.end()
935 }
936}
937
938pub(crate) struct CellView<'a> {
944 pub start_row: usize,
945 pub end_row: usize,
946 pub start_col: usize,
947 pub end_col: usize,
948 pub row_span: usize,
949 pub col_span: usize,
950 pub text: &'a str,
951 pub column_header: bool,
952 pub row_header: bool,
953 pub row_section: bool,
954 pub cell_ref: Option<&'a str>,
956}
957
958pub(crate) struct TableView<'a> {
961 pub num_rows: usize,
962 pub num_cols: usize,
963 pub cells: Vec<CellView<'a>>,
964}
965
966pub(crate) fn table_view<'a>(data: &'a Value, typed: &'a [TableData]) -> TableView<'a> {
969 let ref_of = |r: Option<&'a Value>| r.and_then(|r| r.get("$ref")).and_then(Value::as_str);
970 if let Some(t) = data
971 .get(TYPED_TABLE)
972 .and_then(Value::as_u64)
973 .and_then(|i| typed.get(i as usize))
974 {
975 return TableView {
976 num_rows: t.num_rows,
977 num_cols: t.num_cols,
978 cells: t
979 .cells
980 .iter()
981 .map(|c| CellView {
982 start_row: c.start_row,
983 end_row: c.end_row,
984 start_col: c.start_col,
985 end_col: c.end_col,
986 row_span: c.row_span,
987 col_span: c.col_span,
988 text: &c.text,
989 column_header: c.column_header,
990 row_header: c.row_header,
991 row_section: c.row_section,
992 cell_ref: ref_of(c.cell_ref.as_deref()),
993 })
994 .collect(),
995 };
996 }
997 let num = |v: &Value, k: &str| v.get(k).and_then(Value::as_u64).unwrap_or(0) as usize;
998 let flag = |v: &Value, k: &str| v.get(k).and_then(Value::as_bool).unwrap_or(false);
999 TableView {
1000 num_rows: num(data, "num_rows"),
1001 num_cols: num(data, "num_cols"),
1002 cells: data
1003 .get("table_cells")
1004 .and_then(Value::as_array)
1005 .into_iter()
1006 .flatten()
1007 .map(|c| CellView {
1008 start_row: num(c, "start_row_offset_idx"),
1009 end_row: num(c, "end_row_offset_idx"),
1010 start_col: num(c, "start_col_offset_idx"),
1011 end_col: num(c, "end_col_offset_idx"),
1012 row_span: num(c, "row_span"),
1013 col_span: num(c, "col_span"),
1014 text: c.get("text").and_then(Value::as_str).unwrap_or(""),
1015 column_header: flag(c, "column_header"),
1016 row_header: flag(c, "row_header"),
1017 row_section: flag(c, "row_section"),
1018 cell_ref: ref_of(c.get("ref")),
1019 })
1020 .collect(),
1021 }
1022}
1023
1024struct TableItem {
1030 item: Value,
1031 data: TableData,
1032}
1033
1034impl TableItem {
1035 fn into_value(self) -> Value {
1036 let TableItem { mut item, data } = self;
1037 if let Some(slot) = item.get_mut("data") {
1038 *slot = data.into_value();
1039 }
1040 item
1041 }
1042}
1043
1044impl serde::Serialize for TableItem {
1045 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1046 use serde::ser::SerializeMap;
1047 let Value::Object(obj) = &self.item else {
1048 return self.item.serialize(s);
1049 };
1050 let mut m = s.serialize_map(Some(obj.len()))?;
1051 for (k, v) in obj {
1052 if k == "data" {
1053 m.serialize_entry(k, &self.data)?;
1054 } else {
1055 m.serialize_entry(k, v)?;
1056 }
1057 }
1058 m.end()
1059 }
1060}
1061
1062fn table_data(t: &Table) -> TableData {
1065 table_data_with(t, false)
1066}
1067
1068fn table_data_with(t: &Table, raw: bool) -> TableData {
1071 let cell_text = |s: &str| -> Box<str> {
1072 if raw {
1073 s.into()
1074 } else {
1075 unescape_text(&crate::markdown::strip_hard_breaks(s)).into()
1076 }
1077 };
1078 let widen = |b: [f32; 4]| b.map(f64::from);
1079 let mut num_rows = t.rows.len();
1080 let mut num_cols = t.rows.iter().map(Vec::len).max().unwrap_or(0);
1081 let ragged = t.rows.iter().any(|r| r.len() != num_cols);
1089 if let Some(first_class) = t.cells.as_ref().filter(|c| ragged && !c.is_empty()) {
1090 let (mut rows, mut cols) = (num_rows, num_cols);
1091 for c in first_class {
1092 rows = rows.max(c.start_row.saturating_add(c.row_span));
1093 cols = cols.max(c.start_col.saturating_add(c.col_span));
1094 }
1095 if rows.saturating_mul(cols) <= crate::limits::max_table_cells() {
1099 num_rows = rows;
1100 num_cols = cols;
1101 }
1102 }
1103 let mut cells = Vec::new();
1104 let mut grid: Vec<GridSlot>;
1107 if let Some(first_class) = t.cells.as_ref().filter(|c| !c.is_empty()) {
1108 grid = vec![GridSlot::Filler; num_rows * num_cols];
1110 cells.reserve(first_class.len());
1111 for c in first_class {
1112 let idx = cells.len();
1113 cells.push(GridCell {
1114 row_span: c.row_span,
1115 col_span: c.col_span,
1116 start_row: c.start_row,
1117 end_row: c.start_row + c.row_span,
1118 start_col: c.start_col,
1119 end_col: c.start_col + c.col_span,
1120 text: cell_text(&c.text),
1121 column_header: c.column_header,
1122 row_header: c.row_header,
1123 row_section: c.row_section,
1124 bbox: c.bbox.map(widen),
1125 cell_ref: None,
1126 });
1127 for r in c.start_row..(c.start_row + c.row_span).min(num_rows) {
1128 for k in c.start_col..(c.start_col + c.col_span).min(num_cols) {
1129 grid[r * num_cols + k] = GridSlot::Cell(idx);
1130 }
1131 }
1132 }
1133 } else {
1134 grid = vec![GridSlot::Null; num_rows * num_cols];
1135 let s = t.structure.as_ref();
1136 let flag = |grid: Option<&Vec<Vec<bool>>>, r: usize, c: usize| -> bool {
1137 grid.and_then(|g| g.get(r))
1138 .and_then(|row| row.get(c))
1139 .copied()
1140 .unwrap_or(false)
1141 };
1142 let anchor_of = |r: usize, c: usize| -> (usize, usize) {
1143 let (mut r0, mut c0) = (r, c);
1144 while c0 > 0 && flag(s.map(|s| &s.col_continuation), r, c0) {
1145 c0 -= 1;
1146 }
1147 while r0 > 0 && flag(s.map(|s| &s.row_continuation), r0, c0) {
1148 r0 -= 1;
1149 }
1150 (r0, c0)
1151 };
1152 let anchors: Vec<(usize, usize)> = (0..num_rows)
1155 .flat_map(|r| (0..num_cols).map(move |c| (r, c)))
1156 .map(|(r, c)| anchor_of(r, c))
1157 .collect();
1158 let mut extent: Vec<(usize, usize)> = (0..num_rows)
1159 .flat_map(|r| (0..num_cols).map(move |c| (r, c)))
1160 .collect();
1161 for (i, &(ar, ac)) in anchors.iter().enumerate() {
1162 let (r, c) = (i / num_cols.max(1), i % num_cols.max(1));
1163 let e = &mut extent[ar * num_cols + ac];
1164 e.0 = e.0.max(r);
1165 e.1 = e.1.max(c);
1166 }
1167 let mut anchor_cell: Vec<Option<usize>> = vec![None; num_rows * num_cols];
1170 for (r, row) in t.rows.iter().enumerate() {
1171 for c in 0..num_cols {
1172 let (ar, ac) = anchors[r * num_cols + c];
1173 if (ar, ac) == (r, c) {
1174 let (er, ec) = extent[r * num_cols + c];
1175 let text = row.get(c).map(|s| cell_text(s)).unwrap_or_default();
1176 let column_header = match s.filter(|s| !s.col_header.is_empty()) {
1177 Some(s) => flag(Some(&s.col_header), r, c),
1178 None => r == 0,
1179 };
1180 anchor_cell[r * num_cols + c] = Some(cells.len());
1181 cells.push(GridCell {
1182 row_span: er - r + 1,
1183 col_span: ec - c + 1,
1184 start_row: r,
1185 end_row: er + 1,
1186 start_col: c,
1187 end_col: ec + 1,
1188 text,
1189 column_header,
1190 row_header: flag(s.map(|s| &s.row_header), r, c),
1191 row_section: false,
1192 bbox: None,
1193 cell_ref: None,
1194 });
1195 }
1196 if let Some(i) = anchor_cell[ar * num_cols + ac] {
1197 grid[r * num_cols + c] = GridSlot::Cell(i);
1198 }
1199 }
1200 }
1201 }
1202 TableData {
1203 cells,
1204 num_rows,
1205 num_cols,
1206 grid,
1207 }
1208}
1209
1210#[derive(Clone, Copy)]
1214enum ProvSlot {
1215 None,
1216 One {
1217 page_no: usize,
1218 bbox: [f64; 4],
1220 bottom_left: bool,
1221 charspan: [usize; 2],
1222 },
1223}
1224
1225impl ProvSlot {
1226 fn origin(bottom_left: bool) -> &'static str {
1227 if bottom_left {
1228 "BOTTOMLEFT"
1229 } else {
1230 "TOPLEFT"
1231 }
1232 }
1233
1234 fn to_value(self) -> Value {
1237 let ProvSlot::One {
1238 page_no,
1239 bbox: [l, t, r, b],
1240 bottom_left,
1241 charspan,
1242 } = self
1243 else {
1244 return Value::Array(Vec::new());
1245 };
1246 let bbox = object([
1247 ("l", l.into()),
1248 ("t", t.into()),
1249 ("r", r.into()),
1250 ("b", b.into()),
1251 ("coord_origin", Self::origin(bottom_left).into()),
1252 ]);
1253 let span = Value::Array(vec![charspan[0].into(), charspan[1].into()]);
1254 Value::Array(vec![object([
1255 ("page_no", page_no.into()),
1256 ("bbox", bbox),
1257 ("charspan", span),
1258 ])])
1259 }
1260
1261 fn clamp(&mut self, sizes: &PageSizes) {
1263 if let ProvSlot::One { page_no, bbox, .. } = self {
1264 if let Some(&(w, h)) = sizes.get(page_no) {
1265 for (v, hi) in bbox.iter_mut().zip([w, h, w, h]) {
1266 if v.is_finite() {
1269 *v = round2(v.clamp(0.0, hi.max(0.0)));
1270 }
1271 }
1272 }
1273 }
1274 }
1275}
1276
1277impl serde::Serialize for ProvSlot {
1278 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1279 use serde::ser::{SerializeMap, SerializeSeq};
1280 let ProvSlot::One {
1281 page_no,
1282 bbox: [l, t, r, b],
1283 bottom_left,
1284 charspan,
1285 } = *self
1286 else {
1287 return s.serialize_seq(Some(0))?.end();
1288 };
1289 struct Bbox([f64; 4], &'static str);
1290 impl serde::Serialize for Bbox {
1291 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1292 let mut m = s.serialize_map(Some(5))?;
1293 for (k, v) in ["l", "t", "r", "b"].iter().zip(self.0) {
1294 m.serialize_entry(k, &v)?;
1295 }
1296 m.serialize_entry("coord_origin", self.1)?;
1297 m.end()
1298 }
1299 }
1300 struct Entry(usize, Bbox, [usize; 2]);
1301 impl serde::Serialize for Entry {
1302 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1303 let mut m = s.serialize_map(Some(3))?;
1304 m.serialize_entry("page_no", &self.0)?;
1305 m.serialize_entry("bbox", &self.1)?;
1306 m.serialize_entry("charspan", &self.2)?;
1307 m.end()
1308 }
1309 }
1310 let mut seq = s.serialize_seq(Some(1))?;
1311 seq.serialize_element(&Entry(
1312 page_no,
1313 Bbox([l, t, r, b], Self::origin(bottom_left)),
1314 charspan,
1315 ))?;
1316 seq.end()
1317 }
1318}
1319
1320struct TextItem {
1327 parent: Box<str>,
1328 label: Box<str>,
1329 prov: ProvSlot,
1330 text: Box<str>,
1331}
1332
1333impl TextItem {
1334 fn into_value(self, index: usize) -> Value {
1335 let text = String::from(self.text);
1336 object([
1337 ("self_ref", Value::String(format!("#/texts/{index}"))),
1338 ("parent", ref_value(self.parent)),
1339 ("children", Value::Array(Vec::new())),
1340 ("content_layer", "body".into()),
1341 ("label", Value::String(self.label.into())),
1342 ("prov", self.prov.to_value()),
1343 ("orig", Value::String(text.clone())),
1344 ("text", Value::String(text)),
1345 ])
1346 }
1347}
1348
1349enum Item {
1353 Text(Box<TextItem>),
1354 Json(Box<Value>),
1355}
1356
1357impl Item {
1358 fn json(v: Value) -> Self {
1359 Item::Json(Box::new(v))
1360 }
1361
1362 fn value_mut(&mut self, index: usize) -> &mut Value {
1365 if let Item::Text(t) = self {
1366 let t = std::mem::replace(
1367 t,
1368 Box::new(TextItem {
1369 parent: "".into(),
1370 label: "".into(),
1371 prov: ProvSlot::None,
1372 text: "".into(),
1373 }),
1374 );
1375 *self = Item::json(t.into_value(index));
1376 }
1377 match self {
1378 Item::Json(v) => v,
1379 Item::Text(_) => unreachable!("converted above"),
1380 }
1381 }
1382
1383 fn into_value(self, index: usize) -> Value {
1384 match self {
1385 Item::Text(t) => t.into_value(index),
1386 Item::Json(v) => *v,
1387 }
1388 }
1389}
1390
1391struct Texts<'a>(&'a [Item]);
1393
1394impl serde::Serialize for Texts<'_> {
1395 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1396 use serde::ser::{SerializeMap, SerializeSeq};
1397 struct Text<'a>(usize, &'a TextItem);
1398 impl serde::Serialize for Text<'_> {
1399 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1400 let Text(i, t) = *self;
1401 let mut m = s.serialize_map(Some(8))?;
1402 m.serialize_entry("self_ref", &format_args!("#/texts/{i}"))?;
1403 m.serialize_entry("parent", &Child::Ref(t.parent.clone()))?;
1404 m.serialize_entry("children", &[(); 0])?;
1405 m.serialize_entry("content_layer", "body")?;
1406 m.serialize_entry("label", &t.label)?;
1407 m.serialize_entry("prov", &t.prov)?;
1408 m.serialize_entry("orig", &t.text)?;
1409 m.serialize_entry("text", &t.text)?;
1410 m.end()
1411 }
1412 }
1413 let mut seq = s.serialize_seq(Some(self.0.len()))?;
1414 for (i, item) in self.0.iter().enumerate() {
1415 match item {
1416 Item::Text(t) => seq.serialize_element(&Text(i, t))?,
1417 Item::Json(v) => seq.serialize_element(v)?,
1418 }
1419 }
1420 seq.end()
1421 }
1422}
1423
1424enum Child {
1428 Ref(Box<str>),
1429 Json(Box<Value>),
1430}
1431
1432impl Child {
1433 fn json(v: Value) -> Self {
1434 Child::Json(Box::new(v))
1435 }
1436
1437 fn into_value(self) -> Value {
1438 match self {
1439 Child::Ref(r) => ref_value(r),
1440 Child::Json(v) => *v,
1441 }
1442 }
1443}
1444
1445impl serde::Serialize for Child {
1446 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
1447 use serde::ser::SerializeMap;
1448 match self {
1449 Child::Ref(r) => {
1450 let mut m = s.serialize_map(Some(1))?;
1451 m.serialize_entry("$ref", r)?;
1452 m.end()
1453 }
1454 Child::Json(v) => v.serialize(s),
1455 }
1456 }
1457}
1458
1459fn round2(v: f64) -> f64 {
1460 (v * 100.0).round() / 100.0
1461}
1462
1463#[derive(Default)]
1464struct Builder {
1465 note_keys: bool,
1469 texts: Vec<Item>,
1470 groups: Vec<Value>,
1471 tables: Vec<TableItem>,
1472 pictures: Vec<Value>,
1473 field_regions: Vec<Value>,
1474 field_items: Vec<Value>,
1475 key_value_items: Vec<Value>,
1476 pages: Vec<(usize, f64, f64)>,
1480 cur_page: usize,
1482 cur_w: f64,
1483 cur_h: f64,
1484 pending_loc: Option<[u16; 4]>,
1487 pending_exact: Option<ExactProv>,
1490 pending_track: Option<(crate::tree::TreeTrack, String)>,
1493 pending_anchors: Vec<String>,
1496 pending_siblings: Vec<Value>,
1500 pending_after: Vec<Value>,
1504 pending_body: Vec<Value>,
1508 comment_groups: Vec<String>,
1512 last_comment_section: Option<(String, String)>,
1517 pending_comments: Vec<(String, Vec<usize>)>,
1521}
1522
1523impl Builder {
1524 fn take_prov(&mut self, char_len: usize) -> Value {
1530 self.take_prov_slot(char_len).to_value()
1531 }
1532
1533 fn take_prov_slot(&mut self, char_len: usize) -> ProvSlot {
1535 let prov = self.prov_slot(char_len, false);
1536 self.pending_exact = None;
1537 self.pending_loc = None;
1538 prov
1539 }
1540
1541 fn prov_slot(&self, char_len: usize, span_over_text: bool) -> ProvSlot {
1546 let r2 = round2;
1547 if let Some(ExactProv {
1548 page_no,
1549 bbox: [l, t, r, b],
1550 bottom_left,
1551 charspan,
1552 }) = self.pending_exact
1553 {
1554 let charspan = if span_over_text {
1555 [0, char_len]
1556 } else {
1557 charspan
1558 };
1559 return ProvSlot::One {
1560 page_no,
1561 bbox: [r2(l), r2(t), r2(r), r2(b)],
1562 bottom_left,
1563 charspan,
1564 };
1565 }
1566 let Some([x0, y0, x1, y1]) = self.pending_loc else {
1567 return ProvSlot::None;
1568 };
1569 if [x0, y0, x1, y1] == [0, 0, 0, 0] {
1573 return ProvSlot::One {
1574 page_no: self.cur_page,
1575 bbox: [0.0; 4],
1576 bottom_left: true,
1577 charspan: [0, char_len],
1578 };
1579 }
1580 ProvSlot::One {
1581 page_no: self.cur_page,
1582 bbox: [
1583 r2(x0 as f64 * self.cur_w / 512.0),
1584 r2(self.cur_h - y0 as f64 * self.cur_h / 512.0),
1585 r2(x1 as f64 * self.cur_w / 512.0),
1586 r2(self.cur_h - y1 as f64 * self.cur_h / 512.0),
1587 ],
1588 bottom_left: true,
1589 charspan: [0, char_len],
1590 }
1591 }
1592
1593 fn adopt_loc(&mut self, loc: Option<[u16; 4]>) {
1596 if self.pending_loc.is_none() && self.cur_page > 0 {
1597 self.pending_loc = loc;
1598 }
1599 }
1600
1601 fn link_comments(&mut self) {
1609 let refs: Vec<(String, Vec<Value>)> = std::mem::take(&mut self.pending_comments)
1610 .into_iter()
1611 .map(|(item, comments)| {
1612 let refs = comments
1613 .iter()
1614 .filter_map(|i| self.comment_groups.get(*i))
1615 .map(ref_value)
1616 .collect();
1617 (item, refs)
1618 })
1619 .collect();
1620 for (item, comment_refs) in refs {
1621 if comment_refs.is_empty() {
1622 continue;
1623 }
1624 let Some(target) = self.item_mut(&item) else {
1625 continue;
1626 };
1627 let Some(obj) = target.as_object_mut() else {
1628 continue;
1629 };
1630 let tail: Vec<(String, Value)> = obj
1631 .iter()
1632 .skip_while(|(k, _)| k.as_str() != "prov")
1633 .skip(1)
1634 .map(|(k, v)| (k.clone(), v.clone()))
1635 .collect();
1636 for (k, _) in &tail {
1637 obj.shift_remove(k);
1638 }
1639 obj.insert("comments".into(), Value::Array(comment_refs));
1640 for (k, v) in tail {
1641 obj.insert(k, v);
1642 }
1643 }
1644 }
1645
1646 fn item_mut(&mut self, self_ref: &str) -> Option<&mut Value> {
1648 let idx = ref_index(self_ref)?;
1649 if self_ref.starts_with("#/texts/") {
1650 return self.texts.get_mut(idx).map(|item| item.value_mut(idx));
1651 }
1652 if self_ref.starts_with("#/tables/") {
1653 return self.tables.get_mut(idx).map(|t| &mut t.item);
1654 }
1655 let bucket = if self_ref.starts_with("#/pictures/") {
1656 &mut self.pictures
1657 } else if self_ref.starts_with("#/groups/") {
1658 &mut self.groups
1659 } else {
1660 return None;
1661 };
1662 bucket.get_mut(idx)
1663 }
1664
1665 fn write_tree(&mut self, tree: &crate::tree::ItemTree) -> Vec<Value> {
1669 use crate::tree::TreeKind;
1670 let mut refs: Vec<String> = Vec::with_capacity(tree.items.len());
1673 let (mut nt, mut ng, mut ntb, mut np, mut nf, mut nk) = (0, 0, 0, 0, 0, 0);
1674 for item in &tree.items {
1675 if item.deleted {
1676 refs.push(String::new());
1677 continue;
1678 }
1679 let r = match &item.kind {
1680 TreeKind::Text { .. } | TreeKind::Code { .. } => {
1681 nt += 1;
1682 format!("#/texts/{}", nt - 1)
1683 }
1684 TreeKind::Group { .. } => {
1685 ng += 1;
1686 format!("#/groups/{}", ng - 1)
1687 }
1688 TreeKind::Table { .. } => {
1689 ntb += 1;
1690 format!("#/tables/{}", ntb - 1)
1691 }
1692 TreeKind::Picture { .. } => {
1693 np += 1;
1694 format!("#/pictures/{}", np - 1)
1695 }
1696 TreeKind::FieldRegion { items } => {
1697 nt += items
1700 .iter()
1701 .map(|i| {
1702 [&i.marker, &i.key, &i.value]
1703 .iter()
1704 .filter(|p| p.is_some())
1705 .count()
1706 })
1707 .sum::<usize>();
1708 nf += 1;
1709 format!("#/field_regions/{}", nf - 1)
1710 }
1711 TreeKind::KeyValueGraph { .. } => {
1712 nk += 1;
1713 format!("#/key_value_items/{}", nk - 1)
1714 }
1715 };
1716 refs.push(r);
1717 }
1718 let ref_of = |id: usize| ref_value(refs[id].as_str());
1719 for (id, item) in tree.items.iter().enumerate() {
1720 if item.deleted {
1721 continue;
1722 }
1723 let parent = item.parent.map_or("#/body", |p| refs[p].as_str());
1724 let children: Vec<Value> = item
1728 .children
1729 .iter()
1730 .filter(|&&c| !tree.items[c].deleted)
1731 .map(|&c| ref_of(c))
1732 .collect();
1733 let layer = item.layer.map_or("body", |l| l.value());
1734 self.pending_exact = item.prov.as_ref().map(ExactProv::from);
1737 let self_ref = match &item.kind {
1738 TreeKind::Text {
1739 label,
1740 text,
1741 orig,
1742 formatting,
1743 hyperlink,
1744 level,
1745 list,
1746 } => {
1747 let mut tail = serde_json::Map::new();
1750 if let Some(f) = formatting {
1751 tail.insert("formatting".into(), formatting_json(f));
1752 }
1753 if let Some(h) = hyperlink {
1754 tail.insert("hyperlink".into(), json!(h));
1755 }
1756 if let Some(l) = level {
1757 tail.insert("level".into(), json!(l));
1758 }
1759 if let Some(l) = list {
1760 tail.insert("enumerated".into(), json!(l.enumerated));
1761 tail.insert("marker".into(), json!(l.marker));
1762 }
1763 let r = format!("#/texts/{}", self.texts.len());
1764 let prov = self.take_prov(text.chars().count());
1765 let mut item_json = json!({
1766 "self_ref": r,
1767 "parent": { "$ref": parent },
1768 "children": children,
1769 "content_layer": layer,
1770 "label": label,
1771 "prov": prov,
1772 });
1773 if !item.comments.is_empty() {
1776 item_json["comments"] =
1777 Value::Array(item.comments.iter().map(|&c| ref_of(c)).collect());
1778 }
1779 if let Some(track) = &item.source {
1782 item_json["source"] = json!([track_json(track)]);
1783 }
1784 merge(
1785 &mut item_json,
1786 json!({
1787 "orig": orig.as_deref().unwrap_or(text),
1788 "text": text,
1789 }),
1790 );
1791 merge(&mut item_json, Value::Object(tail));
1792 if self.note_keys {
1793 if !item.notes.is_empty() {
1794 item_json["_notes"] = Value::Array(
1795 item.notes
1796 .iter()
1797 .map(|n| json!([n.offset, n.text]))
1798 .collect(),
1799 );
1800 }
1801 if item.note_body {
1802 item_json["_note_body"] = json!(true);
1803 }
1804 if !item.anchors.is_empty() {
1805 item_json["_anchors"] = json!(item.anchors);
1806 }
1807 if let Some(number) = &item.number {
1808 item_json["_number"] = json!(number);
1809 }
1810 if let Some(rev) = &item.revision {
1811 let mut r = serde_json::Map::new();
1812 r.insert("kind".into(), json!(rev.kind.as_str()));
1813 if let Some(a) = &rev.author {
1814 r.insert("author".into(), json!(a));
1815 }
1816 if let Some(d) = &rev.date {
1817 r.insert("date".into(), json!(d));
1818 }
1819 item_json["_revision"] = Value::Object(r);
1820 }
1821 }
1822 self.texts.push(Item::json(item_json));
1823 r
1824 }
1825 TreeKind::Code {
1826 text,
1827 orig,
1828 language,
1829 formatting,
1830 hyperlink,
1831 captions,
1832 } => {
1833 let r = format!("#/texts/{}", self.texts.len());
1834 let prov = self.take_prov(text.chars().count());
1835 let mut item_json = json!({
1836 "self_ref": r,
1837 "parent": { "$ref": parent },
1838 "children": children,
1839 "content_layer": layer,
1840 "label": "code",
1841 "prov": prov,
1842 });
1843 if !item.comments.is_empty() {
1844 item_json["comments"] =
1845 Value::Array(item.comments.iter().map(|&c| ref_of(c)).collect());
1846 }
1847 merge(
1848 &mut item_json,
1849 json!({
1850 "orig": orig.as_deref().unwrap_or(text),
1851 "text": text,
1852 }),
1853 );
1854 if let Some(f) = formatting {
1855 item_json["formatting"] = formatting_json(f);
1856 }
1857 if let Some(h) = hyperlink {
1858 item_json["hyperlink"] = json!(h);
1859 }
1860 merge(
1861 &mut item_json,
1862 json!({
1863 "captions": captions.iter().map(|&c| ref_of(c)).collect::<Vec<_>>(),
1864 "references": [],
1865 "footnotes": [],
1866 "code_language": code_language(language.as_deref()),
1867 }),
1868 );
1869 self.texts.push(Item::json(item_json));
1870 r
1871 }
1872 TreeKind::Group { label, name } => {
1873 self.pending_exact = None;
1874 let r = format!("#/groups/{}", self.groups.len());
1875 self.groups.push(json!({
1876 "self_ref": r,
1877 "parent": { "$ref": parent },
1878 "children": children,
1879 "content_layer": layer,
1880 "name": name,
1881 "label": label,
1882 }));
1883 r
1884 }
1885 TreeKind::Table {
1886 table,
1887 rich_cells,
1888 captions,
1889 } => {
1890 let r = self.add_table_with(table, parent, true);
1892 let idx = ref_index(&r).expect("table ref");
1893 let t = &mut self.tables[idx];
1894 t.item["children"] = Value::Array(children);
1895 t.item["content_layer"] = json!(layer);
1896 t.item["captions"] =
1897 Value::Array(captions.iter().map(|&c| ref_of(c)).collect());
1898 for &(row, col, group) in rich_cells {
1902 let cell_ref = ref_of(group);
1903 for c in t.data.cells.iter_mut() {
1904 if c.start_row == row && c.start_col == col {
1905 c.cell_ref = Some(Box::new(cell_ref.clone()));
1906 }
1907 }
1908 }
1909 r
1910 }
1911 TreeKind::Picture {
1912 captions,
1913 image,
1914 classification,
1915 extra_classes,
1916 description,
1917 confidence,
1918 chart,
1919 dpi,
1920 } => {
1921 let mut meta = classification.as_ref().map(|c| {
1926 let mut pred = serde_json::Map::new();
1927 if let Some(conf) = confidence {
1928 pred.insert("confidence".into(), json!(conf));
1929 }
1930 pred.insert("class_name".into(), json!(c));
1931 let mut preds = vec![Value::Object(pred)];
1932 preds.extend(extra_classes.iter().map(|c| json!({ "class_name": c })));
1933 json!({ "classification": { "predictions": preds } })
1934 });
1935 if let (Some(m), Some(t)) = (meta.as_mut(), chart) {
1936 if !t.rows.is_empty() {
1937 m["tabular_chart"] =
1938 object([("chart_data", table_data(t).into_value())]);
1939 }
1940 }
1941 if let Some(desc) = description {
1945 let mut d = description_meta(desc);
1946 let mut merged = Map::new();
1947 merged.insert("description".into(), d["description"].take());
1948 if let Some(Value::Object(rest)) = meta {
1949 merged.extend(rest);
1950 }
1951 merged.insert("annotations".into(), d["annotations"].take());
1952 meta = Some(Value::Object(merged));
1953 }
1954 if let Some((text, language)) = &item.code_meta {
1957 let code = json!({ "text": text, "language": language });
1958 match meta.as_mut() {
1959 Some(Value::Object(m)) => {
1960 m.insert("code".into(), code);
1961 }
1962 _ => meta = Some(json!({ "code": code })),
1963 }
1964 }
1965 let prov = self.take_prov(0);
1966 let r = self.push_picture(
1967 prov,
1968 captions.iter().map(|&c| ref_of(c)).collect(),
1969 children,
1970 image.as_ref(),
1971 meta,
1972 parent,
1973 );
1974 if let Some(idx) = ref_index(&r) {
1975 self.pictures[idx]["content_layer"] = json!(layer);
1976 if let (Some(dpi), Some(img)) = (dpi, self.pictures[idx].get_mut("image")) {
1979 img["dpi"] = json!(dpi);
1980 }
1981 }
1982 r
1983 }
1984 TreeKind::FieldRegion { items } => {
1985 self.pending_exact = None;
1986 let r = self.add_field_region(items, parent);
1987 if let Some(region) = self.field_regions.last_mut() {
1988 region["content_layer"] = json!(layer);
1989 }
1990 r
1991 }
1992 TreeKind::KeyValueGraph { cells, links } => {
1993 self.pending_exact = None;
1994 let r = self.add_key_value_graph(cells, links, parent);
1995 if let Some(item) = self.key_value_items.last_mut() {
1996 item["content_layer"] = json!(layer);
1997 }
1998 r
1999 }
2000 };
2001 debug_assert_eq!(self_ref, refs[id], "tree item {id} numbered out of order");
2002 }
2003 tree.body
2004 .iter()
2005 .filter(|&&c| !tree.items[c].deleted)
2006 .map(|&c| ref_of(c))
2007 .collect()
2008 }
2009
2010 fn add_node(&mut self, node: &Node, parent: &str) -> Option<String> {
2011 match node {
2012 Node::Heading { level: 1, text } => {
2013 Some(self.add_text("title", text, parent, json!({})))
2014 }
2015 Node::Heading { level, text } => Some(self.add_text(
2016 "section_header",
2017 text,
2018 parent,
2019 json!({ "level": level.saturating_sub(1) }),
2020 )),
2021 Node::Caption { text, href } => {
2022 let extra = match href {
2023 Some(url) => json!({ "hyperlink": url }),
2024 None => json!({}),
2025 };
2026 Some(self.add_text("caption", text, parent, extra))
2027 }
2028 Node::LabeledText { label, text, href } => {
2031 let extra = match href {
2032 Some(url) => json!({ "hyperlink": url }),
2033 None => json!({}),
2034 };
2035 Some(self.add_text(label, text, parent, extra))
2036 }
2037 Node::Paragraph { text } | Node::ListText { text, .. } => {
2038 let t = text.trim();
2041 match t.strip_prefix("$$").and_then(|s| s.strip_suffix("$$")) {
2042 Some(inner) if !inner.is_empty() => Some(self.add_formula(inner, parent)),
2043 _ => Some(self.add_text("text", text, parent, json!({}))),
2044 }
2045 }
2046 Node::CheckboxItem { checked, text } => {
2047 let label = if *checked {
2053 "checkbox_selected"
2054 } else {
2055 "checkbox_unselected"
2056 };
2057 Some(self.add_text(label, text, parent, json!({})))
2058 }
2059 Node::Code {
2060 language,
2061 text,
2062 orig,
2063 ..
2064 } => Some(self.add_code(text, language.as_deref(), orig.as_deref(), parent)),
2065 Node::Formula {
2068 latex,
2069 orig,
2070 location,
2071 } => {
2072 self.adopt_loc(*location);
2073 Some(self.add_formula_item(latex, orig, parent))
2074 }
2075 Node::CommentSection {
2081 name,
2082 text,
2083 refs_note_text,
2084 grouped,
2085 } => {
2086 if !*grouped {
2087 let child =
2091 self.add_text("text", text, parent, json!({ "content_layer": "notes" }));
2092 self.comment_groups.push(child.clone());
2093 return Some(child);
2094 }
2095 if let Some((_, self_ref)) = self
2096 .last_comment_section
2097 .as_ref()
2098 .filter(|(n, _)| n == name)
2099 .cloned()
2100 {
2101 let child =
2104 self.add_text("text", text, &self_ref, json!({ "content_layer": "notes" }));
2105 if let Some(children) =
2106 self.groups[group_index(&self_ref)]["children"].as_array_mut()
2107 {
2108 children.push(ref_value(child.as_str()));
2109 }
2110 self.comment_groups.push(if *refs_note_text {
2111 child
2112 } else {
2113 self_ref.clone()
2114 });
2115 return None;
2118 }
2119 let self_ref = format!("#/groups/{}", self.groups.len());
2120 self.groups.push(Value::Null);
2121 let child =
2122 self.add_text("text", text, &self_ref, json!({ "content_layer": "notes" }));
2123 self.groups[group_index(&self_ref)] = json!({
2124 "self_ref": self_ref,
2125 "parent": { "$ref": parent },
2126 "children": [{ "$ref": child }],
2127 "content_layer": "notes",
2128 "name": name,
2129 "label": "comment_section",
2130 });
2131 self.last_comment_section = Some((name.clone(), self_ref.clone()));
2132 self.comment_groups.push(if *refs_note_text {
2133 child
2134 } else {
2135 self_ref.clone()
2136 });
2137 Some(self_ref)
2138 }
2139 Node::Commented { comments, inner } => {
2142 let item = self.add_node(inner, parent)?;
2143 if !comments.is_empty() {
2144 self.pending_comments.push((item.clone(), comments.clone()));
2145 }
2146 Some(item)
2147 }
2148 Node::Table(t) => Some(self.add_table(t, parent)),
2149 Node::Picture {
2150 caption,
2151 caption_href,
2152 image,
2153 classification,
2154 description,
2155 caption_parent,
2156 caption_location,
2157 } => Some(self.add_picture(
2158 caption.as_deref(),
2159 caption_href.as_deref(),
2160 image.as_ref(),
2161 picture_meta(classification.as_deref(), description.as_ref()),
2162 parent,
2163 (*caption_parent, *caption_location),
2164 )),
2165 Node::Chart {
2170 kind,
2171 table,
2172 caption,
2173 location,
2174 } => {
2175 self.adopt_loc(*location);
2176 let mut meta = json!({
2177 "classification": { "predictions": [{ "class_name": kind }] },
2178 });
2179 if !table.rows.is_empty() {
2180 meta["tabular_chart"] =
2181 object([("chart_data", table_data(table).into_value())]);
2182 }
2183 let mut captions = Vec::new();
2189 if let Some(cap) = caption.as_deref().filter(|c| !c.is_empty()) {
2190 let prov = self.prov_slot(unescape_text(cap).chars().count(), true);
2191 let cap_ref = self.add_text_with("caption", cap, parent, json!({}), prov);
2192 self.pending_siblings.push(ref_value(cap_ref.as_str()));
2193 captions.push(ref_value(cap_ref));
2194 }
2195 let prov = self.take_prov(0);
2196 Some(self.push_picture(prov, captions, Vec::new(), None, Some(meta), parent))
2197 }
2198 Node::DoclangOnly(_) => None,
2200 Node::Group {
2201 label,
2202 name,
2203 layer,
2204 children,
2205 } => Some(self.add_group(label, name.as_deref(), *layer, children, parent)),
2206 Node::FieldRegion { items } => Some(self.add_field_region(items, parent)),
2207 Node::KeyValueGraph { cells, links } => {
2208 Some(self.add_key_value_graph(cells, links, parent))
2209 }
2210 Node::InlineGroup { md_text, .. } => {
2213 Some(self.add_text("text", md_text, parent, json!({})))
2214 }
2215 Node::TextDump(text) => Some(self.add_text("text", text, parent, json!({}))),
2217 Node::Furniture {
2223 layer: ContentLayer::Notes,
2224 inner,
2225 } => {
2226 let item = self.add_node(inner, parent)?;
2227 self.set_layer(&item, "notes");
2228 Some(item)
2229 }
2230 Node::Furniture { .. } => None,
2231 Node::PageFurniture {
2235 footer,
2236 location,
2237 text,
2238 } => {
2239 if self.cur_page > 0 {
2240 self.pending_loc = Some(*location);
2241 }
2242 let label = if *footer {
2243 "page_footer"
2244 } else {
2245 "page_header"
2246 };
2247 let item = self.add_text(label, text, parent, json!({}));
2248 self.pending_loc = None;
2249 self.set_layer(&item, "furniture");
2250 Some(item)
2251 }
2252 Node::FurnitureText { label, text } => {
2255 let item = self.add_text(label, text, parent, json!({}));
2256 self.set_layer(&item, "furniture");
2257 Some(item)
2258 }
2259 Node::PictureChildren(children) => {
2263 let pic = self.pictures.len().checked_sub(1)?;
2264 let pic_ref = format!("#/pictures/{pic}");
2265 let refs = self.walk_into(children, &pic_ref);
2266 if let Some(list) = self.pictures[pic]["children"].as_array_mut() {
2267 list.extend(refs.into_iter().map(Child::into_value));
2268 }
2269 None
2270 }
2271 Node::Located { location, inner } => {
2276 if self.cur_page > 0 {
2277 self.pending_loc = Some(*location);
2278 }
2279 let r = self.add_node(inner, parent);
2280 self.pending_loc = None;
2281 r
2282 }
2283 Node::Anchor { id } => {
2284 self.pending_anchors.push(id.clone());
2285 None
2286 }
2287 Node::Track { track, cue, inner } => {
2288 self.pending_track = Some((track.clone(), cue.clone()));
2289 let r = self.add_node(inner, parent);
2290 self.pending_track = None;
2291 r
2292 }
2293 Node::Prov {
2294 page_no,
2295 bbox,
2296 charspan,
2297 inner,
2298 ..
2299 } => {
2300 self.pending_exact = Some(ExactProv {
2301 page_no: *page_no,
2302 bbox: bbox.map(f64::from),
2303 bottom_left: false,
2304 charspan: *charspan,
2305 });
2306 let r = self.add_node(inner, parent);
2307 self.pending_exact = None;
2308 r
2309 }
2310 Node::PageBreak => None,
2312 Node::PageInfo {
2315 page_no,
2316 width,
2317 height,
2318 } => {
2319 self.cur_page = *page_no;
2320 self.cur_w = *width as f64;
2321 self.cur_h = *height as f64;
2322 if *page_no > 0 {
2323 self.pages.push((*page_no, self.cur_w, self.cur_h));
2324 }
2325 None
2326 }
2327 Node::ListItem { .. } => None,
2329 }
2330 }
2331
2332 fn add_field_region(&mut self, items: &[crate::FieldItem], parent: &str) -> String {
2336 let self_ref = format!("#/field_regions/{}", self.field_regions.len());
2337 self.field_regions.push(Value::Null);
2338 let region_index = self.field_regions.len() - 1;
2339 let mut item_refs = Vec::new();
2340 for item in items {
2341 item_refs.push(ref_value(self.add_field_item(item, &self_ref)));
2342 }
2343 self.field_regions[region_index] = json!({
2344 "self_ref": self_ref,
2345 "parent": { "$ref": parent },
2346 "children": item_refs,
2347 "content_layer": "body",
2348 "label": "field_region",
2349 "prov": [],
2350 });
2351 self_ref
2352 }
2353
2354 fn add_key_value_graph(
2358 &mut self,
2359 cells: &[crate::GraphCell],
2360 links: &[crate::GraphLink],
2361 parent: &str,
2362 ) -> String {
2363 let self_ref = format!("#/key_value_items/{}", self.key_value_items.len());
2364 let cells: Vec<Value> = cells
2365 .iter()
2366 .map(|c| {
2367 json!({
2368 "label": c.label,
2369 "cell_id": c.cell_id,
2370 "text": c.text,
2371 "orig": c.orig,
2372 })
2373 })
2374 .collect();
2375 let links: Vec<Value> = links
2376 .iter()
2377 .map(|l| {
2378 json!({
2379 "label": l.label,
2380 "source_cell_id": l.source_cell_id,
2381 "target_cell_id": l.target_cell_id,
2382 })
2383 })
2384 .collect();
2385 self.key_value_items.push(json!({
2386 "self_ref": self_ref,
2387 "parent": { "$ref": parent },
2388 "children": [],
2389 "content_layer": "body",
2390 "label": "key_value_region",
2391 "prov": [],
2392 "captions": [],
2393 "references": [],
2394 "footnotes": [],
2395 "graph": { "cells": cells, "links": links },
2396 }));
2397 self_ref
2398 }
2399
2400 fn add_field_item(&mut self, item: &crate::FieldItem, parent: &str) -> String {
2401 let self_ref = format!("#/field_items/{}", self.field_items.len());
2402 self.field_items.push(Value::Null);
2403 let item_index = self.field_items.len() - 1;
2404 let mut child_refs = Vec::new();
2405 for (label, text) in [
2406 ("marker", &item.marker),
2407 ("field_key", &item.key),
2408 ("field_value", &item.value),
2409 ] {
2410 if let Some(text) = text {
2411 let extra = match (label, &item.value_kind) {
2414 ("field_value", Some(kind)) => json!({ "kind": kind }),
2415 _ => json!({}),
2416 };
2417 child_refs.push(ref_value(self.add_text(label, text, &self_ref, extra)));
2418 }
2419 }
2420 self.field_items[item_index] = json!({
2421 "self_ref": self_ref,
2422 "parent": { "$ref": parent },
2423 "children": child_refs,
2424 "content_layer": "body",
2425 "label": "field_item",
2426 "prov": [],
2427 });
2428 self_ref
2429 }
2430
2431 fn set_layer(&mut self, self_ref: &str, layer: &str) {
2435 if let Some(item) = self.item_mut(self_ref) {
2436 item["content_layer"] = json!(layer);
2437 }
2438 }
2439
2440 fn add_text(&mut self, label: &str, text: &str, parent: &str, extra: Value) -> String {
2441 let prov = self.take_prov_slot(unescape_text(text).chars().count());
2442 self.add_text_with(label, text, parent, extra, prov)
2443 }
2444
2445 fn add_text_with(
2448 &mut self,
2449 label: &str,
2450 text: &str,
2451 parent: &str,
2452 extra: Value,
2453 prov: ProvSlot,
2454 ) -> String {
2455 let index = self.texts.len();
2456 let self_ref = format!("#/texts/{index}");
2457 let mut item = match self.pending_track.take() {
2462 None => Item::Text(Box::new(TextItem {
2463 parent: parent.into(),
2464 label: label.into(),
2465 prov,
2466 text: unescape_text(text).into(),
2467 })),
2468 Some((track, cue)) => {
2469 let mut m = serde_json::Map::with_capacity(9);
2470 m.insert("self_ref".into(), Value::String(self_ref.clone()));
2471 m.insert("parent".into(), ref_value(parent));
2472 m.insert("children".into(), Value::Array(Vec::new()));
2473 m.insert("content_layer".into(), "body".into());
2474 m.insert("label".into(), label.into());
2475 m.insert("prov".into(), prov.to_value());
2476 m.insert("source".into(), Value::Array(vec![track_json(&track)]));
2478 m.insert("orig".into(), Value::String(cue.clone()));
2479 m.insert("text".into(), Value::String(cue));
2480 Item::json(Value::Object(m))
2481 }
2482 };
2483 if extra.as_object().is_some_and(|e| !e.is_empty()) {
2484 merge(item.value_mut(index), extra);
2485 }
2486 let anchors = std::mem::take(&mut self.pending_anchors);
2489 if self.note_keys && !anchors.is_empty() {
2490 merge(item.value_mut(index), json!({ "_anchors": anchors }));
2491 }
2492 self.texts.push(item);
2493 self_ref
2494 }
2495
2496 fn add_formula(&mut self, latex: &str, parent: &str) -> String {
2499 let self_ref = format!("#/texts/{}", self.texts.len());
2500 let prov = self.take_prov(latex.chars().count());
2501 self.texts.push(Item::json(json!({
2502 "self_ref": self_ref,
2503 "parent": { "$ref": parent },
2504 "children": [],
2505 "content_layer": "body",
2506 "label": "formula",
2507 "prov": prov,
2508 "orig": latex,
2509 "text": latex,
2510 })));
2511 self_ref
2512 }
2513
2514 fn add_formula_item(&mut self, latex: &str, orig: &str, parent: &str) -> String {
2518 let self_ref = format!("#/texts/{}", self.texts.len());
2519 let prov = self.take_prov(latex.chars().count());
2520 self.texts.push(Item::json(json!({
2521 "self_ref": self_ref,
2522 "parent": { "$ref": parent },
2523 "children": [],
2524 "content_layer": "body",
2525 "label": "formula",
2526 "prov": prov,
2527 "orig": orig,
2528 "text": latex,
2529 })));
2530 self_ref
2531 }
2532
2533 fn add_code(
2534 &mut self,
2535 text: &str,
2536 language: Option<&str>,
2537 orig: Option<&str>,
2538 parent: &str,
2539 ) -> String {
2540 let self_ref = format!("#/texts/{}", self.texts.len());
2541 let raw = unescape_text(text);
2542 let prov = self.take_prov(raw.chars().count());
2543 self.texts.push(Item::json(json!({
2544 "self_ref": self_ref,
2545 "parent": { "$ref": parent },
2546 "children": [],
2547 "content_layer": "body",
2548 "label": "code",
2549 "prov": prov,
2550 "orig": orig.map(unescape_text).unwrap_or_else(|| raw.clone()),
2553 "text": raw,
2554 "captions": [],
2555 "references": [],
2556 "footnotes": [],
2557 "code_language": code_language(language),
2558 })));
2559 self_ref
2560 }
2561
2562 fn add_list(&mut self, items: &[Node], parent: &str) -> String {
2565 let self_ref = format!("#/groups/{}", self.groups.len());
2566 self.groups.push(Value::Null);
2568 let base = level_of(&items[0]);
2569 let mut children = Vec::new();
2570 let mut i = 0;
2571 while i < items.len() {
2572 if !matches!(items[i], Node::ListItem { .. }) {
2575 i += 1;
2576 continue;
2577 }
2578 let lvl = level_of(&items[i]);
2579 if lvl > base {
2580 i += 1;
2582 continue;
2583 }
2584 let item_ref = self.add_list_item(&items[i], &self_ref);
2585 let mut j = i + 1;
2587 while j < items.len() && level_of(&items[j]) > base {
2588 j += 1;
2589 }
2590 if j > i + 1 {
2591 let mut nested = Vec::new();
2592 self.add_sibling_lists(&items[i + 1..j], &item_ref, &mut nested);
2593 if let Some(idx) = ref_index(&item_ref) {
2595 self.texts[idx].value_mut(idx)["children"]
2596 .as_array_mut()
2597 .unwrap()
2598 .extend(nested);
2599 }
2600 }
2601 children.push(ref_value(item_ref));
2602 i = j;
2603 }
2604 self.groups[group_index(&self_ref)] = json!({
2605 "self_ref": self_ref,
2606 "parent": { "$ref": parent },
2607 "children": children,
2608 "content_layer": "body",
2609 "name": "list",
2610 "label": "list",
2611 });
2612 self_ref
2613 }
2614
2615 fn add_list_item(&mut self, node: &Node, parent: &str) -> String {
2616 let Node::ListItem {
2617 ordered,
2618 number,
2619 text,
2620 location,
2621 ..
2622 } = node
2623 else {
2624 unreachable!()
2625 };
2626 self.adopt_loc(*location);
2627 let self_ref = format!("#/texts/{}", self.texts.len());
2628 let raw = unescape_text(text);
2629 let prov = self.take_prov(raw.chars().count());
2630 let marker = if *ordered {
2631 format!("{number}.")
2632 } else {
2633 "-".to_string()
2634 };
2635 self.texts.push(Item::json(json!({
2636 "self_ref": self_ref,
2637 "parent": { "$ref": parent },
2638 "children": [],
2639 "content_layer": "body",
2640 "label": "list_item",
2641 "prov": prov,
2642 "orig": raw,
2643 "text": raw,
2644 "enumerated": ordered,
2645 "marker": marker,
2646 })));
2647 self_ref
2648 }
2649
2650 fn add_table(&mut self, t: &Table, parent: &str) -> String {
2651 self.add_table_with(t, parent, false)
2652 }
2653
2654 fn add_table_with(&mut self, t: &Table, parent: &str, raw: bool) -> String {
2657 let self_ref = format!("#/tables/{}", self.tables.len());
2658 self.adopt_loc(t.location);
2659 let prov = self.take_prov(0);
2660 let (captions, children) = match t.caption.as_deref().filter(|c| !c.is_empty()) {
2664 Some(cap) => self.add_caption(
2665 cap,
2666 json!({}),
2667 &self_ref,
2668 parent,
2669 (t.caption_parent, t.caption_location),
2670 ),
2671 None => (Vec::new(), Vec::new()),
2672 };
2673 let data = table_data_with(t, raw);
2674 let item = json!({
2675 "self_ref": self_ref,
2676 "parent": { "$ref": parent },
2677 "children": children,
2678 "content_layer": "body",
2679 "label": "table",
2680 "prov": prov,
2681 "captions": captions,
2682 "references": [],
2683 "footnotes": [],
2684 "data": null,
2686 "annotations": [],
2687 });
2688 self.tables.push(TableItem { item, data });
2689 self_ref
2690 }
2691
2692 fn add_caption(
2701 &mut self,
2702 text: &str,
2703 extra: Value,
2704 self_ref: &str,
2705 parent: &str,
2706 (choice, location): (CaptionParent, Option<[u16; 4]>),
2707 ) -> (Vec<Value>, Vec<Value>) {
2708 let cap_parent = match choice {
2713 CaptionParent::Item => self_ref,
2714 CaptionParent::Container | CaptionParent::ContainerAfter => parent,
2715 CaptionParent::Body => "#/body",
2716 };
2717 if self.cur_page > 0 && location.is_some() {
2718 self.pending_loc = location;
2719 }
2720 let cap_ref = ref_value(self.add_text("caption", text, cap_parent, extra));
2721 match choice {
2722 CaptionParent::Item => return (vec![cap_ref.clone()], vec![cap_ref]),
2723 CaptionParent::Container => self.pending_siblings.push(cap_ref.clone()),
2726 CaptionParent::Body if parent == "#/body" => {
2727 self.pending_siblings.push(cap_ref.clone())
2728 }
2729 CaptionParent::ContainerAfter => self.pending_after.push(cap_ref.clone()),
2730 CaptionParent::Body => self.pending_body.push(cap_ref.clone()),
2733 }
2734 (vec![cap_ref], Vec::new())
2735 }
2736
2737 fn add_picture(
2740 &mut self,
2741 caption: Option<&str>,
2742 caption_href: Option<&str>,
2743 image: Option<&crate::PictureImage>,
2744 meta: Option<Value>,
2745 parent: &str,
2746 caption_at: (CaptionParent, Option<[u16; 4]>),
2747 ) -> String {
2748 let self_ref = format!("#/pictures/{}", self.pictures.len());
2749 let prov = self.take_prov(0);
2752 let (captions, children) = match caption.filter(|c| !c.is_empty()) {
2753 Some(cap) => {
2754 let extra = match caption_href {
2758 Some(href) => json!({ "hyperlink": href }),
2759 None => json!({}),
2760 };
2761 self.add_caption(cap, extra, &self_ref, parent, caption_at)
2762 }
2763 None => (Vec::new(), Vec::new()),
2764 };
2765 self.push_picture(prov, captions, children, image, meta, parent)
2766 }
2767
2768 fn push_picture(
2771 &mut self,
2772 prov: Value,
2773 captions: Vec<Value>,
2774 children: Vec<Value>,
2775 image: Option<&crate::PictureImage>,
2776 meta: Option<Value>,
2777 parent: &str,
2778 ) -> String {
2779 let self_ref = format!("#/pictures/{}", self.pictures.len());
2780 let annotations = meta
2784 .as_ref()
2785 .and_then(|m| m.get("annotations").cloned())
2786 .unwrap_or_else(|| json!([]));
2787 let meta = meta.map(|mut m| {
2788 if let Some(obj) = m.as_object_mut() {
2789 obj.remove("annotations");
2790 }
2791 m
2792 });
2793 let mut item = match meta {
2797 Some(meta) => json!({
2798 "self_ref": self_ref,
2799 "parent": { "$ref": parent },
2800 "children": children,
2801 "content_layer": "body",
2802 "meta": meta,
2803 "label": "picture",
2804 "prov": prov,
2805 "captions": captions,
2806 "references": [],
2807 "footnotes": [],
2808 "annotations": annotations,
2809 }),
2810 None => json!({
2811 "self_ref": self_ref,
2812 "parent": { "$ref": parent },
2813 "children": children,
2814 "content_layer": "body",
2815 "label": "picture",
2816 "prov": prov,
2817 "captions": captions,
2818 "references": [],
2819 "footnotes": [],
2820 "annotations": annotations,
2821 }),
2822 };
2823 if let Some(img) = image {
2828 let mut image = image_ref_json(img);
2829 if self.note_keys {
2832 if let Some([w, h]) = img.display_size {
2833 image["_display"] = json!([w, h]);
2834 }
2835 }
2836 if let Some(obj) = item.as_object_mut() {
2837 let annotations = obj.remove("annotations").unwrap_or_else(|| json!([]));
2838 obj.insert("image".into(), image);
2839 obj.insert("annotations".into(), annotations);
2840 }
2841 }
2842 self.pictures.push(item);
2843 self_ref
2844 }
2845
2846 fn add_group(
2847 &mut self,
2848 label: &str,
2849 name: Option<&str>,
2850 layer: Option<ContentLayer>,
2851 nodes: &[Node],
2852 parent: &str,
2853 ) -> String {
2854 let self_ref = format!("#/groups/{}", self.groups.len());
2855 self.groups.push(Value::Null);
2856 let mark = (
2860 self.texts.len(),
2861 self.tables.len(),
2862 self.pictures.len(),
2863 self.groups.len(),
2864 );
2865 let children: Vec<Value> = self
2866 .walk_into(nodes, &self_ref)
2867 .into_iter()
2868 .map(Child::into_value)
2869 .collect();
2870 let name = name.unwrap_or(if label == "inline" { "group" } else { label });
2871 let content_layer = layer.map_or("body", |l| l.value());
2872 self.groups[group_index(&self_ref)] = json!({
2873 "self_ref": self_ref,
2874 "parent": { "$ref": parent },
2875 "children": children,
2876 "content_layer": content_layer,
2877 "name": name,
2878 "label": label,
2879 });
2880 if layer.is_some() {
2881 let (t, tb, p, g) = mark;
2882 for item in self.texts[t..]
2883 .iter_mut()
2884 .enumerate()
2885 .map(|(k, item)| item.value_mut(t + k))
2886 .chain(self.tables[tb..].iter_mut().map(|t| &mut t.item))
2887 .chain(self.pictures[p..].iter_mut())
2888 .chain(self.groups[g..].iter_mut())
2889 {
2890 if let Some(obj) = item.as_object_mut() {
2891 obj.insert("content_layer".into(), json!(content_layer));
2892 }
2893 }
2894 }
2895 self_ref
2896 }
2897
2898 fn walk_into(&mut self, nodes: &[Node], parent: &str) -> Vec<Child> {
2901 let seqs: Option<Vec<usize>> = nodes
2906 .iter()
2907 .map(|n| match n {
2908 Node::Prov { seq: Some(s), .. } => Some(*s),
2909 _ => None,
2910 })
2911 .collect();
2912 if let Some(seqs) = seqs.filter(|s| !s.is_empty()) {
2913 let mut order: Vec<usize> = (0..nodes.len()).collect();
2914 order.sort_by_key(|&i| seqs[i]);
2915 let mut slots: Vec<Vec<Child>> = (0..nodes.len()).map(|_| Vec::new()).collect();
2916 for i in order {
2917 if let Some(r) = self.add_node(&nodes[i], parent) {
2918 slots[i].extend(self.pending_siblings.drain(..).map(Child::json));
2919 slots[i].push(Child::Ref(r.into()));
2920 slots[i].extend(self.pending_after.drain(..).map(Child::json));
2921 }
2922 if parent == "#/body" {
2923 slots[i].extend(self.pending_body.drain(..).map(Child::json));
2924 }
2925 }
2926 return slots.into_iter().flatten().collect();
2927 }
2928 let mut children = Vec::new();
2929 let mut i = 0;
2930 while i < nodes.len() {
2931 if matches!(nodes[i], Node::ListItem { .. }) {
2932 let start = i;
2933 i += 1;
2934 loop {
2935 match nodes.get(i) {
2936 Some(Node::ListItem { .. }) => i += 1,
2937 Some(Node::Paragraph { text })
2940 if text.is_empty()
2941 && matches!(nodes.get(i + 1), Some(Node::ListItem { .. })) =>
2942 {
2943 i += 1
2944 }
2945 _ => break,
2946 }
2947 }
2948 let mut lists = Vec::new();
2949 self.add_sibling_lists(&nodes[start..i], parent, &mut lists);
2950 children.extend(lists.into_iter().map(Child::json));
2951 } else {
2952 if let Some(r) = self.add_node(&nodes[i], parent) {
2953 children.extend(self.pending_siblings.drain(..).map(Child::json));
2954 children.push(Child::Ref(r.into()));
2955 children.extend(self.pending_after.drain(..).map(Child::json));
2956 }
2957 i += 1;
2958 }
2959 if parent == "#/body" {
2962 children.extend(self.pending_body.drain(..).map(Child::json));
2963 }
2964 }
2965 children
2966 }
2967
2968 fn add_sibling_lists(&mut self, run: &[Node], parent: &str, out: &mut Vec<Value>) {
2973 let base = level_of(&run[0]);
2974 let mut seg = 0;
2975 for k in 0..run.len() {
2976 let Node::ListItem {
2977 first_in_list,
2978 level,
2979 ..
2980 } = &run[k]
2981 else {
2982 continue;
2983 };
2984 if *level != base {
2985 continue; }
2987 if k > seg && *first_in_list {
2988 out.push(ref_value(self.add_list(&run[seg..k], parent)));
2989 seg = k;
2990 }
2991 }
2992 out.push(ref_value(self.add_list(&run[seg..], parent)));
2993 }
2994}
2995
2996fn level_of(node: &Node) -> u8 {
2997 match node {
2998 Node::ListItem { level, .. } => *level,
2999 _ => 0,
3000 }
3001}
3002
3003fn group_index(self_ref: &str) -> usize {
3004 self_ref.rsplit('/').next().unwrap().parse().unwrap()
3005}
3006
3007fn ref_index(self_ref: &str) -> Option<usize> {
3008 self_ref.rsplit('/').next()?.parse().ok()
3009}
3010
3011fn merge(target: &mut Value, extra: Value) {
3013 if let (Some(t), Value::Object(e)) = (target.as_object_mut(), extra) {
3014 t.extend(e);
3015 }
3016}
3017
3018fn object<const N: usize>(entries: [(&str, Value); N]) -> Value {
3024 let mut m = serde_json::Map::with_capacity(N);
3025 for (k, v) in entries {
3026 m.insert(k.to_owned(), v);
3027 }
3028 Value::Object(m)
3029}
3030
3031fn ref_value(r: impl Into<String>) -> Value {
3033 object([("$ref", Value::String(r.into()))])
3034}
3035
3036fn unescape_text(s: &str) -> String {
3038 s.replace("<", "<")
3039 .replace(">", ">")
3040 .replace("&", "&")
3041 .replace("\\_", "_")
3042}
3043
3044fn fnv1a(s: &str) -> u64 {
3047 let mut h: u64 = 0xcbf29ce484222325;
3048 for b in s.bytes() {
3049 h ^= b as u64;
3050 h = h.wrapping_mul(0x100000001b3);
3051 }
3052 h
3053}
3054
3055#[cfg(test)]
3056mod tests {
3057 use crate::{
3058 CaptionParent, ContentLayer, DoclingDocument, ImageMode, Node, PictureImage, Table,
3059 };
3060 use serde_json::Value;
3061
3062 fn cell_at(text: &str, row: usize, col: usize, col_span: usize) -> crate::TableCell {
3063 crate::TableCell {
3064 text: text.into(),
3065 bbox: None,
3066 start_row: row,
3067 start_col: col,
3068 row_span: 1,
3069 col_span,
3070 column_header: row == 0,
3071 row_header: false,
3072 row_section: false,
3073 }
3074 }
3075
3076 #[test]
3082 fn ragged_rows_take_their_grid_from_first_class_cells() {
3083 let cells = vec![
3084 cell_at("h0", 0, 0, 1),
3085 cell_at("h2", 0, 2, 1),
3086 cell_at("wide", 1, 0, 3),
3087 ];
3088 let export = |rows: Vec<Vec<String>>| -> Value {
3089 let mut doc = DoclingDocument::new("t");
3090 doc.push(Node::Table(Table {
3091 rows,
3092 cells: Some(cells.clone()),
3093 ..Table::default()
3094 }));
3095 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3096 v["tables"][0]["data"].clone()
3097 };
3098 let data = export(vec![
3100 vec!["h0".into(), "h2".into()],
3101 vec!["wide".into(), "wide".into(), "wide".into()],
3102 ]);
3103 assert_eq!(data["num_rows"], 2);
3104 assert_eq!(data["num_cols"], 3);
3105 assert_eq!(data["table_cells"].as_array().unwrap().len(), 3);
3106 let row0: Vec<&str> = data["grid"][0]
3107 .as_array()
3108 .unwrap()
3109 .iter()
3110 .map(|c| c["text"].as_str().unwrap())
3111 .collect();
3112 assert_eq!(row0, ["h0", "", "h2"]);
3113 assert_eq!(data["table_cells"][2]["col_span"], 3);
3114 let data = export(vec![
3117 vec!["h0".into(), "h2".into()],
3118 vec!["wide".into(), "wide".into()],
3119 ]);
3120 assert_eq!(data["num_cols"], 2);
3121 assert_eq!(data["grid"][0].as_array().unwrap().len(), 2);
3122 }
3123
3124 fn doc_with_image() -> DoclingDocument {
3125 let mut doc = DoclingDocument::new("t");
3126 doc.push(Node::Picture {
3127 caption: Some("Fig 1".into()),
3128 caption_href: None,
3129 image: Some(PictureImage {
3130 dpi: crate::PictureImage::DEFAULT_DPI,
3131 mimetype: "image/png".into(),
3132 width: 4,
3133 height: 2,
3134 data: b"foobar".to_vec(),
3135 display_size: None,
3136 }),
3137 classification: None,
3138 description: None,
3139 caption_parent: Default::default(),
3140 caption_location: None,
3141 });
3142 doc
3143 }
3144
3145 #[test]
3150 fn notes_layer_items_reach_the_json_but_furniture_does_not() {
3151 let mut doc = DoclingDocument::new("t");
3152 doc.push(Node::Heading {
3153 level: 1,
3154 text: "Slide One".into(),
3155 });
3156 doc.push(Node::Furniture {
3157 layer: ContentLayer::Notes,
3158 inner: Box::new(Node::Located {
3159 location: [0, 0, 0, 0],
3160 inner: Box::new(Node::Paragraph {
3161 text: "Speaker note for slide 1.".into(),
3162 }),
3163 }),
3164 });
3165 doc.push(Node::Furniture {
3166 layer: ContentLayer::Furniture,
3167 inner: Box::new(Node::Paragraph {
3168 text: "page header".into(),
3169 }),
3170 });
3171
3172 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3173 let texts = v["texts"].as_array().unwrap();
3174 assert_eq!(
3175 texts
3176 .iter()
3177 .map(|t| (
3178 t["label"].as_str().unwrap(),
3179 t["content_layer"].as_str().unwrap(),
3180 t["text"].as_str().unwrap()
3181 ))
3182 .collect::<Vec<_>>(),
3183 vec![
3184 ("title", "body", "Slide One"),
3185 ("text", "notes", "Speaker note for slide 1."),
3186 ],
3187 "the note is carried on its own layer; the furniture is not carried"
3188 );
3189 assert_eq!(doc.export_to_markdown(), "# Slide One\n");
3191 }
3192
3193 #[test]
3199 fn continuation_flags_become_spanning_cells() {
3200 let mut doc = DoclingDocument::new("t");
3201 let rows = vec![
3203 vec!["merged".to_string(), "merged".into(), "merged".into()],
3204 vec!["merged".to_string(), "merged".into(), "merged".into()],
3205 vec!["a".to_string(), "b".into(), "c".into()],
3206 ];
3207 doc.push(Node::Table(crate::Table {
3208 rows,
3209 location: None,
3210 structure: Some(crate::TableStructure {
3211 header_row: vec![true, false, false],
3212 col_continuation: vec![
3213 vec![false, true, true],
3214 vec![false, true, true],
3215 vec![false, false, false],
3216 ],
3217 row_continuation: vec![
3218 vec![false, false, false],
3219 vec![true, true, true],
3220 vec![false, false, false],
3221 ],
3222 row_header: Vec::new(),
3223 col_header: Vec::new(),
3224 }),
3225 cell_blocks: None,
3226 cells: None,
3227 caption: None,
3228 caption_parent: Default::default(),
3229 caption_location: None,
3230 }));
3231 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3232 let data = &v["tables"][0]["data"];
3233 assert_eq!(data["num_rows"], 3);
3234 assert_eq!(data["num_cols"], 3);
3235 let cells = data["table_cells"].as_array().unwrap();
3236 assert_eq!(
3237 cells.len(),
3238 4,
3239 "one cell for the range, three for the plain row"
3240 );
3241 assert_eq!(
3242 cells[0],
3243 serde_json::json!({
3244 "row_span": 2, "col_span": 3,
3245 "start_row_offset_idx": 0, "end_row_offset_idx": 2,
3246 "start_col_offset_idx": 0, "end_col_offset_idx": 3,
3247 "text": "merged", "column_header": true, "row_header": false,
3248 "row_section": false, "fillable": false,
3249 })
3250 );
3251 assert_eq!(cells[1]["text"], "a");
3252 assert_eq!(cells[1]["row_span"], 1);
3253 assert_eq!(cells[1]["column_header"], false);
3254 let grid = data["grid"].as_array().unwrap();
3256 assert_eq!(grid.len(), 3);
3257 for (r, row) in grid.iter().take(2).enumerate() {
3258 for (c, cell) in row.as_array().unwrap().iter().enumerate() {
3259 assert_eq!(*cell, cells[0], "grid[{r}][{c}]");
3260 }
3261 }
3262 assert_eq!(grid[2][2]["text"], "c");
3263 }
3264
3265 #[test]
3271 fn exact_provenance_pages_and_chart_captions_follow_docling() {
3272 let mut doc = DoclingDocument::new("t");
3273 doc.push(Node::PageInfo {
3274 page_no: 1,
3275 width: 3.0,
3276 height: 4.0,
3277 });
3278 let table = crate::Table {
3279 rows: vec![vec!["a".to_string(), "b".into()]],
3280 ..Default::default()
3281 };
3282 doc.push(Node::Group {
3283 label: "sheet".into(),
3284 name: Some("Data".into()),
3285 layer: None,
3286 children: vec![
3287 Node::Prov {
3292 page_no: 1,
3293 bbox: [0.0, 0.0, 3.0, 4.0],
3294 charspan: [0, 0],
3295 seq: Some(1),
3296 inner: Box::new(Node::Table(table.clone())),
3297 },
3298 Node::Prov {
3299 page_no: 1,
3300 bbox: [0.0, 1.0, 1.0, 1.0],
3301 charspan: [0, 0],
3302 seq: Some(0),
3303 inner: Box::new(Node::Chart {
3304 kind: "bar_chart".into(),
3305 table,
3306 caption: Some("Sales".into()),
3307 location: Some([0, 128, 170, 128]),
3308 }),
3309 },
3310 ],
3311 });
3312 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3313 assert_eq!(
3314 v["pages"],
3315 serde_json::json!({"1": {"size": {"width": 3.0, "height": 4.0}, "page_no": 1}})
3316 );
3317 assert_eq!(
3318 v["tables"][0]["prov"],
3319 serde_json::json!([{
3320 "page_no": 1,
3321 "bbox": {"l": 0.0, "t": 0.0, "r": 3.0, "b": 4.0, "coord_origin": "TOPLEFT"},
3322 "charspan": [0, 0],
3323 }])
3324 );
3325 assert_eq!(v["tables"][0]["data"]["orientation"], "rot_0");
3326 let sheet = &v["groups"][0];
3329 assert_eq!(
3330 sheet["children"],
3331 serde_json::json!([
3332 {"$ref": "#/tables/0"}, {"$ref": "#/texts/0"}, {"$ref": "#/pictures/0"}
3333 ])
3334 );
3335 let cap = &v["texts"][0];
3336 assert_eq!(cap["label"], "caption");
3337 assert_eq!(cap["parent"], serde_json::json!({"$ref": "#/groups/0"}));
3338 assert_eq!(cap["prov"][0]["charspan"], serde_json::json!([0, 5]));
3339 assert_eq!(cap["prov"][0]["bbox"]["b"], 1.0);
3340 let pic = &v["pictures"][0];
3341 assert_eq!(pic["captions"], serde_json::json!([{"$ref": "#/texts/0"}]));
3342 assert_eq!(pic["prov"][0]["charspan"], serde_json::json!([0, 0]));
3343 assert_eq!(pic["prov"][0]["bbox"]["coord_origin"], "TOPLEFT");
3344 assert_eq!(
3345 pic["meta"]["classification"]["predictions"][0]["class_name"],
3346 "bar_chart"
3347 );
3348 assert_eq!(pic["meta"]["tabular_chart"]["chart_data"]["num_cols"], 2);
3349 }
3350
3351 #[test]
3355 fn a_zero_location_is_a_zero_bbox_not_the_whole_page() {
3356 let mut doc = DoclingDocument::new("t");
3357 doc.push(Node::PageInfo {
3358 page_no: 1,
3359 width: 12192000.0,
3360 height: 6858000.0,
3361 });
3362 doc.push(Node::Furniture {
3363 layer: ContentLayer::Notes,
3364 inner: Box::new(Node::Located {
3365 location: [0, 0, 0, 0],
3366 inner: Box::new(Node::Paragraph {
3367 text: "a note".into(),
3368 }),
3369 }),
3370 });
3371 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3372 let prov = &v["texts"][0]["prov"][0];
3373 assert_eq!(prov["page_no"], 1);
3374 assert_eq!(prov["charspan"], serde_json::json!([0, 6]));
3375 assert_eq!(
3376 prov["bbox"],
3377 serde_json::json!({"l": 0.0, "t": 0.0, "r": 0.0, "b": 0.0, "coord_origin": "BOTTOMLEFT"})
3378 );
3379 assert_eq!(
3381 v["pages"]["1"]["size"],
3382 serde_json::json!({"width": 12192000.0, "height": 6858000.0})
3383 );
3384 }
3385
3386 #[test]
3396 fn captions_and_checkboxes_carry_their_own_prov() {
3397 let mut doc = DoclingDocument::new("t");
3398 doc.push(Node::PageInfo {
3399 page_no: 1,
3400 width: 512.0,
3401 height: 512.0,
3402 });
3403 doc.push(Node::Located {
3404 location: [64, 64, 448, 256],
3405 inner: Box::new(Node::Picture {
3406 caption: Some("Figure 1".into()),
3407 caption_href: None,
3408 image: None,
3409 classification: None,
3410 description: None,
3411 caption_parent: CaptionParent::Item,
3412 caption_location: Some([64, 264, 448, 280]),
3413 }),
3414 });
3415 doc.push(Node::PageInfo {
3416 page_no: 2,
3417 width: 512.0,
3418 height: 512.0,
3419 });
3420 doc.push(Node::Located {
3421 location: [64, 120, 448, 300],
3422 inner: Box::new(Node::Table(Table {
3423 rows: vec![vec!["a".into()]],
3424 caption: Some("Table 1".into()),
3425 caption_parent: CaptionParent::Item,
3426 caption_location: Some([64, 100, 300, 112]),
3427 ..Table::default()
3428 })),
3429 });
3430 doc.push(Node::Located {
3431 location: [64, 320, 200, 332],
3432 inner: Box::new(Node::CheckboxItem {
3433 checked: false,
3434 text: "First option".into(),
3435 }),
3436 });
3437 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3438 let texts = v["texts"].as_array().unwrap();
3439 let by_text = |t: &str| texts.iter().find(|x| x["text"] == t).expect(t).clone();
3440 let bbox = |p: &Value| {
3441 let b = &p["prov"][0]["bbox"];
3442 [&b["l"], &b["t"], &b["r"], &b["b"]].map(|x| x.as_f64().unwrap())
3443 };
3444 let fig = by_text("Figure 1");
3447 assert_eq!(fig["prov"][0]["page_no"], 1);
3448 assert_eq!(bbox(&fig), [64.0, 248.0, 448.0, 232.0]);
3449 assert_eq!(fig["prov"][0]["charspan"], serde_json::json!([0, 8]));
3450 assert_eq!(fig["parent"]["$ref"], "#/pictures/0");
3451 assert_eq!(v["pictures"][0]["prov"][0]["bbox"]["t"], 448.0);
3452 let tab = by_text("Table 1");
3454 assert_eq!(tab["prov"][0]["page_no"], 2);
3455 assert_eq!(bbox(&tab), [64.0, 412.0, 300.0, 400.0]);
3456 assert_eq!(v["tables"][0]["prov"][0]["bbox"]["t"], 392.0);
3457 let cb = by_text("First option");
3459 assert_eq!(cb["label"], "checkbox_unselected");
3460 assert_eq!(cb["prov"][0]["page_no"], 2);
3461 assert_eq!(bbox(&cb), [64.0, 192.0, 200.0, 180.0]);
3462 assert!(texts
3463 .iter()
3464 .all(|t| !t["prov"].as_array().unwrap().is_empty()));
3465
3466 let mut plain = DoclingDocument::new("t");
3469 plain.push(Node::PageInfo {
3470 page_no: 1,
3471 width: 512.0,
3472 height: 512.0,
3473 });
3474 plain.push(Node::Located {
3475 location: [64, 64, 448, 256],
3476 inner: Box::new(Node::Picture {
3477 caption: Some("Figure 1".into()),
3478 caption_href: None,
3479 image: None,
3480 classification: None,
3481 description: None,
3482 caption_parent: CaptionParent::Item,
3483 caption_location: None,
3484 }),
3485 });
3486 let v: Value = serde_json::from_str(&plain.export_to_json()).unwrap();
3487 assert_eq!(v["texts"][0]["prov"], serde_json::json!([]));
3488 assert_eq!(v["pictures"][0]["prov"][0]["bbox"]["t"], 448.0);
3489 }
3490
3491 #[test]
3492 fn page_markers_produce_pages_and_prov() {
3493 let mut doc = DoclingDocument::new("t");
3494 doc.push(Node::PageInfo {
3495 page_no: 1,
3496 width: 512.0,
3497 height: 1024.0,
3498 });
3499 doc.push(Node::Located {
3500 location: [128, 64, 256, 128], inner: Box::new(Node::Paragraph {
3502 text: "hello".into(),
3503 }),
3504 });
3505 doc.push(Node::Table(Table {
3506 rows: vec![vec!["a".into()]],
3507 location: Some([0, 0, 512, 512]),
3508 ..Table::default()
3509 }));
3510 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3511 assert_eq!(v["pages"]["1"]["page_no"], 1);
3512 assert_eq!(v["pages"]["1"]["size"]["width"], 512.0);
3513 assert_eq!(v["pages"]["1"]["size"]["height"], 1024.0);
3514 let prov = &v["texts"][0]["prov"][0];
3517 assert_eq!(prov["page_no"], 1);
3518 assert_eq!(prov["bbox"]["l"], 128.0);
3519 assert_eq!(prov["bbox"]["t"], 896.0);
3520 assert_eq!(prov["bbox"]["r"], 256.0);
3521 assert_eq!(prov["bbox"]["b"], 768.0);
3522 assert_eq!(prov["bbox"]["coord_origin"], "BOTTOMLEFT");
3523 assert_eq!(prov["charspan"][1], 5);
3524 let tprov = &v["tables"][0]["prov"][0];
3526 assert_eq!(tprov["bbox"]["t"], 1024.0);
3527 assert_eq!(tprov["bbox"]["b"], 0.0);
3528 assert_eq!(tprov["charspan"][1], 0);
3529
3530 let mut plain = DoclingDocument::new("t");
3532 plain.push(Node::Located {
3533 location: [1, 2, 3, 4],
3534 inner: Box::new(Node::Paragraph { text: "x".into() }),
3535 });
3536 let v: Value = serde_json::from_str(&plain.export_to_json()).unwrap();
3537 assert_eq!(v["pages"], serde_json::json!({}));
3538 assert_eq!(v["texts"][0]["prov"], serde_json::json!([]));
3539 }
3540
3541 #[test]
3544 fn page_furniture_becomes_furniture_layer_text() {
3545 let mut doc = DoclingDocument::new("t");
3546 doc.push(Node::PageInfo {
3547 page_no: 1,
3548 width: 512.0,
3549 height: 512.0,
3550 });
3551 doc.push(Node::PageFurniture {
3552 footer: false,
3553 location: [10, 0, 100, 20],
3554 text: "Chapter 1".into(),
3555 });
3556 doc.push(Node::Paragraph {
3557 text: "body".into(),
3558 });
3559 doc.push(Node::PageFurniture {
3560 footer: true,
3561 location: [400, 490, 500, 512],
3562 text: "1.10.2".into(),
3563 });
3564 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3565 let texts = v["texts"].as_array().unwrap();
3566 assert_eq!(texts.len(), 3);
3567 assert_eq!(texts[0]["label"], "page_header");
3568 assert_eq!(texts[0]["content_layer"], "furniture");
3569 assert_eq!(texts[0]["parent"]["$ref"], "#/body");
3570 assert_eq!(texts[0]["prov"][0]["bbox"]["l"], 10.0);
3571 assert_eq!(texts[1]["content_layer"], "body");
3572 assert_eq!(texts[1]["prov"], serde_json::json!([]));
3573 assert_eq!(texts[2]["label"], "page_footer");
3574 assert_eq!(texts[2]["text"], "1.10.2");
3575 assert_eq!(texts[2]["content_layer"], "furniture");
3576 }
3577
3578 #[test]
3579 fn picture_image_in_markdown_modes_and_json() {
3580 let doc = doc_with_image();
3581 assert!(doc.export_to_markdown().contains("<!-- image -->"));
3583 let (md, files) = doc.export_to_markdown_with_images(ImageMode::Embedded, "artifacts");
3585 assert!(
3586 md.contains(""),
3587 "got:\n{md}"
3588 );
3589 assert!(files.is_empty());
3590 let (md, files) = doc.export_to_markdown_with_images(ImageMode::Referenced, "artifacts");
3592 assert!(
3593 md.contains(""),
3594 "got:\n{md}"
3595 );
3596 assert_eq!(
3597 files,
3598 vec![("artifacts/image_000000.png".to_string(), b"foobar".to_vec())]
3599 );
3600 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3603 assert_eq!(v["pictures"][0]["image"]["mimetype"], "image/png");
3604 assert_eq!(v["pictures"][0]["image"]["size"]["width"], 4.0);
3605 let keys: Vec<&str> = v["pictures"][0]
3606 .as_object()
3607 .unwrap()
3608 .keys()
3609 .map(String::as_str)
3610 .collect();
3611 assert_eq!(&keys[keys.len() - 2..], ["image", "annotations"]);
3612 assert_eq!(
3613 v["pictures"][0]["image"]["uri"],
3614 "data:image/png;base64,Zm9vYmFy"
3615 );
3616 }
3617
3618 #[test]
3619 fn exports_docling_schema() {
3620 let mut doc = DoclingDocument::new("t");
3621 doc.push(Node::Heading {
3622 level: 1,
3623 text: "Title".into(),
3624 });
3625 doc.push(Node::Heading {
3626 level: 2,
3627 text: "Sec".into(),
3628 });
3629 doc.push(Node::Paragraph {
3630 text: "Body & more".into(),
3631 }); doc.push(Node::ListItem {
3633 ordered: false,
3634 number: 0,
3635 first_in_list: true,
3636 text: "one".into(),
3637 level: 0,
3638 marker: None,
3639 location: None,
3640 dclx: None,
3641 href: None,
3642 layer: None,
3643 });
3644 doc.push(Node::ListItem {
3645 ordered: false,
3646 number: 0,
3647 first_in_list: false,
3648 text: "two".into(),
3649 level: 0,
3650 marker: None,
3651 location: None,
3652 dclx: None,
3653 href: None,
3654 layer: None,
3655 });
3656 doc.push(Node::Table(Table {
3657 rows: vec![vec!["A".into(), "B".into()]],
3658 location: None,
3659 structure: None,
3660 cell_blocks: None,
3661 cells: None,
3662 caption: None,
3663 caption_parent: Default::default(),
3664 caption_location: None,
3665 }));
3666
3667 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3668 assert_eq!(v["schema_name"], "DoclingDocument");
3669 assert_eq!(v["version"], "1.10.0");
3670 assert_eq!(v["texts"][0]["label"], "title");
3671 assert_eq!(v["texts"][1]["label"], "section_header");
3672 assert_eq!(v["texts"][1]["level"], 1); assert_eq!(v["texts"][2]["text"], "Body & more"); assert_eq!(v["groups"][0]["label"], "list");
3676 assert_eq!(v["groups"][0]["children"].as_array().unwrap().len(), 2);
3677 assert_eq!(v["texts"][3]["parent"]["$ref"], "#/groups/0");
3678 assert_eq!(v["texts"][3]["marker"], "-");
3679 assert_eq!(v["tables"][0]["data"]["num_cols"], 2);
3681 assert_eq!(v["tables"][0]["data"]["grid"][0][0]["column_header"], true);
3682 }
3683 #[test]
3687 fn a_layered_group_stamps_its_whole_subtree() {
3688 let doc = DoclingDocument {
3689 name: "s".into(),
3690 nodes: vec![
3691 Node::Group {
3692 label: "sheet".into(),
3693 name: Some("Sheet1".into()),
3694 layer: None,
3695 children: vec![Node::Paragraph {
3696 text: "visible".into(),
3697 }],
3698 },
3699 Node::Group {
3700 label: "sheet".into(),
3701 name: Some("Sheet2".into()),
3702 layer: Some(ContentLayer::Invisible),
3703 children: vec![Node::Paragraph {
3704 text: "hidden".into(),
3705 }],
3706 },
3707 ],
3708 ..DoclingDocument::new("s")
3709 };
3710 let v = crate::json::to_json(&doc);
3711 assert_eq!(v["groups"][0]["label"], "sheet");
3712 assert_eq!(v["groups"][0]["name"], "Sheet1");
3713 assert_eq!(v["groups"][0]["content_layer"], "body");
3714 assert_eq!(v["texts"][0]["content_layer"], "body");
3715 assert_eq!(v["groups"][1]["name"], "Sheet2");
3716 assert_eq!(v["groups"][1]["content_layer"], "invisible");
3717 assert_eq!(v["texts"][1]["content_layer"], "invisible");
3718 assert_eq!(v["groups"][1]["children"][0]["$ref"], "#/texts/1");
3720 assert_eq!(v["body"]["children"][1]["$ref"], "#/groups/1");
3721 }
3722
3723 #[test]
3733 fn deleted_items_comment_refs_and_chart_meta_in_the_tree() {
3734 use crate::tree::{ItemTree, TreeKind};
3735 let mut t = ItemTree::default();
3736 let text = |txt: &str| TreeKind::Text {
3737 label: "text".into(),
3738 text: txt.into(),
3739 orig: None,
3740 formatting: None,
3741 hyperlink: None,
3742 level: None,
3743 list: None,
3744 };
3745 let a = t.add(None, None, text("a"));
3746 let blank = t.add(None, None, text(""));
3747 let b = t.add(None, None, text("b"));
3748 t.delete(blank);
3749 let group = t.add(
3750 None,
3751 Some(ContentLayer::Notes),
3752 TreeKind::Group {
3753 label: "comment_section".into(),
3754 name: "comment-0".into(),
3755 },
3756 );
3757 t.add(Some(group), Some(ContentLayer::Notes), text("note"));
3758 t.items[a].comments.push(group);
3759 t.add(
3760 None,
3761 None,
3762 TreeKind::Picture {
3763 captions: Vec::new(),
3764 image: None,
3765 extra_classes: Vec::new(),
3766 classification: Some("bar_chart".into()),
3767 description: None,
3768 confidence: None,
3769 chart: Some(Table {
3770 rows: vec![vec!["".into(), "s".into()], vec!["c".into(), "1".into()]],
3771 ..Table::default()
3772 }),
3773 dpi: None,
3774 },
3775 );
3776 assert_eq!(t.last_text(), Some(4), "the note; the blank is skipped");
3777 assert_eq!(t.bucket_index(b), 1, "numbered past the deleted item");
3778 let mut doc = DoclingDocument::new("t");
3779 doc.tree = Some(t);
3780 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3781 let texts = v["texts"].as_array().unwrap();
3782 assert_eq!(texts.len(), 3);
3783 assert_eq!(texts[1]["text"], "b");
3784 assert_eq!(texts[1]["self_ref"], "#/texts/1");
3785 assert_eq!(
3786 v["body"]["children"],
3787 serde_json::json!([{"$ref": "#/texts/0"}, {"$ref": "#/texts/1"}, {"$ref": "#/groups/0"}, {"$ref": "#/pictures/0"}])
3788 );
3789 let keys: Vec<&str> = texts[0]
3790 .as_object()
3791 .unwrap()
3792 .keys()
3793 .map(String::as_str)
3794 .collect();
3795 assert_eq!(
3796 keys,
3797 vec![
3798 "self_ref",
3799 "parent",
3800 "children",
3801 "content_layer",
3802 "label",
3803 "prov",
3804 "comments",
3805 "orig",
3806 "text"
3807 ]
3808 );
3809 assert_eq!(
3810 texts[0]["comments"],
3811 serde_json::json!([{"$ref": "#/groups/0"}])
3812 );
3813 assert!(texts[1].get("comments").is_none());
3814 let meta = &v["pictures"][0]["meta"];
3815 assert_eq!(
3816 meta["classification"]["predictions"][0]["class_name"],
3817 "bar_chart"
3818 );
3819 assert_eq!(meta["tabular_chart"]["chart_data"]["num_rows"], 2);
3820 }
3821
3822 #[test]
3828 fn tree_items_carry_exact_provenance_and_dpi() {
3829 use crate::tree::{ItemTree, TreeKind, TreeProv};
3830 let text = |label: &str, t: &str| TreeKind::Text {
3831 label: label.into(),
3832 text: t.into(),
3833 orig: None,
3834 formatting: None,
3835 hyperlink: None,
3836 level: None,
3837 list: None,
3838 };
3839 let mut t = ItemTree::default();
3840 let slide = t.add(
3841 None,
3842 None,
3843 TreeKind::Group {
3844 label: "chapter".into(),
3845 name: "slide-0".into(),
3846 },
3847 );
3848 t.add_with_prov(
3849 Some(slide),
3850 None,
3851 text("paragraph", "héllo"),
3852 TreeProv {
3853 page_no: 1,
3854 bbox: [914400.0, 1828800.0, 2743200.0, 457200.0],
3855 bottom_left: true,
3856 charspan: [0, 5],
3857 },
3858 );
3859 t.add_with_prov(
3860 Some(slide),
3861 None,
3862 TreeKind::Picture {
3863 captions: Vec::new(),
3864 image: Some(crate::PictureImage {
3865 dpi: crate::PictureImage::DEFAULT_DPI,
3866 mimetype: "image/png".into(),
3867 width: 2,
3868 height: 2,
3869 data: vec![0],
3870 display_size: None,
3871 }),
3872 extra_classes: Vec::new(),
3873 classification: None,
3874 description: None,
3875 confidence: None,
3876 chart: None,
3877 dpi: Some(300),
3878 },
3879 TreeProv {
3880 page_no: 1,
3881 bbox: [0.0; 4],
3882 bottom_left: false,
3883 charspan: [0, 0],
3884 },
3885 );
3886 t.add(
3887 Some(slide),
3888 Some(ContentLayer::Notes),
3889 text("text", "no geometry"),
3890 );
3891 let mut doc = DoclingDocument::new("t");
3892 doc.push(Node::PageInfo {
3893 page_no: 1,
3894 width: 9144000.0,
3895 height: 6858000.0,
3896 });
3897 doc.tree = Some(t);
3898 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3899 assert_eq!(
3900 v["texts"][0]["prov"],
3901 serde_json::json!([{
3902 "page_no": 1,
3903 "bbox": { "l": 914400.0, "t": 1828800.0, "r": 2743200.0, "b": 457200.0, "coord_origin": "BOTTOMLEFT" },
3904 "charspan": [0, 5],
3905 }])
3906 );
3907 assert_eq!(v["texts"][0]["label"], "paragraph");
3908 assert_eq!(
3909 v["pictures"][0]["prov"][0]["bbox"]["coord_origin"],
3910 "TOPLEFT"
3911 );
3912 assert_eq!(v["pictures"][0]["image"]["dpi"], 300);
3913 assert_eq!(v["texts"][1]["prov"], serde_json::json!([]));
3914 assert_eq!(v["texts"][1]["content_layer"], "notes");
3915 assert_eq!(v["pages"]["1"]["size"]["width"], 9144000.0);
3916 assert_eq!(v["pages"]["1"]["page_no"], 1);
3917 }
3918
3919 #[test]
3924 fn provenance_boxes_are_clamped_to_their_page() {
3925 let mut doc = DoclingDocument::new("t");
3926 doc.push(Node::PageInfo {
3927 page_no: 1,
3928 width: 10.0,
3929 height: 8.0,
3930 });
3931 doc.push(Node::Prov {
3932 page_no: 1,
3933 bbox: [-1.0, 2.0, 12.0, 9.5],
3934 charspan: [0, 1],
3935 seq: None,
3936 inner: Box::new(Node::Paragraph { text: "x".into() }),
3937 });
3938 let mut table = Table {
3939 rows: vec![vec!["a".into()]],
3940 ..Table::default()
3941 };
3942 table.cells = Some(vec![crate::TableCell {
3943 text: "a".into(),
3944 bbox: Some([1.0, 1.0, 11.0, 9.0]),
3945 start_row: 0,
3946 start_col: 0,
3947 row_span: 1,
3948 col_span: 1,
3949 column_header: false,
3950 row_header: false,
3951 row_section: false,
3952 }]);
3953 doc.push(Node::Prov {
3954 page_no: 1,
3955 bbox: [0.0, 0.0, 10.0, 8.0],
3956 charspan: [0, 0],
3957 seq: None,
3958 inner: Box::new(Node::Table(table)),
3959 });
3960 doc.push(Node::Prov {
3961 page_no: 7,
3962 bbox: [-5.0, 0.0, 50.0, 50.0],
3963 charspan: [0, 1],
3964 seq: None,
3965 inner: Box::new(Node::Paragraph { text: "y".into() }),
3966 });
3967 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
3968 assert_eq!(
3969 v["texts"][0]["prov"][0]["bbox"],
3970 serde_json::json!({ "l": 0.0, "t": 2.0, "r": 10.0, "b": 8.0, "coord_origin": "TOPLEFT" })
3971 );
3972 let cell = &v["tables"][0]["data"]["table_cells"][0]["bbox"];
3973 assert_eq!(
3974 (cell["l"].as_f64(), cell["r"].as_f64(), cell["b"].as_f64()),
3975 (Some(1.0), Some(10.0), Some(8.0))
3976 );
3977 assert_eq!(v["tables"][0]["data"]["grid"][0][0]["bbox"]["r"], 10.0);
3978 assert_eq!(
3979 v["texts"][1]["prov"][0]["bbox"]["r"], 50.0,
3980 "page 7 is not described"
3981 );
3982 }
3983
3984 #[test]
3991 fn misplaced_list_items_are_wrapped_like_docling_core() {
3992 use crate::tree::{ItemTree, ListMeta, TreeKind};
3993 let mut t = ItemTree::default();
3994 let li = |txt: &str| TreeKind::Text {
3995 label: "list_item".into(),
3996 text: txt.into(),
3997 orig: None,
3998 formatting: None,
3999 hyperlink: None,
4000 level: None,
4001 list: Some(ListMeta {
4002 enumerated: false,
4003 marker: "-".into(),
4004 }),
4005 };
4006 let group = |label: &str, name: &str| TreeKind::Group {
4007 label: label.into(),
4008 name: name.into(),
4009 };
4010 t.add(None, None, li("A"));
4011 t.add(None, None, li("B"));
4012 t.add(
4013 None,
4014 None,
4015 TreeKind::Text {
4016 label: "text".into(),
4017 text: "plain".into(),
4018 orig: None,
4019 formatting: None,
4020 hyperlink: None,
4021 level: None,
4022 list: None,
4023 },
4024 );
4025 let cell = t.add(None, None, group("unspecified", "cell"));
4026 t.add(Some(cell), None, li("C"));
4027 t.add(Some(cell), None, li("D"));
4028 let list = t.add(None, None, group("list", "list"));
4029 t.add(Some(list), None, li("E"));
4030 let doc = DoclingDocument {
4031 tree: Some(t),
4032 ..DoclingDocument::new("t")
4033 };
4034 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
4035 let refs = |x: &Value| -> Vec<String> {
4036 x.as_array()
4037 .unwrap()
4038 .iter()
4039 .map(|r| r["$ref"].as_str().unwrap().to_string())
4040 .collect()
4041 };
4042 assert_eq!(
4043 refs(&v["body"]["children"]),
4044 ["#/groups/4", "#/texts/0", "#/groups/0", "#/groups/1"]
4045 );
4046 assert_eq!(
4047 refs(&v["groups"][0]["children"]),
4048 ["#/groups/3", "#/groups/2"]
4049 );
4050 for (g, name, kids) in [
4051 (2, "group", vec!["#/texts/2"]),
4052 (3, "group", vec!["#/texts/3"]),
4053 (4, "group", vec!["#/texts/4", "#/texts/5"]),
4054 (1, "list", vec!["#/texts/1"]),
4055 ] {
4056 assert_eq!(v["groups"][g]["label"], "list");
4057 assert_eq!(v["groups"][g]["name"], name);
4058 assert_eq!(refs(&v["groups"][g]["children"]), kids);
4059 }
4060 let texts: Vec<&str> = v["texts"]
4061 .as_array()
4062 .unwrap()
4063 .iter()
4064 .map(|t| t["text"].as_str().unwrap())
4065 .collect();
4066 assert_eq!(texts, ["plain", "E", "D", "C", "A", "B"]);
4067 assert_eq!(v["texts"][2]["parent"]["$ref"], "#/groups/2");
4068 }
4069
4070 #[test]
4073 fn a_deleted_child_is_not_written_as_an_empty_ref() {
4074 use crate::tree::{ItemTree, TreeKind};
4075 let mut t = ItemTree::default();
4076 let text = |txt: &str| TreeKind::Text {
4077 label: "text".into(),
4078 text: txt.into(),
4079 orig: None,
4080 formatting: None,
4081 hyperlink: None,
4082 level: None,
4083 list: None,
4084 };
4085 let g = t.add(
4086 None,
4087 None,
4088 TreeKind::Group {
4089 label: "unspecified".into(),
4090 name: "g".into(),
4091 },
4092 );
4093 let gone = t.add(Some(g), None, text(""));
4094 t.add(Some(g), None, text("kept"));
4095 t.items[gone].deleted = true; let doc = DoclingDocument {
4097 tree: Some(t),
4098 ..DoclingDocument::new("t")
4099 };
4100 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
4101 assert_eq!(
4102 v["groups"][0]["children"],
4103 serde_json::json!([{"$ref": "#/texts/0"}])
4104 );
4105 }
4106
4107 #[test]
4108 fn a_backend_item_tree_is_written_verbatim() {
4109 use crate::tree::{Formatting, ItemTree, ListMeta, TreeKind};
4110 let mut t = ItemTree::default();
4111 let text = |label: &str, txt: &str| TreeKind::Text {
4112 label: label.into(),
4113 text: txt.into(),
4114 orig: None,
4115 formatting: None,
4116 hyperlink: None,
4117 level: None,
4118 list: None,
4119 };
4120 let title = t.add(None, Some(ContentLayer::Furniture), text("title", "Page"));
4121 let h = t.add(None, None, text("title", "Heading"));
4122 let group = t.add(
4123 Some(h),
4124 None,
4125 TreeKind::Group {
4126 label: "inline".into(),
4127 name: "group".into(),
4128 },
4129 );
4130 t.add(
4131 Some(group),
4132 None,
4133 TreeKind::Text {
4134 label: "text".into(),
4135 text: "bold".into(),
4136 orig: None,
4137 formatting: Some(Formatting {
4138 bold: true,
4139 ..Formatting::default()
4140 }),
4141 hyperlink: Some("https://example.com/".into()),
4142 level: None,
4143 list: None,
4144 },
4145 );
4146 t.add(
4147 Some(group),
4148 None,
4149 TreeKind::Code {
4150 text: "x = 1".into(),
4151 orig: None,
4152 language: Some("python".into()),
4153 formatting: None,
4154 hyperlink: None,
4155 captions: Vec::new(),
4156 },
4157 );
4158 let sub = t.add(
4159 Some(h),
4160 None,
4161 TreeKind::Text {
4162 label: "section_header".into(),
4163 text: "Sub".into(),
4164 orig: Some("Sub\u{2019}".into()),
4165 formatting: None,
4166 hyperlink: None,
4167 level: Some(1),
4168 list: None,
4169 },
4170 );
4171 let item = t.add(
4172 Some(sub),
4173 None,
4174 TreeKind::Text {
4175 label: "list_item".into(),
4176 text: "item".into(),
4177 orig: None,
4178 formatting: None,
4179 hyperlink: None,
4180 level: None,
4181 list: Some(ListMeta {
4182 enumerated: true,
4183 marker: "3.".into(),
4184 }),
4185 },
4186 );
4187 let _region = t.add(
4188 Some(sub),
4189 None,
4190 TreeKind::FieldRegion {
4191 items: vec![crate::FieldItem {
4192 marker: None,
4193 key: Some("Name".into()),
4194 value: Some("Duck".into()),
4195 value_kind: Some("read_only".into()),
4196 }],
4197 },
4198 );
4199 let table = t.add(
4200 Some(sub),
4201 None,
4202 TreeKind::Table {
4203 table: Table {
4204 rows: vec![vec!["a \n<".into(), "b".into()]],
4205 cells: Some(vec![
4206 crate::TableCell {
4207 text: "a \n<".into(),
4208 bbox: None,
4209 start_row: 0,
4210 start_col: 0,
4211 row_span: 3,
4212 col_span: 1,
4213 column_header: false,
4214 row_header: true,
4215 row_section: false,
4216 },
4217 crate::TableCell {
4218 text: "b".into(),
4219 bbox: None,
4220 start_row: 0,
4221 start_col: 1,
4222 row_span: 1,
4223 col_span: 1,
4224 column_header: false,
4225 row_header: false,
4226 row_section: false,
4227 },
4228 ]),
4229 ..Table::default()
4230 },
4231 rich_cells: vec![(0, 1, 0)], captions: Vec::new(),
4233 },
4234 );
4235 let cell_group = t.add(
4236 Some(table),
4237 None,
4238 TreeKind::Group {
4239 label: "unspecified".into(),
4240 name: "rich_cell_group_1_0_0".into(),
4241 },
4242 );
4243 if let TreeKind::Table { rich_cells, .. } = &mut t.items[table].kind {
4244 *rich_cells = vec![(0, 1, cell_group)];
4245 }
4246 let after = t.add(Some(sub), None, text("text", "after the region"));
4247 let _ = (title, after);
4248 let list = t.add(
4252 Some(sub),
4253 None,
4254 TreeKind::Group {
4255 label: "list".into(),
4256 name: "list".into(),
4257 },
4258 );
4259 t.reparent(item, Some(list));
4260
4261 let doc = DoclingDocument {
4262 tree: Some(t),
4263 ..DoclingDocument::new("t")
4264 };
4265 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
4266 let texts: Vec<&str> = v["texts"]
4268 .as_array()
4269 .unwrap()
4270 .iter()
4271 .map(|t| t["text"].as_str().unwrap())
4272 .collect();
4273 assert_eq!(
4274 texts,
4275 [
4276 "Page",
4277 "Heading",
4278 "bold",
4279 "x = 1",
4280 "Sub",
4281 "item",
4282 "Name",
4283 "Duck",
4284 "after the region"
4285 ]
4286 );
4287 assert_eq!(
4288 v["body"]["children"],
4289 serde_json::json!([{"$ref": "#/texts/0"}, {"$ref": "#/texts/1"}])
4290 );
4291 assert_eq!(v["texts"][0]["content_layer"], "furniture");
4292 assert_eq!(
4293 v["texts"][1]["children"],
4294 serde_json::json!([{"$ref": "#/groups/0"}, {"$ref": "#/texts/4"}])
4295 );
4296 let bold = &v["texts"][2];
4297 assert_eq!(bold["parent"]["$ref"], "#/groups/0");
4298 let keys: Vec<&str> = bold
4299 .as_object()
4300 .unwrap()
4301 .keys()
4302 .map(String::as_str)
4303 .collect();
4304 assert_eq!(
4305 keys,
4306 [
4307 "self_ref",
4308 "parent",
4309 "children",
4310 "content_layer",
4311 "label",
4312 "prov",
4313 "orig",
4314 "text",
4315 "formatting",
4316 "hyperlink"
4317 ]
4318 );
4319 assert_eq!(
4320 bold["formatting"],
4321 serde_json::json!({"bold": true, "italic": false, "underline": false, "strikethrough": false, "script": "baseline"})
4322 );
4323 let code = &v["texts"][3];
4324 assert_eq!(code["label"], "code");
4325 assert_eq!(code["code_language"], "Python");
4326 let sub = &v["texts"][4];
4327 assert_eq!(sub["orig"], "Sub\u{2019}");
4328 assert_eq!(sub["level"], 1);
4329 let item = &v["texts"][5];
4330 let keys: Vec<&str> = item
4331 .as_object()
4332 .unwrap()
4333 .keys()
4334 .map(String::as_str)
4335 .collect();
4336 assert_eq!(
4337 keys,
4338 [
4339 "self_ref",
4340 "parent",
4341 "children",
4342 "content_layer",
4343 "label",
4344 "prov",
4345 "orig",
4346 "text",
4347 "enumerated",
4348 "marker"
4349 ]
4350 );
4351 assert_eq!(item["marker"], "3.");
4352 assert_eq!(v["texts"][7]["kind"], "read_only");
4353 assert_eq!(v["field_regions"][0]["parent"]["$ref"], "#/texts/4");
4354 let table = &v["tables"][0];
4355 assert_eq!(
4356 table["children"],
4357 serde_json::json!([{"$ref": "#/groups/1"}])
4358 );
4359 let cells = table["data"]["table_cells"].as_array().unwrap();
4360 assert_eq!(
4361 cells[0]["text"], "a \n<",
4362 "raw cell text is written verbatim"
4363 );
4364 assert_eq!(
4365 cells[0]["end_row_offset_idx"], 3,
4366 "declared spans are not clamped"
4367 );
4368 assert_eq!(cells[1]["ref"], serde_json::json!({"$ref": "#/groups/1"}));
4369 assert!(cells[0].get("ref").is_none());
4370 assert!(
4371 table["data"]["grid"][0][1].get("ref").is_none(),
4372 "the grid shows plain cells"
4373 );
4374 assert_eq!(v["groups"][1]["name"], "rich_cell_group_1_0_0");
4375 }
4376
4377 #[test]
4381 fn a_comment_section_can_be_referenced_by_its_note_text() {
4382 let doc = DoclingDocument {
4383 name: "c".into(),
4384 nodes: vec![
4385 Node::Commented {
4386 comments: vec![0],
4387 inner: Box::new(Node::Paragraph {
4388 text: "annotated".into(),
4389 }),
4390 },
4391 Node::CommentSection {
4392 name: "comment-Sheet1-A1".into(),
4393 text: "[author: A]: note".into(),
4394 refs_note_text: true,
4395 grouped: true,
4396 },
4397 ],
4398 ..DoclingDocument::new("c")
4399 };
4400 let v = crate::json::to_json(&doc);
4401 assert_eq!(v["groups"][0]["name"], "comment-Sheet1-A1");
4402 assert_eq!(v["texts"][0]["comments"][0]["$ref"], "#/texts/1");
4403 }
4404
4405 #[test]
4408 fn a_comment_thread_is_one_child_of_its_parent() {
4409 let note = |text: &str| Node::CommentSection {
4410 name: "comment-Sheet1-F7".into(),
4411 text: text.into(),
4412 refs_note_text: true,
4413 grouped: true,
4414 };
4415 let doc = DoclingDocument {
4416 name: "c".into(),
4417 nodes: vec![note("[author: A]: first"), note("[author: B]: reply")],
4418 ..DoclingDocument::new("c")
4419 };
4420 let v = crate::json::to_json(&doc);
4421 assert_eq!(v["body"]["children"].as_array().unwrap().len(), 1);
4422 assert_eq!(v["groups"][0]["children"].as_array().unwrap().len(), 2);
4423 }
4424
4425 #[test]
4429 fn comment_sections_link_back_to_their_items() {
4430 let doc = DoclingDocument {
4431 name: "c".into(),
4432 nodes: vec![
4433 Node::Commented {
4434 comments: vec![0],
4435 inner: Box::new(Node::Paragraph {
4436 text: "annotated".into(),
4437 }),
4438 },
4439 Node::Paragraph {
4440 text: "plain".into(),
4441 },
4442 Node::CommentSection {
4443 name: "comment-7".into(),
4444 text: "[time: t]: note".into(),
4445 refs_note_text: false,
4446 grouped: true,
4447 },
4448 ],
4449 ..DoclingDocument::new("c")
4450 };
4451 let v = crate::json::to_json(&doc);
4452 assert_eq!(v["groups"][0]["label"], "comment_section");
4454 assert_eq!(v["groups"][0]["name"], "comment-7");
4455 assert_eq!(v["groups"][0]["content_layer"], "notes");
4456 assert_eq!(v["groups"][0]["children"][0]["$ref"], "#/texts/2");
4457 assert_eq!(v["texts"][2]["content_layer"], "notes");
4458 assert_eq!(v["texts"][0]["comments"][0]["$ref"], "#/groups/0");
4460 assert!(v["texts"][1].get("comments").is_none());
4461 let keys: Vec<&str> = v["texts"][0]
4463 .as_object()
4464 .unwrap()
4465 .keys()
4466 .map(String::as_str)
4467 .collect();
4468 assert_eq!(
4469 &keys[keys.len() - 4..],
4470 &["prov", "comments", "orig", "text"]
4471 );
4472 }
4473
4474 fn picture(caption: &str, caption_parent: CaptionParent) -> Node {
4475 Node::Picture {
4476 caption: Some(caption.into()),
4477 caption_href: None,
4478 image: None,
4479 classification: None,
4480 description: None,
4481 caption_parent,
4482 caption_location: None,
4483 }
4484 }
4485
4486 fn group(children: Vec<Node>) -> Node {
4487 Node::Group {
4488 label: "section".into(),
4489 name: None,
4490 layer: None,
4491 children,
4492 }
4493 }
4494
4495 fn refs(v: &Value) -> Vec<&str> {
4496 v.as_array()
4497 .unwrap()
4498 .iter()
4499 .map(|r| r["$ref"].as_str().unwrap())
4500 .collect()
4501 }
4502
4503 #[test]
4508 fn a_body_caption_follows_the_enclosing_top_level_item() {
4509 let mut doc = DoclingDocument::new("t");
4510 doc.push(picture("top", CaptionParent::Body));
4511 doc.push(group(vec![
4512 Node::Paragraph { text: "p".into() },
4513 picture("nested", CaptionParent::Body),
4514 ]));
4515 doc.push(Node::Paragraph {
4516 text: "after".into(),
4517 });
4518 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
4519 assert_eq!(
4520 refs(&v["body"]["children"]),
4521 [
4522 "#/texts/0",
4523 "#/pictures/0",
4524 "#/groups/0",
4525 "#/texts/2",
4526 "#/texts/3"
4527 ]
4528 );
4529 assert_eq!(
4530 refs(&v["groups"][0]["children"]),
4531 ["#/texts/1", "#/pictures/1"]
4532 );
4533 for (cap, pic) in [(0, 0), (2, 1)] {
4534 assert_eq!(v["texts"][cap]["label"], "caption");
4535 assert_eq!(v["texts"][cap]["parent"]["$ref"], "#/body");
4536 assert_eq!(
4537 refs(&v["pictures"][pic]["captions"]),
4538 [format!("#/texts/{cap}")]
4539 );
4540 assert_eq!(v["pictures"][pic]["children"], serde_json::json!([]));
4541 }
4542 }
4543
4544 #[test]
4547 fn an_item_caption_is_the_items_first_child() {
4548 let mut doc = DoclingDocument::new("t");
4549 doc.push(picture("fig", CaptionParent::Item));
4550 doc.push(Node::Table(Table {
4551 rows: vec![vec!["a".into()]],
4552 caption: Some("tab".into()),
4553 caption_parent: CaptionParent::Item,
4554 ..Table::default()
4555 }));
4556 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
4557 assert_eq!(refs(&v["body"]["children"]), ["#/pictures/0", "#/tables/0"]);
4558 assert_eq!(v["texts"][0]["parent"]["$ref"], "#/pictures/0");
4559 assert_eq!(refs(&v["pictures"][0]["children"]), ["#/texts/0"]);
4560 assert_eq!(refs(&v["pictures"][0]["captions"]), ["#/texts/0"]);
4561 assert_eq!(v["texts"][1]["parent"]["$ref"], "#/tables/0");
4562 assert_eq!(refs(&v["tables"][0]["children"]), ["#/texts/1"]);
4563 assert_eq!(refs(&v["tables"][0]["captions"]), ["#/texts/1"]);
4564 }
4565
4566 #[test]
4572 fn picture_children_hang_off_the_picture_after_its_caption() {
4573 let mut doc = DoclingDocument::new("t");
4574 doc.push(picture("fig", CaptionParent::Item));
4575 doc.push(Node::PictureChildren(vec![
4576 Node::Heading {
4577 level: 2,
4578 text: "in-figure title".into(),
4579 },
4580 Node::Paragraph {
4581 text: "axis label".into(),
4582 },
4583 Node::ListItem {
4584 ordered: false,
4585 number: 0,
4586 first_in_list: true,
4587 text: "callout".into(),
4588 level: 0,
4589 marker: None,
4590 location: None,
4591 dclx: None,
4592 href: None,
4593 layer: None,
4594 },
4595 ]));
4596 doc.push(Node::Paragraph {
4597 text: "after".into(),
4598 });
4599 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
4600 assert_eq!(refs(&v["body"]["children"]), ["#/pictures/0", "#/texts/4"]);
4601 assert_eq!(
4602 refs(&v["pictures"][0]["children"]),
4603 ["#/texts/0", "#/texts/1", "#/texts/2", "#/groups/0"]
4604 );
4605 assert_eq!(refs(&v["pictures"][0]["captions"]), ["#/texts/0"]);
4606 assert_eq!(v["texts"][1]["label"], "section_header");
4607 assert_eq!(v["texts"][2]["label"], "text");
4608 for t in 1..=2 {
4609 assert_eq!(v["texts"][t]["parent"]["$ref"], "#/pictures/0");
4610 assert_eq!(v["texts"][t]["content_layer"], "body");
4611 }
4612 assert_eq!(v["groups"][0]["parent"]["$ref"], "#/pictures/0");
4613 assert_eq!(v["texts"][3]["parent"]["$ref"], "#/groups/0");
4614 let md = doc.export_to_markdown();
4615 assert!(
4616 !md.contains("axis label") && !md.contains("callout"),
4617 "{md}"
4618 );
4619 assert!(md.contains("after"), "{md}");
4620 }
4621
4622 #[test]
4626 fn a_container_caption_is_the_items_sibling() {
4627 let mut doc = DoclingDocument::new("t");
4628 doc.push(group(vec![
4629 picture("chart", CaptionParent::Container),
4630 Node::Table(Table {
4631 rows: vec![vec!["a".into()]],
4632 caption: Some("figcaption".into()),
4633 caption_parent: CaptionParent::ContainerAfter,
4634 ..Table::default()
4635 }),
4636 ]));
4637 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
4638 assert_eq!(refs(&v["body"]["children"]), ["#/groups/0"]);
4639 assert_eq!(
4640 refs(&v["groups"][0]["children"]),
4641 ["#/texts/0", "#/pictures/0", "#/tables/0", "#/texts/1"]
4642 );
4643 assert_eq!(v["texts"][0]["parent"]["$ref"], "#/groups/0");
4644 assert_eq!(v["texts"][1]["parent"]["$ref"], "#/groups/0");
4645 assert_eq!(v["pictures"][0]["children"], serde_json::json!([]));
4646 assert_eq!(v["tables"][0]["children"], serde_json::json!([]));
4647 }
4648
4649 #[test]
4654 fn a_labeled_text_keeps_its_label_and_hyperlink_out_of_the_text() {
4655 let mut doc = DoclingDocument::new("t");
4656 doc.push(Node::LabeledText {
4657 label: "footnote".into(),
4658 text: "1 https://example.com/a\\_b".into(),
4659 href: Some("https://example.com/a_b".into()),
4660 });
4661 doc.push(Node::LabeledText {
4662 label: "footnote".into(),
4663 text: "© 2022 the authors".into(),
4664 href: None,
4665 });
4666 let v: Value = serde_json::from_str(&doc.export_to_json()).unwrap();
4667 let t = &v["texts"][0];
4668 assert_eq!(t["label"], "footnote");
4669 assert_eq!(t["text"], "1 https://example.com/a_b");
4670 assert_eq!(t["orig"], "1 https://example.com/a_b");
4671 assert_eq!(t["hyperlink"], "https://example.com/a_b");
4672 assert_eq!(v["texts"][1]["label"], "footnote");
4673 assert!(v["texts"][1].get("hyperlink").is_none());
4674 assert_eq!(
4675 doc.export_to_markdown(),
4676 "[1 https://example.com/a\\_b](https://example.com/a_b)\n\n© 2022 the authors\n"
4677 );
4678 assert_eq!(
4679 doc.export_to_text(),
4680 "1 https://example.com/a_b\n\n© 2022 the authors"
4681 );
4682 }
4683}