use crate::ooxml::opc::error::{OpcError, Result};
use crate::ooxml::opc::packuri::PackURI;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Relationship {
r_id: String,
reltype: String,
target_ref: String,
base_uri: String,
is_external: bool,
}
impl Relationship {
pub fn new(
r_id: String,
reltype: String,
target_ref: String,
base_uri: String,
is_external: bool,
) -> Self {
Self {
r_id,
reltype,
target_ref,
base_uri,
is_external,
}
}
#[inline]
pub fn r_id(&self) -> &str {
&self.r_id
}
#[inline]
pub fn reltype(&self) -> &str {
&self.reltype
}
#[inline]
pub fn target_ref(&self) -> &str {
&self.target_ref
}
#[inline]
pub fn is_external(&self) -> bool {
self.is_external
}
pub fn target_partname(&self) -> Result<PackURI> {
if self.is_external {
return Err(OpcError::InvalidRelationship(
"Cannot get target_partname for external relationship".to_string(),
));
}
PackURI::from_rel_ref(&self.base_uri, &self.target_ref).map_err(OpcError::InvalidPackUri)
}
}
#[derive(Debug)]
pub struct Relationships {
base_uri: String,
rels: HashMap<String, Relationship>,
}
impl Relationships {
pub fn new(base_uri: String) -> Self {
Self {
base_uri,
rels: HashMap::new(),
}
}
pub fn add_relationship(
&mut self,
reltype: String,
target_ref: String,
r_id: String,
is_external: bool,
) -> &Relationship {
let rel = Relationship::new(
r_id.clone(),
reltype,
target_ref,
self.base_uri.clone(),
is_external,
);
self.rels.insert(r_id.clone(), rel);
self.rels.get(r_id.as_str()).unwrap()
}
#[inline]
pub fn get(&self, r_id: &str) -> Option<&Relationship> {
self.rels.get(r_id)
}
pub fn get_or_add(&mut self, reltype: &str, target_ref: &str) -> &Relationship {
for rel in self.rels.values() {
if rel.reltype() == reltype && rel.target_ref() == target_ref && !rel.is_external() {
let r_id = rel.r_id().to_string();
return self.rels.get(&r_id).unwrap();
}
}
let r_id = self.next_r_id();
self.add_relationship(reltype.to_string(), target_ref.to_string(), r_id, false)
}
pub fn get_or_add_ext_rel(&mut self, reltype: &str, target_ref: &str) -> String {
for rel in self.rels.values() {
if rel.reltype() == reltype && rel.target_ref() == target_ref && rel.is_external() {
return rel.r_id().to_string();
}
}
let r_id = self.next_r_id();
self.add_relationship(
reltype.to_string(),
target_ref.to_string(),
r_id.clone(),
true,
);
r_id
}
fn next_r_id(&self) -> String {
let mut used_numbers: Vec<u32> = self
.rels
.keys()
.filter_map(|r_id| {
if r_id.len() > 3 && &r_id[..3] == "rId" {
atoi_simd::parse::<u32>(&r_id.as_bytes()[3..]).ok()
} else {
None
}
})
.collect();
used_numbers.sort_unstable();
let mut next_num = 1u32;
for &num in &used_numbers {
match num.cmp(&next_num) {
std::cmp::Ordering::Equal => next_num += 1,
std::cmp::Ordering::Greater => break,
std::cmp::Ordering::Less => {}
}
}
format!("rId{}", next_num)
}
pub fn part_with_reltype(&self, reltype: &str) -> Result<&Relationship> {
let matching: Vec<&Relationship> = self
.rels
.values()
.filter(|rel| rel.reltype() == reltype)
.collect();
match matching.len() {
0 => Err(OpcError::RelationshipNotFound(format!(
"No relationship of type '{}'",
reltype
))),
1 => Ok(matching[0]),
_ => Err(OpcError::InvalidRelationship(format!(
"Multiple relationships of type '{}'",
reltype
))),
}
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &Relationship> {
self.rels.values()
}
#[inline]
pub fn len(&self) -> usize {
self.rels.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.rels.is_empty()
}
pub fn remove(&mut self, r_id: &str) -> Option<Relationship> {
self.rels.remove(r_id)
}
}
impl Default for Relationships {
fn default() -> Self {
Self::new("/".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_relationship_creation() {
let rel = Relationship::new(
"rId1".to_string(),
"http://example.com/rel".to_string(),
"target.xml".to_string(),
"/word".to_string(),
false,
);
assert_eq!(rel.r_id(), "rId1");
assert_eq!(rel.reltype(), "http://example.com/rel");
assert!(!rel.is_external());
}
#[test]
fn test_next_r_id() {
let mut rels = Relationships::new("/word".to_string());
let r_id1 = rels.next_r_id();
assert_eq!(r_id1, "rId1");
rels.add_relationship(
"type1".to_string(),
"target1".to_string(),
"rId1".to_string(),
false,
);
let r_id2 = rels.next_r_id();
assert_eq!(r_id2, "rId2");
}
#[test]
fn test_get_or_add() {
let mut rels = Relationships::new("/word".to_string());
let rel1 = rels.get_or_add("type1", "target1");
assert_eq!(rel1.r_id(), "rId1");
let rel2 = rels.get_or_add("type1", "target1");
assert_eq!(rel2.r_id(), "rId1");
let rel3 = rels.get_or_add("type1", "target2");
assert_eq!(rel3.r_id(), "rId2");
}
}