-
namespace odr::internal {
-void html::translate_children(ElementRange range, std::ostream &out,
+void html::translate_children(ElementRange range, HtmlWriter &out,
const HtmlConfig &config) {
for (auto child : range) {
translate_element(child, out, config);
}
}
-void html::translate_element(Element element, std::ostream &out,
+void html::translate_element(Element element, HtmlWriter &out,
const HtmlConfig &config) {
if (element.type() == ElementType::text) {
translate_text(element, out, config);
@@ -60,32 +58,32 @@ void html::translate_element(Element element, std::ostream &out,
}
}
-void html::translate_slide(Element element, std::ostream &out,
+void html::translate_slide(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto slide = element.slide();
- out << "";
- out << "
";
+ out.write_element_begin(
+ "div",
+ {.style = translate_outer_page_style(slide.page_layout()).c_str()});
+ out.write_element_begin(
+ "div",
+ {.style = translate_inner_page_style(slide.page_layout()).c_str()});
+
translate_master_page(slide.master_page(), out, config);
translate_children(slide.children(), out, config);
- out << "
";
- out << "
";
+
+ out.write_element_end("div");
+ out.write_element_end("div");
}
-void html::translate_sheet(Element element, std::ostream &out,
+void html::translate_sheet(Element element, HtmlWriter &out,
const HtmlConfig &config) {
// TODO table column width does not work
// TODO table row height does not work
- out << "";
+ out.write_element_begin("table", {.attributes = {{"cellpadding", "0"},
+ {"border", "0"},
+ {"cellspacing", "0"}}});
auto sheet = element.sheet();
auto dimensions = sheet.dimensions();
@@ -103,31 +101,35 @@ void html::translate_sheet(Element element, std::ostream &out,
end_column = std::max(1u, end_column);
end_row = std::max(1u, end_row);
- out << " ";
+ out.write_element_begin("col", {.close_type = HtmlCloseType::none});
for (std::uint32_t column_index = 0; column_index < end_column;
++column_index) {
auto table_column = sheet.column(column_index);
auto table_column_style = table_column.style();
- out << " ";
+ out.write_element_begin(
+ "col",
+ {.close_type = HtmlCloseType::none,
+ .style = translate_table_column_style(table_column_style).c_str()});
}
{
- out << "";
- out << "";
+ out.write_element_begin("tr");
+
+ out.write_element_begin("td", {.close_type = HtmlCloseType::trailing,
+ .style = "width:30px;height:20px;"});
for (std::uint32_t column_index = 0; column_index < end_column;
++column_index) {
- out << " ";
- out << common::TablePosition::to_column_string(column_index);
- out << " ";
+ out.write_element_begin(
+ "td", {.inline_element = true,
+ .style = "text-align:center;vertical-align:middle;"});
+ out.out() << common::TablePosition::to_column_string(column_index);
+ out.write_element_end("td");
}
- out << " ";
+ out.write_element_end("tr");
}
common::TableCursor cursor;
@@ -136,18 +138,18 @@ void html::translate_sheet(Element element, std::ostream &out,
auto table_row = sheet.row(row_index);
auto table_row_style = table_row.style();
- out << "";
+ out.write_element_begin(
+ "tr", {.style = translate_table_row_style(table_row_style).c_str()});
- out << "to_string() << ";";
- out << "max-height:" << height->to_string() << ";";
+ td_style += "height:" + height->to_string() + ";";
+ td_style += "max-height:" + height->to_string() + ";";
}
- out << "\">";
- out << common::TablePosition::to_row_string(row_index);
- out << " ";
+ out.write_element_begin(
+ "td", {.inline_element = true, .style = td_style.c_str()});
+ out.out() << common::TablePosition::to_row_string(row_index);
+ out.write_element_end("td");
for (std::uint32_t column_index = cursor.column();
column_index < end_column; column_index = cursor.column()) {
@@ -164,56 +166,56 @@ void html::translate_sheet(Element element, std::ostream &out,
auto cell_value_type = cell.value_type();
auto cell_elements = cell.children();
- out << " 1) {
- out << " colspan=\"" << cell_span.columns << "\"";
+ td_attributes.emplace_back("colspan",
+ std::to_string(cell_span.columns));
}
if (cell_span.rows > 1) {
- out << " rowspan=\"" << cell_span.rows << "\"";
+ td_attributes.emplace_back("rowspan", std::to_string(cell_span.rows));
}
- out << optional_style_attribute(translate_table_cell_style(cell_style));
+ std::string td_class;
if (cell_value_type == ValueType::float_number) {
- out << " class=\"odr-value-type-float\"";
+ td_class = "odr-value-type-float";
}
- out << ">";
+ out.write_element_begin("td",
+ {.attributes = td_attributes,
+ .style = translate_table_cell_style(cell_style),
+ .clazz = td_class});
if ((column_index == 0) && (row_index == 0)) {
for (auto shape : sheet.shapes()) {
translate_element(shape, out, config);
}
}
translate_children(cell_elements, out, config);
- out << " ";
+ out.write_element_end("td");
cursor.add_cell(cell_span.columns, cell_span.rows);
}
- out << " ";
+ out.write_element_end("tr");
cursor.add_row();
}
- out << "
";
+ out.write_element_end("table");
}
-void html::translate_page(Element element, std::ostream &out,
+void html::translate_page(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto page = element.page();
- out << "";
- out << "
";
+ out.write_element_begin(
+ "div", {.style = translate_outer_page_style(page.page_layout())});
+ out.write_element_begin(
+ "div", {.style = translate_inner_page_style(page.page_layout())});
translate_master_page(page.master_page(), out, config);
translate_children(page.children(), out, config);
- out << "
";
- out << "
";
+ out.write_element_end("div");
+ out.write_element_end("div");
}
-void html::translate_master_page(MasterPage element, std::ostream &out,
+void html::translate_master_page(MasterPage element, HtmlWriter &out,
const HtmlConfig &config) {
for (auto child : element.children()) {
// TODO filter placeholders
@@ -221,40 +223,40 @@ void html::translate_master_page(MasterPage element, std::ostream &out,
}
}
-void html::translate_text(const Element element, std::ostream &out,
+void html::translate_text(const Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto text = element.text();
- out << "";
- out << internal::html::escape_text(text.content());
- out << " ";
+ out.write_element_begin("x-s", {.inline_element = true,
+ .style = translate_text_style(text.style())});
+ out.out() << internal::html::escape_text(text.content());
+ out.write_element_end("x-s");
}
-void html::translate_line_break(Element element, std::ostream &out,
+void html::translate_line_break(Element element, HtmlWriter &out,
const HtmlConfig &) {
auto line_break = element.line_break();
- out << " ";
- out << " ";
+ out.write_element_begin("br", {.close_type = HtmlCloseType::none});
+ out.write_element_begin("x-s",
+ {.inline_element = true,
+ .style = translate_text_style(line_break.style())});
+ out.write_element_end("x-s");
}
-void html::translate_paragraph(Element element, std::ostream &out,
+void html::translate_paragraph(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto paragraph = element.paragraph();
- out << "";
+ out.write_element_begin(
+ "x-p", {.style = "display:block;" +
+ translate_paragraph_style(paragraph.style())});
translate_children(paragraph.children(), out, config);
if (paragraph.first_child()) {
// TODO if element is content (e.g. bookmark does not count)
@@ -264,90 +266,86 @@ void html::translate_paragraph(Element element, std::ostream &out,
// TODO example `style-missing+image-1.odt` first paragraph has no height
} else {
- auto text_style_attribute =
- optional_style_attribute(translate_text_style(paragraph.text_style()));
- out << " ";
+ out.write_element_begin(
+ "x-s", {.inline_element = true,
+ .style = translate_text_style(paragraph.text_style())});
+ out.write_element_end("x-s");
}
- out << "";
- out << " ";
+ out.write_element_begin("wbr", {.close_type = HtmlCloseType::none});
+ out.write_element_end("x-p");
}
-void html::translate_span(Element element, std::ostream &out,
+void html::translate_span(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto span = element.span();
- out << "";
+ out.write_element_begin("x-s", {.inline_element = true,
+ .style = translate_text_style(span.style())});
translate_children(span.children(), out, config);
- out << " ";
+ out.write_element_end("x-s");
}
-void html::translate_link(Element element, std::ostream &out,
+void html::translate_link(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto link = element.link();
- out << "";
+ out.write_element_begin(
+ "a", {.inline_element = true, .attributes = {{"href", link.href()}}});
translate_children(link.children(), out, config);
- out << " ";
+ out.write_element_end("a");
}
-void html::translate_bookmark(Element element, std::ostream &out,
+void html::translate_bookmark(Element element, HtmlWriter &out,
const HtmlConfig & /*config*/) {
auto bookmark = element.bookmark();
- out << " ";
+ out.write_element_begin(
+ "a", {.inline_element = true, .attributes = {{"id", bookmark.name()}}});
+ out.write_element_end("a");
}
-void html::translate_list(Element element, std::ostream &out,
+void html::translate_list(Element element, HtmlWriter &out,
const HtmlConfig &config) {
- out << "";
+ out.write_element_begin("ul");
translate_children(element.children(), out, config);
- out << " ";
+ out.write_element_end("ul");
}
-void html::translate_list_item(Element element, std::ostream &out,
+void html::translate_list_item(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto list_item = element.list_item();
- out << "";
+ out.write_element_begin("ul",
+ {.style = translate_text_style(list_item.style())});
translate_children(list_item.children(), out, config);
- out << " ";
+ out.write_element_end("ul");
}
-void html::translate_table(Element element, std::ostream &out,
+void html::translate_table(Element element, HtmlWriter &out,
const HtmlConfig &config) {
// TODO table column width does not work
// TODO table row height does not work
auto table = element.table();
- out << "";
+ out.write_element_begin("table",
+ {.attributes = {{"cellpadding", "0"},
+ {"border", "0"},
+ {"cellspacing", "0"}},
+ .style = translate_table_style(table.style())});
for (auto column : table.columns()) {
auto table_column = column.table_column();
- out << " ";
+ out.write_element_begin(
+ "col", {.close_type = HtmlCloseType::none,
+ .style = translate_table_column_style(table_column.style())});
}
for (auto row : table.rows()) {
auto table_row = row.table_row();
- out << "";
+ out.write_element_begin(
+ "tr", {.style = translate_table_row_style(table_row.style())});
for (auto cell : table_row.children()) {
auto table_cell = cell.table_cell();
@@ -355,112 +353,122 @@ void html::translate_table(Element element, std::ostream &out,
if (!table_cell.is_covered()) {
auto cell_span = table_cell.span();
- out << " 1) {
- out << " rowspan=\"" << cell_span.rows << "\"";
- }
+ HtmlAttributes td_attributes;
if (cell_span.columns > 1) {
- out << " colspan=\"" << cell_span.columns << "\"";
+ td_attributes.emplace_back("colspan",
+ std::to_string(cell_span.columns));
}
- out << optional_style_attribute(
- translate_table_cell_style(table_cell.style()));
- out << ">";
+ if (cell_span.rows > 1) {
+ td_attributes.emplace_back("rowspan", std::to_string(cell_span.rows));
+ }
+ out.write_element_begin(
+ "td", {.attributes = td_attributes,
+ .style = translate_table_cell_style(table_cell.style())});
+
translate_children(cell.children(), out, config);
- out << " ";
+
+ out.write_element_end("td");
}
}
- out << " ";
+ out.write_element_end("tr");
}
- out << "
";
+ out.write_element_end("table");
}
-void html::translate_image(Element element, std::ostream &out,
+void html::translate_image(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto image = element.image();
- out << " ";
+ out.out() << "\">";
}
-void html::translate_frame(Element element, std::ostream &out,
+void html::translate_frame(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto frame = element.frame();
auto style = frame.style();
- out << "";
+ out.write_element_begin("div", {.style = translate_frame_properties(frame) +
+ translate_drawing_style(style)});
translate_children(frame.children(), out, config);
- out << "
";
+ out.write_element_end("div");
}
-void html::translate_rect(Element element, std::ostream &out,
+void html::translate_rect(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto rect = element.rect();
+ auto style = rect.style();
- out << "";
+ out.write_element_begin("div", {.style = translate_rect_properties(rect) +
+ translate_drawing_style(style)});
translate_children(rect.children(), out, config);
- out << R"( )";
- out << "
";
+ out.write_new_line();
+ out.out()
+ << R"( )";
+ out.write_element_end("div");
}
-void html::translate_line(Element element, std::ostream &out,
+void html::translate_line(Element element, HtmlWriter &out,
const HtmlConfig & /*config*/) {
auto line = element.line();
-
- out << R"(";
-
- out << " ";
-
- out << " ";
+ auto style = line.style();
+
+ out.write_new_line();
+ out.out()
+ << R"(";
+
+ out.write_new_line();
+ out.out() << " ";
+
+ out.write_new_line();
+ out.out() << " ";
}
-void html::translate_circle(Element element, std::ostream &out,
+void html::translate_circle(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto circle = element.circle();
+ auto style = circle.style();
- out << "";
+ out.write_element_begin("div", {.style = translate_circle_properties(circle) +
+ translate_drawing_style(style)});
+ out.write_new_line();
translate_children(circle.children(), out, config);
- out << R"( )";
- out << "
";
+ out.out()
+ << R"( )";
+ out.write_element_end("div");
}
-void html::translate_custom_shape(Element element, std::ostream &out,
+void html::translate_custom_shape(Element element, HtmlWriter &out,
const HtmlConfig &config) {
auto custom_shape = element.custom_shape();
+ auto style = custom_shape.style();
- out << "";
+ out.write_element_begin(
+ "div", {.style = translate_custom_shape_properties(custom_shape) +
+ translate_drawing_style(style)});
translate_children(custom_shape.children(), out, config);
// TODO draw shape in svg
- out << "
";
+ out.write_element_end("div");
}
} // namespace odr::internal
diff --git a/src/odr/internal/html/document_element.hpp b/src/odr/internal/html/document_element.hpp
index 0a886161..4ee8eeca 100644
--- a/src/odr/internal/html/document_element.hpp
+++ b/src/odr/internal/html/document_element.hpp
@@ -1,8 +1,6 @@
#ifndef ODR_INTERNAL_HTML_DOCUMENT_ELEMENT_H
#define ODR_INTERNAL_HTML_DOCUMENT_ELEMENT_H
-#include
-
namespace odr {
class Element;
@@ -10,51 +8,45 @@ struct HtmlConfig;
} // namespace odr
namespace odr::internal::html {
+class HtmlWriter;
-void translate_children(ElementRange range, std::ostream &out,
+void translate_children(ElementRange range, HtmlWriter &out,
const HtmlConfig &config);
-void translate_element(Element element, std::ostream &out,
+void translate_element(Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_slide(Element element, std::ostream &out,
+void translate_slide(Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_sheet(Element element, std::ostream &out,
+void translate_sheet(Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_page(Element element, std::ostream &out,
- const HtmlConfig &config);
+void translate_page(Element element, HtmlWriter &out, const HtmlConfig &config);
-void translate_master_page(MasterPage element, std::ostream &out,
+void translate_master_page(MasterPage element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_text(const Element element, std::ostream &out,
+void translate_text(const Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_line_break(Element element, std::ostream &out,
- const HtmlConfig &);
-void translate_paragraph(Element element, std::ostream &out,
+void translate_line_break(Element element, HtmlWriter &out, const HtmlConfig &);
+void translate_paragraph(Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_span(Element element, std::ostream &out,
- const HtmlConfig &config);
-void translate_link(Element element, std::ostream &out,
- const HtmlConfig &config);
-void translate_bookmark(Element element, std::ostream &out,
+void translate_span(Element element, HtmlWriter &out, const HtmlConfig &config);
+void translate_link(Element element, HtmlWriter &out, const HtmlConfig &config);
+void translate_bookmark(Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_list(Element element, std::ostream &out,
- const HtmlConfig &config);
-void translate_list_item(Element element, std::ostream &out,
+void translate_list(Element element, HtmlWriter &out, const HtmlConfig &config);
+void translate_list_item(Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_table(Element element, std::ostream &out,
+void translate_table(Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_image(Element element, std::ostream &out,
+void translate_image(Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_frame(Element element, std::ostream &out,
+void translate_frame(Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_rect(Element element, std::ostream &out,
- const HtmlConfig &config);
-void translate_line(Element element, std::ostream &out,
- const HtmlConfig &config);
-void translate_circle(Element element, std::ostream &out,
+void translate_rect(Element element, HtmlWriter &out, const HtmlConfig &config);
+void translate_line(Element element, HtmlWriter &out, const HtmlConfig &config);
+void translate_circle(Element element, HtmlWriter &out,
const HtmlConfig &config);
-void translate_custom_shape(Element element, std::ostream &out,
+void translate_custom_shape(Element element, HtmlWriter &out,
const HtmlConfig &config);
} // namespace odr::internal::html
diff --git a/src/odr/internal/html/document_style.cpp b/src/odr/internal/html/document_style.cpp
index 7af3e792..9c8ff4a7 100644
--- a/src/odr/internal/html/document_style.cpp
+++ b/src/odr/internal/html/document_style.cpp
@@ -304,11 +304,11 @@ std::string html::translate_frame_properties(const Frame &frame) {
if (auto y = frame.y()) {
result += "margin-top:" + *y + ";";
}
- result.append("margin-right:calc(100% - ")
- .append(frame.x().value_or("0in"))
- .append(" - ")
- .append(*frame.width())
- .append(");");
+ result += "margin-right:calc(100% - ";
+ result += frame.x().value_or("0in");
+ result += " - ";
+ result += *frame.width();
+ result += ");";
} else if (text_wrap == TextWrap::after) {
result += "display:block;";
result += "float:left;clear:both;";
diff --git a/src/odr/internal/html/html_writer.cpp b/src/odr/internal/html/html_writer.cpp
new file mode 100644
index 00000000..ef4ed4d8
--- /dev/null
+++ b/src/odr/internal/html/html_writer.cpp
@@ -0,0 +1,209 @@
+#include
+
+#include
+
+#include
+#include
+
+namespace odr::internal::html {
+
+HtmlWriter::HtmlWriter(std::ostream &out, bool format, std::uint8_t indent)
+ : m_out{out}, m_format{format}, m_indent(indent, ' ') {}
+
+void HtmlWriter::write_begin() {
+ m_out << "\n";
+ m_out << "";
+}
+
+void HtmlWriter::write_end() {
+ write_new_line();
+
+ m_out << "";
+}
+
+void HtmlWriter::write_header_begin() {
+ write_new_line();
+ ++m_current_indent;
+
+ m_out << "";
+}
+
+void HtmlWriter::write_header_end() {
+ --m_current_indent;
+ write_new_line();
+
+ m_out << "";
+}
+
+void HtmlWriter::write_header_title(const std::string &title) {
+ write_new_line();
+
+ m_out << "" << title << " ";
+}
+
+void HtmlWriter::write_header_viewport(const std::string &viewport) {
+ write_new_line();
+
+ m_out << " ";
+}
+
+void HtmlWriter::write_header_target(const std::string &target) {
+ write_new_line();
+
+ m_out << " ";
+}
+
+void HtmlWriter::write_header_charset(const std::string &charset) {
+ write_new_line();
+
+ m_out << " ";
+}
+
+void HtmlWriter::write_header_style(const std::string &href) {
+ write_new_line();
+
+ m_out << " ";
+}
+
+void HtmlWriter::write_header_style_begin() {
+ write_new_line();
+ ++m_current_indent;
+
+ m_out << "";
+}
+
+void HtmlWriter::write_script(const std::string &src) {
+ write_new_line();
+
+ m_out << "";
+}
+
+void HtmlWriter::write_script_begin() {
+ write_new_line();
+ ++m_current_indent;
+
+ m_out << "";
+}
+
+void HtmlWriter::write_body_begin(HtmlElementOptions options) {
+ write_new_line();
+ ++m_current_indent;
+
+ m_out << "";
+}
+
+void HtmlWriter::write_body_end() {
+ --m_current_indent;
+ write_new_line();
+
+ m_out << "";
+}
+
+void HtmlWriter::write_element_begin(const std::string &name,
+ HtmlElementOptions options) {
+ write_new_line();
+ if (options.close_type == HtmlCloseType::standard) {
+ ++m_current_indent;
+ m_stack.push_back({name, options.inline_element});
+ }
+
+ m_out << "<" << name;
+
+ if (!options.clazz.empty()) {
+ m_out << " class=\"" << options.clazz << "\"";
+ }
+ if (!options.style.empty()) {
+ m_out << " style=\"" << options.style << "\"";
+ }
+ for (const auto &[key, value] : options.attributes) {
+ m_out << " " << key << "=\"" << value << "\"";
+ }
+ if (!options.extra.empty()) {
+ m_out << " " << options.extra;
+ }
+
+ if (options.close_type == HtmlCloseType::trailing) {
+ m_out << "/>";
+ } else {
+ m_out << ">";
+ }
+}
+
+void HtmlWriter::write_element_end(const std::string &name) {
+ --m_current_indent;
+ write_new_line();
+
+ if (m_stack.empty()) {
+ throw std::logic_error("stack is empty");
+ }
+ if (m_stack.back().name != name) {
+ throw std::invalid_argument("names do not match");
+ }
+ m_stack.pop_back();
+
+ m_out << "" << name << ">";
+}
+
+bool HtmlWriter::is_inline_mode() const {
+ for (const auto &element : m_stack) {
+ if (element.inline_element) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void HtmlWriter::write_new_line() {
+ if (!m_format) {
+ return;
+ }
+ if (is_inline_mode()) {
+ return;
+ }
+
+ m_out << '\n';
+ for (std::uint32_t i = 0; i < m_current_indent; ++i) {
+ m_out << m_indent;
+ }
+}
+
+std::ostream &HtmlWriter::out() { return m_out; }
+
+} // namespace odr::internal::html
diff --git a/src/odr/internal/html/html_writer.hpp b/src/odr/internal/html/html_writer.hpp
new file mode 100644
index 00000000..aab13842
--- /dev/null
+++ b/src/odr/internal/html/html_writer.hpp
@@ -0,0 +1,79 @@
+#ifndef ODR_INTERNAL_HTML_HTML_WRITER_H
+#define ODR_INTERNAL_HTML_HTML_WRITER_H
+
+#include "odr/html.hpp"
+#include
+#include
+#include
+
+namespace odr::internal::html {
+
+enum class HtmlCloseType {
+ standard,
+ trailing,
+ none,
+};
+
+using HtmlAttributes = std::vector>;
+
+struct HtmlElementOptions {
+ bool inline_element{false};
+ HtmlCloseType close_type{HtmlCloseType::standard};
+
+ HtmlAttributes attributes{};
+
+ std::string style{};
+ std::string clazz{};
+
+ std::string extra{};
+};
+
+class HtmlWriter {
+public:
+ explicit HtmlWriter(std::ostream &out, bool format, std::uint8_t indent);
+
+ void write_begin();
+ void write_end();
+
+ void write_header_begin();
+ void write_header_end();
+ void write_header_title(const std::string &title);
+ void write_header_viewport(const std::string &viewport);
+ void write_header_target(const std::string &target);
+ void write_header_charset(const std::string &charset);
+ void write_header_style(const std::string &href);
+ void write_header_style_begin();
+ void write_header_style_end();
+
+ void write_script(const std::string &src);
+ void write_script_begin();
+ void write_script_end();
+
+ void write_body_begin(HtmlElementOptions options = {});
+ void write_body_end();
+
+ void write_element_begin(const std::string &name,
+ HtmlElementOptions options = {});
+ void write_element_end(const std::string &name);
+
+ bool is_inline_mode() const;
+ void write_new_line();
+
+ std::ostream &out();
+
+private:
+ struct StackElement {
+ std::string name;
+ bool inline_element{false};
+ };
+
+ std::ostream &m_out;
+ bool m_format{false};
+ std::string m_indent;
+ std::uint32_t m_current_indent{0};
+ std::vector m_stack;
+};
+
+} // namespace odr::internal::html
+
+#endif // ODR_INTERNAL_HTML_HTML_WRITER_H
diff --git a/src/odr/internal/html/image_file.cpp b/src/odr/internal/html/image_file.cpp
index ff8ad021..1f4dfdbb 100644
--- a/src/odr/internal/html/image_file.cpp
+++ b/src/odr/internal/html/image_file.cpp
@@ -6,7 +6,7 @@
#include
#include
-#include
+#include
#include
#include
#include
@@ -63,33 +63,36 @@ Html html::translate_image_file(const ImageFile &image_file,
const std::string &path,
const HtmlConfig &config) {
auto output_path = path + "/image.html";
- std::ofstream out(output_path);
- if (!out.is_open()) {
+ std::ofstream ostream(output_path);
+ if (!ostream.is_open()) {
throw FileWriteError();
}
+ HtmlWriter out(ostream, config.format_html, config.html_indent);
- out << internal::html::doctype();
- out << "";
- out << internal::html::default_headers();
- out << "";
- out << "";
+ out.write_begin();
+ out.write_header_begin();
+ out.write_header_charset("UTF-8");
+ out.write_header_target("_blank");
+ out.write_header_title("odr");
+ out.write_header_viewport(
+ "width=device-width,initial-scale=1.0,user-scalable=yes");
+ out.write_header_end();
- out << "";
+ out.write_body_begin();
{
- out << " ";
+ out.out() << "\">";
}
- out << "";
- out << "";
+ out.write_body_end();
+ out.write_end();
return {image_file.file_type(), config, {{"image", output_path}}};
}
diff --git a/src/odr/internal/html/text_file.cpp b/src/odr/internal/html/text_file.cpp
index 895faf77..f0240184 100644
--- a/src/odr/internal/html/text_file.cpp
+++ b/src/odr/internal/html/text_file.cpp
@@ -5,6 +5,7 @@
#include
#include
+#include
#include
#include
@@ -16,41 +17,53 @@ Html html::translate_text_file(const TextFile &text_file,
const std::string &path,
const HtmlConfig &config) {
auto output_path = path + "/text.html";
- std::ofstream out(output_path);
- if (!out.is_open()) {
+ std::ofstream ostream(output_path);
+ if (!ostream.is_open()) {
throw FileWriteError();
}
+ HtmlWriter out(ostream, config.format_html, config.html_indent);
- out << internal::html::doctype();
- out << "";
- out << internal::html::default_headers();
- out << "";
- out << "";
+ out.write_begin();
+ out.write_header_begin();
+ out.write_header_charset("UTF-8");
+ out.write_header_target("_blank");
+ out.write_header_title("odr");
+ out.write_header_viewport(
+ "width=device-width,initial-scale=1.0,user-scalable=yes");
+ out.write_header_style_begin();
+ out.out() << "*{font-family:monospace;}";
+ out.out() << "td{padding-left:10px;padding-right:10px;}";
+ out.write_header_style_end();
+ out.write_header_end();
- out << "";
+ out.write_body_begin();
{
auto in = text_file.stream();
std::uint32_t line = 1;
- out << "";
+ out.write_element_begin("table", {.attributes = {{"cellpadding", "0"},
+ {"border", "0"},
+ {"cellspacing", "0"}}});
while (true) {
- out << "" << line
- << " ";
- out << "";
+ out.write_element_begin("tr");
+
+ out.write_element_begin("td",
+ {.inline_element = true,
+ .style = "text-align:right;user-select:none;"});
+ out.out() << line;
+ out.write_element_end("td");
+
+ out.write_element_begin("td");
std::ostringstream ss_out;
util::stream::getline(*in, ss_out);
- out << escape_text(ss_out.str());
+ out.out() << escape_text(ss_out.str());
+
+ out.write_element_end("td");
+ out.write_element_end("tr");
- out << " ";
if (in->eof()) {
break;
}
@@ -58,8 +71,9 @@ Html html::translate_text_file(const TextFile &text_file,
}
}
- out << "";
- out << "";
+ out.write_element_end("table");
+ out.write_body_end();
+ out.write_end();
return {text_file.file_type(), config, {{"text", output_path}}};
}
diff --git a/test/src/output_reference_test.cpp b/test/src/output_reference_test.cpp
index 7854ad62..d85a46c8 100644
--- a/test/src/output_reference_test.cpp
+++ b/test/src/output_reference_test.cpp
@@ -97,6 +97,8 @@ TEST_P(OutputReferenceTests, html_meta) {
config.relative_resource_paths = true;
config.editable = true;
config.spreadsheet_limit = TableDimensions(4000, 500);
+ config.format_html = true;
+ config.html_indent = 2;
std::optional html;
if (file.file_type() == FileType::text_file) {