1 /*=============================================================================
2     Copyright (c) 2002 2004 2006 Joel de Guzman
3     Copyright (c) 2004 Eric Niebler
4     Copyright (c) 2005 Thomas Guest
5     http://spirit.sourceforge.net/
6 
7     Use, modification and distribution is subject to the Boost Software
8     License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
9     http://www.boost.org/LICENSE_1_0.txt)
10 =============================================================================*/
11 #include "actions.hpp"
12 #include <functional>
13 #include <map>
14 #include <numeric>
15 #include <set>
16 #include <vector>
17 #include <boost/algorithm/string/replace.hpp>
18 #include <boost/filesystem/convenience.hpp>
19 #include <boost/filesystem/fstream.hpp>
20 #include <boost/lexical_cast.hpp>
21 #include <boost/next_prior.hpp>
22 #include <boost/range/algorithm/replace.hpp>
23 #include <boost/range/distance.hpp>
24 #include "block_tags.hpp"
25 #include "document_state.hpp"
26 #include "files.hpp"
27 #include "for.hpp"
28 #include "grammar.hpp"
29 #include "markups.hpp"
30 #include "path.hpp"
31 #include "phrase_tags.hpp"
32 #include "quickbook.hpp"
33 #include "state.hpp"
34 #include "state_save.hpp"
35 #include "stream.hpp"
36 #include "syntax_highlight.hpp"
37 #include "utils.hpp"
38 
39 namespace quickbook
40 {
41     namespace
42     {
write_anchors(quickbook::state & state,collector & tgt)43         void write_anchors(quickbook::state& state, collector& tgt)
44         {
45             if (state.source_mode_next) {
46                 detail::outwarn(
47                     state.source_mode_next_pos.get_file(),
48                     state.source_mode_next_pos.get_position())
49                     << "Temporary source mode unsupported here." << std::endl;
50                 state.source_mode_next = 0;
51             }
52 
53             QUICKBOOK_FOR (auto const& anchor_id, state.anchors) {
54                 tgt << "<anchor id=\"";
55                 detail::print_string(anchor_id, tgt.get());
56                 tgt << "\"/>";
57             }
58 
59             state.anchors.clear();
60         }
61 
add_anchor(quickbook::state & state,quickbook::string_view id,id_category::categories category=id_category::explicit_anchor_id)62         std::string add_anchor(
63             quickbook::state& state,
64             quickbook::string_view id,
65             id_category::categories category = id_category::explicit_anchor_id)
66         {
67             std::string placeholder = state.document.add_anchor(id, category);
68             state.anchors.push_back(placeholder);
69             return placeholder;
70         }
71 
get_attribute_value(quickbook::state & state,quickbook::value const & value)72         std::string get_attribute_value(
73             quickbook::state& state, quickbook::value const& value)
74         {
75             std::string x = value.is_encoded() ? value.get_encoded()
76                                                : value.get_quickbook().to_s();
77 
78             if (x.empty()) {
79                 detail::outerr(value.get_file(), value.get_position())
80                     << "Empty attribute value." << std::endl;
81                 ++state.error_count;
82                 x = "xxx";
83             }
84 
85             return x;
86         }
87 
validate_id(quickbook::state & state,quickbook::value const & id_value)88         std::string validate_id(
89             quickbook::state& state, quickbook::value const& id_value)
90         {
91             bool valid = true;
92             std::string id = get_attribute_value(state, id_value);
93 
94             // Special case since I use dollar ids for id placeholders.
95             if (id[0] == '$') {
96                 valid = false;
97                 id[0] = '_';
98             }
99 
100             if (qbk_version_n >= 107u) {
101                 char const* allowed_punctuation = "_.-";
102 
103                 QUICKBOOK_FOR (char c, id) {
104                     if (!std::isalnum(c) &&
105                         !std::strchr(allowed_punctuation, c))
106                         valid = false;
107                 }
108             }
109 
110             if (!valid) {
111                 detail::outerr(id_value.get_file(), id_value.get_position())
112                     << "Invalid id: " << (id_value.is_encoded()
113                                               ? id_value.get_encoded()
114                                               : id_value.get_quickbook().to_s())
115                     << std::endl;
116                 ++state.error_count;
117             }
118 
119             return id;
120         }
121     }
122 
in_range() const123     bool quickbook_range::in_range() const
124     {
125         return qbk_version_n >= lower && qbk_version_n < upper;
126     }
127 
is_strict_checking() const128     bool quickbook_strict::is_strict_checking() const
129     {
130         return state.strict_mode;
131     }
132 
133     void list_action(quickbook::state&, value);
134     void header_action(quickbook::state&, value);
135     void begin_section_action(quickbook::state&, value);
136     void end_section_action(quickbook::state&, value, string_iterator);
137     void block_action(quickbook::state&, value);
138     void block_empty_action(quickbook::state&, value);
139     void macro_definition_action(quickbook::state&, value);
140     void template_body_action(quickbook::state&, value);
141     void variable_list_action(quickbook::state&, value);
142     void table_action(quickbook::state&, value);
143     void xinclude_action(quickbook::state&, value);
144     void include_action(quickbook::state&, value, string_iterator);
145     void image_action(quickbook::state&, value);
146     void anchor_action(quickbook::state&, value);
147     void link_action(quickbook::state&, value);
148     void phrase_action(quickbook::state&, value);
149     void role_action(quickbook::state&, value);
150     void footnote_action(quickbook::state&, value);
151     void raw_phrase_action(quickbook::state&, value);
152     void source_mode_action(quickbook::state&, value);
153     void next_source_mode_action(quickbook::state&, value);
154     void code_action(quickbook::state&, value);
155     void do_template_action(quickbook::state&, value, string_iterator);
156 
operator ()(parse_iterator first,parse_iterator) const157     void element_action::operator()(parse_iterator first, parse_iterator) const
158     {
159         value_consumer values = state.values.release();
160         if (!values.check() || !state.conditional) return;
161         value v = values.consume();
162         values.finish();
163 
164         switch (v.get_tag()) {
165         case block_tags::ordered_list:
166         case block_tags::itemized_list:
167             return list_action(state, v);
168         case block_tags::generic_heading:
169         case block_tags::heading1:
170         case block_tags::heading2:
171         case block_tags::heading3:
172         case block_tags::heading4:
173         case block_tags::heading5:
174         case block_tags::heading6:
175             return header_action(state, v);
176         case block_tags::begin_section:
177             return begin_section_action(state, v);
178         case block_tags::end_section:
179             return end_section_action(state, v, first.base());
180         case block_tags::blurb:
181         case block_tags::preformatted:
182         case block_tags::blockquote:
183         case block_tags::warning:
184         case block_tags::caution:
185         case block_tags::important:
186         case block_tags::note:
187         case block_tags::tip:
188         case block_tags::block:
189             return block_action(state, v);
190         case block_tags::hr:
191             return block_empty_action(state, v);
192         case block_tags::macro_definition:
193             return macro_definition_action(state, v);
194         case block_tags::template_definition:
195             return template_body_action(state, v);
196         case block_tags::variable_list:
197             return variable_list_action(state, v);
198         case block_tags::table:
199             return table_action(state, v);
200         case block_tags::xinclude:
201             return xinclude_action(state, v);
202         case block_tags::import:
203         case block_tags::include:
204             return include_action(state, v, first.base());
205         case phrase_tags::image:
206             return image_action(state, v);
207         case phrase_tags::anchor:
208             return anchor_action(state, v);
209         case phrase_tags::url:
210         case phrase_tags::link:
211         case phrase_tags::funcref:
212         case phrase_tags::classref:
213         case phrase_tags::memberref:
214         case phrase_tags::enumref:
215         case phrase_tags::macroref:
216         case phrase_tags::headerref:
217         case phrase_tags::conceptref:
218         case phrase_tags::globalref:
219             return link_action(state, v);
220         case phrase_tags::bold:
221         case phrase_tags::italic:
222         case phrase_tags::underline:
223         case phrase_tags::teletype:
224         case phrase_tags::strikethrough:
225         case phrase_tags::quote:
226         case phrase_tags::replaceable:
227             return phrase_action(state, v);
228         case phrase_tags::footnote:
229             return footnote_action(state, v);
230         case phrase_tags::escape:
231             return raw_phrase_action(state, v);
232         case phrase_tags::role:
233             return role_action(state, v);
234         case source_mode_tags::cpp:
235         case source_mode_tags::python:
236         case source_mode_tags::teletype:
237             return source_mode_action(state, v);
238         case code_tags::next_source_mode:
239             return next_source_mode_action(state, v);
240         case code_tags::code_block:
241         case code_tags::inline_code_block:
242         case code_tags::inline_code:
243             return code_action(state, v);
244         case template_tags::attribute_template:
245         case template_tags::template_:
246             return do_template_action(state, v, first.base());
247         default:
248             break;
249         }
250     }
251 
operator ()(parse_iterator first,parse_iterator) const252     void break_action::operator()(parse_iterator first, parse_iterator) const
253     {
254         write_anchors(state, state.phrase);
255 
256         if (*first == '\\') {
257             detail::outwarn(state.current_file, first.base())
258                 //<< "in column:" << pos.column << ", "
259                 << "'\\n' is deprecated, pleases use '[br]' instead"
260                 << ".\n";
261         }
262 
263         if (!state.warned_about_breaks) {
264             detail::outwarn(state.current_file, first.base())
265                 << "line breaks generate invalid boostbook "
266                    "(will only note first occurrence).\n";
267 
268             state.warned_about_breaks = true;
269         }
270 
271         state.phrase << detail::get_markup(phrase_tags::break_mark).pre;
272     }
273 
operator ()(parse_iterator first,parse_iterator last) const274     void error_message_action::operator()(
275         parse_iterator first, parse_iterator last) const
276     {
277         file_position const pos = state.current_file->position_of(first.base());
278 
279         std::string value(first, last);
280         std::string formatted_message = message;
281         boost::replace_all(formatted_message, "%s", value);
282         boost::replace_all(
283             formatted_message, "%c",
284             boost::lexical_cast<std::string>(pos.column));
285 
286         detail::outerr(state.current_file->path, pos.line)
287             << formatted_message << std::endl;
288         ++state.error_count;
289     }
290 
operator ()(parse_iterator first,parse_iterator) const291     void error_action::operator()(
292         parse_iterator first, parse_iterator /*last*/) const
293     {
294         file_position const pos = state.current_file->position_of(first.base());
295 
296         detail::outerr(state.current_file->path, pos.line)
297             << "Syntax Error near column " << pos.column << ".\n";
298         ++state.error_count;
299     }
300 
block_action(quickbook::state & state,value block)301     void block_action(quickbook::state& state, value block)
302     {
303         write_anchors(state, state.out);
304 
305         detail::markup markup = detail::get_markup(block.get_tag());
306 
307         value_consumer values = block;
308         state.out << markup.pre << values.consume().get_encoded()
309                   << markup.post;
310         values.finish();
311     }
312 
block_empty_action(quickbook::state & state,value block)313     void block_empty_action(quickbook::state& state, value block)
314     {
315         write_anchors(state, state.out);
316 
317         detail::markup markup = detail::get_markup(block.get_tag());
318         state.out << markup.pre;
319     }
320 
phrase_action(quickbook::state & state,value phrase)321     void phrase_action(quickbook::state& state, value phrase)
322     {
323         write_anchors(state, state.phrase);
324 
325         detail::markup markup = detail::get_markup(phrase.get_tag());
326 
327         value_consumer values = phrase;
328         state.phrase << markup.pre << values.consume().get_encoded()
329                      << markup.post;
330         values.finish();
331     }
332 
role_action(quickbook::state & state,value role_list)333     void role_action(quickbook::state& state, value role_list)
334     {
335         write_anchors(state, state.phrase);
336 
337         value_consumer values = role_list;
338         value role = values.consume();
339         value phrase = values.consume();
340         values.finish();
341 
342         state.phrase << "<phrase role=\"";
343         detail::print_string(
344             get_attribute_value(state, role), state.phrase.get());
345         state.phrase << "\">" << phrase.get_encoded() << "</phrase>";
346     }
347 
footnote_action(quickbook::state & state,value phrase)348     void footnote_action(quickbook::state& state, value phrase)
349     {
350         write_anchors(state, state.phrase);
351 
352         value_consumer values = phrase;
353         state.phrase << "<footnote id=\""
354                      << state.document.add_id("f", id_category::numbered)
355                      << "\"><para>" << values.consume().get_encoded()
356                      << "</para></footnote>";
357         values.finish();
358     }
359 
raw_phrase_action(quickbook::state & state,value phrase)360     void raw_phrase_action(quickbook::state& state, value phrase)
361     {
362         write_anchors(state, state.phrase);
363 
364         detail::markup markup = detail::get_markup(phrase.get_tag());
365         state.phrase << markup.pre << phrase.get_quickbook() << markup.post;
366     }
367 
operator ()() const368     void paragraph_action::operator()() const
369     {
370         std::string str;
371         state.phrase.swap(str);
372 
373         std::string::const_iterator pos = str.begin(), end = str.end();
374 
375         while (pos != end && cl::space_p.test(*pos))
376             ++pos;
377 
378         if (pos != end) {
379             detail::markup markup =
380                 state.in_list
381                     ? detail::get_markup(block_tags::paragraph_in_list)
382                     : detail::get_markup(block_tags::paragraph);
383             state.out << markup.pre << str;
384             write_anchors(state, state.out);
385             state.out << markup.post;
386         }
387     }
388 
operator ()() const389     void explicit_list_action::operator()() const
390     {
391         state.explicit_list = true;
392     }
393 
operator ()() const394     void phrase_end_action::operator()() const
395     {
396         write_anchors(state, state.phrase);
397     }
398 
399     namespace
400     {
write_bridgehead(quickbook::state & state,int level,std::string const & str,std::string const & id,bool self_link)401         void write_bridgehead(
402             quickbook::state& state,
403             int level,
404             std::string const& str,
405             std::string const& id,
406             bool self_link)
407         {
408             if (self_link && !id.empty()) {
409                 state.out << "<bridgehead renderas=\"sect" << level << "\"";
410                 state.out << " id=\"";
411                 state.out << state.document.add_id("h", id_category::numbered);
412                 state.out << "\">";
413                 state.out << "<phrase id=\"" << id << "\"/>";
414                 state.out << "<link linkend=\"" << id << "\">";
415                 state.out << str;
416                 state.out << "</link>";
417                 state.out << "</bridgehead>";
418             }
419             else {
420                 state.out << "<bridgehead renderas=\"sect" << level << "\"";
421                 if (!id.empty()) state.out << " id=\"" << id << "\"";
422                 state.out << ">";
423                 state.out << str;
424                 state.out << "</bridgehead>";
425             }
426         }
427     }
428 
header_action(quickbook::state & state,value heading_list)429     void header_action(quickbook::state& state, value heading_list)
430     {
431         value_consumer values = heading_list;
432 
433         bool generic = heading_list.get_tag() == block_tags::generic_heading;
434         value element_id = values.optional_consume(general_tags::element_id);
435         value content = values.consume();
436         values.finish();
437 
438         int level;
439 
440         if (generic) {
441             level = state.document.section_level() + 1;
442             // We need to use a heading which is one greater
443             // than the current.
444             if (level > 6) // The max is h6, clip it if it goes
445                 level = 6; // further than that
446         }
447         else {
448             level = heading_list.get_tag() - block_tags::heading1 + 1;
449         }
450 
451         write_anchors(state, state.out);
452 
453         if (!element_id.empty()) {
454             // Use an explicit id.
455 
456             std::string anchor = state.document.add_id(
457                 validate_id(state, element_id), id_category::explicit_id);
458 
459             write_bridgehead(
460                 state, level, content.get_encoded(), anchor,
461                 self_linked_headers);
462         }
463         else if (state.document.compatibility_version() >= 106u) {
464             // Generate ids for 1.6+
465 
466             std::string anchor = state.document.add_id(
467                 detail::make_identifier(content.get_quickbook()),
468                 id_category::generated_heading);
469 
470             write_bridgehead(
471                 state, level, content.get_encoded(), anchor,
472                 self_linked_headers);
473         }
474         else {
475             // Generate ids that are compatible with older versions of
476             // quickbook.
477 
478             // Older versions of quickbook used the generated boostbook, but
479             // we only have an intermediate version which can contain id
480             // placeholders. So to generate the ids they must be replaced
481             // by the ids that the older versions would have used - i.e. the
482             // unresolved ids.
483             //
484             // Note that this doesn't affect the actual boostbook generated for
485             // the content, it's just used to generate this id.
486 
487             std::string id = detail::make_identifier(
488                 state.document.replace_placeholders_with_unresolved_ids(
489                     content.get_encoded()));
490 
491             if (generic || state.document.compatibility_version() >= 103) {
492                 std::string anchor =
493                     state.document.add_id(id, id_category::generated_heading);
494 
495                 write_bridgehead(
496                     state, level, content.get_encoded(), anchor,
497                     self_linked_headers);
498             }
499             else {
500                 std::string anchor = state.document.old_style_id(
501                     id, id_category::generated_heading);
502 
503                 write_bridgehead(
504                     state, level, content.get_encoded(), anchor, false);
505             }
506         }
507     }
508 
operator ()(char mark) const509     void simple_phrase_action::operator()(char mark) const
510     {
511         write_anchors(state, state.phrase);
512 
513         int tag = mark == '*'
514                       ? phrase_tags::bold
515                       : mark == '/'
516                             ? phrase_tags::italic
517                             : mark == '_'
518                                   ? phrase_tags::underline
519                                   : mark == '=' ? phrase_tags::teletype : 0;
520 
521         assert(tag != 0);
522         detail::markup markup = detail::get_markup(tag);
523 
524         value_consumer values = state.values.release();
525         value content = values.consume();
526         values.finish();
527 
528         state.phrase << markup.pre;
529         state.phrase << content.get_encoded();
530         state.phrase << markup.post;
531     }
532 
start()533     bool cond_phrase_push::start()
534     {
535         value_consumer values = state.values.release();
536 
537         saved_conditional = state.conditional;
538 
539         if (saved_conditional) {
540             bool positive = values.consume().get_quickbook().empty();
541             quickbook::string_view macro1 = values.consume().get_quickbook();
542             std::string macro(macro1.begin(), macro1.end());
543 
544             state.conditional =
545                 (bool)find(state.macro, macro.c_str()) == positive;
546 
547             if (!state.conditional) {
548                 state.push_output();
549                 state.anchors.swap(anchors);
550             }
551         }
552 
553         return true;
554     }
555 
cleanup()556     void cond_phrase_push::cleanup()
557     {
558         if (saved_conditional && !state.conditional) {
559             state.pop_output();
560             state.anchors.swap(anchors);
561         }
562 
563         state.conditional = saved_conditional;
564     }
565 
start_list(char mark)566     void state::start_list(char mark)
567     {
568         push_tagged_source_mode(source_mode_next);
569         source_mode_next = 0;
570 
571         write_anchors(*this, (in_list ? phrase : out));
572         assert(mark == '*' || mark == '#');
573         push_output();
574         out << ((mark == '#') ? "<orderedlist>\n" : "<itemizedlist>\n");
575         in_list = true;
576     }
577 
end_list(char mark)578     void state::end_list(char mark)
579     {
580         write_anchors(*this, out);
581         assert(mark == '*' || mark == '#');
582         out << ((mark == '#') ? "\n</orderedlist>" : "\n</itemizedlist>");
583 
584         std::string list_output;
585         out.swap(list_output);
586 
587         pop_output();
588 
589         (in_list ? phrase : out) << list_output;
590 
591         pop_tagged_source_mode();
592     }
593 
start_list_item()594     void state::start_list_item()
595     {
596         out << "<listitem>";
597         write_anchors(*this, phrase);
598     }
599 
end_list_item()600     void state::end_list_item()
601     {
602         write_anchors(*this, phrase);
603         paragraph_action para(*this);
604         para();
605         out << "</listitem>";
606     }
607 
608     namespace
609     {
610         bool parse_template(
611             value const&,
612             quickbook::state& state,
613             bool is_attribute_template = false);
614     }
615 
start_callouts()616     void state::start_callouts() { ++callout_depth; }
617 
add_callout(value v)618     std::string state::add_callout(value v)
619     {
620         std::string callout_id1 = document.add_id("c", id_category::numbered);
621         std::string callout_id2 = document.add_id("c", id_category::numbered);
622 
623         callouts.insert(encoded_value(callout_id1));
624         callouts.insert(encoded_value(callout_id2));
625         callouts.insert(v);
626 
627         std::string code;
628         code += "<co id=\"" + callout_id1 + "\" ";
629         code += "linkends=\"" + callout_id2 + "\" />";
630 
631         return code;
632     }
633 
end_callouts()634     std::string state::end_callouts()
635     {
636         assert(callout_depth > 0);
637         std::string block;
638 
639         --callout_depth;
640         if (callout_depth > 0) return block;
641 
642         value_consumer c = callouts.release();
643         if (!c.check()) return block;
644 
645         block += "<calloutlist>";
646         while (c.check()) {
647             std::string callout_id1 = c.consume().get_encoded();
648             std::string callout_id2 = c.consume().get_encoded();
649             value callout_body = c.consume();
650 
651             std::string callout_value;
652 
653             {
654                 state_save save(*this, state_save::scope_all);
655                 ++template_depth;
656 
657                 bool r = parse_template(callout_body, *this);
658 
659                 if (!r) {
660                     detail::outerr(
661                         callout_body.get_file(), callout_body.get_position())
662                         << "Expanding callout." << std::endl
663                         << "------------------begin------------------"
664                         << std::endl
665                         << callout_body.get_quickbook() << std::endl
666                         << "------------------end--------------------"
667                         << std::endl;
668                     ++error_count;
669                 }
670 
671                 out.swap(callout_value);
672             }
673 
674             block += "<callout arearefs=\"" + callout_id1 + "\" ";
675             block += "id=\"" + callout_id2 + "\">";
676             block += callout_value;
677             block += "</callout>";
678         }
679         block += "</calloutlist>";
680 
681         return block;
682     }
683 
list_action(quickbook::state & state,value list)684     void list_action(quickbook::state& state, value list)
685     {
686         write_anchors(state, state.out);
687 
688         detail::markup markup = detail::get_markup(list.get_tag());
689 
690         state.out << markup.pre;
691 
692         QUICKBOOK_FOR (value item, list) {
693             state.out << "<listitem>";
694             state.out << item.get_encoded();
695             state.out << "</listitem>";
696         }
697 
698         state.out << markup.post;
699     }
700 
anchor_action(quickbook::state & state,value anchor)701     void anchor_action(quickbook::state& state, value anchor)
702     {
703         value_consumer values = anchor;
704         value anchor_id = values.consume();
705         // Note: anchor_id is never encoded as boostbook. If it
706         // is encoded, it's just things like escapes.
707         add_anchor(state, validate_id(state, anchor_id));
708         values.finish();
709     }
710 
operator ()(std::string const & str) const711     void do_macro_action::operator()(std::string const& str) const
712     {
713         write_anchors(state, state.phrase);
714 
715         if (str == quickbook_get_date) {
716             char strdate[64];
717             strftime(strdate, sizeof(strdate), "%Y-%b-%d", current_time);
718             state.phrase << strdate;
719         }
720         else if (str == quickbook_get_time) {
721             char strdate[64];
722             strftime(strdate, sizeof(strdate), "%I:%M:%S %p", current_time);
723             state.phrase << strdate;
724         }
725         else {
726             state.phrase << str;
727         }
728     }
729 
operator ()(char ch) const730     void raw_char_action::operator()(char ch) const { state.phrase << ch; }
731 
operator ()(parse_iterator first,parse_iterator last) const732     void raw_char_action::operator()(
733         parse_iterator first, parse_iterator last) const
734     {
735         while (first != last)
736             state.phrase << *first++;
737     }
738 
source_mode_action(quickbook::state & state,value source_mode)739     void source_mode_action(quickbook::state& state, value source_mode)
740     {
741         state.change_source_mode(source_mode.get_tag());
742     }
743 
next_source_mode_action(quickbook::state & state,value source_mode)744     void next_source_mode_action(quickbook::state& state, value source_mode)
745     {
746         value_consumer values = source_mode;
747         state.source_mode_next_pos = values.consume();
748         state.source_mode_next = values.consume().get_int();
749         values.finish();
750     }
751 
code_action(quickbook::state & state,value code_block)752     void code_action(quickbook::state& state, value code_block)
753     {
754         int code_tag = code_block.get_tag();
755 
756         value_consumer values = code_block;
757         quickbook::string_view code_value = values.consume().get_quickbook();
758         values.finish();
759 
760         bool inline_code =
761             code_tag == code_tags::inline_code ||
762             (code_tag == code_tags::inline_code_block && qbk_version_n < 106u);
763         bool block = code_tag != code_tags::inline_code;
764 
765         source_mode_type source_mode =
766             state.source_mode_next ? state.source_mode_next
767                                    : state.current_source_mode().source_mode;
768         state.source_mode_next = 0;
769 
770         if (inline_code) {
771             write_anchors(state, state.phrase);
772         }
773         else {
774             paragraph_action para(state);
775             para();
776             write_anchors(state, state.out);
777         }
778 
779         if (block) {
780             // preprocess the code section to remove the initial indentation
781             mapped_file_builder mapped;
782             mapped.start(state.current_file);
783             mapped.unindent_and_add(code_value);
784 
785             file_ptr f = mapped.release();
786 
787             if (f->source().empty())
788                 return; // Nothing left to do here. The program is empty.
789 
790             if (qbk_version_n >= 107u) state.start_callouts();
791 
792             parse_iterator first_(f->source().begin());
793             parse_iterator last_(f->source().end());
794 
795             file_ptr saved_file = f;
796             boost::swap(state.current_file, saved_file);
797 
798             // print the code with syntax coloring
799             //
800             // We must not place a \n after the <programlisting> tag
801             // otherwise PDF output starts code blocks with a blank line:
802             state.phrase << "<programlisting>";
803             syntax_highlight(first_, last_, state, source_mode, block);
804             state.phrase << "</programlisting>\n";
805 
806             boost::swap(state.current_file, saved_file);
807 
808             if (qbk_version_n >= 107u) state.phrase << state.end_callouts();
809 
810             if (!inline_code) {
811                 state.out << state.phrase.str();
812                 state.phrase.clear();
813             }
814         }
815         else {
816             parse_iterator first_(code_value.begin());
817             parse_iterator last_(code_value.end());
818 
819             state.phrase << "<code>";
820             syntax_highlight(first_, last_, state, source_mode, block);
821             state.phrase << "</code>";
822         }
823     }
824 
operator ()(char ch) const825     void plain_char_action::operator()(char ch) const
826     {
827         write_anchors(state, state.phrase);
828 
829         detail::print_char(ch, state.phrase.get());
830     }
831 
operator ()(parse_iterator first,parse_iterator last) const832     void plain_char_action::operator()(
833         parse_iterator first, parse_iterator last) const
834     {
835         write_anchors(state, state.phrase);
836 
837         while (first != last)
838             detail::print_char(*first++, state.phrase.get());
839     }
840 
operator ()(parse_iterator first,parse_iterator last) const841     void escape_unicode_action::operator()(
842         parse_iterator first, parse_iterator last) const
843     {
844         write_anchors(state, state.phrase);
845 
846         while (first != last && *first == '0')
847             ++first;
848 
849         // Just ignore \u0000
850         // Maybe I should issue a warning?
851         if (first == last) return;
852 
853         std::string hex_digits(first, last);
854 
855         if (hex_digits.size() == 2 && *first > '0' && *first <= '7') {
856             using namespace std;
857             detail::print_char(
858                 (char)strtol(hex_digits.c_str(), 0, 16), state.phrase.get());
859         }
860         else {
861             state.phrase << "&#x" << hex_digits << ";";
862         }
863     }
864 
write_plain_text(std::ostream & out,value const & v)865     void write_plain_text(std::ostream& out, value const& v)
866     {
867         if (v.is_encoded()) {
868             detail::print_string(v.get_encoded(), out);
869         }
870         else {
871             quickbook::string_view value = v.get_quickbook();
872             for (string_iterator first = value.begin(), last = value.end();
873                  first != last; ++first) {
874                 if (*first == '\\' && ++first == last) break;
875                 detail::print_char(*first, out);
876             }
877         }
878     }
879 
image_action(quickbook::state & state,value image)880     void image_action(quickbook::state& state, value image)
881     {
882         write_anchors(state, state.phrase);
883 
884         // Note: attributes are never encoded as boostbook, if they're
885         // encoded, it's just things like escapes.
886         typedef std::map<std::string, value> attribute_map;
887         attribute_map attributes;
888 
889         value_consumer values = image;
890         attributes["fileref"] = values.consume();
891 
892         QUICKBOOK_FOR (value pair_, values) {
893             value_consumer pair = pair_;
894             value name = pair.consume();
895             value value = pair.consume();
896             std::string name_str(
897                 name.get_quickbook().begin(), name.get_quickbook().end());
898             pair.finish();
899             if (!attributes.insert(std::make_pair(name_str, value)).second) {
900                 detail::outwarn(name.get_file(), name.get_position())
901                     << "Duplicate image attribute: " << name.get_quickbook()
902                     << std::endl;
903             }
904         }
905 
906         values.finish();
907 
908         // Find the file basename and extension.
909         //
910         // Not using Boost.Filesystem because I want to stay in UTF-8.
911         // Need to think about uri encoding.
912 
913         std::string fileref =
914             attributes["fileref"].is_encoded()
915                 ? attributes["fileref"].get_encoded()
916                 : attributes["fileref"].get_quickbook().to_s();
917 
918         // Check for windows paths, then convert.
919         // A bit crude, but there you go.
920 
921         if (fileref.find('\\') != std::string::npos) {
922             (qbk_version_n >= 106u ? detail::outerr(
923                                          attributes["fileref"].get_file(),
924                                          attributes["fileref"].get_position())
925                                    : detail::outwarn(
926                                          attributes["fileref"].get_file(),
927                                          attributes["fileref"].get_position()))
928                 << "Image path isn't portable: '" << fileref << "'"
929                 << std::endl;
930             if (qbk_version_n >= 106u) ++state.error_count;
931         }
932 
933         boost::replace(fileref, '\\', '/');
934 
935         // Find the file basename and extension.
936         //
937         // Not using Boost.Filesystem because I want to stay in UTF-8.
938         // Need to think about uri encoding.
939 
940         std::string::size_type pos;
941         std::string stem, extension;
942 
943         pos = fileref.rfind('/');
944         stem = pos == std::string::npos ? fileref : fileref.substr(pos + 1);
945 
946         pos = stem.rfind('.');
947         if (pos != std::string::npos) {
948             extension = stem.substr(pos + 1);
949             stem = stem.substr(0, pos);
950         }
951 
952         // Extract the alt tag, to use as a text description.
953         // Or if there isn't one, use the stem of the file name.
954 
955         attribute_map::iterator alt_pos = attributes.find("alt");
956         quickbook::value alt_text = alt_pos != attributes.end()
957                                         ? alt_pos->second
958                                         : qbk_version_n < 106u
959                                               ? encoded_value(stem)
960                                               : quickbook::value();
961         attributes.erase("alt");
962 
963         if (extension == "svg") {
964             //
965             // SVG's need special handling:
966             //
967             // 1) We must set the "format" attribute, otherwise
968             //    HTML generation produces code that will not display
969             //    the image at all.
970             // 2) We need to set the "contentwidth" and "contentdepth"
971             //    attributes, otherwise the image will be displayed inside
972             //    a tiny box with scrollbars (Firefox), or else cropped to
973             //    fit in a tiny box (IE7).
974             //
975 
976             attributes.insert(
977                 attribute_map::value_type("format", encoded_value("SVG")));
978 
979             //
980             // Image paths are relative to the html subdirectory:
981             //
982             fs::path img = detail::generic_to_path(fileref);
983             if (!img.has_root_directory())
984                 img = quickbook::image_location / img; // relative path
985 
986             //
987             // Now load the SVG file:
988             //
989             std::string svg_text;
990             if (state.dependencies.add_dependency(img)) {
991                 fs::ifstream fs(img);
992                 std::stringstream buffer;
993                 buffer << fs.rdbuf();
994                 svg_text = buffer.str();
995             }
996 
997             //
998             // Extract the svg header from the file:
999             //
1000             std::string::size_type a, b;
1001             a = svg_text.find("<svg");
1002             b = svg_text.find('>', a);
1003             svg_text =
1004                 (a == std::string::npos) ? "" : svg_text.substr(a, b - a);
1005             //
1006             // Now locate the "width" and "height" attributes
1007             // and borrow their values:
1008             //
1009             a = svg_text.find("width");
1010             a = svg_text.find('=', a);
1011             a = svg_text.find('\"', a);
1012             b = svg_text.find('\"', a + 1);
1013             if (a != std::string::npos) {
1014                 attributes.insert(std::make_pair(
1015                     "contentwidth", encoded_value(std::string(
1016                                         boost::next(svg_text.begin(), a + 1),
1017                                         boost::next(svg_text.begin(), b)))));
1018             }
1019             a = svg_text.find("height");
1020             a = svg_text.find('=', a);
1021             a = svg_text.find('\"', a);
1022             b = svg_text.find('\"', a + 1);
1023             if (a != std::string::npos) {
1024                 attributes.insert(std::make_pair(
1025                     "contentdepth", encoded_value(std::string(
1026                                         boost::next(svg_text.begin(), a + 1),
1027                                         boost::next(svg_text.begin(), b)))));
1028             }
1029         }
1030 
1031         state.phrase << "<inlinemediaobject>";
1032 
1033         state.phrase << "<imageobject><imagedata";
1034 
1035         QUICKBOOK_FOR (attribute_map::value_type const& attr, attributes) {
1036             state.phrase << " " << attr.first << "=\"";
1037             write_plain_text(state.phrase.get(), attr.second);
1038             state.phrase << "\"";
1039         }
1040 
1041         state.phrase << "></imagedata></imageobject>";
1042 
1043         // Add a textobject containing the alt tag from earlier.
1044         // This will be used for the alt tag in html.
1045         if (alt_text.check()) {
1046             state.phrase << "<textobject><phrase>";
1047             write_plain_text(state.phrase.get(), alt_text);
1048             state.phrase << "</phrase></textobject>";
1049         }
1050 
1051         state.phrase << "</inlinemediaobject>";
1052     }
1053 
macro_definition_action(quickbook::state & state,quickbook::value macro_definition)1054     void macro_definition_action(
1055         quickbook::state& state, quickbook::value macro_definition)
1056     {
1057         value_consumer values = macro_definition;
1058         std::string macro_id = values.consume().get_quickbook().to_s();
1059         value phrase_value = values.optional_consume();
1060         std::string phrase;
1061         if (phrase_value.check()) phrase = phrase_value.get_encoded();
1062         values.finish();
1063 
1064         std::string* existing_macro =
1065             boost::spirit::classic::find(state.macro, macro_id.c_str());
1066         quickbook::ignore_variable(&existing_macro);
1067 
1068         if (existing_macro) {
1069             if (qbk_version_n < 106) return;
1070 
1071             // Do this if you're using spirit's TST.
1072             //
1073             // *existing_macro = phrase;
1074             // return;
1075         }
1076 
1077         state.macro.add(macro_id.begin(), macro_id.end(), phrase);
1078     }
1079 
template_body_action(quickbook::state & state,quickbook::value template_definition)1080     void template_body_action(
1081         quickbook::state& state, quickbook::value template_definition)
1082     {
1083         value_consumer values = template_definition;
1084         std::string identifier = values.consume().get_quickbook().to_s();
1085 
1086         std::vector<std::string> template_values;
1087         QUICKBOOK_FOR (value const& p, values.consume()) {
1088             template_values.push_back(p.get_quickbook().to_s());
1089         }
1090 
1091         BOOST_ASSERT(
1092             values.check(template_tags::block) ||
1093             values.check(template_tags::phrase));
1094         value body = values.consume();
1095         BOOST_ASSERT(!values.check());
1096 
1097         if (!state.templates.add(template_symbol(
1098                 identifier, template_values, body,
1099                 &state.templates.top_scope()))) {
1100             detail::outwarn(body.get_file(), body.get_position())
1101                 << "Template Redefinition: " << identifier << std::endl;
1102             ++state.error_count;
1103         }
1104     }
1105 
1106     namespace
1107     {
find_first_seperator(string_iterator begin,string_iterator end)1108         string_iterator find_first_seperator(
1109             string_iterator begin, string_iterator end)
1110         {
1111             if (qbk_version_n < 105) {
1112                 for (; begin != end; ++begin) {
1113                     switch (*begin) {
1114                     case ' ':
1115                     case '\t':
1116                     case '\n':
1117                     case '\r':
1118                         return begin;
1119                     default:
1120                         break;
1121                     }
1122                 }
1123             }
1124             else {
1125                 unsigned int depth = 0;
1126 
1127                 for (; begin != end; ++begin) {
1128                     switch (*begin) {
1129                     case '[':
1130                         ++depth;
1131                         break;
1132                     case '\\':
1133                         if (++begin == end) return begin;
1134                         break;
1135                     case ']':
1136                         if (depth > 0) --depth;
1137                         break;
1138                     case ' ':
1139                     case '\t':
1140                     case '\n':
1141                     case '\r':
1142                         if (depth == 0) return begin;
1143                     default:
1144                         break;
1145                     }
1146                 }
1147             }
1148 
1149             return begin;
1150         }
1151 
find_seperator(string_iterator begin,string_iterator end)1152         std::pair<string_iterator, string_iterator> find_seperator(
1153             string_iterator begin, string_iterator end)
1154         {
1155             string_iterator first = begin = find_first_seperator(begin, end);
1156 
1157             for (; begin != end; ++begin) {
1158                 switch (*begin) {
1159                 case ' ':
1160                 case '\t':
1161                 case '\n':
1162                 case '\r':
1163                     break;
1164                 default:
1165                     return std::make_pair(first, begin);
1166                 }
1167             }
1168 
1169             return std::make_pair(first, begin);
1170         }
1171 
break_arguments(std::vector<value> & args,std::vector<std::string> const & params,fs::path const &)1172         void break_arguments(
1173             std::vector<value>& args,
1174             std::vector<std::string> const& params,
1175             fs::path const& /* filename */
1176             )
1177         {
1178             // Quickbook 1.4-: If there aren't enough parameters seperated by
1179             //                 '..' then seperate the last parameter using
1180             //                 whitespace.
1181             // Quickbook 1.5+: If '..' isn't used to seperate the parameters
1182             //                 then use whitespace to separate them
1183             //                 (2 = template name + argument).
1184 
1185             if (qbk_version_n < 105 ? args.size() : args.size() == 1) {
1186 
1187                 while (args.size() < params.size()) {
1188                     // Try to break the last argument at the first space found
1189                     // and push it into the back of args. Do this
1190                     // recursively until we have all the expected number of
1191                     // arguments, or if there are no more spaces left.
1192 
1193                     value last_arg = args.back();
1194                     string_iterator begin = last_arg.get_quickbook().begin();
1195                     string_iterator end = last_arg.get_quickbook().end();
1196 
1197                     std::pair<string_iterator, string_iterator> pos =
1198                         find_seperator(begin, end);
1199                     if (pos.second == end) break;
1200                     value new_arg(qbk_value(
1201                         last_arg.get_file(), pos.second, end,
1202                         template_tags::phrase));
1203 
1204                     args.back() = qbk_value(
1205                         last_arg.get_file(), begin, pos.first,
1206                         last_arg.get_tag());
1207                     args.push_back(new_arg);
1208                 }
1209             }
1210         }
1211 
get_arguments(std::vector<value> const & args,std::vector<std::string> const & params,template_scope const & scope,string_iterator first,quickbook::state & state)1212         std::pair<bool, std::vector<std::string>::const_iterator> get_arguments(
1213             std::vector<value> const& args,
1214             std::vector<std::string> const& params,
1215             template_scope const& scope,
1216             string_iterator first,
1217             quickbook::state& state)
1218         {
1219             std::vector<value>::const_iterator arg = args.begin();
1220             std::vector<std::string>::const_iterator tpl = params.begin();
1221             std::vector<std::string> empty_params;
1222 
1223             // Store each of the argument passed in as local templates:
1224             while (arg != args.end()) {
1225                 if (!state.templates.add(
1226                         template_symbol(*tpl, empty_params, *arg, &scope))) {
1227                     detail::outerr(state.current_file, first)
1228                         << "Duplicate Symbol Found" << std::endl;
1229                     ++state.error_count;
1230                     return std::make_pair(false, tpl);
1231                 }
1232                 ++arg;
1233                 ++tpl;
1234             }
1235             return std::make_pair(true, tpl);
1236         }
1237 
parse_template(value const & content,quickbook::state & state,bool is_attribute_template)1238         bool parse_template(
1239             value const& content,
1240             quickbook::state& state,
1241             bool is_attribute_template)
1242         {
1243             file_ptr saved_current_file = state.current_file;
1244 
1245             state.current_file = content.get_file();
1246             quickbook::string_view source = content.get_quickbook();
1247 
1248             parse_iterator first(source.begin());
1249             parse_iterator last(source.end());
1250 
1251             bool r = cl::parse(
1252                          first, last,
1253                          is_attribute_template
1254                              ? state.grammar().attribute_template_body
1255                              : content.get_tag() == template_tags::phrase
1256                                    ? state.grammar().inline_phrase
1257                                    : state.grammar().block_start)
1258                          .full;
1259 
1260             boost::swap(state.current_file, saved_current_file);
1261 
1262             return r;
1263         }
1264     }
1265 
call_template(quickbook::state & state,template_symbol const * symbol,std::vector<value> const & args,string_iterator first,bool is_attribute_template=false)1266     void call_template(
1267         quickbook::state& state,
1268         template_symbol const* symbol,
1269         std::vector<value> const& args,
1270         string_iterator first,
1271         bool is_attribute_template = false)
1272     {
1273         bool is_block = symbol->content.get_tag() != template_tags::phrase;
1274         assert(!(is_attribute_template && is_block));
1275 
1276         quickbook::paragraph_action paragraph_action(state);
1277 
1278         // Finish off any existing paragraphs.
1279         if (is_block) paragraph_action();
1280 
1281         // If this template contains already encoded text, then just
1282         // write it out, without going through any of the rigamarole.
1283 
1284         if (symbol->content.is_encoded()) {
1285             (is_block ? state.out : state.phrase)
1286                 << symbol->content.get_encoded();
1287             return;
1288         }
1289 
1290         // The template arguments should have the scope that the template was
1291         // called from, not the template's own scope.
1292         //
1293         // Note that for quickbook 1.4- this value is just ignored when the
1294         // arguments are expanded.
1295         template_scope const& call_scope = state.templates.top_scope();
1296 
1297         {
1298             state_save save(state, state_save::scope_callables);
1299             std::string save_block;
1300             std::string save_phrase;
1301 
1302             state.templates.start_template(symbol);
1303 
1304             qbk_version_n = symbol->content.get_file()->version();
1305 
1306             ++state.template_depth;
1307             if (state.template_depth > state.max_template_depth) {
1308                 detail::outerr(state.current_file, first)
1309                     << "Infinite loop detected" << std::endl;
1310                 ++state.error_count;
1311                 return;
1312             }
1313 
1314             // Store the current section level so that we can ensure that
1315             // [section] and [endsect] tags in the template are balanced.
1316             state.min_section_level = state.document.section_level();
1317 
1318             ///////////////////////////////////
1319             // Prepare the arguments as local templates
1320             bool get_arg_result;
1321             std::vector<std::string>::const_iterator tpl;
1322             boost::tie(get_arg_result, tpl) =
1323                 get_arguments(args, symbol->params, call_scope, first, state);
1324 
1325             if (!get_arg_result) {
1326                 return;
1327             }
1328 
1329             ///////////////////////////////////
1330             // parse the template body:
1331 
1332             if (symbol->content.get_file()->version() < 107u) {
1333                 state.out.swap(save_block);
1334                 state.phrase.swap(save_phrase);
1335             }
1336 
1337             if (!parse_template(
1338                     symbol->content, state, is_attribute_template)) {
1339                 detail::outerr(state.current_file, first)
1340                     << "Expanding " << (is_block ? "block" : "phrase")
1341                     << " template: " << symbol->identifier << "\n\n"
1342                     << "------------------begin------------------\n"
1343                     << symbol->content.get_quickbook()
1344                     << "------------------end--------------------\n"
1345                     << std::endl;
1346                 ++state.error_count;
1347                 return;
1348             }
1349 
1350             if (state.document.section_level() != state.min_section_level) {
1351                 detail::outerr(state.current_file, first)
1352                     << "Mismatched sections in template " << symbol->identifier
1353                     << std::endl;
1354                 ++state.error_count;
1355                 return;
1356             }
1357 
1358             if (symbol->content.get_file()->version() < 107u) {
1359                 state.out.swap(save_block);
1360                 state.phrase.swap(save_phrase);
1361 
1362                 if (is_block || !save_block.empty()) {
1363                     paragraph_action();
1364                     state.out << save_block;
1365                     state.phrase << save_phrase;
1366                     paragraph_action();
1367                 }
1368                 else {
1369                     state.phrase << save_phrase;
1370                 }
1371             }
1372             else {
1373                 if (is_block) paragraph_action();
1374             }
1375         }
1376     }
1377 
call_code_snippet(quickbook::state & state,template_symbol const * symbol,string_iterator first)1378     void call_code_snippet(
1379         quickbook::state& state,
1380         template_symbol const* symbol,
1381         string_iterator first)
1382     {
1383         assert(symbol->params.size() == 0);
1384         std::vector<value> args;
1385 
1386         // Create a fake symbol for call_template
1387         template_symbol t(
1388             symbol->identifier, symbol->params, symbol->content,
1389             symbol->lexical_parent);
1390 
1391         state.start_callouts();
1392         call_template(state, &t, args, first);
1393         state.out << state.end_callouts();
1394     }
1395 
do_template_action(quickbook::state & state,value template_list,string_iterator first)1396     void do_template_action(
1397         quickbook::state& state, value template_list, string_iterator first)
1398     {
1399         bool const is_attribute_template =
1400             template_list.get_tag() == template_tags::attribute_template;
1401 
1402         // Get the arguments
1403         value_consumer values = template_list;
1404 
1405         bool template_escape = values.check(template_tags::escape);
1406         if (template_escape) values.consume();
1407 
1408         std::string identifier =
1409             values.consume(template_tags::identifier).get_quickbook().to_s();
1410 
1411         std::vector<value> args;
1412 
1413         QUICKBOOK_FOR (value arg, values) {
1414             args.push_back(arg);
1415         }
1416 
1417         values.finish();
1418 
1419         template_symbol const* symbol = state.templates.find(identifier);
1420         BOOST_ASSERT(symbol);
1421 
1422         // Deal with escaped templates.
1423 
1424         if (template_escape) {
1425             if (!args.empty()) {
1426                 detail::outerr(state.current_file, first)
1427                     << "Arguments for escaped template." << std::endl;
1428                 ++state.error_count;
1429             }
1430 
1431             if (symbol->content.is_encoded()) {
1432                 state.phrase << symbol->content.get_encoded();
1433             }
1434             else {
1435                 state.phrase << symbol->content.get_quickbook();
1436 
1437                 /*
1438 
1439                 This would surround the escaped template in escape
1440                 comments to indicate to the post-processor that it
1441                 isn't quickbook generated markup. But I'm not sure if
1442                 it would work.
1443 
1444                 quickbook::detail::markup escape_markup
1445                     = detail::get_markup(phrase_tags::escape);
1446 
1447                 state.phrase
1448                     << escape_markup.pre
1449                     << symbol->content.get_quickbook()
1450                     << escape_markup.post
1451                     ;
1452                 */
1453             }
1454 
1455             return;
1456         }
1457 
1458         ///////////////////////////////////
1459         // Check that attribute templates are phrase templates
1460 
1461         if (is_attribute_template &&
1462             symbol->content.get_tag() != template_tags::phrase) {
1463             detail::outerr(state.current_file, first)
1464                 << "Only phrase templates can be used in attribute values."
1465                 << std::endl;
1466 
1467             ++state.error_count;
1468             return;
1469         }
1470 
1471         ///////////////////////////////////
1472         // Initialise the arguments
1473 
1474         switch (symbol->content.get_tag()) {
1475         case template_tags::block:
1476         case template_tags::phrase:
1477             // Break the arguments for a template
1478 
1479             break_arguments(args, symbol->params, state.current_file->path);
1480 
1481             if (args.size() != symbol->params.size()) {
1482                 detail::outerr(state.current_file, first)
1483                     << "Invalid number of arguments passed. Expecting: "
1484                     << symbol->params.size()
1485                     << " argument(s), got: " << args.size()
1486                     << " argument(s) instead." << std::endl;
1487 
1488                 ++state.error_count;
1489                 return;
1490             }
1491 
1492             call_template(state, symbol, args, first, is_attribute_template);
1493             break;
1494 
1495         case template_tags::snippet:
1496 
1497             if (!args.empty()) {
1498                 detail::outerr(state.current_file, first)
1499                     << "Arguments for code snippet." << std::endl;
1500                 ++state.error_count;
1501 
1502                 args.clear();
1503             }
1504 
1505             call_code_snippet(state, symbol, first);
1506             break;
1507 
1508         default:
1509             assert(0);
1510         }
1511     }
1512 
link_action(quickbook::state & state,value link)1513     void link_action(quickbook::state& state, value link)
1514     {
1515         write_anchors(state, state.phrase);
1516 
1517         detail::markup markup = detail::get_markup(link.get_tag());
1518 
1519         value_consumer values = link;
1520         value dst_value = values.consume();
1521         value content = values.consume();
1522         values.finish();
1523 
1524         std::string dst;
1525 
1526         if (link.get_tag() == phrase_tags::link) {
1527             dst = validate_id(state, dst_value);
1528         }
1529         else {
1530             dst = get_attribute_value(state, dst_value);
1531 
1532             if (link.get_tag() == phrase_tags::url) {
1533                 dst = detail::partially_escape_uri(dst);
1534             }
1535         }
1536 
1537         state.phrase << markup.pre;
1538         detail::print_string(dst, state.phrase.get());
1539         state.phrase << "\">";
1540 
1541         if (content.empty())
1542             detail::print_string(dst, state.phrase.get());
1543         else
1544             state.phrase << content.get_encoded();
1545 
1546         state.phrase << markup.post;
1547     }
1548 
variable_list_action(quickbook::state & state,value variable_list)1549     void variable_list_action(quickbook::state& state, value variable_list)
1550     {
1551         write_anchors(state, state.out);
1552 
1553         value_consumer values = variable_list;
1554         std::string title =
1555             values.consume(table_tags::title).get_quickbook().to_s();
1556 
1557         state.out << "<variablelist>\n";
1558 
1559         state.out << "<title>";
1560         detail::print_string(title, state.out.get());
1561         state.out << "</title>\n";
1562 
1563         QUICKBOOK_FOR (value_consumer entry, values) {
1564             state.out << "<varlistentry>";
1565 
1566             if (entry.check()) {
1567                 state.out << "<term>";
1568                 state.out << entry.consume().get_encoded();
1569                 state.out << "</term>";
1570             }
1571 
1572             if (entry.check()) {
1573                 state.out << "<listitem>";
1574                 QUICKBOOK_FOR (value phrase, entry)
1575                     state.out << phrase.get_encoded();
1576                 state.out << "</listitem>";
1577             }
1578 
1579             state.out << "</varlistentry>\n";
1580         }
1581 
1582         state.out << "</variablelist>\n";
1583 
1584         values.finish();
1585     }
1586 
table_action(quickbook::state & state,value table)1587     void table_action(quickbook::state& state, value table)
1588     {
1589         write_anchors(state, state.out);
1590 
1591         value_consumer values = table;
1592 
1593         std::string element_id;
1594         if (values.check(general_tags::element_id)) {
1595             element_id = validate_id(state, values.consume());
1596         }
1597 
1598         value title = values.consume(table_tags::title);
1599         bool has_title = !title.empty();
1600 
1601         std::string table_id;
1602 
1603         if (!element_id.empty()) {
1604             table_id =
1605                 state.document.add_id(element_id, id_category::explicit_id);
1606         }
1607         else if (has_title) {
1608             if (state.document.compatibility_version() >= 105) {
1609                 table_id = state.document.add_id(
1610                     detail::make_identifier(title.get_quickbook()),
1611                     id_category::generated);
1612             }
1613             else {
1614                 table_id = state.document.add_id("t", id_category::numbered);
1615             }
1616         }
1617 
1618         // Emulating the old behaviour which used the width of the final
1619         // row for span_count.
1620         int row_count = 0;
1621         int span_count = 0;
1622 
1623         value_consumer lookahead = values;
1624         QUICKBOOK_FOR (value row, lookahead) {
1625             ++row_count;
1626             span_count = boost::distance(row);
1627         }
1628         lookahead.finish();
1629 
1630         if (has_title) {
1631             state.out << "<table frame=\"all\"";
1632             if (!table_id.empty()) state.out << " id=\"" << table_id << "\"";
1633             state.out << ">\n";
1634             state.out << "<title>";
1635             if (qbk_version_n < 106u) {
1636                 detail::print_string(title.get_quickbook(), state.out.get());
1637             }
1638             else {
1639                 state.out << title.get_encoded();
1640             }
1641             state.out << "</title>";
1642         }
1643         else {
1644             state.out << "<informaltable frame=\"all\"";
1645             if (!table_id.empty()) state.out << " id=\"" << table_id << "\"";
1646             state.out << ">\n";
1647         }
1648 
1649         state.out << "<tgroup cols=\"" << span_count << "\">\n";
1650 
1651         if (row_count > 1) {
1652             state.out << "<thead>"
1653                       << "<row>";
1654             QUICKBOOK_FOR (value cell, values.consume()) {
1655                 state.out << "<entry>" << cell.get_encoded() << "</entry>";
1656             }
1657             state.out << "</row>\n"
1658                       << "</thead>\n";
1659         }
1660 
1661         state.out << "<tbody>\n";
1662 
1663         QUICKBOOK_FOR (value row, values) {
1664             state.out << "<row>";
1665             QUICKBOOK_FOR (value cell, row) {
1666                 state.out << "<entry>" << cell.get_encoded() << "</entry>";
1667             }
1668             state.out << "</row>\n";
1669         }
1670 
1671         values.finish();
1672 
1673         state.out << "</tbody>\n"
1674                   << "</tgroup>\n";
1675 
1676         if (has_title) {
1677             state.out << "</table>\n";
1678         }
1679         else {
1680             state.out << "</informaltable>\n";
1681         }
1682     }
1683 
begin_section_action(quickbook::state & state,value begin_section_list)1684     void begin_section_action(quickbook::state& state, value begin_section_list)
1685     {
1686         value_consumer values = begin_section_list;
1687 
1688         value element_id = values.optional_consume(general_tags::element_id);
1689         value content = values.consume();
1690         values.finish();
1691 
1692         std::string full_id = state.document.begin_section(
1693             element_id,
1694             element_id.empty()
1695                 ? detail::make_identifier(content.get_quickbook())
1696                 : validate_id(state, element_id),
1697             element_id.empty() ? id_category::generated_section
1698                                : id_category::explicit_section_id,
1699             state.current_source_mode());
1700 
1701         state.out << "\n<section id=\"" << full_id << "\">\n";
1702 
1703         std::string title = content.get_encoded();
1704 
1705         if (!title.empty()) {
1706             state.out << "<title>";
1707 
1708             write_anchors(state, state.out);
1709 
1710             if (self_linked_headers &&
1711                 state.document.compatibility_version() >= 103) {
1712                 state.out << quickbook::detail::linkify(title, full_id);
1713             }
1714             else {
1715                 state.out << title;
1716             }
1717 
1718             state.out << "</title>\n";
1719         }
1720     }
1721 
end_section_action(quickbook::state & state,value end_section_list,string_iterator first)1722     void end_section_action(
1723         quickbook::state& state, value end_section_list, string_iterator first)
1724     {
1725         value_consumer values = end_section_list;
1726         value element_id = values.optional_consume(general_tags::element_id);
1727         values.finish();
1728 
1729         write_anchors(state, state.out);
1730 
1731         if (state.document.section_level() <= state.min_section_level) {
1732             file_position const pos = state.current_file->position_of(first);
1733 
1734             detail::outerr(state.current_file->path, pos.line)
1735                 << "Mismatched [endsect] near column " << pos.column << ".\n";
1736             ++state.error_count;
1737 
1738             return;
1739         }
1740 
1741         if (!element_id.empty() &&
1742             !(element_id == state.document.explicit_id())) {
1743             file_position const pos = state.current_file->position_of(first);
1744             value section_element_id = state.document.explicit_id();
1745 
1746             if (section_element_id.empty()) {
1747                 detail::outerr(state.current_file->path, pos.line)
1748                     << "Endsect has unexpected id '"
1749                     << element_id.get_quickbook()
1750                     << "' in section with no explicit id, near column "
1751                     << pos.column << ".\n";
1752             }
1753             else {
1754                 detail::outerr(state.current_file->path, pos.line)
1755                     << "Endsect has incorrect id '"
1756                     << element_id.get_quickbook() << "', expected '"
1757                     << state.document.explicit_id().get_quickbook()
1758                     << "', near column " << pos.column << ".\n";
1759             }
1760             ++state.error_count;
1761         }
1762 
1763         state.out << "</section>";
1764         state.document.end_section();
1765     }
1766 
operator ()(parse_iterator first,parse_iterator) const1767     void element_id_warning_action::operator()(
1768         parse_iterator first, parse_iterator) const
1769     {
1770         detail::outwarn(state.current_file, first.base()) << "Empty id.\n";
1771     }
1772 
xinclude_action(quickbook::state & state,value xinclude)1773     void xinclude_action(quickbook::state& state, value xinclude)
1774     {
1775         write_anchors(state, state.out);
1776 
1777         value_consumer values = xinclude;
1778         path_parameter x = check_xinclude_path(values.consume(), state);
1779         values.finish();
1780 
1781         if (x.type == path_parameter::path) {
1782             quickbook_path path = resolve_xinclude_path(x.value, state, true);
1783 
1784             state.out << "\n<xi:include href=\"";
1785             detail::print_string(
1786                 file_path_to_url(path.abstract_file_path), state.out.get());
1787             state.out << "\" />\n";
1788         }
1789     }
1790 
load_quickbook(quickbook::state & state,quickbook_path const & path,value::tag_type load_type,value const & include_doc_id=value ())1791     void load_quickbook(
1792         quickbook::state& state,
1793         quickbook_path const& path,
1794         value::tag_type load_type,
1795         value const& include_doc_id = value())
1796     {
1797         assert(
1798             load_type == block_tags::include ||
1799             load_type == block_tags::import);
1800 
1801         // Check this before qbk_version_n gets changed by the inner file.
1802         bool keep_inner_source_mode = (qbk_version_n < 106);
1803 
1804         {
1805             // When importing, state doesn't scope templates and macros so that
1806             // they're added to the existing scope. It might be better to add
1807             // them to a new scope then explicitly import them into the
1808             // existing scope.
1809             //
1810             // For old versions of quickbook, templates aren't scoped by the
1811             // file.
1812             state_save save(
1813                 state,
1814                 load_type == block_tags::import
1815                     ? state_save::scope_output
1816                     : qbk_version_n >= 106u ? state_save::scope_callables
1817                                             : state_save::scope_macros);
1818 
1819             state.current_file = load(path.file_path); // Throws load_error
1820             state.current_path = path;
1821             state.imported = (load_type == block_tags::import);
1822 
1823             // update the __FILENAME__ macro
1824             state.update_filename_macro();
1825 
1826             // parse the file
1827             quickbook::parse_file(state, include_doc_id, true);
1828 
1829             // Don't restore source_mode on older versions.
1830             if (keep_inner_source_mode) save.source_mode = state.source_mode;
1831         }
1832 
1833         // restore the __FILENAME__ macro
1834         state.update_filename_macro();
1835     }
1836 
load_source_file(quickbook::state & state,quickbook_path const & path,value::tag_type load_type,string_iterator first,value const &=value ())1837     void load_source_file(
1838         quickbook::state& state,
1839         quickbook_path const& path,
1840         value::tag_type load_type,
1841         string_iterator first,
1842         value const& /* include_doc_id */ = value())
1843     {
1844         assert(
1845             load_type == block_tags::include ||
1846             load_type == block_tags::import);
1847 
1848         std::string ext = path.file_path.extension().generic_string();
1849         std::vector<template_symbol> storage;
1850         // Throws load_error
1851         state.error_count +=
1852             load_snippets(path.file_path, storage, ext, load_type);
1853 
1854         if (load_type == block_tags::include) {
1855             state.templates.push();
1856         }
1857 
1858         QUICKBOOK_FOR (template_symbol& ts, storage) {
1859             std::string tname = ts.identifier;
1860             if (tname != "!") {
1861                 ts.lexical_parent = &state.templates.top_scope();
1862                 if (!state.templates.add(ts)) {
1863                     detail::outerr(
1864                         ts.content.get_file(), ts.content.get_position())
1865                         << "Template Redefinition: " << tname << std::endl;
1866                     ++state.error_count;
1867                 }
1868             }
1869         }
1870 
1871         if (load_type == block_tags::include) {
1872             QUICKBOOK_FOR (template_symbol& ts, storage) {
1873                 std::string tname = ts.identifier;
1874 
1875                 if (tname == "!") {
1876                     ts.lexical_parent = &state.templates.top_scope();
1877                     call_code_snippet(state, &ts, first);
1878                 }
1879             }
1880 
1881             state.templates.pop();
1882         }
1883     }
1884 
include_action(quickbook::state & state,value include,string_iterator first)1885     void include_action(
1886         quickbook::state& state, value include, string_iterator first)
1887     {
1888         write_anchors(state, state.out);
1889 
1890         value_consumer values = include;
1891         value include_doc_id =
1892             values.optional_consume(general_tags::include_id);
1893         path_parameter parameter = check_path(values.consume(), state);
1894         values.finish();
1895 
1896         std::set<quickbook_path> search =
1897             include_search(parameter, state, first);
1898         QUICKBOOK_FOR (quickbook_path const& path, search) {
1899             try {
1900                 if (qbk_version_n >= 106) {
1901                     if (state.imported &&
1902                         include.get_tag() == block_tags::include)
1903                         return;
1904 
1905                     std::string ext =
1906                         path.file_path.extension().generic_string();
1907 
1908                     if (ext == ".qbk" || ext == ".quickbook") {
1909                         load_quickbook(
1910                             state, path, include.get_tag(), include_doc_id);
1911                     }
1912                     else {
1913                         load_source_file(
1914                             state, path, include.get_tag(), first,
1915                             include_doc_id);
1916                     }
1917                 }
1918                 else {
1919                     if (include.get_tag() == block_tags::include) {
1920                         load_quickbook(
1921                             state, path, include.get_tag(), include_doc_id);
1922                     }
1923                     else {
1924                         load_source_file(
1925                             state, path, include.get_tag(), first,
1926                             include_doc_id);
1927                     }
1928                 }
1929             } catch (load_error& e) {
1930                 ++state.error_count;
1931 
1932                 detail::outerr(state.current_file, first)
1933                     << "Loading file " << path.file_path << ": " << e.what()
1934                     << std::endl;
1935             }
1936         }
1937     }
1938 
start(value::tag_type t)1939     bool to_value_scoped_action::start(value::tag_type t)
1940     {
1941         state.push_output();
1942         state.anchors.swap(saved_anchors);
1943         tag = t;
1944 
1945         return true;
1946     }
1947 
success(parse_iterator first,parse_iterator last)1948     void to_value_scoped_action::success(
1949         parse_iterator first, parse_iterator last)
1950     {
1951         std::string value;
1952 
1953         if (!state.out.str().empty()) {
1954             paragraph_action para(state);
1955             para(); // For paragraphs before the template call.
1956             write_anchors(state, state.out);
1957             state.out.swap(value);
1958         }
1959         else {
1960             write_anchors(state, state.phrase);
1961             state.phrase.swap(value);
1962         }
1963 
1964         state.values.builder.insert(encoded_qbk_value(
1965             state.current_file, first.base(), last.base(), value, tag));
1966     }
1967 
cleanup()1968     void to_value_scoped_action::cleanup()
1969     {
1970         state.pop_output();
1971         state.anchors.swap(saved_anchors);
1972     }
1973 }
1974