use crate::{ContentLayer, FieldItem, PictureImage, Script, Table};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Formatting {
pub bold: bool,
pub italic: bool,
pub underline: bool,
pub strikethrough: bool,
pub script: Script,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ListMeta {
pub enumerated: bool,
pub marker: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TreeKind {
Text {
label: String,
text: String,
orig: Option<String>,
formatting: Option<Formatting>,
hyperlink: Option<String>,
level: Option<u8>,
list: Option<ListMeta>,
},
Code {
text: String,
orig: Option<String>,
language: Option<String>,
formatting: Option<Formatting>,
hyperlink: Option<String>,
},
Group { label: String, name: String },
Table {
table: Table,
rich_cells: Vec<(usize, usize, usize)>,
captions: Vec<usize>,
},
Picture {
captions: Vec<usize>,
image: Option<PictureImage>,
classification: Option<String>,
},
FieldRegion { items: Vec<FieldItem> },
}
#[derive(Debug, Clone, PartialEq)]
pub struct TreeItem {
pub parent: Option<usize>,
pub children: Vec<usize>,
pub layer: Option<ContentLayer>,
pub kind: TreeKind,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ItemTree {
pub items: Vec<TreeItem>,
pub body: Vec<usize>,
}
impl ItemTree {
pub fn add(
&mut self,
parent: Option<usize>,
layer: Option<ContentLayer>,
kind: TreeKind,
) -> usize {
let id = self.items.len();
self.items.push(TreeItem {
parent,
children: Vec::new(),
layer,
kind,
});
match parent {
Some(p) => self.items[p].children.push(id),
None => self.body.push(id),
}
id
}
pub fn reparent(&mut self, id: usize, new_parent: Option<usize>) {
let old = self.items[id].parent;
let siblings = match old {
Some(p) => &mut self.items[p].children,
None => &mut self.body,
};
siblings.retain(|&c| c != id);
self.items[id].parent = new_parent;
match new_parent {
Some(p) => self.items[p].children.push(id),
None => self.body.push(id),
}
}
pub fn bucket_index(&self, id: usize) -> usize {
let same = |k: &TreeKind| {
std::mem::discriminant(k) == std::mem::discriminant(&self.items[id].kind)
|| matches!(
(k, &self.items[id].kind),
(TreeKind::Text { .. }, TreeKind::Code { .. })
| (TreeKind::Code { .. }, TreeKind::Text { .. })
)
};
self.items[..id].iter().filter(|it| same(&it.kind)).count()
}
pub fn table_count(&self) -> usize {
self.items
.iter()
.filter(|it| matches!(it.kind, TreeKind::Table { .. }))
.count()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn text(t: &str) -> TreeKind {
TreeKind::Text {
label: "text".into(),
text: t.into(),
orig: None,
formatting: None,
hyperlink: None,
level: None,
list: None,
}
}
#[test]
fn add_and_reparent_keep_docling_children_order() {
let mut t = ItemTree::default();
let title = t.add(None, None, text("Title"));
let a = t.add(Some(title), None, text("a"));
let b = t.add(Some(title), None, text("b"));
let table = t.add(
Some(title),
None,
TreeKind::Table {
table: Table::default(),
rich_cells: Vec::new(),
captions: Vec::new(),
},
);
let group = t.add(
Some(table),
None,
TreeKind::Group {
label: "unspecified".into(),
name: "rich_cell_group_1_0_0".into(),
},
);
assert_eq!(t.body, vec![title]);
assert_eq!(t.items[title].children, vec![a, b, table]);
t.reparent(a, Some(group));
assert_eq!(t.items[title].children, vec![b, table]);
assert_eq!(t.items[group].children, vec![a]);
assert_eq!(t.items[a].parent, Some(group));
assert_eq!(t.table_count(), 1);
let code = t.add(
None,
None,
TreeKind::Code {
text: "x".into(),
orig: None,
language: None,
formatting: None,
hyperlink: None,
},
);
assert_eq!(t.bucket_index(code), 3, "title, a, b precede it in `texts`");
assert_eq!(t.bucket_index(group), 0);
assert_eq!(t.body, vec![title, code]);
}
}