use std::collections::HashMap;
use crate::com::dispatch::Dispatch;
use crate::com::variant::Variant;
use crate::office::constants::MsoShapeType;
use crate::shapes::matcher::{self, ShapePrefix, TableType};
#[derive(Debug)]
pub struct ShapeRef {
pub dispatch: Dispatch,
pub name: String,
pub slide_index: i32,
}
#[derive(Debug)]
pub struct TableInfo {
pub dispatch: Dispatch,
pub table_type: TableType,
pub name: String,
}
#[derive(Debug)]
pub struct SlideInventory {
pub ole_shapes: Vec<ShapeRef>,
pub tables: HashMap<(i32, String), TableInfo>,
pub delts: HashMap<(i32, String), ShapeRef>,
pub ccst_tables: Vec<ShapeRef>,
pub charts: Vec<ShapeRef>,
pub count_ntbl: usize,
pub count_htmp: usize,
pub count_trns: usize,
pub count_delt: usize,
pub count_ccst: usize,
}
impl SlideInventory {
fn new() -> Self {
Self {
ole_shapes: Vec::new(),
tables: HashMap::new(),
delts: HashMap::new(),
ccst_tables: Vec::new(),
charts: Vec::new(),
count_ntbl: 0,
count_htmp: 0,
count_trns: 0,
count_delt: 0,
count_ccst: 0,
}
}
}
struct TableCandidate {
slide_index: i32,
dispatch: Dispatch,
table_type: TableType,
name: String,
}
struct DeltCandidate {
slide_index: i32,
dispatch: Dispatch,
name: String,
}
pub fn build_inventory(presentation: &mut Dispatch) -> SlideInventory {
let mut inventory = SlideInventory::new();
let mut table_candidates: Vec<TableCandidate> = Vec::new();
let mut delt_candidates: Vec<DeltCandidate> = Vec::new();
let mut slides = match presentation.get("Slides") {
Ok(v) => match v.as_dispatch() {
Ok(d) => Dispatch::new(d),
Err(_) => return inventory,
},
Err(_) => return inventory,
};
let slide_count = match slides.get("Count") {
Ok(v) => v.as_i32().unwrap_or(0),
Err(_) => return inventory,
};
for s in 1..=slide_count {
let mut slide = match slides.call("Item", &[Variant::from(s)]) {
Ok(v) => match v.as_dispatch() {
Ok(d) => Dispatch::new(d),
Err(_) => continue,
},
Err(_) => continue,
};
let slide_index = slide.get("SlideIndex")
.and_then(|v| v.as_i32().map_err(|e| e))
.unwrap_or(s);
let mut shapes = match slide.get("Shapes") {
Ok(v) => match v.as_dispatch() {
Ok(d) => Dispatch::new(d),
Err(_) => continue,
},
Err(_) => continue,
};
let shape_count = match shapes.get("Count") {
Ok(v) => v.as_i32().unwrap_or(0),
Err(_) => continue,
};
let shape_dispid_cache = shapes.cache();
for i in 1..=shape_count {
if let Ok(v) = shapes.call("Item", &[Variant::from(i)]) {
if let Ok(d) = v.as_dispatch() {
let shape = Dispatch::new_with_cache(d, shape_dispid_cache.clone());
scan_shape_recursive(
shape,
slide_index,
&mut inventory,
&mut table_candidates,
&mut delt_candidates,
);
}
}
}
}
for ole_ref in &inventory.ole_shapes {
let key = (ole_ref.slide_index, ole_ref.name.clone());
if !inventory.tables.contains_key(&key) {
for priority in &[TableType::Normal, TableType::Heatmap, TableType::Transposed] {
let found = table_candidates.iter().position(|tc| {
tc.slide_index == ole_ref.slide_index
&& tc.table_type == *priority
&& matcher::is_exact_token_match(&tc.name, &ole_ref.name)
});
if let Some(idx) = found {
let tc = &table_candidates[idx];
inventory.tables.insert(key.clone(), TableInfo {
dispatch: tc.dispatch.clone(),
table_type: tc.table_type,
name: tc.name.clone(),
});
break;
}
}
}
if !inventory.delts.contains_key(&key) {
let found = delt_candidates.iter().position(|dc| {
dc.slide_index == ole_ref.slide_index
&& matcher::is_exact_token_match(&dc.name, &ole_ref.name)
});
if let Some(idx) = found {
let dc = &delt_candidates[idx];
inventory.delts.insert(key, ShapeRef {
dispatch: dc.dispatch.clone(),
name: dc.name.clone(),
slide_index: dc.slide_index,
});
}
}
}
inventory
}
fn scan_shape_recursive(
mut shape: Dispatch,
slide_index: i32,
inventory: &mut SlideInventory,
table_candidates: &mut Vec<TableCandidate>,
delt_candidates: &mut Vec<DeltCandidate>,
) {
let name = shape.get("Name")
.and_then(|v| v.as_string().map_err(|e| e))
.unwrap_or_default();
let shape_type = shape.get("Type")
.and_then(|v| v.as_i32().map_err(|e| e))
.unwrap_or(0);
if shape_type == MsoShapeType::Group as i32 {
if name.contains("delt_") {
delt_candidates.push(DeltCandidate {
slide_index,
dispatch: shape.clone(),
name: name.clone(),
});
inventory.count_delt += 1;
}
if let Ok(gi_variant) = shape.get("GroupItems") {
if let Ok(gi_dispatch) = gi_variant.as_dispatch() {
let mut group_items = Dispatch::new(gi_dispatch);
let count = group_items.get("Count")
.and_then(|v| v.as_i32().map_err(|e| e))
.unwrap_or(0);
for i in 1..=count {
if let Ok(v) = group_items.call("Item", &[Variant::from(i)]) {
if let Ok(d) = v.as_dispatch() {
scan_shape_recursive(
Dispatch::new(d),
slide_index,
inventory,
table_candidates,
delt_candidates,
);
}
}
}
}
}
return;
}
if shape_type == MsoShapeType::LinkedOleObject as i32 {
if let Ok(lf_variant) = shape.get("LinkFormat") {
if lf_variant.as_dispatch().is_ok() {
let prog_id = shape.nav("OLEFormat")
.and_then(|mut ole| ole.get("ProgID"))
.and_then(|v| v.as_string().map_err(|e| e))
.unwrap_or_default();
if prog_id.contains("Excel.Sheet") {
inventory.ole_shapes.push(ShapeRef {
dispatch: shape.clone(),
name: name.clone(),
slide_index,
});
}
}
}
if name.contains("delt_") {
delt_candidates.push(DeltCandidate {
slide_index,
dispatch: shape.clone(),
name,
});
inventory.count_delt += 1;
}
return; }
let has_chart = shape.get("HasChart")
.and_then(|v| v.as_i32().map_err(|e| e))
.unwrap_or(0);
if has_chart != 0 {
let is_linked = shape.nav("Chart.ChartData")
.and_then(|mut cd| cd.get("IsLinked"))
.and_then(|v| v.as_bool().map_err(|e| e))
.unwrap_or(false);
if is_linked {
inventory.charts.push(ShapeRef {
dispatch: shape.clone(),
name: name.clone(),
slide_index,
});
}
return; }
let has_table = shape.get("HasTable")
.and_then(|v| v.as_i32().map_err(|e| e))
.unwrap_or(0);
if has_table != 0 {
if name.contains("_ccst") {
inventory.ccst_tables.push(ShapeRef {
dispatch: shape.clone(),
name: name.clone(),
slide_index,
});
inventory.count_ccst += 1;
}
if let Some(prefix) = matcher::classify_shape_name(&name) {
if let Some(table_type) = matcher::prefix_to_table_type(prefix) {
table_candidates.push(TableCandidate {
slide_index,
dispatch: shape.clone(),
table_type,
name: name.clone(),
});
match prefix {
ShapePrefix::NormalTable => inventory.count_ntbl += 1,
ShapePrefix::Heatmap => inventory.count_htmp += 1,
ShapePrefix::Transposed => inventory.count_trns += 1,
_ => {}
}
}
}
}
if name.contains("delt_") && shape_type != MsoShapeType::Group as i32 {
delt_candidates.push(DeltCandidate {
slide_index,
dispatch: shape.clone(),
name,
});
inventory.count_delt += 1;
}
}