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