1mod css;
6mod emitter;
7mod markdown;
8mod sanitize;
9
10use std::collections::HashMap;
11
12use rdocx_oxml::document::CT_Document;
13use rdocx_oxml::numbering::CT_Numbering;
14use rdocx_oxml::styles::CT_Styles;
15
16#[derive(Debug, Clone)]
18pub struct HtmlOptions {
19 pub inline_images: bool,
21}
22
23impl Default for HtmlOptions {
24 fn default() -> Self {
25 Self {
26 inline_images: true,
27 }
28 }
29}
30
31pub struct HtmlInput {
33 pub document: CT_Document,
34 pub styles: CT_Styles,
35 pub numbering: Option<CT_Numbering>,
36 pub images: HashMap<String, ImageData>,
38 pub hyperlink_urls: HashMap<String, String>,
40}
41
42pub struct ImageData {
44 pub data: Vec<u8>,
45 pub content_type: String,
46}
47
48pub fn to_html_document(input: &HtmlInput, options: &HtmlOptions) -> String {
50 let body = to_html_fragment(input, options);
51 let css = css::generate_base_css();
52 format!(
53 "<!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>"
54 )
55}
56
57pub fn to_html_fragment(input: &HtmlInput, options: &HtmlOptions) -> String {
59 emitter::emit_body(
60 &input.document.body,
61 &input.styles,
62 input.numbering.as_ref(),
63 &input.images,
64 &input.hyperlink_urls,
65 options,
66 )
67}
68
69pub fn to_markdown(input: &HtmlInput) -> String {
71 markdown::emit_markdown(
72 &input.document.body,
73 &input.styles,
74 input.numbering.as_ref(),
75 &input.hyperlink_urls,
76 )
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use rdocx_oxml::document::{BodyContent, CT_Document};
83 use rdocx_oxml::styles::CT_Styles;
84 use rdocx_oxml::text::CT_P;
85
86 fn simple_input(text: &str) -> HtmlInput {
87 let mut doc = CT_Document::new();
88 let mut p = CT_P::new();
89 p.add_run(text);
90 doc.body.add_paragraph(p);
91
92 HtmlInput {
93 document: doc,
94 styles: CT_Styles::new_default(),
95 numbering: None,
96 images: HashMap::new(),
97 hyperlink_urls: HashMap::new(),
98 }
99 }
100
101 #[test]
102 fn html_document_basic() {
103 let input = simple_input("Hello, World!");
104 let html = to_html_document(&input, &HtmlOptions::default());
105 assert!(html.contains("<!DOCTYPE html>"));
106 assert!(html.contains("Hello, World!"));
107 assert!(html.contains("<p"));
108 }
109
110 #[test]
111 fn html_fragment_basic() {
112 let input = simple_input("Test paragraph");
113 let html = to_html_fragment(&input, &HtmlOptions::default());
114 assert!(html.contains("Test paragraph"));
115 assert!(html.contains("<p"));
116 assert!(!html.contains("<!DOCTYPE"));
117 }
118
119 #[test]
120 fn markdown_basic() {
121 let input = simple_input("Test paragraph");
122 let md = to_markdown(&input);
123 assert!(md.contains("Test paragraph"));
124 }
125
126 #[test]
127 fn html_heading() {
128 let mut doc = CT_Document::new();
129 let mut p = CT_P::new();
130 p.add_run("Chapter 1");
131 p.properties = Some(rdocx_oxml::properties::CT_PPr {
132 style_id: Some("Heading1".to_string()),
133 ..Default::default()
134 });
135 doc.body.add_paragraph(p);
136
137 let input = HtmlInput {
138 document: doc,
139 styles: CT_Styles::new_default(),
140 numbering: None,
141 images: HashMap::new(),
142 hyperlink_urls: HashMap::new(),
143 };
144
145 let html = to_html_fragment(&input, &HtmlOptions::default());
146 assert!(html.contains("<h1"));
147 assert!(html.contains("Chapter 1"));
148 }
149
150 #[test]
151 fn html_table() {
152 let mut doc = CT_Document::new();
153 let mut tbl = rdocx_oxml::table::CT_Tbl::new();
154 let mut row = rdocx_oxml::table::CT_Row::new();
155 let mut cell = rdocx_oxml::table::CT_Tc::new();
156 let mut p = CT_P::new();
157 p.add_run("Cell text");
158 cell.content = vec![rdocx_oxml::table::CellContent::Paragraph(p)];
159 row.cells.push(cell);
160 tbl.rows.push(row);
161 doc.body.content.push(BodyContent::Table(tbl));
162
163 let input = HtmlInput {
164 document: doc,
165 styles: CT_Styles::new_default(),
166 numbering: None,
167 images: HashMap::new(),
168 hyperlink_urls: HashMap::new(),
169 };
170
171 let html = to_html_fragment(&input, &HtmlOptions::default());
172 assert!(html.contains("<table"));
173 assert!(html.contains("<td"));
174 assert!(html.contains("Cell text"));
175 }
176
177 #[test]
178 fn markdown_heading() {
179 let mut doc = CT_Document::new();
180 let mut p = CT_P::new();
181 p.add_run("Title");
182 p.properties = Some(rdocx_oxml::properties::CT_PPr {
183 style_id: Some("Heading1".to_string()),
184 ..Default::default()
185 });
186 doc.body.add_paragraph(p);
187
188 let input = HtmlInput {
189 document: doc,
190 styles: CT_Styles::new_default(),
191 numbering: None,
192 images: HashMap::new(),
193 hyperlink_urls: HashMap::new(),
194 };
195
196 let md = to_markdown(&input);
197 assert!(md.contains("# Title"));
198 }
199
200 #[test]
201 fn html_bold_italic() {
202 let mut doc = CT_Document::new();
203 let mut p = CT_P::new();
204 let mut r = rdocx_oxml::text::CT_R::new("bold text");
205 r.properties = Some(rdocx_oxml::properties::CT_RPr {
206 bold: Some(true),
207 italic: Some(true),
208 ..Default::default()
209 });
210 p.runs.push(r);
211 doc.body.add_paragraph(p);
212
213 let input = HtmlInput {
214 document: doc,
215 styles: CT_Styles::new_default(),
216 numbering: None,
217 images: HashMap::new(),
218 hyperlink_urls: HashMap::new(),
219 };
220
221 let html = to_html_fragment(&input, &HtmlOptions::default());
222 assert!(html.contains("<strong"));
223 assert!(html.contains("<em"));
224 assert!(html.contains("bold text"));
225 }
226}