pub use crate::enums::action::PpActionType;
use crate::units::RelationshipId;
use crate::xml_util::xml_escape;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Hyperlink {
pub address: Option<String>,
pub tooltip: Option<String>,
pub r_id: Option<RelationshipId>,
}
impl Hyperlink {
#[must_use]
pub fn new(url: &str) -> Self {
Self {
address: Some(url.to_string()),
tooltip: None,
r_id: None,
}
}
#[must_use]
pub fn with_tooltip(url: &str, tooltip: &str) -> Self {
Self {
address: Some(url.to_string()),
tooltip: Some(tooltip.to_string()),
r_id: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActionSetting {
pub action: PpActionType,
pub hyperlink: Option<Hyperlink>,
pub target_slide: Option<String>,
pub target_slide_r_id: Option<RelationshipId>,
}
impl ActionSetting {
#[must_use]
pub fn hyperlink(url: &str) -> Self {
Self {
action: PpActionType::Hyperlink,
hyperlink: Some(Hyperlink::new(url)),
target_slide: None,
target_slide_r_id: None,
}
}
#[must_use]
pub fn hyperlink_with_tooltip(url: &str, tooltip: &str) -> Self {
Self {
action: PpActionType::Hyperlink,
hyperlink: Some(Hyperlink::with_tooltip(url, tooltip)),
target_slide: None,
target_slide_r_id: None,
}
}
#[must_use]
pub const fn next_slide() -> Self {
Self {
action: PpActionType::NextSlide,
hyperlink: None,
target_slide: None,
target_slide_r_id: None,
}
}
#[must_use]
pub const fn previous_slide() -> Self {
Self {
action: PpActionType::PreviousSlide,
hyperlink: None,
target_slide: None,
target_slide_r_id: None,
}
}
#[must_use]
pub fn named_slide(r_id: &RelationshipId) -> Self {
Self {
action: PpActionType::NamedSlide,
hyperlink: None,
target_slide: None,
target_slide_r_id: Some(r_id.clone()),
}
}
pub fn write_xml<W: std::fmt::Write>(&self, w: &mut W, r_id: Option<&str>) -> std::fmt::Result {
self.write_xml_element(w, "a:hlinkClick", r_id)
}
pub fn write_hover_xml<W: std::fmt::Write>(
&self,
w: &mut W,
r_id: Option<&str>,
) -> std::fmt::Result {
self.write_xml_element(w, "a:hlinkHover", r_id)
}
#[must_use]
pub fn to_xml_string(&self, r_id: Option<&str>) -> String {
let mut s = String::new();
self.write_xml(&mut s, r_id)
.unwrap_or_else(|_| unreachable!("write to String should not fail"));
s
}
#[must_use]
pub fn to_hover_xml_string(&self, r_id: Option<&str>) -> String {
let mut s = String::new();
self.write_hover_xml(&mut s, r_id)
.unwrap_or_else(|_| unreachable!("write to String should not fail"));
s
}
fn write_xml_element<W: std::fmt::Write>(
&self,
w: &mut W,
element_name: &str,
r_id: Option<&str>,
) -> std::fmt::Result {
write!(w, "<{element_name}")?;
match self.action {
PpActionType::Hyperlink => {
if let Some(rid) = r_id {
write!(w, r#" r:id="{}""#, xml_escape(rid))?;
} else if let Some(ref hlink) = self.hyperlink {
if let Some(ref rid) = hlink.r_id {
write!(w, r#" r:id="{}""#, xml_escape(rid.as_str()))?;
}
}
}
PpActionType::NamedSlide => {
if let Some(ref rid) = self.target_slide_r_id {
write!(w, r#" r:id="{}""#, xml_escape(rid.as_str()))?;
} else if let Some(rid) = r_id {
write!(w, r#" r:id="{}""#, xml_escape(rid))?;
}
w.write_str(r#" action="ppaction://hlinksldjump""#)?;
}
PpActionType::NextSlide => {
w.write_str(r#" action="ppaction://hlinkshowjump?jump=nextslide""#)?;
}
PpActionType::PreviousSlide => {
w.write_str(r#" action="ppaction://hlinkshowjump?jump=previousslide""#)?;
}
PpActionType::FirstSlide => {
w.write_str(r#" action="ppaction://hlinkshowjump?jump=firstslide""#)?;
}
PpActionType::LastSlide => {
w.write_str(r#" action="ppaction://hlinkshowjump?jump=lastslide""#)?;
}
PpActionType::EndShow => {
w.write_str(r#" action="ppaction://hlinkshowjump?jump=endshow""#)?;
}
_ => {}
}
if let Some(ref hlink) = self.hyperlink {
if let Some(ref tooltip) = hlink.tooltip {
write!(w, r#" tooltip="{}""#, xml_escape(tooltip))?;
}
}
w.write_str("/>")?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hyperlink_action_xml() {
let action = ActionSetting::hyperlink("https://example.com");
let xml = action.to_xml_string(Some("rId2"));
assert!(xml.contains(r#"r:id="rId2""#));
assert!(xml.starts_with("<a:hlinkClick"));
assert!(xml.ends_with("/>"));
}
#[test]
fn test_hyperlink_with_tooltip_xml() {
let action = ActionSetting::hyperlink_with_tooltip("https://example.com", "Click me");
let xml = action.to_xml_string(Some("rId3"));
assert!(xml.contains(r#"r:id="rId3""#));
assert!(xml.contains(r#"tooltip="Click me""#));
}
#[test]
fn test_next_slide_action_xml() {
let action = ActionSetting::next_slide();
let xml = action.to_xml_string(None);
assert!(xml.contains("ppaction://hlinkshowjump?jump=nextslide"));
}
#[test]
fn test_previous_slide_action_xml() {
let action = ActionSetting::previous_slide();
let xml = action.to_xml_string(None);
assert!(xml.contains("ppaction://hlinkshowjump?jump=previousslide"));
}
#[test]
fn test_tooltip_escaping() {
let action = ActionSetting::hyperlink_with_tooltip("https://example.com", "A & B <test>");
let xml = action.to_xml_string(Some("rId1"));
assert!(xml.contains(r#"tooltip="A & B <test>""#));
}
#[test]
fn test_named_slide_action_xml() {
let r_id = RelationshipId::try_from("rId5").unwrap();
let action = ActionSetting::named_slide(&r_id);
let xml = action.to_xml_string(None);
assert!(xml.contains(r#"r:id="rId5""#));
assert!(xml.contains(r#"action="ppaction://hlinksldjump""#));
}
#[test]
fn test_hover_action_xml() {
let action = ActionSetting::next_slide();
let xml = action.to_hover_xml_string(None);
assert!(xml.starts_with("<a:hlinkHover"));
assert!(xml.contains("nextslide"));
}
#[test]
fn test_hover_hyperlink_xml() {
let action = ActionSetting::hyperlink("https://example.com");
let xml = action.to_hover_xml_string(Some("rId3"));
assert!(xml.starts_with("<a:hlinkHover"));
assert!(xml.contains(r#"r:id="rId3""#));
}
}