use rs_docx::formatting::{CharacterProperty, ParagraphProperty};
use rs_docx::styles::Style;
use std::collections::HashMap;
pub struct StyleResolver<'a> {
styles: &'a rs_docx::styles::Styles<'a>,
style_map: HashMap<&'a str, &'a Style<'a>>,
}
impl<'a> StyleResolver<'a> {
pub fn new(styles: &'a rs_docx::styles::Styles<'a>) -> Self {
let mut style_map = HashMap::new();
for style in &styles.styles {
style_map.insert(style.style_id.as_ref(), style);
}
Self { styles, style_map }
}
pub fn resolve_run_property(
&self,
direct_props: Option<&CharacterProperty<'a>>,
run_style_id: Option<&str>,
para_style_id: Option<&str>,
) -> CharacterProperty<'a> {
let mut merged = CharacterProperty::default();
if let Some(defaults) = &self.styles.default {
if let Some(r_pr) = &defaults.character.inner {
merge_char_props_mut(&mut merged, r_pr);
}
}
if let Some(pid) = para_style_id {
self.apply_style_chain_char(&mut merged, pid);
}
if let Some(rid) = run_style_id {
self.apply_style_chain_char(&mut merged, rid);
}
if let Some(direct) = direct_props {
merge_char_props_mut(&mut merged, direct);
}
merged
}
pub fn resolve_paragraph_property(
&self,
direct_props: Option<&ParagraphProperty<'a>>,
para_style_id: Option<&str>,
) -> ParagraphProperty<'a> {
let mut merged = ParagraphProperty::default();
if let Some(defaults) = &self.styles.default {
if let Some(p_pr) = &defaults.paragraph.inner {
merge_para_props_mut(&mut merged, p_pr);
}
}
if let Some(pid) = para_style_id {
self.apply_style_chain_para(&mut merged, pid);
}
if let Some(direct) = direct_props {
merge_para_props_mut(&mut merged, direct);
}
merged
}
fn apply_style_chain_char(&self, target: &mut CharacterProperty<'a>, style_id: &str) {
let mut chain = Vec::new();
let mut current_id = Some(style_id);
let mut visited = std::collections::HashSet::new();
while let Some(id) = current_id {
if !visited.insert(id) {
break; }
if let Some(style) = self.style_map.get(id) {
chain.push(style);
current_id = style.base.as_ref().map(|b| b.value.as_ref());
} else {
break;
}
}
for style in chain.into_iter().rev() {
if let Some(r_pr) = &style.character {
merge_char_props_mut(target, r_pr);
}
}
}
fn apply_style_chain_para(&self, target: &mut ParagraphProperty<'a>, style_id: &str) {
let mut chain = Vec::new();
let mut current_id = Some(style_id);
let mut visited = std::collections::HashSet::new();
while let Some(id) = current_id {
if !visited.insert(id) {
break; }
if let Some(style) = self.style_map.get(id) {
chain.push(style);
current_id = style.base.as_ref().map(|b| b.value.as_ref());
} else {
break;
}
}
for style in chain.into_iter().rev() {
if let Some(p_pr) = &style.paragraph {
merge_para_props_mut(target, p_pr);
}
}
}
}
fn merge_char_props_mut<'a>(target: &mut CharacterProperty<'a>, overlay: &CharacterProperty<'a>) {
if overlay.bold.is_some() {
target.bold = overlay.bold.clone();
}
if overlay.italics.is_some() {
target.italics = overlay.italics.clone();
}
if overlay.strike.is_some() {
target.strike = overlay.strike.clone();
}
if overlay.underline.is_some() {
target.underline = overlay.underline.clone();
}
if overlay.vertical_align.is_some() {
target.vertical_align = overlay.vertical_align.clone();
}
if overlay.fonts.is_some() {
target.fonts = overlay.fonts.clone();
}
}
fn merge_para_props_mut<'a>(target: &mut ParagraphProperty<'a>, overlay: &ParagraphProperty<'a>) {
if overlay.justification.is_some() {
target.justification = overlay.justification.clone();
}
if overlay.numbering.is_some() {
target.numbering = overlay.numbering.clone();
}
if overlay.style_id.is_some() {
target.style_id = overlay.style_id.clone();
}
}
#[cfg(test)]
mod tests {
use super::*;
use rs_docx::styles::{BasedOn, Style, StyleType, Styles};
fn make_cyclic_styles() -> Styles<'static> {
let mut style_a = Style::new(StyleType::Paragraph, "styleA");
style_a.base = Some(BasedOn {
value: std::borrow::Cow::Borrowed("styleB"),
});
let mut style_b = Style::new(StyleType::Paragraph, "styleB");
style_b.base = Some(BasedOn {
value: std::borrow::Cow::Borrowed("styleA"),
});
let mut styles = Styles::new();
styles.push(style_a);
styles.push(style_b);
styles
}
#[test]
fn test_style_chain_cycle_terminates() {
let styles = make_cyclic_styles();
let resolver = StyleResolver::new(&styles);
let _props = resolver.resolve_run_property(None, Some("styleA"), None);
}
#[test]
fn test_style_chain_cycle_para_terminates() {
let styles = make_cyclic_styles();
let resolver = StyleResolver::new(&styles);
let _props = resolver.resolve_paragraph_property(None, Some("styleA"));
}
}