use crate::error::{Result, TdbError};
use oxicode::Decode;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct PrefixCompressor {
prefixes: HashMap<u32, String>,
prefix_to_id: HashMap<String, u32>,
next_prefix_id: u32,
min_prefix_len: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CompressedString {
pub prefix_id: u32,
pub suffix: String,
}
impl PrefixCompressor {
pub fn new(min_prefix_len: usize) -> Self {
Self {
prefixes: HashMap::new(),
prefix_to_id: HashMap::new(),
next_prefix_id: 1,
min_prefix_len,
}
}
pub fn compress(&mut self, s: &str) -> Result<CompressedString> {
let mut best_prefix_id = 0;
let mut best_prefix_len = 0;
for (prefix, &prefix_id) in &self.prefix_to_id {
if s.starts_with(prefix) && prefix.len() > best_prefix_len {
best_prefix_id = prefix_id;
best_prefix_len = prefix.len();
}
}
if best_prefix_len > 0 {
Ok(CompressedString {
prefix_id: best_prefix_id,
suffix: s[best_prefix_len..].to_string(),
})
} else if s.len() >= self.min_prefix_len {
if let Some(prefix_end) = self.find_prefix_boundary(s) {
let prefix = &s[..prefix_end];
let prefix_id = self.add_prefix(prefix);
Ok(CompressedString {
prefix_id,
suffix: s[prefix_end..].to_string(),
})
} else {
Ok(CompressedString {
prefix_id: 0,
suffix: s.to_string(),
})
}
} else {
Ok(CompressedString {
prefix_id: 0,
suffix: s.to_string(),
})
}
}
pub fn decompress(&self, compressed: &CompressedString) -> Result<String> {
if compressed.prefix_id == 0 {
Ok(compressed.suffix.clone())
} else {
let prefix = self.prefixes.get(&compressed.prefix_id).ok_or_else(|| {
TdbError::Other(format!("Prefix {} not found", compressed.prefix_id))
})?;
Ok(format!("{}{}", prefix, compressed.suffix))
}
}
fn add_prefix(&mut self, prefix: &str) -> u32 {
if let Some(&id) = self.prefix_to_id.get(prefix) {
return id;
}
let id = self.next_prefix_id;
self.next_prefix_id += 1;
self.prefixes.insert(id, prefix.to_string());
self.prefix_to_id.insert(prefix.to_string(), id);
id
}
fn find_prefix_boundary(&self, s: &str) -> Option<usize> {
if let Some(pos) = s.rfind('#') {
return Some(pos + 1);
}
if let Some(pos) = s.rfind('/') {
if pos + 1 < s.len() {
return Some(pos + 1);
}
}
None
}
pub fn stats(&self) -> CompressionStats {
CompressionStats {
num_prefixes: self.prefixes.len(),
total_prefix_bytes: self.prefixes.values().map(|p| p.len()).sum(),
}
}
pub fn num_prefixes(&self) -> usize {
self.prefixes.len()
}
pub fn get_prefix(&self, prefix_id: u32) -> Option<&String> {
self.prefixes.get(&prefix_id)
}
}
impl Default for PrefixCompressor {
fn default() -> Self {
Self::new(10) }
}
#[derive(Debug, Clone, Copy)]
pub struct CompressionStats {
pub num_prefixes: usize,
pub total_prefix_bytes: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prefix_compression_basic() {
let mut compressor = PrefixCompressor::new(10);
let iri1 = "http://example.org/Person";
let iri2 = "http://example.org/name";
let c1 = compressor.compress(iri1).unwrap();
let c2 = compressor.compress(iri2).unwrap();
assert_eq!(c1.prefix_id, c2.prefix_id);
assert!(c1.prefix_id > 0);
assert_eq!(c1.suffix, "Person");
assert_eq!(c2.suffix, "name");
let d1 = compressor.decompress(&c1).unwrap();
let d2 = compressor.decompress(&c2).unwrap();
assert_eq!(d1, iri1);
assert_eq!(d2, iri2);
}
#[test]
fn test_prefix_compression_no_prefix() {
let mut compressor = PrefixCompressor::new(10);
let short_str = "abc";
let c = compressor.compress(short_str).unwrap();
assert_eq!(c.prefix_id, 0);
assert_eq!(c.suffix, "abc");
let d = compressor.decompress(&c).unwrap();
assert_eq!(d, short_str);
}
#[test]
fn test_prefix_compression_hash_iri() {
let mut compressor = PrefixCompressor::new(10);
let iri = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
let c = compressor.compress(iri).unwrap();
assert!(c.prefix_id > 0);
assert_eq!(c.suffix, "type");
let d = compressor.decompress(&c).unwrap();
assert_eq!(d, iri);
}
#[test]
fn test_prefix_compression_multiple_prefixes() {
let mut compressor = PrefixCompressor::new(10);
let iri1 = "http://example.org/Person";
let iri2 = "http://xmlns.com/foaf/0.1/name";
let c1 = compressor.compress(iri1).unwrap();
let c2 = compressor.compress(iri2).unwrap();
assert_ne!(c1.prefix_id, c2.prefix_id);
assert!(c1.prefix_id > 0);
assert!(c2.prefix_id > 0);
assert_eq!(compressor.num_prefixes(), 2);
}
#[test]
fn test_prefix_compression_stats() {
let mut compressor = PrefixCompressor::new(10);
compressor.compress("http://example.org/Person").unwrap();
compressor.compress("http://example.org/name").unwrap();
let stats = compressor.stats();
assert_eq!(stats.num_prefixes, 1);
assert!(stats.total_prefix_bytes > 0);
}
#[test]
fn test_prefix_reuse() {
let mut compressor = PrefixCompressor::new(10);
let iri1 = "http://example.org/Person";
let iri2 = "http://example.org/name";
let iri3 = "http://example.org/age";
let c1 = compressor.compress(iri1).unwrap();
let c2 = compressor.compress(iri2).unwrap();
let c3 = compressor.compress(iri3).unwrap();
assert_eq!(c1.prefix_id, c2.prefix_id);
assert_eq!(c2.prefix_id, c3.prefix_id);
assert_eq!(compressor.num_prefixes(), 1);
}
#[test]
fn test_prefix_get() {
let mut compressor = PrefixCompressor::new(10);
let iri = "http://example.org/Person";
let c = compressor.compress(iri).unwrap();
let prefix = compressor.get_prefix(c.prefix_id).unwrap();
assert_eq!(prefix, "http://example.org/");
}
#[test]
fn test_compression_serialization() {
let compressed = CompressedString {
prefix_id: 1,
suffix: "Person".to_string(),
};
let serialized =
oxicode::serde::encode_to_vec(&compressed, oxicode::config::standard()).unwrap();
let deserialized: CompressedString =
oxicode::serde::decode_from_slice(&serialized, oxicode::config::standard())
.unwrap()
.0;
assert_eq!(compressed, deserialized);
}
}