use crate::common;
#[derive(Debug, Clone)]
pub struct TocElement {
pub level: i32,
pub url: String,
pub title: String,
pub raw_title: Option<String>,
pub children: Vec<TocElement>,
}
impl TocElement {
pub fn new<S1: Into<String>, S2: Into<String>>(url: S1, title: S2) -> TocElement {
TocElement {
level: 1,
url: url.into(),
title: title.into(),
raw_title: None,
children: vec![],
}
}
pub fn raw_title<S: Into<String>>(mut self, title: S) -> TocElement {
self.raw_title = Option::Some(title.into());
self
}
pub fn level(mut self, level: i32) -> Self {
self.level = level;
self
}
fn level_up(&mut self, level: i32) {
self.level = level;
for child in &mut self.children {
if child.level <= self.level {
child.level_up(level + 1);
}
}
}
pub fn child(mut self, mut child: TocElement) -> Self {
if child.level <= self.level {
child.level_up(self.level + 1);
}
self.children.push(child);
self
}
pub fn add(&mut self, element: TocElement) {
let mut inserted = false;
if let Some(ref mut last_elem) = self.children.last_mut() {
if element.level > last_elem.level {
last_elem.add(element.clone());
inserted = true;
}
}
if !inserted {
self.children.push(element);
}
}
#[doc(hidden)]
pub fn render_epub(&self, mut offset: u32, escape_html: bool) -> (u32, String) {
offset += 1;
let id = offset;
let children = if self.children.is_empty() {
String::new()
} else {
let mut output: Vec<String> = Vec::new();
for child in &self.children {
let (n, s) = child.render_epub(offset, escape_html);
offset = n;
output.push(s);
}
format!("\n{}", common::indent(output.join("\n"), 1))
};
let mut title = html_escape::encode_text(&self.title);
if let Some(raw_title) = &self.raw_title {
title = std::borrow::Cow::Borrowed(raw_title);
}
(
offset,
format!(
"\
<navPoint playOrder=\"{id}\" id=\"navPoint-{id}\">
<navLabel>
<text>{title}</text>
</navLabel>
<content src=\"{url}\"/>{children}
</navPoint>",
id = html_escape::encode_double_quoted_attribute(&id.to_string()),
title = title.trim(),
url = html_escape::encode_double_quoted_attribute(&self.url),
children = children, ),
)
}
#[doc(hidden)]
pub fn render(&self, numbered: bool, escape_html: bool) -> String {
if self.title.is_empty() {
return String::new();
}
if self.children.is_empty() {
format!(
"<li><a href=\"{link}\">{title}</a></li>",
link = html_escape::encode_double_quoted_attribute(&self.url),
title = common::encode_html(&self.title, escape_html),
)
} else {
let mut output: Vec<String> = Vec::new();
for child in &self.children {
output.push(child.render(numbered, escape_html));
}
let children = format!(
"<{oul}>\n{children}\n</{oul}>",
oul = if numbered { "ol" } else { "ul" }, children = common::indent(output.join("\n"), 1), );
format!(
"\
<li>
<a href=\"{link}\">{title}</a>
{children}
</li>",
link = html_escape::encode_double_quoted_attribute(&self.url),
title = common::encode_html(&self.title, escape_html),
children = common::indent(children, 1), )
}
}
}
#[derive(Debug, Default)]
pub struct Toc {
pub elements: Vec<TocElement>,
}
impl Toc {
pub fn new() -> Toc {
Toc { elements: vec![] }
}
pub fn is_empty(&self) -> bool {
self.elements.len() <= 1
}
pub fn add(&mut self, element: TocElement) -> &mut Self {
let mut inserted = false;
if let Some(ref mut last_elem) = self.elements.last_mut() {
if element.level > last_elem.level {
last_elem.add(element.clone());
inserted = true;
}
}
if !inserted {
self.elements.push(element);
}
self
}
pub fn render_epub(&mut self, escape_html: bool) -> String {
let mut output: Vec<String> = Vec::new();
let mut offset = 0;
for elem in &self.elements {
let (n, s) = elem.render_epub(offset, escape_html);
offset = n;
output.push(s);
}
common::indent(output.join("\n"), 2)
}
pub fn render(&mut self, numbered: bool, escape_html: bool) -> String {
let mut output: Vec<String> = Vec::new();
for elem in &self.elements {
log::debug!("rendered elem: {:?}", &elem.render(numbered, escape_html));
output.push(elem.render(numbered, escape_html));
}
common::indent(
format!(
"<{oul}>\n{output}\n</{oul}>",
output = common::indent(output.join("\n"), 1), oul = if numbered { "ol" } else { "ul" } ),
2,
)
}
}
#[test]
fn toc_simple() {
let mut toc = Toc::new();
toc.add(TocElement::new("#1", "0.0.1").level(3));
toc.add(TocElement::new("#2", "1").level(1));
toc.add(TocElement::new("#3", "1.0.1").level(3));
toc.add(TocElement::new("#4", "1.1").level(2));
toc.add(TocElement::new("#5", "2"));
let actual = toc.render(false, true);
let expected = " <ul>
<li><a href=\"#1\">0.0.1</a></li>
<li>
<a href=\"#2\">1</a>
<ul>
<li><a href=\"#3\">1.0.1</a></li>
<li><a href=\"#4\">1.1</a></li>
</ul>
</li>
<li><a href=\"#5\">2</a></li>
</ul>";
assert_eq!(&actual, expected);
}
#[test]
fn toc_epub_simple() {
let mut toc = Toc::new();
toc.add(TocElement::new("#1", "1"));
toc.add(TocElement::new("#2", "2"));
let actual = toc.render_epub(true);
let expected = " <navPoint playOrder=\"1\" id=\"navPoint-1\">
<navLabel>
<text>1</text>
</navLabel>
<content src=\"#1\"/>
</navPoint>
<navPoint playOrder=\"2\" id=\"navPoint-2\">
<navLabel>
<text>2</text>
</navLabel>
<content src=\"#2\"/>
</navPoint>";
assert_eq!(&actual, expected);
}
#[test]
fn toc_epub_simple_sublevels() {
let mut toc = Toc::new();
toc.add(TocElement::new("#1", "1"));
toc.add(TocElement::new("#1.1", "1.1").level(2));
toc.add(TocElement::new("#2", "2"));
toc.add(TocElement::new("#2.1", "2.1").level(2));
let actual = toc.render_epub(true);
let expected = " <navPoint playOrder=\"1\" id=\"navPoint-1\">
<navLabel>
<text>1</text>
</navLabel>
<content src=\"#1\"/>
<navPoint playOrder=\"2\" id=\"navPoint-2\">
<navLabel>
<text>1.1</text>
</navLabel>
<content src=\"#1.1\"/>
</navPoint>
</navPoint>
<navPoint playOrder=\"3\" id=\"navPoint-3\">
<navLabel>
<text>2</text>
</navLabel>
<content src=\"#2\"/>
<navPoint playOrder=\"4\" id=\"navPoint-4\">
<navLabel>
<text>2.1</text>
</navLabel>
<content src=\"#2.1\"/>
</navPoint>
</navPoint>";
assert_eq!(&actual, expected);
}
#[test]
fn toc_epub_broken_sublevels() {
let mut toc = Toc::new();
toc.add(TocElement::new("#1.1", "1.1").level(2));
toc.add(TocElement::new("#2", "2"));
toc.add(TocElement::new("#2.1", "2.1").level(2));
let actual = toc.render_epub(true);
let expected = " <navPoint playOrder=\"1\" id=\"navPoint-1\">
<navLabel>
<text>1.1</text>
</navLabel>
<content src=\"#1.1\"/>
</navPoint>
<navPoint playOrder=\"2\" id=\"navPoint-2\">
<navLabel>
<text>2</text>
</navLabel>
<content src=\"#2\"/>
<navPoint playOrder=\"3\" id=\"navPoint-3\">
<navLabel>
<text>2.1</text>
</navLabel>
<content src=\"#2.1\"/>
</navPoint>
</navPoint>";
assert_eq!(&actual, expected);
}
#[test]
fn toc_epub_title_escaped() {
let mut toc = Toc::new();
toc.add(TocElement::new("#1", "D&D"));
let actual = toc.render_epub(true);
let expected = " <navPoint playOrder=\"1\" id=\"navPoint-1\">
<navLabel>
<text>D&D</text>
</navLabel>
<content src=\"#1\"/>
</navPoint>";
assert_eq!(&actual, expected);
}
#[test]
fn toc_epub_title_not_escaped() {
let mut toc = Toc::new();
toc.add(TocElement::new("#1", "<em>D&D<em>").raw_title("D&D"));
let actual = toc.render_epub(false);
let expected = " <navPoint playOrder=\"1\" id=\"navPoint-1\">
<navLabel>
<text>D&D</text>
</navLabel>
<content src=\"#1\"/>
</navPoint>";
assert_eq!(&actual, expected);
}