1 /*============================================================================= 2 Copyright (c) 2017 Daniel James 3 4 Use, modification and distribution is subject to the Boost Software 5 License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at 6 http://www.boost.org/LICENSE_1_0.txt) 7 =============================================================================*/ 8 9 #include "bb2html.hpp" 10 #include <cassert> 11 #include <vector> 12 #include <boost/algorithm/string/predicate.hpp> 13 #include <boost/filesystem/fstream.hpp> 14 #include <boost/filesystem/operations.hpp> 15 #include <boost/lexical_cast.hpp> 16 #include <boost/preprocessor/cat.hpp> 17 #include <boost/preprocessor/stringize.hpp> 18 #include <boost/unordered_map.hpp> 19 #include <boost/unordered_set.hpp> 20 #include "boostbook_chunker.hpp" 21 #include "files.hpp" 22 #include "for.hpp" 23 #include "html_printer.hpp" 24 #include "path.hpp" 25 #include "post_process.hpp" 26 #include "stream.hpp" 27 #include "utils.hpp" 28 #include "xml_parse.hpp" 29 30 namespace quickbook 31 { 32 namespace fs = boost::filesystem; 33 } 34 35 namespace quickbook 36 { 37 namespace detail 38 { 39 struct html_state; 40 struct html_gen; 41 struct docinfo_gen; 42 struct id_info; 43 44 typedef boost::unordered_map<string_view, id_info> ids_type; 45 46 typedef void (*node_parser)(html_gen&, xml_element*); 47 typedef boost::unordered_map<quickbook::string_view, node_parser> 48 node_parsers_type; 49 static node_parsers_type node_parsers; 50 51 struct docinfo_node_parser 52 { 53 typedef void (*parser_type)(docinfo_gen&, xml_element*); 54 enum docinfo_node_category 55 { 56 docinfo_general = 0, 57 docinfo_author 58 }; 59 60 docinfo_node_category category; 61 parser_type parser; 62 }; 63 typedef boost:: 64 unordered_map<quickbook::string_view, docinfo_node_parser> 65 docinfo_node_pasers_type; 66 static docinfo_node_pasers_type docinfo_node_parsers; 67 68 void generate_chunked_documentation( 69 chunk*, ids_type const&, html_options const&); 70 void generate_chunks(html_state&, chunk*); 71 void generate_chunk_navigation(html_gen&, chunk*); 72 void generate_inline_chunks(html_gen&, chunk*); 73 void generate_chunk_body(html_gen&, chunk*); 74 void generate_toc_html(html_gen& gen, chunk*); 75 void generate_toc_subtree( 76 html_gen& gen, chunk* page, chunk*, unsigned section_depth); 77 void generate_toc_item_html(html_gen&, xml_element*); 78 void generate_footnotes_html(html_gen&); 79 void number_callouts(html_gen& gen, xml_element* x); 80 void number_calloutlist_children( 81 html_gen& gen, unsigned& count, xml_element* x); 82 void generate_docinfo_html(html_gen&, xml_element*); 83 void generate_tree_html(html_gen&, xml_element*); 84 void generate_children_html(html_gen&, xml_element*); 85 void write_file( 86 html_state&, std::string const& path, std::string const& content); 87 std::string get_link_from_path( 88 html_gen&, quickbook::string_view, quickbook::string_view); 89 std::string relative_path_or_url(html_gen&, path_or_url const&); 90 std::string relative_path_from_fs_paths( 91 fs::path const&, fs::path const&); 92 std::string relative_path_from_url_paths( 93 quickbook::string_view, quickbook::string_view); 94 95 ids_type get_id_paths(chunk* chunk); 96 void get_id_paths_impl(ids_type&, chunk*); 97 void get_id_paths_impl2(ids_type&, chunk*, xml_element*); 98 99 void tag(html_gen& gen, quickbook::string_view name, xml_element* x); 100 void tag_start_with_id( 101 html_gen& gen, quickbook::string_view name, xml_element* x); 102 void open_tag_with_id( 103 html_gen& gen, quickbook::string_view name, xml_element* x); 104 void tag_self_close( 105 html_gen& gen, quickbook::string_view name, xml_element* x); 106 void graphics_tag( 107 html_gen& gen, 108 quickbook::string_view path, 109 quickbook::string_view fallback); 110 111 struct id_info 112 { 113 private: 114 chunk* chunk_; 115 xml_element* element_; 116 117 public: id_infoquickbook::detail::id_info118 explicit id_info(chunk* c, xml_element* x) : chunk_(c), element_(x) 119 { 120 assert(c); 121 assert(!x || x->has_attribute("id")); 122 } 123 pathquickbook::detail::id_info124 std::string path() const 125 { 126 std::string p = chunk_->path_; 127 128 if (element_) { 129 p += '#'; 130 p += element_->get_attribute("id"); 131 } 132 else if (chunk_->inline_) { 133 p += '#'; 134 p += chunk_->id_; 135 } 136 return p; 137 } 138 }; 139 140 struct html_state 141 { 142 ids_type const& ids; 143 html_options const& options; 144 unsigned int error_count; 145 html_statequickbook::detail::html_state146 explicit html_state( 147 ids_type const& ids_, html_options const& options_) 148 : ids(ids_), options(options_), error_count(0) 149 { 150 } 151 }; 152 153 struct callout_data 154 { 155 quickbook::string_view link_id; 156 unsigned number; 157 }; 158 159 struct chunk_state 160 { 161 std::vector<xml_element*> footnotes; 162 boost::unordered_map<string_view, callout_data> callout_numbers; 163 boost::unordered_set<string_view> fragment_ids; 164 }; 165 166 struct html_gen 167 { 168 html_printer printer; 169 html_state& state; 170 chunk_state& chunk; 171 string_view path; 172 bool in_toc; 173 html_genquickbook::detail::html_gen174 explicit html_gen( 175 html_state& state_, chunk_state& chunk_, string_view p) 176 : printer() 177 , state(state_) 178 , chunk(chunk_) 179 , path(p) 180 , in_toc(false) 181 { 182 } 183 html_genquickbook::detail::html_gen184 html_gen(html_gen const& x) 185 : printer() 186 , state(x.state) 187 , chunk(x.chunk) 188 , path(x.path) 189 , in_toc(false) 190 { 191 } 192 }; 193 194 struct docinfo_gen 195 { 196 html_gen& gen; 197 std::vector<std::string> copyrights; 198 std::vector<std::string> pubdates; 199 std::vector<std::string> legalnotices; 200 std::vector<std::string> authors; 201 std::vector<std::string> editors; 202 std::vector<std::string> collabs; 203 docinfo_genquickbook::detail::docinfo_gen204 docinfo_gen(html_gen& gen_) : gen(gen_) {} 205 }; 206 boostbook_to_html(quickbook::string_view source,html_options const & options)207 int boostbook_to_html( 208 quickbook::string_view source, html_options const& options) 209 { 210 xml_tree tree; 211 try { 212 tree = xml_parse(source); 213 } catch (quickbook::detail::xml_parse_error e) { 214 string_view source_view(source); 215 file_position p = relative_position(source_view.begin(), e.pos); 216 string_view::iterator line_start = 217 e.pos - (p.column < 40 ? p.column - 1 : 39); 218 string_view::iterator line_end = 219 std::find(e.pos, source_view.end(), '\n'); 220 if (line_end - e.pos > 80) { 221 line_end = e.pos + 80; 222 } 223 std::string indent; 224 for (auto i = e.pos - line_start; i; --i) { 225 indent += ' '; 226 } 227 ::quickbook::detail::outerr() 228 << "converting boostbook at line " << p.line << " char " 229 << p.column << ": " << e.message << "\n" 230 << string_view(line_start, line_end - line_start) << "\n" 231 << indent << "^" 232 << "\n\n"; 233 234 return 1; 235 } 236 237 chunk_tree chunked = chunk_document(tree); 238 // Overwrite paths depending on whether output is chunked or not. 239 // Really want to do something better, e.g. incorporate many section 240 // chunks into their parent. 241 chunked.root()->path_ = 242 path_to_generic(options.home_path.filename()); 243 if (options.chunked_output) { 244 inline_sections(chunked.root(), 0); 245 246 // Create the root directory if necessary for chunked 247 // documentation. 248 fs::path parent = options.home_path.parent_path(); 249 if (!parent.empty() && !fs::exists(parent)) { 250 fs::create_directory(parent); 251 } 252 } 253 else { 254 inline_all(chunked.root()); 255 } 256 ids_type ids = get_id_paths(chunked.root()); 257 html_state state(ids, options); 258 if (chunked.root()) { 259 generate_chunks(state, chunked.root()); 260 } 261 return state.error_count; 262 } 263 gather_chunk_ids(chunk_state & c_state,xml_element * x)264 void gather_chunk_ids(chunk_state& c_state, xml_element* x) 265 { 266 if (!x) { 267 return; 268 } 269 if (x->has_attribute("id")) { 270 c_state.fragment_ids.emplace(x->get_attribute("id")); 271 } 272 for (auto it = x->children(); it; it = it->next()) { 273 gather_chunk_ids(c_state, it); 274 } 275 } 276 gather_chunk_ids(chunk_state & c_state,chunk * x)277 void gather_chunk_ids(chunk_state& c_state, chunk* x) 278 { 279 gather_chunk_ids(c_state, x->contents_.root()); 280 gather_chunk_ids(c_state, x->title_.root()); 281 gather_chunk_ids(c_state, x->info_.root()); 282 283 for (chunk* it = x->children(); it && it->inline_; 284 it = it->next()) { 285 gather_chunk_ids(c_state, it); 286 } 287 } 288 generate_id(chunk_state & c_state,xml_element * x,string_view name,string_view base)289 string_view generate_id( 290 chunk_state& c_state, 291 xml_element* x, 292 string_view name, 293 string_view base) 294 { 295 std::string result; 296 result.reserve(base.size() + 2); 297 result.assign(base.begin(), base.end()); 298 result += '-'; 299 // TODO: Share implementation with id_generation.cpp? 300 for (unsigned count = 1;; ++count) { 301 auto num = boost::lexical_cast<std::string>(count); 302 result.reserve(base.size() + 1 + num.size()); 303 result.erase(base.size() + 1); 304 result += num; 305 if (c_state.fragment_ids.find(result) == 306 c_state.fragment_ids.end()) { 307 auto r = x->set_attribute(name, result); 308 c_state.fragment_ids.emplace(r); 309 return r; 310 } 311 } 312 } 313 generate_chunks(html_state & state,chunk * x)314 void generate_chunks(html_state& state, chunk* x) 315 { 316 chunk_state c_state; 317 gather_chunk_ids(c_state, x); 318 html_gen gen(state, c_state, x->path_); 319 gen.printer.html += "<!DOCTYPE html>\n"; 320 open_tag(gen.printer, "html"); 321 open_tag(gen.printer, "head"); 322 if (state.options.css_path) { 323 tag_start(gen.printer, "link"); 324 tag_attribute(gen.printer, "rel", "stylesheet"); 325 tag_attribute(gen.printer, "type", "text/css"); 326 tag_attribute( 327 gen.printer, "href", 328 relative_path_or_url(gen, state.options.css_path)); 329 tag_end_self_close(gen.printer); 330 } 331 close_tag(gen.printer, "head"); 332 open_tag(gen.printer, "body"); 333 generate_chunk_navigation(gen, x); 334 generate_chunk_body(gen, x); 335 chunk* it = x->children(); 336 for (; it && it->inline_; it = it->next()) { 337 generate_inline_chunks(gen, it); 338 } 339 generate_footnotes_html(gen); 340 close_tag(gen.printer, "body"); 341 close_tag(gen.printer, "html"); 342 write_file(state, x->path_, gen.printer.html); 343 for (; it; it = it->next()) { 344 assert(!it->inline_); 345 generate_chunks(state, it); 346 } 347 } 348 generate_chunk_navigation(html_gen & gen,chunk * x)349 void generate_chunk_navigation(html_gen& gen, chunk* x) 350 { 351 chunk* next = 0; 352 for (chunk* it = x->children(); it; it = it->next()) { 353 if (!it->inline_) { 354 next = it; 355 break; 356 } 357 } 358 if (!next) { 359 next = x->next(); 360 } 361 362 chunk* prev = x->prev(); 363 if (prev) { 364 while (prev->children()) { 365 for (prev = prev->children(); prev->next(); 366 prev = prev->next()) { 367 } 368 } 369 } 370 else { 371 prev = x->parent(); 372 } 373 374 if (next || prev || x->parent()) { 375 tag_start(gen.printer, "div"); 376 tag_attribute(gen.printer, "class", "spirit-nav"); 377 tag_end(gen.printer); 378 if (prev) { 379 tag_start(gen.printer, "a"); 380 tag_attribute( 381 gen.printer, "href", 382 get_link_from_path(gen, prev->path_, x->path_)); 383 tag_attribute(gen.printer, "accesskey", "p"); 384 tag_end(gen.printer); 385 graphics_tag(gen, "/prev.png", "prev"); 386 close_tag(gen.printer, "a"); 387 gen.printer.html += " "; 388 } 389 if (x->parent()) { 390 tag_start(gen.printer, "a"); 391 tag_attribute( 392 gen.printer, "href", 393 get_link_from_path(gen, x->parent()->path_, x->path_)); 394 tag_attribute(gen.printer, "accesskey", "u"); 395 tag_end(gen.printer); 396 graphics_tag(gen, "/up.png", "up"); 397 close_tag(gen.printer, "a"); 398 gen.printer.html += " "; 399 400 tag_start(gen.printer, "a"); 401 tag_attribute( 402 gen.printer, "href", 403 get_link_from_path(gen, "index.html", x->path_)); 404 tag_attribute(gen.printer, "accesskey", "h"); 405 tag_end(gen.printer); 406 graphics_tag(gen, "/home.png", "home"); 407 close_tag(gen.printer, "a"); 408 if (next) { 409 gen.printer.html += " "; 410 } 411 } 412 if (next) { 413 tag_start(gen.printer, "a"); 414 tag_attribute( 415 gen.printer, "href", 416 get_link_from_path(gen, next->path_, x->path_)); 417 tag_attribute(gen.printer, "accesskey", "n"); 418 tag_end(gen.printer); 419 graphics_tag(gen, "/next.png", "next"); 420 close_tag(gen.printer, "a"); 421 } 422 close_tag(gen.printer, "div"); 423 } 424 } 425 generate_inline_chunks(html_gen & gen,chunk * x)426 void generate_inline_chunks(html_gen& gen, chunk* x) 427 { 428 tag_start(gen.printer, "div"); 429 tag_attribute(gen.printer, "id", x->id_); 430 tag_end(gen.printer); 431 generate_chunk_body(gen, x); 432 for (chunk* it = x->children(); it; it = it->next()) { 433 assert(it->inline_); 434 generate_inline_chunks(gen, it); 435 } 436 close_tag(gen.printer, "div"); 437 } 438 generate_chunk_body(html_gen & gen,chunk * x)439 void generate_chunk_body(html_gen& gen, chunk* x) 440 { 441 gen.chunk.callout_numbers.clear(); 442 443 number_callouts(gen, x->title_.root()); 444 number_callouts(gen, x->info_.root()); 445 number_callouts(gen, x->contents_.root()); 446 447 generate_tree_html(gen, x->title_.root()); 448 generate_docinfo_html(gen, x->info_.root()); 449 generate_toc_html(gen, x); 450 generate_tree_html(gen, x->contents_.root()); 451 } 452 generate_toc_html(html_gen & gen,chunk * x)453 void generate_toc_html(html_gen& gen, chunk* x) 454 { 455 if (x->children() && x->contents_.root()->name_ != "section") { 456 tag_start(gen.printer, "div"); 457 tag_attribute(gen.printer, "class", "toc"); 458 tag_end(gen.printer); 459 open_tag(gen.printer, "p"); 460 open_tag(gen.printer, "b"); 461 gen.printer.html += "Table of contents"; 462 close_tag(gen.printer, "b"); 463 close_tag(gen.printer, "p"); 464 generate_toc_subtree(gen, x, x, 1); 465 close_tag(gen.printer, "div"); 466 } 467 } 468 generate_toc_subtree(html_gen & gen,chunk * page,chunk * x,unsigned section_depth)469 void generate_toc_subtree( 470 html_gen& gen, chunk* page, chunk* x, unsigned section_depth) 471 { 472 if (x != page && section_depth == 0) { 473 bool has_non_section_child = false; 474 for (chunk* it = x->children(); it; it = it->next()) { 475 if (it->contents_.root()->name_ != "section") { 476 has_non_section_child = true; 477 } 478 } 479 if (!has_non_section_child) { 480 return; 481 } 482 } 483 484 gen.printer.html += "<ul>"; 485 for (chunk* it = x->children(); it; it = it->next()) { 486 auto link = gen.state.ids.find(it->id_); 487 gen.printer.html += "<li>"; 488 if (link != gen.state.ids.end()) { 489 gen.printer.html += "<a href=\""; 490 gen.printer.html += encode_string(get_link_from_path( 491 gen, link->second.path(), page->path_)); 492 gen.printer.html += "\">"; 493 generate_toc_item_html(gen, it->title_.root()); 494 gen.printer.html += "</a>"; 495 } 496 else { 497 generate_toc_item_html(gen, it->title_.root()); 498 } 499 if (it->children()) { 500 generate_toc_subtree( 501 gen, page, it, 502 it->contents_.root()->name_ == "section" && 503 section_depth > 0 504 ? section_depth - 1 505 : section_depth); 506 } 507 gen.printer.html += "</li>"; 508 } 509 gen.printer.html += "</ul>"; 510 } 511 generate_toc_item_html(html_gen & gen,xml_element * x)512 void generate_toc_item_html(html_gen& gen, xml_element* x) 513 { 514 if (x) { 515 bool old = gen.in_toc; 516 gen.in_toc = true; 517 generate_children_html(gen, x); 518 gen.in_toc = old; 519 } 520 else { 521 gen.printer.html += "<i>Untitled</i>"; 522 } 523 } 524 generate_footnotes_html(html_gen & gen)525 void generate_footnotes_html(html_gen& gen) 526 { 527 if (!gen.chunk.footnotes.empty()) { 528 tag_start(gen.printer, "div"); 529 tag_attribute(gen.printer, "class", "footnotes"); 530 tag_end(gen.printer); 531 gen.printer.html += "<br/>"; 532 gen.printer.html += "<hr/>"; 533 for (std::vector<xml_element*>::iterator it = 534 gen.chunk.footnotes.begin(); 535 it != gen.chunk.footnotes.end(); ++it) { 536 auto footnote_id = 537 (*it)->get_attribute("(((footnote-id)))"); 538 tag_start(gen.printer, "div"); 539 tag_attribute(gen.printer, "id", footnote_id); 540 tag_attribute(gen.printer, "class", "footnote"); 541 tag_end(gen.printer); 542 543 generate_children_html(gen, *it); 544 close_tag(gen.printer, "div"); 545 } 546 close_tag(gen.printer, "div"); 547 } 548 } 549 number_callouts(html_gen & gen,xml_element * x)550 void number_callouts(html_gen& gen, xml_element* x) 551 { 552 if (!x) { 553 return; 554 } 555 556 if (x->type_ == xml_element::element_node) { 557 if (x->name_ == "calloutlist") { 558 unsigned count = 0; 559 number_calloutlist_children(gen, count, x); 560 } 561 else if (x->name_ == "co") { 562 if (x->has_attribute("linkends")) { 563 auto linkends = x->get_attribute("linkends"); 564 if (!x->has_attribute("id")) { 565 generate_id(gen.chunk, x, "id", linkends); 566 } 567 gen.chunk.callout_numbers[linkends].link_id = 568 x->get_attribute("id"); 569 } 570 } 571 } 572 for (xml_element* it = x->children(); it; it = it->next()) { 573 number_callouts(gen, it); 574 } 575 } 576 number_calloutlist_children(html_gen & gen,unsigned & count,xml_element * x)577 void number_calloutlist_children( 578 html_gen& gen, unsigned& count, xml_element* x) 579 { 580 for (xml_element* it = x->children(); it; it = it->next()) { 581 if (it->type_ == xml_element::element_node && 582 it->name_ == "callout") { 583 if (it->has_attribute("id")) { 584 gen.chunk.callout_numbers[it->get_attribute("id")] 585 .number = ++count; 586 } 587 } 588 number_calloutlist_children(gen, count, it); 589 } 590 } 591 generate_tree_html(html_gen & gen,xml_element * x)592 void generate_tree_html(html_gen& gen, xml_element* x) 593 { 594 if (!x) { 595 return; 596 } 597 switch (x->type_) { 598 case xml_element::element_text: { 599 gen.printer.html += x->contents_; 600 break; 601 } 602 case xml_element::element_html: { 603 gen.printer.html += x->contents_; 604 break; 605 } 606 case xml_element::element_node: { 607 node_parsers_type::iterator parser = 608 node_parsers.find(x->name_); 609 if (parser != node_parsers.end()) { 610 parser->second(gen, x); 611 } 612 else { 613 quickbook::detail::out() 614 << "Unsupported tag: " << x->name_ << std::endl; 615 generate_children_html(gen, x); 616 } 617 break; 618 } 619 default: 620 assert(false); 621 } 622 } 623 generate_children_html(html_gen & gen,xml_element * x)624 void generate_children_html(html_gen& gen, xml_element* x) 625 { 626 for (xml_element* it = x->children(); it; it = it->next()) { 627 generate_tree_html(gen, it); 628 } 629 } 630 generate_docinfo_html_impl(docinfo_gen & d,xml_element * x)631 void generate_docinfo_html_impl(docinfo_gen& d, xml_element* x) 632 { 633 for (xml_element* it = x->children(); it; it = it->next()) { 634 if (it->type_ == xml_element::element_node) { 635 auto parser = docinfo_node_parsers.find(it->name_); 636 if (parser != docinfo_node_parsers.end()) { 637 parser->second.parser(d, it); 638 } 639 else { 640 quickbook::detail::out() 641 << "Unsupported docinfo tag: " << x->name_ 642 << std::endl; 643 generate_docinfo_html_impl(d, it); 644 } 645 } 646 } 647 } 648 generate_docinfo_html(html_gen & gen,xml_element * x)649 void generate_docinfo_html(html_gen& gen, xml_element* x) 650 { 651 if (!x) { 652 return; 653 } 654 655 docinfo_gen d(gen); 656 generate_docinfo_html_impl(d, x); 657 658 if (!d.authors.empty() || !d.editors.empty() || 659 !d.collabs.empty()) { 660 gen.printer.html += "<div class=\"authorgroup\">\n"; 661 QUICKBOOK_FOR (auto const& author, d.authors) { 662 gen.printer.html += "<h3 class=\"author\">"; 663 gen.printer.html += author; 664 gen.printer.html += "</h3>\n"; 665 } 666 QUICKBOOK_FOR (auto const& editor, d.editors) { 667 gen.printer.html += "<h3 class=\"editor\">"; 668 gen.printer.html += editor; 669 gen.printer.html += "</h3>\n"; 670 } 671 QUICKBOOK_FOR (auto const& collab, d.collabs) { 672 gen.printer.html += "<h3 class=\"collab\">"; 673 gen.printer.html += collab; 674 gen.printer.html += "</h3>\n"; 675 } 676 gen.printer.html += "</div>\n"; 677 } 678 679 QUICKBOOK_FOR (auto const& copyright, d.copyrights) { 680 gen.printer.html += "<p class=\"copyright\">"; 681 gen.printer.html += copyright; 682 gen.printer.html += "</p>"; 683 } 684 685 QUICKBOOK_FOR (auto const& legalnotice, d.legalnotices) { 686 gen.printer.html += "<div class=\"legalnotice\">"; 687 gen.printer.html += legalnotice; 688 gen.printer.html += "</div>"; 689 } 690 } 691 write_file(html_state & state,std::string const & generic_path,std::string const & content)692 void write_file( 693 html_state& state, 694 std::string const& generic_path, 695 std::string const& content) 696 { 697 fs::path path = state.options.home_path.parent_path() / 698 generic_to_path(generic_path); 699 std::string html = content; 700 701 if (state.options.pretty_print) { 702 try { 703 html = post_process(html, -1, -1, true); 704 } catch (quickbook::post_process_failure&) { 705 ::quickbook::detail::outerr(path) 706 << "Post Processing Failed." << std::endl; 707 ++state.error_count; 708 } 709 } 710 711 fs::path parent = path.parent_path(); 712 if (state.options.chunked_output && !parent.empty() && 713 !fs::exists(parent)) { 714 fs::create_directories(parent); 715 } 716 717 fs::ofstream fileout(path); 718 719 if (fileout.fail()) { 720 ::quickbook::detail::outerr(path) 721 << "Error opening output file" << std::endl; 722 ++state.error_count; 723 return; 724 } 725 726 fileout << html; 727 728 if (fileout.fail()) { 729 ::quickbook::detail::outerr(path) 730 << "Error writing to output file" << std::endl; 731 ++state.error_count; 732 return; 733 } 734 } 735 get_link_from_path(html_gen & gen,quickbook::string_view link,quickbook::string_view path)736 std::string get_link_from_path( 737 html_gen& gen, 738 quickbook::string_view link, 739 quickbook::string_view path) 740 { 741 if (boost::starts_with(link, "boost:")) { 742 // TODO: Parameterize the boost location, so that it can use 743 // relative paths. 744 string_iterator it = link.begin() + strlen("boost:"); 745 if (*it == '/') { 746 ++it; 747 } 748 if (!gen.state.options.boost_root_path) { 749 std::string result = 750 "http://www.boost.org/doc/libs/release/"; 751 result.append(it, link.end()); 752 return result; 753 } 754 else { 755 return relative_path_or_url( 756 gen, 757 gen.state.options.boost_root_path / 758 string_view(it, link.end() - it)); 759 } 760 } 761 762 return relative_path_from_url_paths(link, path); 763 } 764 relative_path_or_url(html_gen & gen,path_or_url const & x)765 std::string relative_path_or_url(html_gen& gen, path_or_url const& x) 766 { 767 assert(x); 768 if (x.is_url()) { 769 return x.get_url(); 770 } 771 else { 772 return relative_path_from_fs_paths( 773 x.get_path(), 774 gen.state.options.home_path.parent_path() / 775 gen.path.to_s()); 776 } 777 } 778 779 // Note: assume that base is a file, not a directory. relative_path_from_fs_paths(fs::path const & p,fs::path const & base)780 std::string relative_path_from_fs_paths( 781 fs::path const& p, fs::path const& base) 782 { 783 return path_to_generic(path_difference(base.parent_path(), p)); 784 } 785 relative_path_from_url_paths(quickbook::string_view path,quickbook::string_view base)786 std::string relative_path_from_url_paths( 787 quickbook::string_view path, quickbook::string_view base) 788 { 789 string_iterator path_it = path.begin(); 790 string_iterator base_it = base.begin(); 791 string_iterator path_diff_start = path_it; 792 string_iterator base_diff_start = base_it; 793 794 for (; path_it != path.end() && base_it != base.end() && 795 *path_it == *base_it; 796 ++path_it, ++base_it) { 797 if (*path_it == '/') { 798 path_diff_start = path_it + 1; 799 base_diff_start = base_it + 1; 800 } 801 else if (*path_it == '#') { 802 return std::string(path_it, path.end()); 803 } 804 } 805 806 if (base_it == base.end() && path_it != path.end() && 807 *path_it == '#') { 808 return std::string(path_it, path.end()); 809 } 810 811 if (path_it == path.end() && 812 (base_it == base.end() || *base_it == '#')) { 813 return std::string("#"); 814 } 815 816 auto up_count = std::count( 817 base_diff_start, std::find(base_it, base.end(), '#'), '/'); 818 819 std::string result; 820 for (int i = 0; i < up_count; ++i) { 821 result += "../"; 822 } 823 result.append(path_diff_start, path.end()); 824 return result; 825 } 826 827 // get_id_paths 828 get_id_paths(chunk * chunk)829 ids_type get_id_paths(chunk* chunk) 830 { 831 ids_type ids; 832 if (chunk) { 833 get_id_paths_impl(ids, chunk); 834 } 835 return ids; 836 } 837 get_id_paths_impl(ids_type & ids,chunk * c)838 void get_id_paths_impl(ids_type& ids, chunk* c) 839 { 840 std::string p = c->path_; 841 if (c->inline_) { 842 p += '#'; 843 p += c->id_; 844 } 845 ids.emplace(c->id_, id_info(c, 0)); 846 847 get_id_paths_impl2(ids, c, c->title_.root()); 848 get_id_paths_impl2(ids, c, c->info_.root()); 849 get_id_paths_impl2(ids, c, c->contents_.root()); 850 for (chunk* i = c->children(); i; i = i->next()) { 851 get_id_paths_impl(ids, i); 852 } 853 } 854 get_id_paths_impl2(ids_type & ids,chunk * c,xml_element * node)855 void get_id_paths_impl2(ids_type& ids, chunk* c, xml_element* node) 856 { 857 if (!node) { 858 return; 859 } 860 if (node->has_attribute("id")) { 861 ids.emplace(node->get_attribute("id"), id_info(c, node)); 862 } 863 for (xml_element* i = node->children(); i; i = i->next()) { 864 get_id_paths_impl2(ids, c, i); 865 } 866 } 867 tag(html_gen & gen,quickbook::string_view name,xml_element * x)868 void tag(html_gen& gen, quickbook::string_view name, xml_element* x) 869 { 870 open_tag_with_id(gen, name, x); 871 generate_children_html(gen, x); 872 close_tag(gen.printer, name); 873 } 874 open_tag_with_id(html_gen & gen,quickbook::string_view name,xml_element * x)875 void open_tag_with_id( 876 html_gen& gen, quickbook::string_view name, xml_element* x) 877 { 878 tag_start_with_id(gen, name, x); 879 tag_end(gen.printer); 880 } 881 tag_self_close(html_gen & gen,quickbook::string_view name,xml_element * x)882 void tag_self_close( 883 html_gen& gen, quickbook::string_view name, xml_element* x) 884 { 885 tag_start_with_id(gen, name, x); 886 tag_end_self_close(gen.printer); 887 } 888 graphics_tag(html_gen & gen,quickbook::string_view path,quickbook::string_view fallback)889 void graphics_tag( 890 html_gen& gen, 891 quickbook::string_view path, 892 quickbook::string_view fallback) 893 { 894 if (gen.state.options.graphics_path) { 895 tag_start(gen.printer, "img"); 896 tag_attribute( 897 gen.printer, "src", 898 relative_path_or_url( 899 gen, gen.state.options.graphics_path / path)); 900 tag_attribute(gen.printer, "alt", fallback); 901 tag_end(gen.printer); 902 } 903 else { 904 gen.printer.html.append(fallback.begin(), fallback.end()); 905 } 906 } 907 tag_start_with_id(html_gen & gen,quickbook::string_view name,xml_element * x)908 void tag_start_with_id( 909 html_gen& gen, quickbook::string_view name, xml_element* x) 910 { 911 tag_start(gen.printer, name); 912 if (!gen.in_toc) { 913 if (x->has_attribute("id")) { 914 tag_attribute(gen.printer, "id", x->get_attribute("id")); 915 } 916 } 917 } 918 919 // Handle boostbook nodes 920 921 #define NODE_RULE(tag_name, gen, x) \ 922 void BOOST_PP_CAT(parser_, tag_name)(html_gen&, xml_element*); \ 923 static struct BOOST_PP_CAT(register_parser_type_, tag_name) \ 924 { \ 925 BOOST_PP_CAT(register_parser_type_, tag_name)() \ 926 { \ 927 node_parsers.emplace( \ 928 BOOST_PP_STRINGIZE(tag_name), \ 929 &BOOST_PP_CAT(parser_, tag_name)); \ 930 } \ 931 } BOOST_PP_CAT(register_parser_, tag_name); \ 932 void BOOST_PP_CAT(parser_, tag_name)(html_gen & gen, xml_element * x) 933 934 #define DOCINFO_NODE_RULE(tag_name, category, gen, x) \ 935 void BOOST_PP_CAT(docinfo_parser_, tag_name)(docinfo_gen&, xml_element*); \ 936 static struct BOOST_PP_CAT(register_docinfo_parser_type_, tag_name) \ 937 { \ 938 BOOST_PP_CAT(register_docinfo_parser_type_, tag_name)() \ 939 { \ 940 docinfo_node_parser p = { \ 941 docinfo_node_parser::category, \ 942 &BOOST_PP_CAT(docinfo_parser_, tag_name)}; \ 943 docinfo_node_parsers.emplace(BOOST_PP_STRINGIZE(tag_name), p); \ 944 } \ 945 } BOOST_PP_CAT(register_docinfo_parser_, tag_name); \ 946 void BOOST_PP_CAT(docinfo_parser_, tag_name)( \ 947 docinfo_gen & gen, xml_element * x) 948 949 #define NODE_MAP(tag_name, html_name) \ 950 NODE_RULE(tag_name, gen, x) { tag(gen, BOOST_PP_STRINGIZE(html_name), x); } 951 952 #define NODE_MAP_CLASS(tag_name, html_name, class_name) \ 953 NODE_RULE(tag_name, gen, x) \ 954 { \ 955 tag_start_with_id(gen, BOOST_PP_STRINGIZE(html_name), x); \ 956 tag_attribute(gen.printer, "class", BOOST_PP_STRINGIZE(class_name)); \ 957 tag_end(gen.printer); \ 958 generate_children_html(gen, x); \ 959 close_tag(gen.printer, BOOST_PP_STRINGIZE(html_name)); \ 960 } 961 962 // TODO: For some reason 'hr' generates an empty paragraph? NODE_MAP(simpara,div)963 NODE_MAP(simpara, div) 964 NODE_MAP(orderedlist, ol) 965 NODE_MAP(itemizedlist, ul) 966 NODE_MAP(listitem, li) 967 NODE_MAP(blockquote, blockquote) 968 NODE_MAP(quote, q) 969 NODE_MAP(code, code) 970 NODE_MAP(macronname, code) 971 NODE_MAP(classname, code) 972 NODE_MAP_CLASS(programlisting, pre, programlisting) 973 NODE_MAP(literal, tt) 974 NODE_MAP(subscript, sub) 975 NODE_MAP(superscript, sup) 976 NODE_MAP(section, div) 977 NODE_MAP(anchor, span) 978 979 NODE_MAP(title, h3) 980 981 NODE_MAP_CLASS(warning, div, warning) 982 NODE_MAP_CLASS(caution, div, caution) 983 NODE_MAP_CLASS(important, div, important) 984 NODE_MAP_CLASS(note, div, note) 985 NODE_MAP_CLASS(tip, div, tip) 986 NODE_MAP_CLASS(replaceable, em, replaceable) 987 988 NODE_RULE(sidebar, gen, x) 989 { 990 auto role = x->get_attribute("role"); 991 992 tag_start_with_id(gen, "div", x); 993 if (role == "blurb") { 994 tag_attribute(gen.printer, "class", "blurb"); 995 } 996 else { 997 tag_attribute(gen.printer, "class", "sidebar"); 998 } 999 1000 tag_end(gen.printer); 1001 generate_children_html(gen, x); 1002 close_tag(gen.printer, "div"); 1003 } 1004 NODE_RULE(sbr,gen,x)1005 NODE_RULE(sbr, gen, x) 1006 { 1007 if (!x->children()) { 1008 tag_self_close(gen, "br", x); 1009 } 1010 else { 1011 tag(gen, "br", x); 1012 } 1013 } 1014 NODE_RULE(bridgehead,gen,x)1015 NODE_RULE(bridgehead, gen, x) 1016 { 1017 auto renderas = x->get_attribute("renderas"); 1018 char header[3] = "h3"; 1019 if (renderas.size() == 5 && boost::starts_with(renderas, "sect")) { 1020 char l = renderas[4]; 1021 if (l >= '1' && l <= '6') { 1022 header[1] = l; 1023 } 1024 } 1025 return tag(gen, header, x); 1026 } 1027 NODE_RULE(ulink,gen,x)1028 NODE_RULE(ulink, gen, x) 1029 { 1030 tag_start_with_id(gen, "a", x); 1031 // TODO: error if missing? 1032 if (x->has_attribute("url")) { 1033 tag_attribute( 1034 gen.printer, "href", 1035 get_link_from_path(gen, x->get_attribute("url"), gen.path)); 1036 } 1037 tag_end(gen.printer); 1038 generate_children_html(gen, x); 1039 close_tag(gen.printer, "a"); 1040 } 1041 NODE_RULE(link,gen,x)1042 NODE_RULE(link, gen, x) 1043 { 1044 // TODO: error if missing or not found? 1045 auto it = gen.state.ids.end(); 1046 if (x->has_attribute("linkend")) { 1047 it = gen.state.ids.find(x->get_attribute("linkend")); 1048 1049 if (it == gen.state.ids.end()) { 1050 fs::path docbook("(generated docbook)"); 1051 detail::outwarn(docbook) 1052 << "link not found: " << x->get_attribute("linkend") 1053 << std::endl; 1054 } 1055 } 1056 1057 tag_start_with_id(gen, "a", x); 1058 if (it != gen.state.ids.end()) { 1059 tag_attribute( 1060 gen.printer, "href", 1061 relative_path_from_url_paths(it->second.path(), gen.path)); 1062 } 1063 tag_end(gen.printer); 1064 generate_children_html(gen, x); 1065 close_tag(gen.printer, "a"); 1066 } 1067 NODE_RULE(phrase,gen,x)1068 NODE_RULE(phrase, gen, x) 1069 { 1070 auto role = x->get_attribute("role"); 1071 1072 tag_start_with_id(gen, "span", x); 1073 if (!role.empty()) { 1074 tag_attribute(gen.printer, "class", role); 1075 } 1076 tag_end(gen.printer); 1077 generate_children_html(gen, x); 1078 close_tag(gen.printer, "span"); 1079 } 1080 NODE_RULE(para,gen,x)1081 NODE_RULE(para, gen, x) 1082 { 1083 auto role = x->get_attribute("role"); 1084 1085 tag_start_with_id(gen, "p", x); 1086 if (!role.empty()) { 1087 tag_attribute(gen.printer, "class", role); 1088 } 1089 tag_end(gen.printer); 1090 generate_children_html(gen, x); 1091 close_tag(gen.printer, "p"); 1092 } 1093 NODE_RULE(emphasis,gen,x)1094 NODE_RULE(emphasis, gen, x) 1095 { 1096 auto role = x->get_attribute("role"); 1097 quickbook::string_view tag_name; 1098 quickbook::string_view class_name; 1099 1100 if (role.empty()) { 1101 tag_name = "em"; 1102 class_name = "emphasis"; 1103 } 1104 else if (role == "bold" || role == "strong") { 1105 tag_name = "strong"; 1106 class_name = role; 1107 } 1108 else { 1109 class_name = role; 1110 } 1111 tag_start_with_id(gen, "span", x); 1112 if (!class_name.empty()) { 1113 tag_attribute(gen.printer, "class", class_name); 1114 } 1115 tag_end(gen.printer); 1116 if (!tag_name.empty()) { 1117 open_tag(gen.printer, tag_name); 1118 generate_children_html(gen, x); 1119 close_tag(gen.printer, tag_name); 1120 } 1121 else { 1122 generate_children_html(gen, x); 1123 } 1124 close_tag(gen.printer, "span"); 1125 } 1126 NODE_RULE(inlinemediaobject,gen,x)1127 NODE_RULE(inlinemediaobject, gen, x) 1128 { 1129 bool has_image = false; 1130 string_view image; 1131 1132 // Get image link 1133 for (xml_element* i = x->children(); i; i = i->next()) { 1134 if (i->type_ == xml_element::element_node && 1135 i->name_ == "imageobject") { 1136 for (xml_element* j = i->children(); j; j = j->next()) { 1137 if (j->type_ == xml_element::element_node && 1138 j->name_ == "imagedata") { 1139 if (j->has_attribute("fileref")) { 1140 has_image = true; 1141 image = j->get_attribute("fileref"); 1142 break; 1143 } 1144 } 1145 } 1146 } 1147 } 1148 1149 std::string alt; 1150 for (xml_element* i = x->children(); i; i = i->next()) { 1151 if (i->type_ == xml_element::element_node && 1152 i->name_ == "textobject") { 1153 for (xml_element* j = i->children(); j; j = j->next()) { 1154 if (j->type_ == xml_element::element_node && 1155 j->name_ == "phrase") { 1156 if (j->get_attribute("role") == "alt") { 1157 html_gen gen2(gen); 1158 generate_tree_html(gen2, j); 1159 alt = gen2.printer.html; 1160 } 1161 } 1162 } 1163 } 1164 } 1165 // TODO: This was in the original php code, not sure why. 1166 if (alt.empty()) { 1167 alt = "[]"; 1168 } 1169 if (has_image) { 1170 tag_start(gen.printer, "span"); 1171 tag_attribute(gen.printer, "class", "inlinemediaobject"); 1172 tag_end(gen.printer); 1173 tag_start_with_id(gen, "img", x); 1174 tag_attribute( 1175 gen.printer, "src", 1176 get_link_from_path(gen, image, gen.path)); 1177 tag_attribute(gen.printer, "alt", alt); 1178 tag_end_self_close(gen.printer); 1179 close_tag(gen.printer, "span"); 1180 } 1181 } 1182 NODE_RULE(variablelist,gen,x)1183 NODE_RULE(variablelist, gen, x) 1184 { 1185 typedef std::vector<std::pair<xml_element*, xml_element*> > 1186 items_type; 1187 items_type items; 1188 for (xml_element* i = x->children(); i; i = i->next()) { 1189 if (i && i->type_ == xml_element::element_node) { 1190 if (i->name_ == "title") { 1191 // TODO: What to do with titles? 1192 continue; 1193 } 1194 else if (i->name_ == "varlistentry") { 1195 // TODO: What if i has an id? 1196 xml_element* term = 0; 1197 xml_element* listitem = 0; 1198 for (xml_element* j = i->children(); j; j = j->next()) { 1199 if (j && j->type_ == xml_element::element_node) { 1200 if (j->name_ == "term") { 1201 term = j; 1202 } 1203 else if (j->name_ == "listitem") { 1204 listitem = j; 1205 } 1206 } 1207 } 1208 if (term && listitem) { 1209 items.push_back(std::make_pair(term, listitem)); 1210 } 1211 } 1212 } 1213 } 1214 1215 if (!items.empty()) { 1216 open_tag_with_id(gen, "dl", x); 1217 for (items_type::iterator i = items.begin(); i != items.end(); 1218 ++i) { 1219 tag(gen, "dt", i->first); 1220 tag(gen, "dd", i->second); 1221 } 1222 close_tag(gen.printer, "dl"); 1223 } 1224 } 1225 write_table_rows(html_gen & gen,xml_element * x,char const * td_tag)1226 void write_table_rows(html_gen& gen, xml_element* x, char const* td_tag) 1227 { 1228 for (xml_element* i = x->children(); i; i = i->next()) { 1229 if (i->type_ == xml_element::element_node && 1230 i->name_ == "row") { 1231 open_tag_with_id(gen, "tr", i); 1232 for (xml_element* j = i->children(); j; j = j->next()) { 1233 if (j->type_ == xml_element::element_node && 1234 j->name_ == "entry") { 1235 auto role = x->get_attribute("role"); 1236 tag_start_with_id(gen, td_tag, j); 1237 if (!role.empty()) { 1238 tag_attribute(gen.printer, "class", role); 1239 } 1240 tag_end(gen.printer); 1241 generate_children_html(gen, j); 1242 close_tag(gen.printer, td_tag); 1243 } 1244 } 1245 close_tag(gen.printer, "tr"); 1246 } 1247 } 1248 } 1249 write_table(html_gen & gen,xml_element * x)1250 void write_table(html_gen& gen, xml_element* x) 1251 { 1252 xml_element* title = 0; 1253 xml_element* tgroup = 0; 1254 xml_element* thead = 0; 1255 xml_element* tbody = 0; 1256 1257 for (xml_element* i = x->children(); i; i = i->next()) { 1258 if (i->type_ == xml_element::element_node && 1259 i->name_ == "title") { 1260 title = i; 1261 } 1262 if (i->type_ == xml_element::element_node && 1263 i->name_ == "tgroup") { 1264 tgroup = i; 1265 } 1266 } 1267 1268 if (!tgroup) { 1269 return; 1270 } 1271 1272 for (xml_element* i = tgroup->children(); i; i = i->next()) { 1273 if (i->type_ == xml_element::element_node && 1274 i->name_ == "thead") { 1275 thead = i; 1276 } 1277 if (i->type_ == xml_element::element_node && 1278 i->name_ == "tbody") { 1279 tbody = i; 1280 } 1281 } 1282 1283 tag_start_with_id(gen, "div", x); 1284 tag_attribute(gen.printer, "class", x->name_); 1285 tag_end(gen.printer); 1286 open_tag(gen.printer, "table"); 1287 if (title) { 1288 tag(gen, "caption", title); 1289 } 1290 if (thead) { 1291 open_tag(gen.printer, "thead"); 1292 write_table_rows(gen, thead, "th"); 1293 close_tag(gen.printer, "thead"); 1294 } 1295 if (tbody) { 1296 open_tag(gen.printer, "tbody"); 1297 write_table_rows(gen, tbody, "td"); 1298 close_tag(gen.printer, "tbody"); 1299 } 1300 close_tag(gen.printer, "table"); 1301 close_tag(gen.printer, "div"); 1302 } 1303 NODE_RULE(table,gen,x)1304 NODE_RULE(table, gen, x) { write_table(gen, x); } NODE_RULE(informaltable,gen,x)1305 NODE_RULE(informaltable, gen, x) { write_table(gen, x); } 1306 NODE_MAP(calloutlist,div)1307 NODE_MAP(calloutlist, div) 1308 1309 NODE_RULE(callout, gen, x) 1310 { 1311 boost::unordered_map<string_view, callout_data>::const_iterator 1312 data = gen.chunk.callout_numbers.end(); 1313 auto link = gen.state.ids.end(); 1314 if (x->has_attribute("id")) { 1315 data = gen.chunk.callout_numbers.find(x->get_attribute("id")); 1316 } 1317 if (data != gen.chunk.callout_numbers.end() && 1318 !data->second.link_id.empty()) { 1319 link = gen.state.ids.find(data->second.link_id); 1320 } 1321 1322 open_tag_with_id(gen, "div", x); 1323 if (link != gen.state.ids.end()) { 1324 tag_start(gen.printer, "a"); 1325 tag_attribute( 1326 gen.printer, "href", relative_path_from_url_paths( 1327 link->second.path(), gen.path)); 1328 tag_end(gen.printer); 1329 } 1330 graphics_tag( 1331 gen, 1332 "/callouts/" + 1333 boost::lexical_cast<std::string>(data->second.number) + 1334 ".png", 1335 "(" + boost::lexical_cast<std::string>(data->second.number) + 1336 ")"); 1337 if (link != gen.state.ids.end()) { 1338 close_tag(gen.printer, "a"); 1339 } 1340 gen.printer.html += " "; 1341 generate_children_html(gen, x); 1342 close_tag(gen.printer, "div"); 1343 } 1344 NODE_RULE(co,gen,x)1345 NODE_RULE(co, gen, x) 1346 { 1347 boost::unordered_map<string_view, callout_data>::const_iterator 1348 data = gen.chunk.callout_numbers.end(); 1349 auto link = gen.state.ids.end(); 1350 if (x->has_attribute("linkends")) { 1351 auto linkends = x->get_attribute("linkends"); 1352 data = gen.chunk.callout_numbers.find(linkends); 1353 link = gen.state.ids.find(linkends); 1354 } 1355 1356 if (link != gen.state.ids.end()) { 1357 tag_start(gen.printer, "a"); 1358 tag_attribute( 1359 gen.printer, "href", relative_path_from_url_paths( 1360 link->second.path(), gen.path)); 1361 tag_end(gen.printer); 1362 } 1363 if (data != gen.chunk.callout_numbers.end()) { 1364 graphics_tag( 1365 gen, 1366 "/callouts/" + 1367 boost::lexical_cast<std::string>(data->second.number) + 1368 ".png", 1369 "(" + 1370 boost::lexical_cast<std::string>(data->second.number) + 1371 ")"); 1372 } 1373 else { 1374 gen.printer.html += "(0)"; 1375 } 1376 if (link != gen.state.ids.end()) { 1377 close_tag(gen.printer, "a"); 1378 } 1379 } 1380 NODE_RULE(footnote,gen,x)1381 NODE_RULE(footnote, gen, x) 1382 { 1383 // TODO: Better id generation.... 1384 static int footnote_number = 0; 1385 ++footnote_number; 1386 std::string footnote_label = 1387 boost::lexical_cast<std::string>(footnote_number); 1388 auto footnote_id = 1389 generate_id(gen.chunk, x, "(((footnote-id)))", "footnote"); 1390 if (!x->has_attribute("id")) { 1391 generate_id(gen.chunk, x, "id", "footnote"); 1392 } 1393 1394 tag_start_with_id(gen, "a", x); 1395 std::string href = "#"; 1396 href += footnote_id; 1397 tag_attribute(gen.printer, "href", href); 1398 tag_end(gen.printer); 1399 tag_start(gen.printer, "sup"); 1400 tag_attribute(gen.printer, "class", "footnote"); 1401 tag_end(gen.printer); 1402 gen.printer.html += "[" + footnote_label + "]"; 1403 close_tag(gen.printer, "sup"); 1404 close_tag(gen.printer, "a"); 1405 1406 // Generate HTML to add to footnote. 1407 html_printer printer; 1408 tag_start(printer, "a"); 1409 std::string href2 = "#"; 1410 href2 += x->get_attribute("id"); 1411 tag_attribute(printer, "href", href2); 1412 tag_end(printer); 1413 tag_start(printer, "sup"); 1414 tag_end(printer); 1415 printer.html += "[" + footnote_label + "]"; 1416 close_tag(printer, "sup"); 1417 close_tag(printer, "a"); 1418 printer.html += ' '; 1419 xml_tree_builder builder; 1420 builder.add_element(xml_element::html_node(printer.html)); 1421 1422 // Find position to insert. 1423 auto pos = x->children(); 1424 for (; pos && pos->type_ == xml_element::element_text; 1425 pos = pos->next()) { 1426 if (pos->contents_.find_first_not_of("\t\n ") != 1427 std::string::npos) { 1428 break; 1429 } 1430 } 1431 if (!pos) { 1432 x->add_first_child(builder.release()); 1433 } 1434 else 1435 switch (pos->type_) { 1436 case xml_element::element_node: 1437 // TODO: Check type of node? Recurse? 1438 pos->add_first_child(builder.release()); 1439 break; 1440 default: 1441 pos->add_before(builder.release()); 1442 break; 1443 } 1444 1445 gen.chunk.footnotes.push_back(x); 1446 } 1447 docinfo_get_contents(docinfo_gen & d,xml_element * x)1448 std::string docinfo_get_contents(docinfo_gen& d, xml_element* x) 1449 { 1450 html_gen gen2(d.gen); 1451 generate_children_html(gen2, x); 1452 return gen2.printer.html; 1453 } 1454 docinfo_get_author(docinfo_gen & d,xml_element * x)1455 std::string docinfo_get_author(docinfo_gen& d, xml_element* x) 1456 { 1457 auto personname = x->get_child("personname"); 1458 if (personname) { 1459 return docinfo_get_author(d, personname); 1460 } 1461 1462 std::string name; 1463 1464 char const* name_parts[] = {"honorific", "firstname", "surname"}; 1465 std::size_t const length = 1466 sizeof(name_parts) / sizeof(name_parts[0]); 1467 for (std::size_t i = 0; i < length; ++i) { 1468 auto child = x->get_child(name_parts[i]); 1469 if (child) { 1470 if (name.size()) { 1471 name += " "; 1472 } 1473 name += docinfo_get_contents(d, child); 1474 } 1475 } 1476 1477 return name; 1478 } 1479 1480 // docinfo parsers 1481 1482 // No support for: 1483 // 1484 // graphic, mediaobject 1485 // modespec 1486 // subjectset, keywordset 1487 // itermset, indexterm 1488 // abbrev 1489 // abstract 1490 // address 1491 // artpagenums 1492 // authorinitials 1493 // bibliomisc, biblioset 1494 // confgroup 1495 // contractnum, contractsponsor 1496 // corpname 1497 // date 1498 // edition 1499 // invpartnumber, isbn, issn, issuenum, biblioid 1500 // orgname 1501 // citebiblioid, citetitle 1502 // bibliosource, bibliorelation, bibliocoverage - Dublin core 1503 // pagenums 1504 // printhistory 1505 // productname, productnumber 1506 // pubdate *** 1507 // publisher, publishername, pubsnumber 1508 // releaseinfo 1509 // revhistory 1510 // seriesvolnums 1511 // title, subtitle, titleabbrev - *** extract into parent? 1512 // volumenum 1513 // personname, honorific, firstname, surname, lineage, othername, 1514 // affiliation, authorblurb, contrib - add to authors? 1515 DOCINFO_NODE_RULE(copyright,docinfo_general,d,x)1516 DOCINFO_NODE_RULE(copyright, docinfo_general, d, x) 1517 { 1518 std::vector<xml_element*> years; 1519 std::vector<xml_element*> holders; 1520 1521 for (auto child = x->children(); child; child = child->next()) { 1522 if (child->type_ == xml_element::element_node) { 1523 if (child->name_ == "year") { 1524 years.push_back(child); 1525 } 1526 else if (child->name_ == "holder") { 1527 holders.push_back(child); 1528 } 1529 else { 1530 quickbook::detail::out() 1531 << "Unsupported copyright tag: " << x->name_ 1532 << std::endl; 1533 } 1534 } 1535 } 1536 1537 // TODO: Format years, e.g. 2005 2006 2007 2010 => 2005-2007, 2010 1538 1539 std::string copyright; 1540 QUICKBOOK_FOR (auto year, years) { 1541 if (!copyright.empty()) { 1542 copyright += ", "; 1543 } 1544 copyright += docinfo_get_contents(d, year); 1545 } 1546 bool first = true; 1547 QUICKBOOK_FOR (auto holder, holders) { 1548 if (first) { 1549 if (!copyright.empty()) { 1550 copyright += " "; 1551 } 1552 first = false; 1553 } 1554 else { 1555 copyright += ", "; 1556 } 1557 copyright += docinfo_get_contents(d, holder); 1558 } 1559 d.copyrights.push_back(copyright); 1560 } 1561 DOCINFO_NODE_RULE(legalnotice,docinfo_general,d,x)1562 DOCINFO_NODE_RULE(legalnotice, docinfo_general, d, x) 1563 { 1564 d.legalnotices.push_back(docinfo_get_contents(d, x)); 1565 } 1566 DOCINFO_NODE_RULE(pubdate,docinfo_general,d,x)1567 DOCINFO_NODE_RULE(pubdate, docinfo_general, d, x) 1568 { 1569 d.pubdates.push_back(docinfo_get_contents(d, x)); 1570 } 1571 DOCINFO_NODE_RULE(authorgroup,docinfo_general,d,x)1572 DOCINFO_NODE_RULE(authorgroup, docinfo_general, d, x) 1573 { 1574 // TODO: Check children are docinfo_author 1575 generate_docinfo_html_impl(d, x); 1576 } 1577 DOCINFO_NODE_RULE(author,docinfo_author,d,x)1578 DOCINFO_NODE_RULE(author, docinfo_author, d, x) 1579 { 1580 d.authors.push_back(docinfo_get_author(d, x)); 1581 } 1582 DOCINFO_NODE_RULE(editor,docinfo_author,d,x)1583 DOCINFO_NODE_RULE(editor, docinfo_author, d, x) 1584 { 1585 d.editors.push_back(docinfo_get_author(d, x)); 1586 } 1587 DOCINFO_NODE_RULE(collab,docinfo_author,d,x)1588 DOCINFO_NODE_RULE(collab, docinfo_author, d, x) 1589 { 1590 // Ignoring affiliation. 1591 auto collabname = x->get_child("collabname"); 1592 if (collabname) { 1593 d.collabs.push_back(docinfo_get_contents(d, collabname)); 1594 } 1595 } 1596 DOCINFO_NODE_RULE(corpauthor,docinfo_author,d,x)1597 DOCINFO_NODE_RULE(corpauthor, docinfo_author, d, x) 1598 { 1599 d.authors.push_back(docinfo_get_contents(d, x)); 1600 } 1601 DOCINFO_NODE_RULE(corpcredit,docinfo_author,d,x)1602 DOCINFO_NODE_RULE(corpcredit, docinfo_author, d, x) 1603 { 1604 std::string text = docinfo_get_contents(d, x); 1605 1606 string_view class_ = x->get_attribute("class"); 1607 if (!class_.empty()) { 1608 text = class_.to_s() + ": " + text; 1609 } 1610 1611 d.authors.push_back(text); 1612 } 1613 DOCINFO_NODE_RULE(othercredit,docinfo_author,d,x)1614 DOCINFO_NODE_RULE(othercredit, docinfo_author, d, x) 1615 { 1616 std::string text = docinfo_get_author(d, x); 1617 1618 string_view class_ = x->get_attribute("class"); 1619 if (!class_.empty()) { 1620 text = class_.to_s() + ": " + text; 1621 } 1622 1623 d.authors.push_back(text); 1624 } 1625 } 1626 } 1627