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