use std::{collections::HashMap, str::FromStr};
use once_cell::sync::Lazy;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Application {
Pages,
Keynote,
Numbers,
Common,
}
impl FromStr for Application {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"pages" => Ok(Self::Pages),
"keynote" => Ok(Self::Keynote),
"numbers" => Ok(Self::Numbers),
"common" => Ok(Self::Common),
_ => Err("Invalid input")
}
}
}
#[derive(Debug, Clone)]
pub struct MessageType {
pub name: &'static str,
pub application: Application,
}
pub struct MessageRegistry {
types: HashMap<u32, MessageType>,
}
impl Default for MessageRegistry {
fn default() -> Self {
Self::new()
}
}
impl MessageRegistry {
pub fn new() -> Self {
Self {
types: HashMap::new(),
}
}
pub fn register(&mut self, id: u32, name: &'static str, application: Application) {
self.types.insert(id, MessageType { name, application });
}
pub fn lookup(&self, id: u32) -> Option<&MessageType> {
self.types.get(&id)
}
pub fn types_for_application(&self, app: Application) -> Vec<(u32, &MessageType)> {
self.types.iter()
.filter(|(_, mt)| mt.application == app)
.map(|(id, mt)| (*id, mt))
.collect()
}
}
pub static MESSAGE_REGISTRY: Lazy<MessageRegistry> = Lazy::new(|| {
let mut registry = MessageRegistry::new();
register_common_types(&mut registry);
register_keynote_types(&mut registry);
register_numbers_types(&mut registry);
register_pages_types(&mut registry);
register_shared_types(&mut registry);
registry
});
fn register_common_types(registry: &mut MessageRegistry) {
registry.register(1, "TSP.ArchiveInfo", Application::Common);
registry.register(2, "TSP.MessageInfo", Application::Common);
registry.register(10, "TSP.DatabaseData", Application::Common);
registry.register(100, "TSP.DocumentMetadata", Application::Common);
registry.register(110, "TSP.ObjectReference", Application::Common);
registry.register(200, "TSP.DataReference", Application::Common);
}
fn register_keynote_types(registry: &mut MessageRegistry) {
registry.register(100, "KN.ArchiveInfo", Application::Keynote);
registry.register(101, "KN.ShowArchive", Application::Keynote);
registry.register(102, "KN.SlideArchive", Application::Keynote);
registry.register(103, "KN.SlideNodeArchive", Application::Keynote);
registry.register(104, "KN.PlaceholderArchive", Application::Keynote);
registry.register(105, "KN.MasterSlideArchive", Application::Keynote);
registry.register(106, "KN.ThemeArchive", Application::Keynote);
registry.register(107, "KN.SlideStyleArchive", Application::Keynote);
registry.register(148, "KN.CommandSlideReapplyMasterArchive", Application::Keynote);
registry.register(147, "KN.SlideCollectionCommandSelectionBehaviorArchive", Application::Keynote);
registry.register(146, "KN.CommandSlideReapplyMasterArchive", Application::Keynote);
registry.register(145, "KN.CommandMasterSetBodyStylesArchive", Application::Keynote);
registry.register(200, "KN.PresentationArchive", Application::Keynote);
registry.register(201, "KN.SlideTreeArchive", Application::Keynote);
registry.register(202, "KN.BuildArchive", Application::Keynote);
registry.register(203, "KN.TransitionArchive", Application::Keynote);
}
fn register_numbers_types(registry: &mut MessageRegistry) {
registry.register(1, "TN.SheetArchive", Application::Numbers);
registry.register(2, "TN.TableArchive", Application::Numbers);
registry.register(3, "TN.CellArchive", Application::Numbers);
registry.register(4, "TN.FormulaArchive", Application::Numbers);
registry.register(5, "TN.ChartArchive", Application::Numbers);
registry.register(6, "TN.DocumentArchive", Application::Numbers);
registry.register(7, "TN.WorkbookArchive", Application::Numbers);
registry.register(100, "TN.CommandSetTableDataArchive", Application::Numbers);
registry.register(101, "TN.CommandSetCellValueArchive", Application::Numbers);
registry.register(102, "TN.CommandAddTableArchive", Application::Numbers);
registry.register(103, "TN.CommandRemoveTableArchive", Application::Numbers);
}
fn register_pages_types(registry: &mut MessageRegistry) {
registry.register(1, "TP.DocumentArchive", Application::Pages);
registry.register(2, "TP.SectionArchive", Application::Pages);
registry.register(3, "TP.PageArchive", Application::Pages);
registry.register(4, "TP.TextArchive", Application::Pages);
registry.register(5, "TP.ParagraphArchive", Application::Pages);
registry.register(6, "TP.CharacterArchive", Application::Pages);
registry.register(7, "TP.ImageArchive", Application::Pages);
registry.register(100, "TP.CommandSetTextArchive", Application::Pages);
registry.register(101, "TP.CommandInsertTextArchive", Application::Pages);
registry.register(102, "TP.CommandDeleteTextArchive", Application::Pages);
registry.register(103, "TP.CommandSetStyleArchive", Application::Pages);
}
fn register_shared_types(registry: &mut MessageRegistry) {
registry.register(1, "TSA.StyleArchive", Application::Common);
registry.register(2, "TSA.ParagraphStyleArchive", Application::Common);
registry.register(3, "TSA.CharacterStyleArchive", Application::Common);
registry.register(4, "TSA.ListStyleArchive", Application::Common);
registry.register(1, "TSD.DrawingArchive", Application::Common);
registry.register(2, "TSD.ShapeArchive", Application::Common);
registry.register(3, "TSD.ImageArchive", Application::Common);
registry.register(4, "TSD.GroupArchive", Application::Common);
registry.register(1, "TSCH.ChartArchive", Application::Common);
registry.register(2, "TSCH.ChartSeriesArchive", Application::Common);
registry.register(3, "TSCH.ChartAxisArchive", Application::Common);
registry.register(4, "TSCH.ChartLegendArchive", Application::Common);
registry.register(1, "TSK.DocumentArchive", Application::Common);
registry.register(2, "TSK.TaskArchive", Application::Common);
registry.register(1, "TSS.StyleSheetArchive", Application::Common);
registry.register(2, "TSS.StylesArchive", Application::Common);
registry.register(1, "TST.TableArchive", Application::Common);
registry.register(2, "TST.TableCellArchive", Application::Common);
registry.register(3, "TST.TableRowArchive", Application::Common);
registry.register(4, "TST.TableColumnArchive", Application::Common);
registry.register(1, "TSWP.DocumentArchive", Application::Pages);
registry.register(2, "TSWP.SectionArchive", Application::Pages);
registry.register(3, "TSWP.ParagraphArchive", Application::Pages);
registry.register(4, "TSWP.CharacterArchive", Application::Pages);
registry.register(5, "TSWP.TextArchive", Application::Pages);
}
pub fn get_message_type(id: u32) -> Option<&'static MessageType> {
MESSAGE_REGISTRY.lookup(id)
}
pub fn get_message_types_for_app(app: Application) -> Vec<(u32, &'static MessageType)> {
MESSAGE_REGISTRY.types_for_application(app)
}
pub fn detect_application(message_type_ids: &[u32]) -> Option<Application> {
let mut app_counts = std::collections::HashMap::new();
for &id in message_type_ids {
if let Some(msg_type) = get_message_type(id) {
*app_counts.entry(msg_type.application).or_insert(0) += 1;
}
}
app_counts.into_iter()
.max_by_key(|&(_, count)| count)
.map(|(app, _)| app)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_type_lookup() {
let archive_info = get_message_type(1);
assert!(archive_info.is_some());
let kn_show = get_message_type(101);
assert!(kn_show.is_some());
assert!(get_message_type(1).is_some());
assert!(get_message_type(999).is_none()); }
#[test]
fn test_application_detection() {
let keynote_ids = vec![101, 102, 103]; let keynote_result = detect_application(&keynote_ids);
assert!(keynote_result.is_some());
let common_ids = vec![1, 2, 3]; let common_result = detect_application(&common_ids);
assert!(common_result.is_some());
assert_eq!(detect_application(&[]), None);
}
#[test]
fn test_application_from_string() {
assert_eq!(Application::from_str("pages"), Ok(Application::Pages));
assert_eq!(Application::from_str("Pages"), Ok(Application::Pages));
assert_eq!(Application::from_str("keynote"), Ok(Application::Keynote));
assert_eq!(Application::from_str("numbers"), Ok(Application::Numbers));
assert_eq!(Application::from_str("unknown"), Err("Invalid input"));
}
}