#![doc = include_str!("../README.md")]
mod css;
mod emitter;
mod markdown;
mod sanitize;
use std::collections::HashMap;
use rdocx_oxml::document::CT_Document;
use rdocx_oxml::numbering::CT_Numbering;
use rdocx_oxml::styles::CT_Styles;
#[derive(Debug, Clone)]
pub struct HtmlOptions {
pub inline_images: bool,
}
impl Default for HtmlOptions {
fn default() -> Self {
Self {
inline_images: true,
}
}
}
pub struct HtmlInput {
pub document: CT_Document,
pub styles: CT_Styles,
pub numbering: Option<CT_Numbering>,
pub images: HashMap<String, ImageData>,
pub hyperlink_urls: HashMap<String, String>,
}
pub struct ImageData {
pub data: Vec<u8>,
pub content_type: String,
}
pub fn to_html_document(input: &HtmlInput, options: &HtmlOptions) -> String {
let body = to_html_fragment(input, options);
let css = css::generate_base_css();
format!(
"<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style>\n{css}\n</style>\n</head>\n<body>\n{body}\n</body>\n</html>"
)
}
pub fn to_html_fragment(input: &HtmlInput, options: &HtmlOptions) -> String {
emitter::emit_body(
&input.document.body,
&input.styles,
input.numbering.as_ref(),
&input.images,
&input.hyperlink_urls,
options,
)
}
pub fn to_markdown(input: &HtmlInput) -> String {
markdown::emit_markdown(
&input.document.body,
&input.styles,
input.numbering.as_ref(),
&input.hyperlink_urls,
)
}
#[cfg(test)]
mod tests {
use super::*;
use rdocx_oxml::document::{BodyContent, CT_Document};
use rdocx_oxml::styles::CT_Styles;
use rdocx_oxml::text::CT_P;
fn simple_input(text: &str) -> HtmlInput {
let mut doc = CT_Document::new();
let mut p = CT_P::new();
p.add_run(text);
doc.body.add_paragraph(p);
HtmlInput {
document: doc,
styles: CT_Styles::new_default(),
numbering: None,
images: HashMap::new(),
hyperlink_urls: HashMap::new(),
}
}
#[test]
fn html_document_basic() {
let input = simple_input("Hello, World!");
let html = to_html_document(&input, &HtmlOptions::default());
assert!(html.contains("<!DOCTYPE html>"));
assert!(html.contains("Hello, World!"));
assert!(html.contains("<p"));
}
#[test]
fn html_fragment_basic() {
let input = simple_input("Test paragraph");
let html = to_html_fragment(&input, &HtmlOptions::default());
assert!(html.contains("Test paragraph"));
assert!(html.contains("<p"));
assert!(!html.contains("<!DOCTYPE"));
}
#[test]
fn markdown_basic() {
let input = simple_input("Test paragraph");
let md = to_markdown(&input);
assert!(md.contains("Test paragraph"));
}
#[test]
fn html_heading() {
let mut doc = CT_Document::new();
let mut p = CT_P::new();
p.add_run("Chapter 1");
p.properties = Some(rdocx_oxml::properties::CT_PPr {
style_id: Some("Heading1".to_string()),
..Default::default()
});
doc.body.add_paragraph(p);
let input = HtmlInput {
document: doc,
styles: CT_Styles::new_default(),
numbering: None,
images: HashMap::new(),
hyperlink_urls: HashMap::new(),
};
let html = to_html_fragment(&input, &HtmlOptions::default());
assert!(html.contains("<h1"));
assert!(html.contains("Chapter 1"));
}
#[test]
fn html_table() {
let mut doc = CT_Document::new();
let mut tbl = rdocx_oxml::table::CT_Tbl::new();
let mut row = rdocx_oxml::table::CT_Row::new();
let mut cell = rdocx_oxml::table::CT_Tc::new();
let mut p = CT_P::new();
p.add_run("Cell text");
cell.content = vec![rdocx_oxml::table::CellContent::Paragraph(p)];
row.cells.push(cell);
tbl.rows.push(row);
doc.body.content.push(BodyContent::Table(tbl));
let input = HtmlInput {
document: doc,
styles: CT_Styles::new_default(),
numbering: None,
images: HashMap::new(),
hyperlink_urls: HashMap::new(),
};
let html = to_html_fragment(&input, &HtmlOptions::default());
assert!(html.contains("<table"));
assert!(html.contains("<td"));
assert!(html.contains("Cell text"));
}
#[test]
fn markdown_heading() {
let mut doc = CT_Document::new();
let mut p = CT_P::new();
p.add_run("Title");
p.properties = Some(rdocx_oxml::properties::CT_PPr {
style_id: Some("Heading1".to_string()),
..Default::default()
});
doc.body.add_paragraph(p);
let input = HtmlInput {
document: doc,
styles: CT_Styles::new_default(),
numbering: None,
images: HashMap::new(),
hyperlink_urls: HashMap::new(),
};
let md = to_markdown(&input);
assert!(md.contains("# Title"));
}
#[test]
fn html_bold_italic() {
let mut doc = CT_Document::new();
let mut p = CT_P::new();
let mut r = rdocx_oxml::text::CT_R::new("bold text");
r.properties = Some(rdocx_oxml::properties::CT_RPr {
bold: Some(true),
italic: Some(true),
..Default::default()
});
p.runs.push(r);
doc.body.add_paragraph(p);
let input = HtmlInput {
document: doc,
styles: CT_Styles::new_default(),
numbering: None,
images: HashMap::new(),
hyperlink_urls: HashMap::new(),
};
let html = to_html_fragment(&input, &HtmlOptions::default());
assert!(html.contains("<strong"));
assert!(html.contains("<em"));
assert!(html.contains("bold text"));
}
}