diff options
Diffstat (limited to 'src/boost/tools/quickbook/src/bb2html.cpp')
-rw-r--r-- | src/boost/tools/quickbook/src/bb2html.cpp | 1626 |
1 files changed, 1626 insertions, 0 deletions
diff --git a/src/boost/tools/quickbook/src/bb2html.cpp b/src/boost/tools/quickbook/src/bb2html.cpp new file mode 100644 index 000000000..44c8e7558 --- /dev/null +++ b/src/boost/tools/quickbook/src/bb2html.cpp @@ -0,0 +1,1626 @@ +/*============================================================================= +Copyright (c) 2017 Daniel James + +Use, modification and distribution is subject to the Boost Software +License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +http://www.boost.org/LICENSE_1_0.txt) +=============================================================================*/ + +#include "bb2html.hpp" +#include <cassert> +#include <vector> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/filesystem/fstream.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/preprocessor/cat.hpp> +#include <boost/preprocessor/stringize.hpp> +#include <boost/unordered_map.hpp> +#include <boost/unordered_set.hpp> +#include "boostbook_chunker.hpp" +#include "files.hpp" +#include "for.hpp" +#include "html_printer.hpp" +#include "path.hpp" +#include "post_process.hpp" +#include "stream.hpp" +#include "utils.hpp" +#include "xml_parse.hpp" + +namespace quickbook +{ + namespace fs = boost::filesystem; +} + +namespace quickbook +{ + namespace detail + { + struct html_state; + struct html_gen; + struct docinfo_gen; + struct id_info; + + typedef boost::unordered_map<string_view, id_info> ids_type; + + typedef void (*node_parser)(html_gen&, xml_element*); + typedef boost::unordered_map<quickbook::string_view, node_parser> + node_parsers_type; + static node_parsers_type node_parsers; + + struct docinfo_node_parser + { + typedef void (*parser_type)(docinfo_gen&, xml_element*); + enum docinfo_node_category + { + docinfo_general = 0, + docinfo_author + }; + + docinfo_node_category category; + parser_type parser; + }; + typedef boost:: + unordered_map<quickbook::string_view, docinfo_node_parser> + docinfo_node_pasers_type; + static docinfo_node_pasers_type docinfo_node_parsers; + + void generate_chunked_documentation( + chunk*, ids_type const&, html_options const&); + void generate_chunks(html_state&, chunk*); + void generate_chunk_navigation(html_gen&, chunk*); + void generate_inline_chunks(html_gen&, chunk*); + void generate_chunk_body(html_gen&, chunk*); + void generate_toc_html(html_gen& gen, chunk*); + void generate_toc_subtree( + html_gen& gen, chunk* page, chunk*, unsigned section_depth); + void generate_toc_item_html(html_gen&, xml_element*); + void generate_footnotes_html(html_gen&); + void number_callouts(html_gen& gen, xml_element* x); + void number_calloutlist_children( + html_gen& gen, unsigned& count, xml_element* x); + void generate_docinfo_html(html_gen&, xml_element*); + void generate_tree_html(html_gen&, xml_element*); + void generate_children_html(html_gen&, xml_element*); + void write_file( + html_state&, std::string const& path, std::string const& content); + std::string get_link_from_path( + html_gen&, quickbook::string_view, quickbook::string_view); + std::string relative_path_or_url(html_gen&, path_or_url const&); + std::string relative_path_from_fs_paths( + fs::path const&, fs::path const&); + std::string relative_path_from_url_paths( + quickbook::string_view, quickbook::string_view); + + ids_type get_id_paths(chunk* chunk); + void get_id_paths_impl(ids_type&, chunk*); + void get_id_paths_impl2(ids_type&, chunk*, xml_element*); + + void tag(html_gen& gen, quickbook::string_view name, xml_element* x); + void tag_start_with_id( + html_gen& gen, quickbook::string_view name, xml_element* x); + void open_tag_with_id( + html_gen& gen, quickbook::string_view name, xml_element* x); + void tag_self_close( + html_gen& gen, quickbook::string_view name, xml_element* x); + void graphics_tag( + html_gen& gen, + quickbook::string_view path, + quickbook::string_view fallback); + + struct id_info + { + private: + chunk* chunk_; + xml_element* element_; + + public: + explicit id_info(chunk* c, xml_element* x) : chunk_(c), element_(x) + { + assert(c); + assert(!x || x->has_attribute("id")); + } + + std::string path() const + { + std::string p = chunk_->path_; + + if (element_) { + p += '#'; + p += element_->get_attribute("id"); + } + else if (chunk_->inline_) { + p += '#'; + p += chunk_->id_; + } + return p; + } + }; + + struct html_state + { + ids_type const& ids; + html_options const& options; + unsigned int error_count; + + explicit html_state( + ids_type const& ids_, html_options const& options_) + : ids(ids_), options(options_), error_count(0) + { + } + }; + + struct callout_data + { + quickbook::string_view link_id; + unsigned number; + }; + + struct chunk_state + { + std::vector<xml_element*> footnotes; + boost::unordered_map<string_view, callout_data> callout_numbers; + boost::unordered_set<string_view> fragment_ids; + }; + + struct html_gen + { + html_printer printer; + html_state& state; + chunk_state& chunk; + string_view path; + bool in_toc; + + explicit html_gen( + html_state& state_, chunk_state& chunk_, string_view p) + : printer() + , state(state_) + , chunk(chunk_) + , path(p) + , in_toc(false) + { + } + + html_gen(html_gen const& x) + : printer() + , state(x.state) + , chunk(x.chunk) + , path(x.path) + , in_toc(false) + { + } + }; + + struct docinfo_gen + { + html_gen& gen; + std::vector<std::string> copyrights; + std::vector<std::string> pubdates; + std::vector<std::string> legalnotices; + std::vector<std::string> authors; + std::vector<std::string> editors; + std::vector<std::string> collabs; + + docinfo_gen(html_gen& gen_) : gen(gen_) {} + }; + + int boostbook_to_html( + quickbook::string_view source, html_options const& options) + { + xml_tree tree; + try { + tree = xml_parse(source); + } catch (quickbook::detail::xml_parse_error e) { + string_view source_view(source); + file_position p = relative_position(source_view.begin(), e.pos); + string_view::iterator line_start = + e.pos - (p.column < 40 ? p.column - 1 : 39); + string_view::iterator line_end = + std::find(e.pos, source_view.end(), '\n'); + if (line_end - e.pos > 80) { + line_end = e.pos + 80; + } + std::string indent; + for (auto i = e.pos - line_start; i; --i) { + indent += ' '; + } + ::quickbook::detail::outerr() + << "converting boostbook at line " << p.line << " char " + << p.column << ": " << e.message << "\n" + << string_view(line_start, line_end - line_start) << "\n" + << indent << "^" + << "\n\n"; + + return 1; + } + + chunk_tree chunked = chunk_document(tree); + // Overwrite paths depending on whether output is chunked or not. + // Really want to do something better, e.g. incorporate many section + // chunks into their parent. + chunked.root()->path_ = + path_to_generic(options.home_path.filename()); + if (options.chunked_output) { + inline_sections(chunked.root(), 0); + + // Create the root directory if necessary for chunked + // documentation. + fs::path parent = options.home_path.parent_path(); + if (!parent.empty() && !fs::exists(parent)) { + fs::create_directory(parent); + } + } + else { + inline_all(chunked.root()); + } + ids_type ids = get_id_paths(chunked.root()); + html_state state(ids, options); + if (chunked.root()) { + generate_chunks(state, chunked.root()); + } + return state.error_count; + } + + void gather_chunk_ids(chunk_state& c_state, xml_element* x) + { + if (!x) { + return; + } + if (x->has_attribute("id")) { + c_state.fragment_ids.emplace(x->get_attribute("id")); + } + for (auto it = x->children(); it; it = it->next()) { + gather_chunk_ids(c_state, it); + } + } + + void gather_chunk_ids(chunk_state& c_state, chunk* x) + { + gather_chunk_ids(c_state, x->contents_.root()); + gather_chunk_ids(c_state, x->title_.root()); + gather_chunk_ids(c_state, x->info_.root()); + + for (chunk* it = x->children(); it && it->inline_; + it = it->next()) { + gather_chunk_ids(c_state, it); + } + } + + string_view generate_id( + chunk_state& c_state, + xml_element* x, + string_view name, + string_view base) + { + std::string result; + result.reserve(base.size() + 2); + result.assign(base.begin(), base.end()); + result += '-'; + // TODO: Share implementation with id_generation.cpp? + for (unsigned count = 1;; ++count) { + auto num = boost::lexical_cast<std::string>(count); + result.reserve(base.size() + 1 + num.size()); + result.erase(base.size() + 1); + result += num; + if (c_state.fragment_ids.find(result) == + c_state.fragment_ids.end()) { + auto r = x->set_attribute(name, result); + c_state.fragment_ids.emplace(r); + return r; + } + } + } + + void generate_chunks(html_state& state, chunk* x) + { + chunk_state c_state; + gather_chunk_ids(c_state, x); + html_gen gen(state, c_state, x->path_); + gen.printer.html += "<!DOCTYPE html>\n"; + open_tag(gen.printer, "html"); + open_tag(gen.printer, "head"); + if (state.options.css_path) { + tag_start(gen.printer, "link"); + tag_attribute(gen.printer, "rel", "stylesheet"); + tag_attribute(gen.printer, "type", "text/css"); + tag_attribute( + gen.printer, "href", + relative_path_or_url(gen, state.options.css_path)); + tag_end_self_close(gen.printer); + } + close_tag(gen.printer, "head"); + open_tag(gen.printer, "body"); + generate_chunk_navigation(gen, x); + generate_chunk_body(gen, x); + chunk* it = x->children(); + for (; it && it->inline_; it = it->next()) { + generate_inline_chunks(gen, it); + } + generate_footnotes_html(gen); + close_tag(gen.printer, "body"); + close_tag(gen.printer, "html"); + write_file(state, x->path_, gen.printer.html); + for (; it; it = it->next()) { + assert(!it->inline_); + generate_chunks(state, it); + } + } + + void generate_chunk_navigation(html_gen& gen, chunk* x) + { + chunk* next = 0; + for (chunk* it = x->children(); it; it = it->next()) { + if (!it->inline_) { + next = it; + break; + } + } + if (!next) { + next = x->next(); + } + + chunk* prev = x->prev(); + if (prev) { + while (prev->children()) { + for (prev = prev->children(); prev->next(); + prev = prev->next()) { + } + } + } + else { + prev = x->parent(); + } + + if (next || prev || x->parent()) { + tag_start(gen.printer, "div"); + tag_attribute(gen.printer, "class", "spirit-nav"); + tag_end(gen.printer); + if (prev) { + tag_start(gen.printer, "a"); + tag_attribute( + gen.printer, "href", + get_link_from_path(gen, prev->path_, x->path_)); + tag_attribute(gen.printer, "accesskey", "p"); + tag_end(gen.printer); + graphics_tag(gen, "/prev.png", "prev"); + close_tag(gen.printer, "a"); + gen.printer.html += " "; + } + if (x->parent()) { + tag_start(gen.printer, "a"); + tag_attribute( + gen.printer, "href", + get_link_from_path(gen, x->parent()->path_, x->path_)); + tag_attribute(gen.printer, "accesskey", "u"); + tag_end(gen.printer); + graphics_tag(gen, "/up.png", "up"); + close_tag(gen.printer, "a"); + gen.printer.html += " "; + + tag_start(gen.printer, "a"); + tag_attribute( + gen.printer, "href", + get_link_from_path(gen, "index.html", x->path_)); + tag_attribute(gen.printer, "accesskey", "h"); + tag_end(gen.printer); + graphics_tag(gen, "/home.png", "home"); + close_tag(gen.printer, "a"); + if (next) { + gen.printer.html += " "; + } + } + if (next) { + tag_start(gen.printer, "a"); + tag_attribute( + gen.printer, "href", + get_link_from_path(gen, next->path_, x->path_)); + tag_attribute(gen.printer, "accesskey", "n"); + tag_end(gen.printer); + graphics_tag(gen, "/next.png", "next"); + close_tag(gen.printer, "a"); + } + close_tag(gen.printer, "div"); + } + } + + void generate_inline_chunks(html_gen& gen, chunk* x) + { + tag_start(gen.printer, "div"); + tag_attribute(gen.printer, "id", x->id_); + tag_end(gen.printer); + generate_chunk_body(gen, x); + for (chunk* it = x->children(); it; it = it->next()) { + assert(it->inline_); + generate_inline_chunks(gen, it); + } + close_tag(gen.printer, "div"); + } + + void generate_chunk_body(html_gen& gen, chunk* x) + { + gen.chunk.callout_numbers.clear(); + + number_callouts(gen, x->title_.root()); + number_callouts(gen, x->info_.root()); + number_callouts(gen, x->contents_.root()); + + generate_tree_html(gen, x->title_.root()); + generate_docinfo_html(gen, x->info_.root()); + generate_toc_html(gen, x); + generate_tree_html(gen, x->contents_.root()); + } + + void generate_toc_html(html_gen& gen, chunk* x) + { + if (x->children() && x->contents_.root()->name_ != "section") { + tag_start(gen.printer, "div"); + tag_attribute(gen.printer, "class", "toc"); + tag_end(gen.printer); + open_tag(gen.printer, "p"); + open_tag(gen.printer, "b"); + gen.printer.html += "Table of contents"; + close_tag(gen.printer, "b"); + close_tag(gen.printer, "p"); + generate_toc_subtree(gen, x, x, 1); + close_tag(gen.printer, "div"); + } + } + + void generate_toc_subtree( + html_gen& gen, chunk* page, chunk* x, unsigned section_depth) + { + if (x != page && section_depth == 0) { + bool has_non_section_child = false; + for (chunk* it = x->children(); it; it = it->next()) { + if (it->contents_.root()->name_ != "section") { + has_non_section_child = true; + } + } + if (!has_non_section_child) { + return; + } + } + + gen.printer.html += "<ul>"; + for (chunk* it = x->children(); it; it = it->next()) { + auto link = gen.state.ids.find(it->id_); + gen.printer.html += "<li>"; + if (link != gen.state.ids.end()) { + gen.printer.html += "<a href=\""; + gen.printer.html += encode_string(get_link_from_path( + gen, link->second.path(), page->path_)); + gen.printer.html += "\">"; + generate_toc_item_html(gen, it->title_.root()); + gen.printer.html += "</a>"; + } + else { + generate_toc_item_html(gen, it->title_.root()); + } + if (it->children()) { + generate_toc_subtree( + gen, page, it, + it->contents_.root()->name_ == "section" && + section_depth > 0 + ? section_depth - 1 + : section_depth); + } + gen.printer.html += "</li>"; + } + gen.printer.html += "</ul>"; + } + + void generate_toc_item_html(html_gen& gen, xml_element* x) + { + if (x) { + bool old = gen.in_toc; + gen.in_toc = true; + generate_children_html(gen, x); + gen.in_toc = old; + } + else { + gen.printer.html += "<i>Untitled</i>"; + } + } + + void generate_footnotes_html(html_gen& gen) + { + if (!gen.chunk.footnotes.empty()) { + tag_start(gen.printer, "div"); + tag_attribute(gen.printer, "class", "footnotes"); + tag_end(gen.printer); + gen.printer.html += "<br/>"; + gen.printer.html += "<hr/>"; + for (std::vector<xml_element*>::iterator it = + gen.chunk.footnotes.begin(); + it != gen.chunk.footnotes.end(); ++it) { + auto footnote_id = + (*it)->get_attribute("(((footnote-id)))"); + tag_start(gen.printer, "div"); + tag_attribute(gen.printer, "id", footnote_id); + tag_attribute(gen.printer, "class", "footnote"); + tag_end(gen.printer); + + generate_children_html(gen, *it); + close_tag(gen.printer, "div"); + } + close_tag(gen.printer, "div"); + } + } + + void number_callouts(html_gen& gen, xml_element* x) + { + if (!x) { + return; + } + + if (x->type_ == xml_element::element_node) { + if (x->name_ == "calloutlist") { + unsigned count = 0; + number_calloutlist_children(gen, count, x); + } + else if (x->name_ == "co") { + if (x->has_attribute("linkends")) { + auto linkends = x->get_attribute("linkends"); + if (!x->has_attribute("id")) { + generate_id(gen.chunk, x, "id", linkends); + } + gen.chunk.callout_numbers[linkends].link_id = + x->get_attribute("id"); + } + } + } + for (xml_element* it = x->children(); it; it = it->next()) { + number_callouts(gen, it); + } + } + + void number_calloutlist_children( + html_gen& gen, unsigned& count, xml_element* x) + { + for (xml_element* it = x->children(); it; it = it->next()) { + if (it->type_ == xml_element::element_node && + it->name_ == "callout") { + if (it->has_attribute("id")) { + gen.chunk.callout_numbers[it->get_attribute("id")] + .number = ++count; + } + } + number_calloutlist_children(gen, count, it); + } + } + + void generate_tree_html(html_gen& gen, xml_element* x) + { + if (!x) { + return; + } + switch (x->type_) { + case xml_element::element_text: { + gen.printer.html += x->contents_; + break; + } + case xml_element::element_html: { + gen.printer.html += x->contents_; + break; + } + case xml_element::element_node: { + node_parsers_type::iterator parser = + node_parsers.find(x->name_); + if (parser != node_parsers.end()) { + parser->second(gen, x); + } + else { + quickbook::detail::out() + << "Unsupported tag: " << x->name_ << std::endl; + generate_children_html(gen, x); + } + break; + } + default: + assert(false); + } + } + + void generate_children_html(html_gen& gen, xml_element* x) + { + for (xml_element* it = x->children(); it; it = it->next()) { + generate_tree_html(gen, it); + } + } + + void generate_docinfo_html_impl(docinfo_gen& d, xml_element* x) + { + for (xml_element* it = x->children(); it; it = it->next()) { + if (it->type_ == xml_element::element_node) { + auto parser = docinfo_node_parsers.find(it->name_); + if (parser != docinfo_node_parsers.end()) { + parser->second.parser(d, it); + } + else { + quickbook::detail::out() + << "Unsupported docinfo tag: " << x->name_ + << std::endl; + generate_docinfo_html_impl(d, it); + } + } + } + } + + void generate_docinfo_html(html_gen& gen, xml_element* x) + { + if (!x) { + return; + } + + docinfo_gen d(gen); + generate_docinfo_html_impl(d, x); + + if (!d.authors.empty() || !d.editors.empty() || + !d.collabs.empty()) { + gen.printer.html += "<div class=\"authorgroup\">\n"; + QUICKBOOK_FOR (auto const& author, d.authors) { + gen.printer.html += "<h3 class=\"author\">"; + gen.printer.html += author; + gen.printer.html += "</h3>\n"; + } + QUICKBOOK_FOR (auto const& editor, d.editors) { + gen.printer.html += "<h3 class=\"editor\">"; + gen.printer.html += editor; + gen.printer.html += "</h3>\n"; + } + QUICKBOOK_FOR (auto const& collab, d.collabs) { + gen.printer.html += "<h3 class=\"collab\">"; + gen.printer.html += collab; + gen.printer.html += "</h3>\n"; + } + gen.printer.html += "</div>\n"; + } + + QUICKBOOK_FOR (auto const& copyright, d.copyrights) { + gen.printer.html += "<p class=\"copyright\">"; + gen.printer.html += copyright; + gen.printer.html += "</p>"; + } + + QUICKBOOK_FOR (auto const& legalnotice, d.legalnotices) { + gen.printer.html += "<div class=\"legalnotice\">"; + gen.printer.html += legalnotice; + gen.printer.html += "</div>"; + } + } + + void write_file( + html_state& state, + std::string const& generic_path, + std::string const& content) + { + fs::path path = state.options.home_path.parent_path() / + generic_to_path(generic_path); + std::string html = content; + + if (state.options.pretty_print) { + try { + html = post_process(html, -1, -1, true); + } catch (quickbook::post_process_failure&) { + ::quickbook::detail::outerr(path) + << "Post Processing Failed." << std::endl; + ++state.error_count; + } + } + + fs::path parent = path.parent_path(); + if (state.options.chunked_output && !parent.empty() && + !fs::exists(parent)) { + fs::create_directories(parent); + } + + fs::ofstream fileout(path); + + if (fileout.fail()) { + ::quickbook::detail::outerr(path) + << "Error opening output file" << std::endl; + ++state.error_count; + return; + } + + fileout << html; + + if (fileout.fail()) { + ::quickbook::detail::outerr(path) + << "Error writing to output file" << std::endl; + ++state.error_count; + return; + } + } + + std::string get_link_from_path( + html_gen& gen, + quickbook::string_view link, + quickbook::string_view path) + { + if (boost::starts_with(link, "boost:")) { + // TODO: Parameterize the boost location, so that it can use + // relative paths. + string_iterator it = link.begin() + strlen("boost:"); + if (*it == '/') { + ++it; + } + if (!gen.state.options.boost_root_path) { + std::string result = + "http://www.boost.org/doc/libs/release/"; + result.append(it, link.end()); + return result; + } + else { + return relative_path_or_url( + gen, + gen.state.options.boost_root_path / + string_view(it, link.end() - it)); + } + } + + return relative_path_from_url_paths(link, path); + } + + std::string relative_path_or_url(html_gen& gen, path_or_url const& x) + { + assert(x); + if (x.is_url()) { + return x.get_url(); + } + else { + return relative_path_from_fs_paths( + x.get_path(), + gen.state.options.home_path.parent_path() / + gen.path.to_s()); + } + } + + // Note: assume that base is a file, not a directory. + std::string relative_path_from_fs_paths( + fs::path const& p, fs::path const& base) + { + return path_to_generic(path_difference(base.parent_path(), p)); + } + + std::string relative_path_from_url_paths( + quickbook::string_view path, quickbook::string_view base) + { + string_iterator path_it = path.begin(); + string_iterator base_it = base.begin(); + string_iterator path_diff_start = path_it; + string_iterator base_diff_start = base_it; + + for (; path_it != path.end() && base_it != base.end() && + *path_it == *base_it; + ++path_it, ++base_it) { + if (*path_it == '/') { + path_diff_start = path_it + 1; + base_diff_start = base_it + 1; + } + else if (*path_it == '#') { + return std::string(path_it, path.end()); + } + } + + if (base_it == base.end() && path_it != path.end() && + *path_it == '#') { + return std::string(path_it, path.end()); + } + + if (path_it == path.end() && + (base_it == base.end() || *base_it == '#')) { + return std::string("#"); + } + + auto up_count = std::count( + base_diff_start, std::find(base_it, base.end(), '#'), '/'); + + std::string result; + for (int i = 0; i < up_count; ++i) { + result += "../"; + } + result.append(path_diff_start, path.end()); + return result; + } + + // get_id_paths + + ids_type get_id_paths(chunk* chunk) + { + ids_type ids; + if (chunk) { + get_id_paths_impl(ids, chunk); + } + return ids; + } + + void get_id_paths_impl(ids_type& ids, chunk* c) + { + std::string p = c->path_; + if (c->inline_) { + p += '#'; + p += c->id_; + } + ids.emplace(c->id_, id_info(c, 0)); + + get_id_paths_impl2(ids, c, c->title_.root()); + get_id_paths_impl2(ids, c, c->info_.root()); + get_id_paths_impl2(ids, c, c->contents_.root()); + for (chunk* i = c->children(); i; i = i->next()) { + get_id_paths_impl(ids, i); + } + } + + void get_id_paths_impl2(ids_type& ids, chunk* c, xml_element* node) + { + if (!node) { + return; + } + if (node->has_attribute("id")) { + ids.emplace(node->get_attribute("id"), id_info(c, node)); + } + for (xml_element* i = node->children(); i; i = i->next()) { + get_id_paths_impl2(ids, c, i); + } + } + + void tag(html_gen& gen, quickbook::string_view name, xml_element* x) + { + open_tag_with_id(gen, name, x); + generate_children_html(gen, x); + close_tag(gen.printer, name); + } + + void open_tag_with_id( + html_gen& gen, quickbook::string_view name, xml_element* x) + { + tag_start_with_id(gen, name, x); + tag_end(gen.printer); + } + + void tag_self_close( + html_gen& gen, quickbook::string_view name, xml_element* x) + { + tag_start_with_id(gen, name, x); + tag_end_self_close(gen.printer); + } + + void graphics_tag( + html_gen& gen, + quickbook::string_view path, + quickbook::string_view fallback) + { + if (gen.state.options.graphics_path) { + tag_start(gen.printer, "img"); + tag_attribute( + gen.printer, "src", + relative_path_or_url( + gen, gen.state.options.graphics_path / path)); + tag_attribute(gen.printer, "alt", fallback); + tag_end(gen.printer); + } + else { + gen.printer.html.append(fallback.begin(), fallback.end()); + } + } + + void tag_start_with_id( + html_gen& gen, quickbook::string_view name, xml_element* x) + { + tag_start(gen.printer, name); + if (!gen.in_toc) { + if (x->has_attribute("id")) { + tag_attribute(gen.printer, "id", x->get_attribute("id")); + } + } + } + +// Handle boostbook nodes + +#define NODE_RULE(tag_name, gen, x) \ + void BOOST_PP_CAT(parser_, tag_name)(html_gen&, xml_element*); \ + static struct BOOST_PP_CAT(register_parser_type_, tag_name) \ + { \ + BOOST_PP_CAT(register_parser_type_, tag_name)() \ + { \ + node_parsers.emplace( \ + BOOST_PP_STRINGIZE(tag_name), \ + &BOOST_PP_CAT(parser_, tag_name)); \ + } \ + } BOOST_PP_CAT(register_parser_, tag_name); \ + void BOOST_PP_CAT(parser_, tag_name)(html_gen & gen, xml_element * x) + +#define DOCINFO_NODE_RULE(tag_name, category, gen, x) \ + void BOOST_PP_CAT(docinfo_parser_, tag_name)(docinfo_gen&, xml_element*); \ + static struct BOOST_PP_CAT(register_docinfo_parser_type_, tag_name) \ + { \ + BOOST_PP_CAT(register_docinfo_parser_type_, tag_name)() \ + { \ + docinfo_node_parser p = { \ + docinfo_node_parser::category, \ + &BOOST_PP_CAT(docinfo_parser_, tag_name)}; \ + docinfo_node_parsers.emplace(BOOST_PP_STRINGIZE(tag_name), p); \ + } \ + } BOOST_PP_CAT(register_docinfo_parser_, tag_name); \ + void BOOST_PP_CAT(docinfo_parser_, tag_name)( \ + docinfo_gen & gen, xml_element * x) + +#define NODE_MAP(tag_name, html_name) \ + NODE_RULE(tag_name, gen, x) { tag(gen, BOOST_PP_STRINGIZE(html_name), x); } + +#define NODE_MAP_CLASS(tag_name, html_name, class_name) \ + NODE_RULE(tag_name, gen, x) \ + { \ + tag_start_with_id(gen, BOOST_PP_STRINGIZE(html_name), x); \ + tag_attribute(gen.printer, "class", BOOST_PP_STRINGIZE(class_name)); \ + tag_end(gen.printer); \ + generate_children_html(gen, x); \ + close_tag(gen.printer, BOOST_PP_STRINGIZE(html_name)); \ + } + + // TODO: For some reason 'hr' generates an empty paragraph? + NODE_MAP(simpara, div) + NODE_MAP(orderedlist, ol) + NODE_MAP(itemizedlist, ul) + NODE_MAP(listitem, li) + NODE_MAP(blockquote, blockquote) + NODE_MAP(quote, q) + NODE_MAP(code, code) + NODE_MAP(macronname, code) + NODE_MAP(classname, code) + NODE_MAP_CLASS(programlisting, pre, programlisting) + NODE_MAP(literal, tt) + NODE_MAP(subscript, sub) + NODE_MAP(superscript, sup) + NODE_MAP(section, div) + NODE_MAP(anchor, span) + + NODE_MAP(title, h3) + + NODE_MAP_CLASS(warning, div, warning) + NODE_MAP_CLASS(caution, div, caution) + NODE_MAP_CLASS(important, div, important) + NODE_MAP_CLASS(note, div, note) + NODE_MAP_CLASS(tip, div, tip) + NODE_MAP_CLASS(replaceable, em, replaceable) + + NODE_RULE(sidebar, gen, x) + { + auto role = x->get_attribute("role"); + + tag_start_with_id(gen, "div", x); + if (role == "blurb") { + tag_attribute(gen.printer, "class", "blurb"); + } + else { + tag_attribute(gen.printer, "class", "sidebar"); + } + + tag_end(gen.printer); + generate_children_html(gen, x); + close_tag(gen.printer, "div"); + } + + NODE_RULE(sbr, gen, x) + { + if (!x->children()) { + tag_self_close(gen, "br", x); + } + else { + tag(gen, "br", x); + } + } + + NODE_RULE(bridgehead, gen, x) + { + auto renderas = x->get_attribute("renderas"); + char header[3] = "h3"; + if (renderas.size() == 5 && boost::starts_with(renderas, "sect")) { + char l = renderas[4]; + if (l >= '1' && l <= '6') { + header[1] = l; + } + } + return tag(gen, header, x); + } + + NODE_RULE(ulink, gen, x) + { + tag_start_with_id(gen, "a", x); + // TODO: error if missing? + if (x->has_attribute("url")) { + tag_attribute( + gen.printer, "href", + get_link_from_path(gen, x->get_attribute("url"), gen.path)); + } + tag_end(gen.printer); + generate_children_html(gen, x); + close_tag(gen.printer, "a"); + } + + NODE_RULE(link, gen, x) + { + // TODO: error if missing or not found? + auto it = gen.state.ids.end(); + if (x->has_attribute("linkend")) { + it = gen.state.ids.find(x->get_attribute("linkend")); + + if (it == gen.state.ids.end()) { + fs::path docbook("(generated docbook)"); + detail::outwarn(docbook) + << "link not found: " << x->get_attribute("linkend") + << std::endl; + } + } + + tag_start_with_id(gen, "a", x); + if (it != gen.state.ids.end()) { + tag_attribute( + gen.printer, "href", + relative_path_from_url_paths(it->second.path(), gen.path)); + } + tag_end(gen.printer); + generate_children_html(gen, x); + close_tag(gen.printer, "a"); + } + + NODE_RULE(phrase, gen, x) + { + auto role = x->get_attribute("role"); + + tag_start_with_id(gen, "span", x); + if (!role.empty()) { + tag_attribute(gen.printer, "class", role); + } + tag_end(gen.printer); + generate_children_html(gen, x); + close_tag(gen.printer, "span"); + } + + NODE_RULE(para, gen, x) + { + auto role = x->get_attribute("role"); + + tag_start_with_id(gen, "p", x); + if (!role.empty()) { + tag_attribute(gen.printer, "class", role); + } + tag_end(gen.printer); + generate_children_html(gen, x); + close_tag(gen.printer, "p"); + } + + NODE_RULE(emphasis, gen, x) + { + auto role = x->get_attribute("role"); + quickbook::string_view tag_name; + quickbook::string_view class_name; + + if (role.empty()) { + tag_name = "em"; + class_name = "emphasis"; + } + else if (role == "bold" || role == "strong") { + tag_name = "strong"; + class_name = role; + } + else { + class_name = role; + } + tag_start_with_id(gen, "span", x); + if (!class_name.empty()) { + tag_attribute(gen.printer, "class", class_name); + } + tag_end(gen.printer); + if (!tag_name.empty()) { + open_tag(gen.printer, tag_name); + generate_children_html(gen, x); + close_tag(gen.printer, tag_name); + } + else { + generate_children_html(gen, x); + } + close_tag(gen.printer, "span"); + } + + NODE_RULE(inlinemediaobject, gen, x) + { + bool has_image = false; + string_view image; + + // Get image link + for (xml_element* i = x->children(); i; i = i->next()) { + if (i->type_ == xml_element::element_node && + i->name_ == "imageobject") { + for (xml_element* j = i->children(); j; j = j->next()) { + if (j->type_ == xml_element::element_node && + j->name_ == "imagedata") { + if (j->has_attribute("fileref")) { + has_image = true; + image = j->get_attribute("fileref"); + break; + } + } + } + } + } + + std::string alt; + for (xml_element* i = x->children(); i; i = i->next()) { + if (i->type_ == xml_element::element_node && + i->name_ == "textobject") { + for (xml_element* j = i->children(); j; j = j->next()) { + if (j->type_ == xml_element::element_node && + j->name_ == "phrase") { + if (j->get_attribute("role") == "alt") { + html_gen gen2(gen); + generate_tree_html(gen2, j); + alt = gen2.printer.html; + } + } + } + } + } + // TODO: This was in the original php code, not sure why. + if (alt.empty()) { + alt = "[]"; + } + if (has_image) { + tag_start(gen.printer, "span"); + tag_attribute(gen.printer, "class", "inlinemediaobject"); + tag_end(gen.printer); + tag_start_with_id(gen, "img", x); + tag_attribute( + gen.printer, "src", + get_link_from_path(gen, image, gen.path)); + tag_attribute(gen.printer, "alt", alt); + tag_end_self_close(gen.printer); + close_tag(gen.printer, "span"); + } + } + + NODE_RULE(variablelist, gen, x) + { + typedef std::vector<std::pair<xml_element*, xml_element*> > + items_type; + items_type items; + for (xml_element* i = x->children(); i; i = i->next()) { + if (i && i->type_ == xml_element::element_node) { + if (i->name_ == "title") { + // TODO: What to do with titles? + continue; + } + else if (i->name_ == "varlistentry") { + // TODO: What if i has an id? + xml_element* term = 0; + xml_element* listitem = 0; + for (xml_element* j = i->children(); j; j = j->next()) { + if (j && j->type_ == xml_element::element_node) { + if (j->name_ == "term") { + term = j; + } + else if (j->name_ == "listitem") { + listitem = j; + } + } + } + if (term && listitem) { + items.push_back(std::make_pair(term, listitem)); + } + } + } + } + + if (!items.empty()) { + open_tag_with_id(gen, "dl", x); + for (items_type::iterator i = items.begin(); i != items.end(); + ++i) { + tag(gen, "dt", i->first); + tag(gen, "dd", i->second); + } + close_tag(gen.printer, "dl"); + } + } + + void write_table_rows(html_gen& gen, xml_element* x, char const* td_tag) + { + for (xml_element* i = x->children(); i; i = i->next()) { + if (i->type_ == xml_element::element_node && + i->name_ == "row") { + open_tag_with_id(gen, "tr", i); + for (xml_element* j = i->children(); j; j = j->next()) { + if (j->type_ == xml_element::element_node && + j->name_ == "entry") { + auto role = x->get_attribute("role"); + tag_start_with_id(gen, td_tag, j); + if (!role.empty()) { + tag_attribute(gen.printer, "class", role); + } + tag_end(gen.printer); + generate_children_html(gen, j); + close_tag(gen.printer, td_tag); + } + } + close_tag(gen.printer, "tr"); + } + } + } + + void write_table(html_gen& gen, xml_element* x) + { + xml_element* title = 0; + xml_element* tgroup = 0; + xml_element* thead = 0; + xml_element* tbody = 0; + + for (xml_element* i = x->children(); i; i = i->next()) { + if (i->type_ == xml_element::element_node && + i->name_ == "title") { + title = i; + } + if (i->type_ == xml_element::element_node && + i->name_ == "tgroup") { + tgroup = i; + } + } + + if (!tgroup) { + return; + } + + for (xml_element* i = tgroup->children(); i; i = i->next()) { + if (i->type_ == xml_element::element_node && + i->name_ == "thead") { + thead = i; + } + if (i->type_ == xml_element::element_node && + i->name_ == "tbody") { + tbody = i; + } + } + + tag_start_with_id(gen, "div", x); + tag_attribute(gen.printer, "class", x->name_); + tag_end(gen.printer); + open_tag(gen.printer, "table"); + if (title) { + tag(gen, "caption", title); + } + if (thead) { + open_tag(gen.printer, "thead"); + write_table_rows(gen, thead, "th"); + close_tag(gen.printer, "thead"); + } + if (tbody) { + open_tag(gen.printer, "tbody"); + write_table_rows(gen, tbody, "td"); + close_tag(gen.printer, "tbody"); + } + close_tag(gen.printer, "table"); + close_tag(gen.printer, "div"); + } + + NODE_RULE(table, gen, x) { write_table(gen, x); } + NODE_RULE(informaltable, gen, x) { write_table(gen, x); } + + NODE_MAP(calloutlist, div) + + NODE_RULE(callout, gen, x) + { + boost::unordered_map<string_view, callout_data>::const_iterator + data = gen.chunk.callout_numbers.end(); + auto link = gen.state.ids.end(); + if (x->has_attribute("id")) { + data = gen.chunk.callout_numbers.find(x->get_attribute("id")); + } + if (data != gen.chunk.callout_numbers.end() && + !data->second.link_id.empty()) { + link = gen.state.ids.find(data->second.link_id); + } + + open_tag_with_id(gen, "div", x); + if (link != gen.state.ids.end()) { + tag_start(gen.printer, "a"); + tag_attribute( + gen.printer, "href", relative_path_from_url_paths( + link->second.path(), gen.path)); + tag_end(gen.printer); + } + graphics_tag( + gen, + "/callouts/" + + boost::lexical_cast<std::string>(data->second.number) + + ".png", + "(" + boost::lexical_cast<std::string>(data->second.number) + + ")"); + if (link != gen.state.ids.end()) { + close_tag(gen.printer, "a"); + } + gen.printer.html += " "; + generate_children_html(gen, x); + close_tag(gen.printer, "div"); + } + + NODE_RULE(co, gen, x) + { + boost::unordered_map<string_view, callout_data>::const_iterator + data = gen.chunk.callout_numbers.end(); + auto link = gen.state.ids.end(); + if (x->has_attribute("linkends")) { + auto linkends = x->get_attribute("linkends"); + data = gen.chunk.callout_numbers.find(linkends); + link = gen.state.ids.find(linkends); + } + + if (link != gen.state.ids.end()) { + tag_start(gen.printer, "a"); + tag_attribute( + gen.printer, "href", relative_path_from_url_paths( + link->second.path(), gen.path)); + tag_end(gen.printer); + } + if (data != gen.chunk.callout_numbers.end()) { + graphics_tag( + gen, + "/callouts/" + + boost::lexical_cast<std::string>(data->second.number) + + ".png", + "(" + + boost::lexical_cast<std::string>(data->second.number) + + ")"); + } + else { + gen.printer.html += "(0)"; + } + if (link != gen.state.ids.end()) { + close_tag(gen.printer, "a"); + } + } + + NODE_RULE(footnote, gen, x) + { + // TODO: Better id generation.... + static int footnote_number = 0; + ++footnote_number; + std::string footnote_label = + boost::lexical_cast<std::string>(footnote_number); + auto footnote_id = + generate_id(gen.chunk, x, "(((footnote-id)))", "footnote"); + if (!x->has_attribute("id")) { + generate_id(gen.chunk, x, "id", "footnote"); + } + + tag_start_with_id(gen, "a", x); + std::string href = "#"; + href += footnote_id; + tag_attribute(gen.printer, "href", href); + tag_end(gen.printer); + tag_start(gen.printer, "sup"); + tag_attribute(gen.printer, "class", "footnote"); + tag_end(gen.printer); + gen.printer.html += "[" + footnote_label + "]"; + close_tag(gen.printer, "sup"); + close_tag(gen.printer, "a"); + + // Generate HTML to add to footnote. + html_printer printer; + tag_start(printer, "a"); + std::string href2 = "#"; + href2 += x->get_attribute("id"); + tag_attribute(printer, "href", href2); + tag_end(printer); + tag_start(printer, "sup"); + tag_end(printer); + printer.html += "[" + footnote_label + "]"; + close_tag(printer, "sup"); + close_tag(printer, "a"); + printer.html += ' '; + xml_tree_builder builder; + builder.add_element(xml_element::html_node(printer.html)); + + // Find position to insert. + auto pos = x->children(); + for (; pos && pos->type_ == xml_element::element_text; + pos = pos->next()) { + if (pos->contents_.find_first_not_of("\t\n ") != + std::string::npos) { + break; + } + } + if (!pos) { + x->add_first_child(builder.release()); + } + else + switch (pos->type_) { + case xml_element::element_node: + // TODO: Check type of node? Recurse? + pos->add_first_child(builder.release()); + break; + default: + pos->add_before(builder.release()); + break; + } + + gen.chunk.footnotes.push_back(x); + } + + std::string docinfo_get_contents(docinfo_gen& d, xml_element* x) + { + html_gen gen2(d.gen); + generate_children_html(gen2, x); + return gen2.printer.html; + } + + std::string docinfo_get_author(docinfo_gen& d, xml_element* x) + { + auto personname = x->get_child("personname"); + if (personname) { + return docinfo_get_author(d, personname); + } + + std::string name; + + char const* name_parts[] = {"honorific", "firstname", "surname"}; + std::size_t const length = + sizeof(name_parts) / sizeof(name_parts[0]); + for (std::size_t i = 0; i < length; ++i) { + auto child = x->get_child(name_parts[i]); + if (child) { + if (name.size()) { + name += " "; + } + name += docinfo_get_contents(d, child); + } + } + + return name; + } + + // docinfo parsers + + // No support for: + // + // graphic, mediaobject + // modespec + // subjectset, keywordset + // itermset, indexterm + // abbrev + // abstract + // address + // artpagenums + // authorinitials + // bibliomisc, biblioset + // confgroup + // contractnum, contractsponsor + // corpname + // date + // edition + // invpartnumber, isbn, issn, issuenum, biblioid + // orgname + // citebiblioid, citetitle + // bibliosource, bibliorelation, bibliocoverage - Dublin core + // pagenums + // printhistory + // productname, productnumber + // pubdate *** + // publisher, publishername, pubsnumber + // releaseinfo + // revhistory + // seriesvolnums + // title, subtitle, titleabbrev - *** extract into parent? + // volumenum + // personname, honorific, firstname, surname, lineage, othername, + // affiliation, authorblurb, contrib - add to authors? + + DOCINFO_NODE_RULE(copyright, docinfo_general, d, x) + { + std::vector<xml_element*> years; + std::vector<xml_element*> holders; + + for (auto child = x->children(); child; child = child->next()) { + if (child->type_ == xml_element::element_node) { + if (child->name_ == "year") { + years.push_back(child); + } + else if (child->name_ == "holder") { + holders.push_back(child); + } + else { + quickbook::detail::out() + << "Unsupported copyright tag: " << x->name_ + << std::endl; + } + } + } + + // TODO: Format years, e.g. 2005 2006 2007 2010 => 2005-2007, 2010 + + std::string copyright; + QUICKBOOK_FOR (auto year, years) { + if (!copyright.empty()) { + copyright += ", "; + } + copyright += docinfo_get_contents(d, year); + } + bool first = true; + QUICKBOOK_FOR (auto holder, holders) { + if (first) { + if (!copyright.empty()) { + copyright += " "; + } + first = false; + } + else { + copyright += ", "; + } + copyright += docinfo_get_contents(d, holder); + } + d.copyrights.push_back(copyright); + } + + DOCINFO_NODE_RULE(legalnotice, docinfo_general, d, x) + { + d.legalnotices.push_back(docinfo_get_contents(d, x)); + } + + DOCINFO_NODE_RULE(pubdate, docinfo_general, d, x) + { + d.pubdates.push_back(docinfo_get_contents(d, x)); + } + + DOCINFO_NODE_RULE(authorgroup, docinfo_general, d, x) + { + // TODO: Check children are docinfo_author + generate_docinfo_html_impl(d, x); + } + + DOCINFO_NODE_RULE(author, docinfo_author, d, x) + { + d.authors.push_back(docinfo_get_author(d, x)); + } + + DOCINFO_NODE_RULE(editor, docinfo_author, d, x) + { + d.editors.push_back(docinfo_get_author(d, x)); + } + + DOCINFO_NODE_RULE(collab, docinfo_author, d, x) + { + // Ignoring affiliation. + auto collabname = x->get_child("collabname"); + if (collabname) { + d.collabs.push_back(docinfo_get_contents(d, collabname)); + } + } + + DOCINFO_NODE_RULE(corpauthor, docinfo_author, d, x) + { + d.authors.push_back(docinfo_get_contents(d, x)); + } + + DOCINFO_NODE_RULE(corpcredit, docinfo_author, d, x) + { + std::string text = docinfo_get_contents(d, x); + + string_view class_ = x->get_attribute("class"); + if (!class_.empty()) { + text = class_.to_s() + ": " + text; + } + + d.authors.push_back(text); + } + + DOCINFO_NODE_RULE(othercredit, docinfo_author, d, x) + { + std::string text = docinfo_get_author(d, x); + + string_view class_ = x->get_attribute("class"); + if (!class_.empty()) { + text = class_.to_s() + ": " + text; + } + + d.authors.push_back(text); + } + } +} |