diff options
Diffstat (limited to '')
-rw-r--r-- | src/spreadsheet/html_dumper.cpp | 695 |
1 files changed, 695 insertions, 0 deletions
diff --git a/src/spreadsheet/html_dumper.cpp b/src/spreadsheet/html_dumper.cpp new file mode 100644 index 0000000..cdf04de --- /dev/null +++ b/src/spreadsheet/html_dumper.cpp @@ -0,0 +1,695 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "html_dumper.hpp" +#include "impl_types.hpp" +#include "number_format.hpp" + +#include "orcus/spreadsheet/styles.hpp" +#include "orcus/spreadsheet/shared_strings.hpp" +#include "orcus/spreadsheet/document.hpp" +#include "orcus/spreadsheet/sheet.hpp" + +#include <ixion/address.hpp> +#include <ixion/model_context.hpp> +#include <ixion/formula.hpp> +#include <ixion/formula_result.hpp> +#include <ixion/cell.hpp> + +#include <sstream> + +namespace orcus { namespace spreadsheet { namespace detail { + +namespace { + +void build_rgb_color(std::ostringstream& os, const color_t& color_value) +{ + // Special colors. + if (color_value.alpha == 255 && color_value.red == 0 && color_value.green == 0 && color_value.blue == 0) + { + os << "black"; + return; + } + + if (color_value.alpha == 255 && color_value.red == 255 && color_value.green == 0 && color_value.blue == 0) + { + os << "red"; + return; + } + + if (color_value.alpha == 255 && color_value.red == 0 && color_value.green == 255 && color_value.blue == 0) + { + os << "green"; + return; + } + + if (color_value.alpha == 255 && color_value.red == 0 && color_value.green == 0 && color_value.blue == 255) + { + os << "blue"; + return; + } + + os << "rgb(" + << static_cast<short>(color_value.red) << "," + << static_cast<short>(color_value.green) << "," + << static_cast<short>(color_value.blue) << ")"; +} + +const char* css_style_global = +"table, td { " + "border-collapse : collapse; " +"}\n" + +"table { " + "border-spacing : 0px; " +"}\n" + +"td { " + "width : 1in; border: 1px solid lightgray; " +"}\n" + +"td.empty { " + "color : white; " +"}\n"; + +class html_elem +{ +public: + struct attr + { + std::string name; + std::string value; + + attr(const std::string& _name, const std::string& _value) : name(_name), value(_value) {} + }; + + typedef std::vector<attr> attrs_type; + + html_elem(std::ostream& strm, const char* name, const char* style = nullptr, const char* style_class = nullptr) : + m_strm(strm), m_name(name) + { + m_strm << '<' << m_name; + + if (style) + m_strm << " style=\"" << style << "\""; + + if (style_class) + m_strm << " class=\"" << style_class << "\""; + + m_strm << '>'; + } + + html_elem(std::ostream& strm, const char* name, const attrs_type& attrs) : + m_strm(strm), m_name(name) + { + m_strm << '<' << m_name; + + attrs_type::const_iterator it = attrs.begin(), it_end = attrs.end(); + for (; it != it_end; ++it) + m_strm << " " << it->name << "=\"" << it->value << "\""; + + m_strm << '>'; + } + + ~html_elem() + { + m_strm << "</" << m_name << '>'; + } + +private: + std::ostream& m_strm; + const char* m_name; +}; + +void print_formatted_text(std::ostream& strm, const std::string& text, const format_runs_t& formats) +{ + typedef html_elem elem; + + const char* p_span = "span"; + + size_t pos = 0; + format_runs_t::const_iterator itr = formats.begin(), itr_end = formats.end(); + for (; itr != itr_end; ++itr) + { + const format_run& run = *itr; + if (pos < run.pos) + { + // flush unformatted text. + strm << std::string(&text[pos], run.pos-pos); + pos = run.pos; + } + + if (!run.size) + continue; + + std::string style = ""; + if (run.bold) + style += "font-weight: bold;"; + else + style += "font-weight: normal;"; + + if (run.italic) + style += "font-style: italic;"; + else + style += "font-style: normal;"; + + if (!run.font.empty()) + { + style += "font-family: "; + style += run.font; + style += ";"; + } + + if (run.font_size) + { + std::ostringstream os; + os << "font-size: " << run.font_size << "pt;"; + style += os.str(); + } + + const color_t& col = run.color; + if (col.red || col.green || col.blue) + { + std::ostringstream os; + os << "color: "; + build_rgb_color(os, col); + os << ";"; + style += os.str(); + } + + if (style.empty()) + strm << std::string(&text[pos], run.size); + else + { + elem span(strm, p_span, style.c_str()); + strm << std::string(&text[pos], run.size); + } + + pos += run.size; + } + + if (pos < text.size()) + { + // flush the remaining unformatted text. + strm << std::string(&text[pos], text.size() - pos); + } +} + +void build_border_style(std::ostringstream& os, const char* style_name, const border_attrs_t& attrs) +{ + if (!attrs.style || *attrs.style == border_style_t::none) + return; + + os << style_name << ": "; + switch (*attrs.style) + { + case border_style_t::thin: + { + os << "solid 1px "; + break; + } + case border_style_t::medium: + { + os << "solid 2px "; + break; + } + case border_style_t::thick: + { + os << "solid 3px "; + break; + } + case border_style_t::hair: + { + os << "solid 0.5px "; + break; + } + case border_style_t::dotted: + { + os << "dotted 1px "; + break; + } + case border_style_t::dashed: + { + os << "dashed 1px "; + break; + } + case border_style_t::double_border: + { + os << "3px double "; + break; + } + case border_style_t::dash_dot: + { + // CSS doesn't support dash-dot. + os << "dashed 1px "; + break; + } + case border_style_t::dash_dot_dot: + { + // CSS doesn't support dash-dot-dot. + os << "dashed 1px "; + break; + } + case border_style_t::medium_dashed: + { + os << "dashed 2px "; + break; + } + case border_style_t::medium_dash_dot: + { + // CSS doesn't support dash-dot. + os << "dashed 2px "; + break; + } + case border_style_t::medium_dash_dot_dot: + { + // CSS doesn't support dash-dot-dot. + os << "dashed 2px "; + break; + } + case border_style_t::slant_dash_dot: + { + // CSS doesn't support dash-dot. + os << "dashed 2px "; + break; + } + default:; + } + + build_rgb_color(os, *attrs.border_color); + os << "; "; +} + +void build_style_string(std::string& str, const styles& styles, const cell_format_t& fmt) +{ + std::ostringstream os; + + { + const font_t* p = styles.get_font(fmt.font); + if (p) + { + if (p->name && !p->name.value().empty()) + os << "font-family: " << *p->name << ";"; + if (p->size) + os << "font-size: " << *p->size << "pt;"; + if (p->bold && *p->bold) + os << "font-weight: bold;"; + if (p->italic && *p->italic) + os << "font-style: italic;"; + + if (p->color) + { + const color_t& r = *p->color; + if (r.red || r.green || r.blue) + { + os << "color: "; + build_rgb_color(os, r); + os << ";"; + } + } + } + } + + { + const fill_t* p = styles.get_fill(fmt.fill); + if (p) + { + if (p->pattern_type && *p->pattern_type == fill_pattern_t::solid && p->fg_color) + { + const color_t& r = *p->fg_color; + os << "background-color: "; + build_rgb_color(os, r); + os << ";"; + } + } + } + + { + const border_t* p = styles.get_border(fmt.border); + if (p) + { + build_border_style(os, "border-top", p->top); + build_border_style(os, "border-bottom", p->bottom); + build_border_style(os, "border-left", p->left); + build_border_style(os, "border-right", p->right); + } + } + + if (fmt.apply_alignment) + { + if (fmt.hor_align != hor_alignment_t::unknown) + { + os << "text-align: "; + switch (fmt.hor_align) + { + case hor_alignment_t::left: + os << "left"; + break; + case hor_alignment_t::center: + os << "center"; + break; + case hor_alignment_t::right: + os << "right"; + break; + default: + ; + } + os << ";"; + } + + if (fmt.ver_align != ver_alignment_t::unknown) + { + os << "vertical-align: "; + switch (fmt.ver_align) + { + case ver_alignment_t::top: + os << "top"; + break; + case ver_alignment_t::middle: + os << "middle"; + break; + case ver_alignment_t::bottom: + os << "bottom"; + break; + default: + ; + } + os << ";"; + } + } + + str += os.str(); +} + +void dump_html_head(std::ostream& os) +{ + typedef html_elem elem; + + const char* p_head = "head"; + const char* p_style = "style"; + + elem elem_head(os, p_head); + { + elem elem_style(os, p_style); + os << css_style_global; + } +} + +void build_html_elem_attributes(html_elem::attrs_type& attrs, const std::string& style, const merge_size* p_merge_size) +{ + attrs.push_back(html_elem::attr("style", style)); + if (p_merge_size) + { + if (p_merge_size->width > 1) + { + std::ostringstream os2; + os2 << p_merge_size->width; + attrs.push_back(html_elem::attr("colspan", os2.str())); + } + + if (p_merge_size->height > 1) + { + std::ostringstream os2; + os2 << p_merge_size->height; + attrs.push_back(html_elem::attr("rowspan", os2.str())); + } + } +} + +} + +html_dumper::html_dumper( + const document& doc, + const col_merge_size_type& merge_ranges, + sheet_t sheet_id) : + m_doc(doc), + m_merge_ranges(merge_ranges), + m_sheet_id(sheet_id) +{ + build_overlapped_ranges(); +} + +void html_dumper::dump(std::ostream& os) const +{ + const sheet* sh = m_doc.get_sheet(m_sheet_id); + if (!sh) + return; + + typedef html_elem elem; + + const char* p_html = "html"; + const char* p_body = "body"; + const char* p_table = "table"; + const char* p_tr = "tr"; + const char* p_td = "td"; + + ixion::abs_range_t range = sh->get_data_range(); + + elem root(os, p_html); + dump_html_head(os); + + { + elem elem_body(os, p_body); + + if (!range.valid()) + // Sheet is empty. Nothing to print. + return; + + const ixion::model_context& cxt = m_doc.get_model_context(); + const ixion::formula_name_resolver* resolver = + m_doc.get_formula_name_resolver(spreadsheet::formula_ref_context_t::global); + const shared_strings& sstrings = m_doc.get_shared_strings(); + + elem table(os, p_table); + + row_t row_count = range.last.row + 1; + col_t col_count = range.last.column + 1; + for (row_t row = 0; row < row_count; ++row) + { + // Set the row height. + std::string row_style; + row_height_t rh = sh->get_row_height(row, nullptr, nullptr); + + // Convert height from twip to inches. + if (rh != get_default_row_height()) + { + std::string style; + double val = orcus::convert(rh, length_unit_t::twip, length_unit_t::inch); + std::ostringstream os_style; + os_style << "height: " << val << "in;"; + row_style += os_style.str(); + } + + const char* style_str = nullptr; + if (!row_style.empty()) + style_str = row_style.c_str(); + elem tr(os, p_tr, style_str); + + const detail::overlapped_col_index_type* p_overlapped = get_overlapped_ranges(row); + + for (col_t col = 0; col < col_count; ++col) + { + ixion::abs_address_t pos(m_sheet_id, row, col); + + const merge_size* p_merge_size = get_merge_size(row, col); + if (!p_merge_size && p_overlapped) + { + // Check if this cell is overlapped by a merged cell. + col_t overlapped_origin = -1; + col_t last_col = -1; + if (p_overlapped->search_tree(col, overlapped_origin, nullptr, &last_col).second && overlapped_origin >= 0) + { + // Skip all overlapped cells on this row. + col = last_col - 1; + continue; + } + } + size_t xf_id = sh->get_cell_format(row, col); + std::string style; + + if (row == 0) + { + // Set the column width. + col_width_t cw = sh->get_col_width(col, nullptr, nullptr); + + // Convert width from twip to inches. + if (cw != get_default_column_width()) + { + double val = orcus::convert(cw, length_unit_t::twip, length_unit_t::inch); + std::ostringstream os_style; + os_style << "width: " << val << "in;"; + style += os_style.str(); + } + } + + { + // Apply cell format. + const styles& styles = m_doc.get_styles(); + const cell_format_t* fmt = styles.get_cell_format(xf_id); + if (fmt) + build_style_string(style, styles, *fmt); + } + + ixion::celltype_t ct = cxt.get_celltype(pos); + if (ct == ixion::celltype_t::empty) + { + html_elem::attrs_type attrs; + build_html_elem_attributes(attrs, style, p_merge_size); + attrs.push_back(html_elem::attr("class", "empty")); + elem td(os, p_td, attrs); + os << '-'; // empty cell. + continue; + } + + html_elem::attrs_type attrs; + build_html_elem_attributes(attrs, style, p_merge_size); + elem td(os, p_td, attrs); + + switch (ct) + { + case ixion::celltype_t::string: + { + size_t sindex = cxt.get_string_identifier(pos); + const std::string* p = cxt.get_string(sindex); + assert(p); + const format_runs_t* pformat = sstrings.get_format_runs(sindex); + if (pformat) + print_formatted_text(os, *p, *pformat); + else + os << *p; + + break; + } + case ixion::celltype_t::numeric: + format_to_file_output(os, cxt.get_numeric_value(pos)); + break; + case ixion::celltype_t::boolean: + os << (cxt.get_boolean_value(pos) ? "true" : "false"); + break; + case ixion::celltype_t::formula: + { + // print the formula and the formula result. + const ixion::formula_cell* cell = cxt.get_formula_cell(pos); + assert(cell); + const ixion::formula_tokens_store_ptr_t& ts = cell->get_tokens(); + if (ts) + { + const ixion::formula_tokens_t& tokens = ts->get(); + + std::string formula; + if (resolver) + { + pos = cell->get_parent_position(pos); + formula = ixion::print_formula_tokens( + m_doc.get_model_context(), pos, *resolver, tokens); + } + else + formula = "???"; + + ixion::formula_group_t fg = cell->get_group_properties(); + + if (fg.grouped) + os << '{' << formula << '}'; + else + os << formula; + + try + { + ixion::formula_result res = cell->get_result_cache( + ixion::formula_result_wait_policy_t::throw_exception); + os << " (" << res.str(m_doc.get_model_context()) << ")"; + } + catch (const std::exception&) + { + os << " (#RES!)"; + } + } + + break; + } + default: + ; + } + } + } + } +} + +const overlapped_col_index_type* html_dumper::get_overlapped_ranges(row_t row) const +{ + overlapped_cells_type::const_iterator it = m_overlapped_ranges.find(row); + if (it == m_overlapped_ranges.end()) + return nullptr; + + return it->second.get(); +} + +const merge_size* html_dumper::get_merge_size(row_t row, col_t col) const +{ + col_merge_size_type::const_iterator it_col = m_merge_ranges.find(col); + if (it_col == m_merge_ranges.end()) + return nullptr; + + merge_size_type& col_merge_sizes = *it_col->second; + merge_size_type::const_iterator it_row = col_merge_sizes.find(row); + if (it_row == col_merge_sizes.end()) + return nullptr; + + return &it_row->second; +} + +void html_dumper::build_overlapped_ranges() +{ + const sheet* sh = m_doc.get_sheet(m_sheet_id); + if (!sh) + return; + + range_size_t sheet_size = m_doc.get_sheet_size(); + + detail::col_merge_size_type::const_iterator it_col = m_merge_ranges.begin(), it_col_end = m_merge_ranges.end(); + for (; it_col != it_col_end; ++it_col) + { + col_t col = it_col->first; + const detail::merge_size_type& data = *it_col->second; + detail::merge_size_type::const_iterator it = data.begin(), it_end = data.end(); + for (; it != it_end; ++it) + { + row_t row = it->first; + const detail::merge_size& item = it->second; + for (row_t i = 0; i < item.height; ++i, ++row) + { + // Get the container for this row. + detail::overlapped_cells_type::iterator it_cont = m_overlapped_ranges.find(row); + if (it_cont == m_overlapped_ranges.end()) + { + auto p = std::make_unique<detail::overlapped_col_index_type>(0, sheet_size.columns, -1); + std::pair<detail::overlapped_cells_type::iterator, bool> r = + m_overlapped_ranges.insert(detail::overlapped_cells_type::value_type(row, std::move(p))); + + if (!r.second) + { + // Insertion failed. + return; + } + + it_cont = r.first; + } + + detail::overlapped_col_index_type& cont = *it_cont->second; + cont.insert_back(col, col+item.width, col); + } + } + } + + // Build trees. + for (auto& range : m_overlapped_ranges) + range.second->build_tree(); +} + +}}} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |