]> git.proxmox.com Git - ceph.git/blame - ceph/src/boost/tools/quickbook/src/actions.cpp
import new upstream nautilus stable release 14.2.8
[ceph.git] / ceph / src / boost / tools / quickbook / src / actions.cpp
CommitLineData
7c673cae
FG
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=============================================================================*/
11fdf7f2 11#include "actions.hpp"
7c673cae 12#include <functional>
7c673cae 13#include <map>
11fdf7f2 14#include <numeric>
7c673cae 15#include <set>
11fdf7f2
TL
16#include <vector>
17#include <boost/algorithm/string/replace.hpp>
7c673cae
FG
18#include <boost/filesystem/convenience.hpp>
19#include <boost/filesystem/fstream.hpp>
7c673cae 20#include <boost/lexical_cast.hpp>
7c673cae 21#include <boost/next_prior.hpp>
11fdf7f2
TL
22#include <boost/range/algorithm/replace.hpp>
23#include <boost/range/distance.hpp>
24#include "block_tags.hpp"
25#include "document_state.hpp"
7c673cae 26#include "files.hpp"
92f5a8d4 27#include "for.hpp"
11fdf7f2 28#include "grammar.hpp"
7c673cae 29#include "markups.hpp"
11fdf7f2
TL
30#include "path.hpp"
31#include "phrase_tags.hpp"
32#include "quickbook.hpp"
7c673cae
FG
33#include "state.hpp"
34#include "state_save.hpp"
b32b8144 35#include "stream.hpp"
11fdf7f2
TL
36#include "syntax_highlight.hpp"
37#include "utils.hpp"
7c673cae
FG
38
39namespace quickbook
40{
11fdf7f2
TL
41 namespace
42 {
7c673cae
FG
43 void write_anchors(quickbook::state& state, collector& tgt)
44 {
7c673cae 45 if (state.source_mode_next) {
11fdf7f2
TL
46 detail::outwarn(
47 state.source_mode_next_pos.get_file(),
7c673cae 48 state.source_mode_next_pos.get_position())
11fdf7f2 49 << "Temporary source mode unsupported here." << std::endl;
7c673cae
FG
50 state.source_mode_next = 0;
51 }
52
92f5a8d4 53 QUICKBOOK_FOR (auto const& anchor_id, state.anchors) {
7c673cae 54 tgt << "<anchor id=\"";
92f5a8d4 55 detail::print_string(anchor_id, tgt.get());
7c673cae
FG
56 tgt << "\"/>";
57 }
11fdf7f2 58
7c673cae
FG
59 state.anchors.clear();
60 }
11fdf7f2
TL
61
62 std::string add_anchor(
63 quickbook::state& state,
64 quickbook::string_view id,
65 id_category::categories category = id_category::explicit_anchor_id)
7c673cae
FG
66 {
67 std::string placeholder = state.document.add_anchor(id, category);
68 state.anchors.push_back(placeholder);
69 return placeholder;
70 }
71
11fdf7f2
TL
72 std::string get_attribute_value(
73 quickbook::state& state, quickbook::value const& value)
7c673cae 74 {
11fdf7f2
TL
75 std::string x = value.is_encoded() ? value.get_encoded()
76 : value.get_quickbook().to_s();
7c673cae
FG
77
78 if (x.empty()) {
79 detail::outerr(value.get_file(), value.get_position())
11fdf7f2 80 << "Empty attribute value." << std::endl;
7c673cae
FG
81 ++state.error_count;
82 x = "xxx";
83 }
84
85 return x;
86 }
87
11fdf7f2
TL
88 std::string validate_id(
89 quickbook::state& state, quickbook::value const& id_value)
7c673cae
FG
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.
11fdf7f2
TL
95 if (id[0] == '$') {
96 valid = false;
97 id[0] = '_';
98 }
7c673cae
FG
99
100 if (qbk_version_n >= 107u) {
101 char const* allowed_punctuation = "_.-";
102
92f5a8d4 103 QUICKBOOK_FOR (char c, id) {
7c673cae 104 if (!std::isalnum(c) &&
11fdf7f2 105 !std::strchr(allowed_punctuation, c))
7c673cae
FG
106 valid = false;
107 }
108 }
109
110 if (!valid) {
111 detail::outerr(id_value.get_file(), id_value.get_position())
11fdf7f2
TL
112 << "Invalid id: " << (id_value.is_encoded()
113 ? id_value.get_encoded()
114 : id_value.get_quickbook().to_s())
7c673cae
FG
115 << std::endl;
116 ++state.error_count;
117 }
118
119 return id;
120 }
121 }
122
11fdf7f2
TL
123 bool quickbook_range::in_range() const
124 {
7c673cae
FG
125 return qbk_version_n >= lower && qbk_version_n < upper;
126 }
127
11fdf7f2
TL
128 bool quickbook_strict::is_strict_checking() const
129 {
b32b8144
FG
130 return state.strict_mode;
131 }
132
133 void list_action(quickbook::state&, value);
7c673cae
FG
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);
11fdf7f2 156
7c673cae
FG
157 void element_action::operator()(parse_iterator first, parse_iterator) const
158 {
159 value_consumer values = state.values.release();
11fdf7f2 160 if (!values.check() || !state.conditional) return;
7c673cae
FG
161 value v = values.consume();
162 values.finish();
11fdf7f2
TL
163
164 switch (v.get_tag()) {
7c673cae
FG
165 case block_tags::ordered_list:
166 case block_tags::itemized_list:
b32b8144 167 return list_action(state, v);
7c673cae
FG
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:
11fdf7f2 189 return block_action(state, v);
7c673cae 190 case block_tags::hr:
11fdf7f2 191 return block_empty_action(state, v);
7c673cae 192 case block_tags::macro_definition:
11fdf7f2 193 return macro_definition_action(state, v);
7c673cae 194 case block_tags::template_definition:
11fdf7f2 195 return template_body_action(state, v);
7c673cae
FG
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
252 void break_action::operator()(parse_iterator first, parse_iterator) const
253 {
254 write_anchors(state, state.phrase);
255
11fdf7f2 256 if (*first == '\\') {
7c673cae
FG
257 detail::outwarn(state.current_file, first.base())
258 //<< "in column:" << pos.column << ", "
11fdf7f2
TL
259 << "'\\n' is deprecated, pleases use '[br]' instead"
260 << ".\n";
7c673cae
FG
261 }
262
11fdf7f2 263 if (!state.warned_about_breaks) {
7c673cae
FG
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 }
11fdf7f2 270
7c673cae
FG
271 state.phrase << detail::get_markup(phrase_tags::break_mark).pre;
272 }
273
11fdf7f2
TL
274 void error_message_action::operator()(
275 parse_iterator first, parse_iterator last) const
7c673cae
FG
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);
11fdf7f2
TL
282 boost::replace_all(
283 formatted_message, "%c",
7c673cae
FG
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
11fdf7f2
TL
291 void error_action::operator()(
292 parse_iterator first, parse_iterator /*last*/) const
7c673cae
FG
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
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;
11fdf7f2
TL
308 state.out << markup.pre << values.consume().get_encoded()
309 << markup.post;
7c673cae
FG
310 values.finish();
311 }
312
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
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;
11fdf7f2
TL
328 state.phrase << markup.pre << values.consume().get_encoded()
329 << markup.post;
7c673cae
FG
330 values.finish();
331 }
332
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
11fdf7f2
TL
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>";
7c673cae
FG
346 }
347
348 void footnote_action(quickbook::state& state, value phrase)
349 {
350 write_anchors(state, state.phrase);
351
352 value_consumer values = phrase;
11fdf7f2
TL
353 state.phrase << "<footnote id=\""
354 << state.document.add_id("f", id_category::numbered)
355 << "\"><para>" << values.consume().get_encoded()
356 << "</para></footnote>";
7c673cae
FG
357 values.finish();
358 }
359
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
368 void paragraph_action::operator()() const
369 {
370 std::string str;
371 state.phrase.swap(str);
372
11fdf7f2 373 std::string::const_iterator pos = str.begin(), end = str.end();
7c673cae 374
11fdf7f2
TL
375 while (pos != end && cl::space_p.test(*pos))
376 ++pos;
7c673cae 377
11fdf7f2
TL
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);
7c673cae
FG
383 state.out << markup.pre << str;
384 write_anchors(state, state.out);
385 state.out << markup.post;
386 }
387 }
388
389 void explicit_list_action::operator()() const
390 {
391 state.explicit_list = true;
392 }
393
394 void phrase_end_action::operator()() const
395 {
396 write_anchors(state, state.phrase);
397 }
11fdf7f2
TL
398
399 namespace
400 {
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)
7c673cae 407 {
11fdf7f2 408 if (self_link && !id.empty()) {
7c673cae
FG
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 }
11fdf7f2 419 else {
7c673cae 420 state.out << "<bridgehead renderas=\"sect" << level << "\"";
11fdf7f2 421 if (!id.empty()) state.out << " id=\"" << id << "\"";
7c673cae
FG
422 state.out << ">";
423 state.out << str;
424 state.out << "</bridgehead>";
425 }
426 }
427 }
428
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
11fdf7f2 440 if (generic) {
7c673cae 441 level = state.document.section_level() + 1;
11fdf7f2
TL
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
7c673cae 446 }
11fdf7f2 447 else {
7c673cae
FG
448 level = heading_list.get_tag() - block_tags::heading1 + 1;
449 }
450
451 write_anchors(state, state.out);
452
11fdf7f2 453 if (!element_id.empty()) {
7c673cae
FG
454 // Use an explicit id.
455
456 std::string anchor = state.document.add_id(
11fdf7f2 457 validate_id(state, element_id), id_category::explicit_id);
7c673cae 458
11fdf7f2
TL
459 write_bridgehead(
460 state, level, content.get_encoded(), anchor,
461 self_linked_headers);
7c673cae 462 }
11fdf7f2 463 else if (state.document.compatibility_version() >= 106u) {
7c673cae
FG
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
11fdf7f2
TL
470 write_bridgehead(
471 state, level, content.get_encoded(), anchor,
472 self_linked_headers);
7c673cae 473 }
11fdf7f2
TL
474 else {
475 // Generate ids that are compatible with older versions of
476 // quickbook.
7c673cae
FG
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(
11fdf7f2
TL
488 state.document.replace_placeholders_with_unresolved_ids(
489 content.get_encoded()));
7c673cae
FG
490
491 if (generic || state.document.compatibility_version() >= 103) {
492 std::string anchor =
493 state.document.add_id(id, id_category::generated_heading);
494
11fdf7f2
TL
495 write_bridgehead(
496 state, level, content.get_encoded(), anchor,
497 self_linked_headers);
7c673cae
FG
498 }
499 else {
11fdf7f2
TL
500 std::string anchor = state.document.old_style_id(
501 id, id_category::generated_heading);
7c673cae 502
11fdf7f2
TL
503 write_bridgehead(
504 state, level, content.get_encoded(), anchor, false);
7c673cae
FG
505 }
506 }
507 }
508
509 void simple_phrase_action::operator()(char mark) const
510 {
511 write_anchors(state, state.phrase);
512
11fdf7f2
TL
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
7c673cae
FG
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
533 bool cond_phrase_push::start()
534 {
535 value_consumer values = state.values.release();
536
537 saved_conditional = state.conditional;
538
11fdf7f2 539 if (saved_conditional) {
b32b8144
FG
540 bool positive = values.consume().get_quickbook().empty();
541 quickbook::string_view macro1 = values.consume().get_quickbook();
7c673cae
FG
542 std::string macro(macro1.begin(), macro1.end());
543
b32b8144
FG
544 state.conditional =
545 (bool)find(state.macro, macro.c_str()) == positive;
7c673cae
FG
546
547 if (!state.conditional) {
548 state.push_output();
549 state.anchors.swap(anchors);
550 }
551 }
552
553 return true;
554 }
11fdf7f2 555
7c673cae
FG
556 void cond_phrase_push::cleanup()
557 {
11fdf7f2 558 if (saved_conditional && !state.conditional) {
7c673cae
FG
559 state.pop_output();
560 state.anchors.swap(anchors);
561 }
562
563 state.conditional = saved_conditional;
564 }
565
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
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
594 void state::start_list_item()
595 {
596 out << "<listitem>";
597 write_anchors(*this, phrase);
598 }
599
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 {
11fdf7f2
TL
610 bool parse_template(
611 value const&,
612 quickbook::state& state,
613 bool is_attribute_template = false);
7c673cae
FG
614 }
615
11fdf7f2 616 void state::start_callouts() { ++callout_depth; }
7c673cae
FG
617
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
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>";
11fdf7f2 646 while (c.check()) {
7c673cae
FG
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
11fdf7f2
TL
659 if (!r) {
660 detail::outerr(
661 callout_body.get_file(), callout_body.get_position())
7c673cae 662 << "Expanding callout." << std::endl
11fdf7f2 663 << "------------------begin------------------"
7c673cae 664 << std::endl
11fdf7f2
TL
665 << callout_body.get_quickbook() << std::endl
666 << "------------------end--------------------"
667 << std::endl;
7c673cae
FG
668 ++error_count;
669 }
670
671 out.swap(callout_value);
672 }
11fdf7f2 673
7c673cae
FG
674 block += "<callout arearefs=\"" + callout_id1 + "\" ";
675 block += "id=\"" + callout_id2 + "\">";
676 block += callout_value;
677 block += "</callout>";
678 }
679 block += "</calloutlist>";
11fdf7f2 680
7c673cae
FG
681 return block;
682 }
683
b32b8144 684 void list_action(quickbook::state& state, value list)
7c673cae
FG
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
92f5a8d4 692 QUICKBOOK_FOR (value item, list) {
7c673cae
FG
693 state.out << "<listitem>";
694 state.out << item.get_encoded();
695 state.out << "</listitem>";
696 }
697
698 state.out << markup.post;
699 }
700
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
711 void do_macro_action::operator()(std::string const& str) const
712 {
713 write_anchors(state, state.phrase);
714
11fdf7f2 715 if (str == quickbook_get_date) {
7c673cae
FG
716 char strdate[64];
717 strftime(strdate, sizeof(strdate), "%Y-%b-%d", current_time);
718 state.phrase << strdate;
719 }
11fdf7f2 720 else if (str == quickbook_get_time) {
7c673cae
FG
721 char strdate[64];
722 strftime(strdate, sizeof(strdate), "%I:%M:%S %p", current_time);
723 state.phrase << strdate;
724 }
11fdf7f2 725 else {
7c673cae
FG
726 state.phrase << str;
727 }
728 }
729
11fdf7f2 730 void raw_char_action::operator()(char ch) const { state.phrase << ch; }
7c673cae 731
11fdf7f2
TL
732 void raw_char_action::operator()(
733 parse_iterator first, parse_iterator last) const
7c673cae
FG
734 {
735 while (first != last)
736 state.phrase << *first++;
737 }
738
739 void source_mode_action(quickbook::state& state, value source_mode)
740 {
741 state.change_source_mode(source_mode.get_tag());
742 }
743
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
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;
b32b8144 757 quickbook::string_view code_value = values.consume().get_quickbook();
7c673cae
FG
758 values.finish();
759
11fdf7f2
TL
760 bool inline_code =
761 code_tag == code_tags::inline_code ||
7c673cae
FG
762 (code_tag == code_tags::inline_code_block && qbk_version_n < 106u);
763 bool block = code_tag != code_tags::inline_code;
764
11fdf7f2
TL
765 source_mode_type source_mode =
766 state.source_mode_next ? state.source_mode_next
767 : state.current_source_mode().source_mode;
7c673cae
FG
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
825 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
11fdf7f2
TL
832 void plain_char_action::operator()(
833 parse_iterator first, parse_iterator last) const
7c673cae
FG
834 {
835 write_anchors(state, state.phrase);
836
837 while (first != last)
838 detail::print_char(*first++, state.phrase.get());
839 }
840
11fdf7f2
TL
841 void escape_unicode_action::operator()(
842 parse_iterator first, parse_iterator last) const
7c673cae
FG
843 {
844 write_anchors(state, state.phrase);
845
11fdf7f2
TL
846 while (first != last && *first == '0')
847 ++first;
7c673cae
FG
848
849 // Just ignore \u0000
850 // Maybe I should issue a warning?
11fdf7f2
TL
851 if (first == last) return;
852
7c673cae 853 std::string hex_digits(first, last);
11fdf7f2
TL
854
855 if (hex_digits.size() == 2 && *first > '0' && *first <= '7') {
7c673cae 856 using namespace std;
b32b8144 857 detail::print_char(
11fdf7f2 858 (char)strtol(hex_digits.c_str(), 0, 16), state.phrase.get());
7c673cae
FG
859 }
860 else {
861 state.phrase << "&#x" << hex_digits << ";";
862 }
863 }
864
865 void write_plain_text(std::ostream& out, value const& v)
866 {
11fdf7f2 867 if (v.is_encoded()) {
7c673cae
FG
868 detail::print_string(v.get_encoded(), out);
869 }
870 else {
b32b8144 871 quickbook::string_view value = v.get_quickbook();
11fdf7f2
TL
872 for (string_iterator first = value.begin(), last = value.end();
873 first != last; ++first) {
7c673cae
FG
874 if (*first == '\\' && ++first == last) break;
875 detail::print_char(*first, out);
876 }
877 }
878 }
879
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
92f5a8d4 892 QUICKBOOK_FOR (value pair_, values) {
7c673cae
FG
893 value_consumer pair = pair_;
894 value name = pair.consume();
895 value value = pair.consume();
11fdf7f2
TL
896 std::string name_str(
897 name.get_quickbook().begin(), name.get_quickbook().end());
7c673cae 898 pair.finish();
11fdf7f2 899 if (!attributes.insert(std::make_pair(name_str, value)).second) {
7c673cae 900 detail::outwarn(name.get_file(), name.get_position())
11fdf7f2 901 << "Duplicate image attribute: " << name.get_quickbook()
7c673cae
FG
902 << std::endl;
903 }
904 }
11fdf7f2 905
7c673cae
FG
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.
11fdf7f2
TL
912
913 std::string fileref =
914 attributes["fileref"].is_encoded()
915 ? attributes["fileref"].get_encoded()
916 : attributes["fileref"].get_quickbook().to_s();
7c673cae
FG
917
918 // Check for windows paths, then convert.
919 // A bit crude, but there you go.
920
11fdf7f2
TL
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 << "'"
7c673cae
FG
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('/');
11fdf7f2 944 stem = pos == std::string::npos ? fileref : fileref.substr(pos + 1);
7c673cae
FG
945
946 pos = stem.rfind('.');
11fdf7f2 947 if (pos != std::string::npos) {
7c673cae
FG
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");
11fdf7f2
TL
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();
7c673cae
FG
961 attributes.erase("alt");
962
11fdf7f2
TL
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 }
7c673cae
FG
1029 }
1030
1031 state.phrase << "<inlinemediaobject>";
1032
1033 state.phrase << "<imageobject><imagedata";
11fdf7f2 1034
92f5a8d4 1035 QUICKBOOK_FOR (attribute_map::value_type const& attr, attributes) {
7c673cae
FG
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
11fdf7f2
TL
1054 void macro_definition_action(
1055 quickbook::state& state, quickbook::value macro_definition)
7c673cae
FG
1056 {
1057 value_consumer values = macro_definition;
b32b8144 1058 std::string macro_id = values.consume().get_quickbook().to_s();
7c673cae
FG
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
11fdf7f2 1068 if (existing_macro) {
7c673cae
FG
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
11fdf7f2 1077 state.macro.add(macro_id.begin(), macro_id.end(), phrase);
7c673cae
FG
1078 }
1079
11fdf7f2
TL
1080 void template_body_action(
1081 quickbook::state& state, quickbook::value template_definition)
7c673cae
FG
1082 {
1083 value_consumer values = template_definition;
b32b8144 1084 std::string identifier = values.consume().get_quickbook().to_s();
7c673cae
FG
1085
1086 std::vector<std::string> template_values;
92f5a8d4 1087 QUICKBOOK_FOR (value const& p, values.consume()) {
b32b8144 1088 template_values.push_back(p.get_quickbook().to_s());
7c673cae
FG
1089 }
1090
11fdf7f2
TL
1091 BOOST_ASSERT(
1092 values.check(template_tags::block) ||
1093 values.check(template_tags::phrase));
7c673cae
FG
1094 value body = values.consume();
1095 BOOST_ASSERT(!values.check());
11fdf7f2
TL
1096
1097 if (!state.templates.add(template_symbol(
1098 identifier, template_values, body,
1099 &state.templates.top_scope()))) {
7c673cae
FG
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 {
11fdf7f2
TL
1108 string_iterator find_first_seperator(
1109 string_iterator begin, string_iterator end)
7c673cae 1110 {
11fdf7f2
TL
1111 if (qbk_version_n < 105) {
1112 for (; begin != end; ++begin) {
1113 switch (*begin) {
7c673cae
FG
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
11fdf7f2
TL
1127 for (; begin != end; ++begin) {
1128 switch (*begin) {
7c673cae
FG
1129 case '[':
1130 ++depth;
1131 break;
1132 case '\\':
11fdf7f2 1133 if (++begin == end) return begin;
7c673cae
FG
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 }
11fdf7f2 1148
7c673cae
FG
1149 return begin;
1150 }
11fdf7f2
TL
1151
1152 std::pair<string_iterator, string_iterator> find_seperator(
1153 string_iterator begin, string_iterator end)
7c673cae
FG
1154 {
1155 string_iterator first = begin = find_first_seperator(begin, end);
1156
11fdf7f2
TL
1157 for (; begin != end; ++begin) {
1158 switch (*begin) {
7c673cae
FG
1159 case ' ':
1160 case '\t':
1161 case '\n':
1162 case '\r':
1163 break;
1164 default:
1165 return std::make_pair(first, begin);
1166 }
1167 }
11fdf7f2 1168
7c673cae
FG
1169 return std::make_pair(first, begin);
1170 }
11fdf7f2 1171
7c673cae 1172 void break_arguments(
11fdf7f2
TL
1173 std::vector<value>& args,
1174 std::vector<std::string> const& params,
1175 fs::path const& /* filename */
1176 )
7c673cae
FG
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
11fdf7f2
TL
1185 if (qbk_version_n < 105 ? args.size() : args.size() == 1) {
1186
1187 while (args.size() < params.size()) {
7c673cae
FG
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();
11fdf7f2 1196
7c673cae
FG
1197 std::pair<string_iterator, string_iterator> pos =
1198 find_seperator(begin, end);
1199 if (pos.second == end) break;
11fdf7f2
TL
1200 value new_arg(qbk_value(
1201 last_arg.get_file(), pos.second, end,
1202 template_tags::phrase));
7c673cae 1203
11fdf7f2
TL
1204 args.back() = qbk_value(
1205 last_arg.get_file(), begin, pos.first,
1206 last_arg.get_tag());
7c673cae
FG
1207 args.push_back(new_arg);
1208 }
1209 }
1210 }
1211
11fdf7f2
TL
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)
7c673cae
FG
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:
11fdf7f2 1224 while (arg != args.end()) {
7c673cae 1225 if (!state.templates.add(
11fdf7f2 1226 template_symbol(*tpl, empty_params, *arg, &scope))) {
7c673cae
FG
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 }
11fdf7f2
TL
1232 ++arg;
1233 ++tpl;
7c673cae
FG
1234 }
1235 return std::make_pair(true, tpl);
1236 }
11fdf7f2 1237
7c673cae 1238 bool parse_template(
11fdf7f2
TL
1239 value const& content,
1240 quickbook::state& state,
1241 bool is_attribute_template)
7c673cae
FG
1242 {
1243 file_ptr saved_current_file = state.current_file;
1244
1245 state.current_file = content.get_file();
b32b8144 1246 quickbook::string_view source = content.get_quickbook();
7c673cae
FG
1247
1248 parse_iterator first(source.begin());
1249 parse_iterator last(source.end());
1250
11fdf7f2
TL
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;
7c673cae
FG
1259
1260 boost::swap(state.current_file, saved_current_file);
1261
1262 return r;
1263 }
1264 }
1265
11fdf7f2
TL
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)
7c673cae
FG
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
11fdf7f2
TL
1284 if (symbol->content.is_encoded()) {
1285 (is_block ? state.out : state.phrase)
1286 << symbol->content.get_encoded();
7c673cae
FG
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;
11fdf7f2 1307 if (state.template_depth > state.max_template_depth) {
7c673cae
FG
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
11fdf7f2 1325 if (!get_arg_result) {
7c673cae
FG
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
11fdf7f2
TL
1337 if (!parse_template(
1338 symbol->content, state, is_attribute_template)) {
7c673cae 1339 detail::outerr(state.current_file, first)
11fdf7f2 1340 << "Expanding " << (is_block ? "block" : "phrase")
7c673cae
FG
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
11fdf7f2 1350 if (state.document.section_level() != state.min_section_level) {
7c673cae 1351 detail::outerr(state.current_file, first)
11fdf7f2 1352 << "Mismatched sections in template " << symbol->identifier
7c673cae
FG
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
11fdf7f2 1362 if (is_block || !save_block.empty()) {
7c673cae
FG
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 }
11fdf7f2 1372 else {
7c673cae
FG
1373 if (is_block) paragraph_action();
1374 }
1375 }
1376 }
1377
11fdf7f2
TL
1378 void call_code_snippet(
1379 quickbook::state& state,
1380 template_symbol const* symbol,
1381 string_iterator first)
7c673cae
FG
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(
11fdf7f2 1388 symbol->identifier, symbol->params, symbol->content,
7c673cae
FG
1389 symbol->lexical_parent);
1390
1391 state.start_callouts();
1392 call_template(state, &t, args, first);
1393 state.out << state.end_callouts();
1394 }
1395
11fdf7f2
TL
1396 void do_template_action(
1397 quickbook::state& state, value template_list, string_iterator first)
7c673cae
FG
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);
11fdf7f2 1406 if (template_escape) values.consume();
7c673cae 1407
11fdf7f2
TL
1408 std::string identifier =
1409 values.consume(template_tags::identifier).get_quickbook().to_s();
7c673cae
FG
1410
1411 std::vector<value> args;
1412
92f5a8d4 1413 QUICKBOOK_FOR (value arg, values) {
7c673cae
FG
1414 args.push_back(arg);
1415 }
11fdf7f2 1416
7c673cae
FG
1417 values.finish();
1418
1419 template_symbol const* symbol = state.templates.find(identifier);
1420 BOOST_ASSERT(symbol);
1421
1422 // Deal with escaped templates.
1423
11fdf7f2
TL
1424 if (template_escape) {
1425 if (!args.empty()) {
7c673cae 1426 detail::outerr(state.current_file, first)
11fdf7f2 1427 << "Arguments for escaped template." << std::endl;
7c673cae
FG
1428 ++state.error_count;
1429 }
1430
11fdf7f2 1431 if (symbol->content.is_encoded()) {
7c673cae
FG
1432 state.phrase << symbol->content.get_encoded();
1433 }
11fdf7f2 1434 else {
7c673cae
FG
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 &&
11fdf7f2 1462 symbol->content.get_tag() != template_tags::phrase) {
7c673cae
FG
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
11fdf7f2 1474 switch (symbol->content.get_tag()) {
7c673cae
FG
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
11fdf7f2 1481 if (args.size() != symbol->params.size()) {
7c673cae
FG
1482 detail::outerr(state.current_file, first)
1483 << "Invalid number of arguments passed. Expecting: "
1484 << symbol->params.size()
11fdf7f2
TL
1485 << " argument(s), got: " << args.size()
1486 << " argument(s) instead." << std::endl;
7c673cae
FG
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
11fdf7f2 1497 if (!args.empty()) {
7c673cae 1498 detail::outerr(state.current_file, first)
11fdf7f2 1499 << "Arguments for code snippet." << std::endl;
7c673cae
FG
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
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
7c673cae
FG
1532 if (link.get_tag() == phrase_tags::url) {
1533 dst = detail::partially_escape_uri(dst);
1534 }
11fdf7f2
TL
1535 }
1536
7c673cae
FG
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
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;
11fdf7f2
TL
1554 std::string title =
1555 values.consume(table_tags::title).get_quickbook().to_s();
7c673cae
FG
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
92f5a8d4 1563 QUICKBOOK_FOR (value_consumer entry, values) {
7c673cae 1564 state.out << "<varlistentry>";
11fdf7f2
TL
1565
1566 if (entry.check()) {
7c673cae
FG
1567 state.out << "<term>";
1568 state.out << entry.consume().get_encoded();
1569 state.out << "</term>";
1570 }
11fdf7f2
TL
1571
1572 if (entry.check()) {
7c673cae 1573 state.out << "<listitem>";
92f5a8d4 1574 QUICKBOOK_FOR (value phrase, entry)
11fdf7f2 1575 state.out << phrase.get_encoded();
7c673cae
FG
1576 state.out << "</listitem>";
1577 }
1578
1579 state.out << "</varlistentry>\n";
1580 }
1581
1582 state.out << "</variablelist>\n";
11fdf7f2 1583
7c673cae
FG
1584 values.finish();
1585 }
1586
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;
11fdf7f2 1594 if (values.check(general_tags::element_id)) {
7c673cae
FG
1595 element_id = validate_id(state, values.consume());
1596 }
1597
1598 value title = values.consume(table_tags::title);
1599 bool has_title = !title.empty();
11fdf7f2 1600
7c673cae
FG
1601 std::string table_id;
1602
1603 if (!element_id.empty()) {
11fdf7f2
TL
1604 table_id =
1605 state.document.add_id(element_id, id_category::explicit_id);
7c673cae
FG
1606 }
1607 else if (has_title) {
1608 if (state.document.compatibility_version() >= 105) {
11fdf7f2
TL
1609 table_id = state.document.add_id(
1610 detail::make_identifier(title.get_quickbook()),
1611 id_category::generated);
7c673cae
FG
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;
92f5a8d4 1624 QUICKBOOK_FOR (value row, lookahead) {
7c673cae
FG
1625 ++row_count;
1626 span_count = boost::distance(row);
1627 }
1628 lookahead.finish();
1629
11fdf7f2 1630 if (has_title) {
7c673cae 1631 state.out << "<table frame=\"all\"";
11fdf7f2 1632 if (!table_id.empty()) state.out << " id=\"" << table_id << "\"";
7c673cae
FG
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 }
11fdf7f2 1643 else {
7c673cae 1644 state.out << "<informaltable frame=\"all\"";
11fdf7f2 1645 if (!table_id.empty()) state.out << " id=\"" << table_id << "\"";
7c673cae
FG
1646 state.out << ">\n";
1647 }
1648
1649 state.out << "<tgroup cols=\"" << span_count << "\">\n";
1650
11fdf7f2
TL
1651 if (row_count > 1) {
1652 state.out << "<thead>"
1653 << "<row>";
92f5a8d4 1654 QUICKBOOK_FOR (value cell, values.consume()) {
7c673cae
FG
1655 state.out << "<entry>" << cell.get_encoded() << "</entry>";
1656 }
11fdf7f2
TL
1657 state.out << "</row>\n"
1658 << "</thead>\n";
7c673cae
FG
1659 }
1660
1661 state.out << "<tbody>\n";
1662
92f5a8d4 1663 QUICKBOOK_FOR (value row, values) {
7c673cae 1664 state.out << "<row>";
92f5a8d4 1665 QUICKBOOK_FOR (value cell, row) {
7c673cae
FG
1666 state.out << "<entry>" << cell.get_encoded() << "</entry>";
1667 }
1668 state.out << "</row>\n";
1669 }
11fdf7f2 1670
7c673cae
FG
1671 values.finish();
1672
1673 state.out << "</tbody>\n"
11fdf7f2 1674 << "</tgroup>\n";
7c673cae 1675
11fdf7f2 1676 if (has_title) {
7c673cae
FG
1677 state.out << "</table>\n";
1678 }
11fdf7f2 1679 else {
7c673cae
FG
1680 state.out << "</informaltable>\n";
1681 }
1682 }
1683
1684 void begin_section_action(quickbook::state& state, value begin_section_list)
11fdf7f2 1685 {
7c673cae
FG
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(
b32b8144 1693 element_id,
11fdf7f2
TL
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,
7c673cae
FG
1699 state.current_source_mode());
1700
1701 state.out << "\n<section id=\"" << full_id << "\">\n";
7c673cae 1702
b32b8144 1703 std::string title = content.get_encoded();
7c673cae 1704
b32b8144
FG
1705 if (!title.empty()) {
1706 state.out << "<title>";
1707
1708 write_anchors(state, state.out);
1709
11fdf7f2
TL
1710 if (self_linked_headers &&
1711 state.document.compatibility_version() >= 103) {
b32b8144
FG
1712 state.out << quickbook::detail::linkify(title, full_id);
1713 }
11fdf7f2 1714 else {
b32b8144
FG
1715 state.out << title;
1716 }
1717
1718 state.out << "</title>\n";
7c673cae 1719 }
7c673cae
FG
1720 }
1721
11fdf7f2
TL
1722 void end_section_action(
1723 quickbook::state& state, value end_section_list, string_iterator first)
7c673cae 1724 {
b32b8144
FG
1725 value_consumer values = end_section_list;
1726 value element_id = values.optional_consume(general_tags::element_id);
1727 values.finish();
1728
7c673cae
FG
1729 write_anchors(state, state.out);
1730
11fdf7f2 1731 if (state.document.section_level() <= state.min_section_level) {
7c673cae
FG
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;
11fdf7f2 1737
7c673cae
FG
1738 return;
1739 }
1740
11fdf7f2
TL
1741 if (!element_id.empty() &&
1742 !(element_id == state.document.explicit_id())) {
b32b8144
FG
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";
11fdf7f2
TL
1752 }
1753 else {
b32b8144
FG
1754 detail::outerr(state.current_file->path, pos.line)
1755 << "Endsect has incorrect id '"
11fdf7f2 1756 << element_id.get_quickbook() << "', expected '"
b32b8144 1757 << state.document.explicit_id().get_quickbook()
11fdf7f2 1758 << "', near column " << pos.column << ".\n";
b32b8144
FG
1759 }
1760 ++state.error_count;
1761 }
1762
7c673cae
FG
1763 state.out << "</section>";
1764 state.document.end_section();
1765 }
11fdf7f2
TL
1766
1767 void element_id_warning_action::operator()(
1768 parse_iterator first, parse_iterator) const
7c673cae
FG
1769 {
1770 detail::outwarn(state.current_file, first.base()) << "Empty id.\n";
1771 }
1772
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
11fdf7f2 1781 if (x.type == path_parameter::path) {
b32b8144 1782 quickbook_path path = resolve_xinclude_path(x.value, state, true);
7c673cae
FG
1783
1784 state.out << "\n<xi:include href=\"";
11fdf7f2
TL
1785 detail::print_string(
1786 file_path_to_url(path.abstract_file_path), state.out.get());
7c673cae
FG
1787 state.out << "\" />\n";
1788 }
1789 }
1790
11fdf7f2
TL
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())
7c673cae 1796 {
11fdf7f2
TL
1797 assert(
1798 load_type == block_tags::include ||
7c673cae
FG
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);
11fdf7f2 1803
7c673cae
FG
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.
11fdf7f2
TL
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);
7c673cae
FG
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();
11fdf7f2 1825
7c673cae
FG
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
11fdf7f2
TL
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())
7c673cae 1843 {
11fdf7f2
TL
1844 assert(
1845 load_type == block_tags::include ||
7c673cae
FG
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
11fdf7f2 1854 if (load_type == block_tags::include) {
7c673cae
FG
1855 state.templates.push();
1856 }
1857
92f5a8d4 1858 QUICKBOOK_FOR (template_symbol& ts, storage) {
7c673cae 1859 std::string tname = ts.identifier;
11fdf7f2 1860 if (tname != "!") {
7c673cae 1861 ts.lexical_parent = &state.templates.top_scope();
11fdf7f2
TL
1862 if (!state.templates.add(ts)) {
1863 detail::outerr(
1864 ts.content.get_file(), ts.content.get_position())
7c673cae
FG
1865 << "Template Redefinition: " << tname << std::endl;
1866 ++state.error_count;
1867 }
1868 }
1869 }
1870
11fdf7f2 1871 if (load_type == block_tags::include) {
92f5a8d4 1872 QUICKBOOK_FOR (template_symbol& ts, storage) {
7c673cae
FG
1873 std::string tname = ts.identifier;
1874
11fdf7f2 1875 if (tname == "!") {
7c673cae
FG
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
11fdf7f2
TL
1885 void include_action(
1886 quickbook::state& state, value include, string_iterator first)
7c673cae
FG
1887 {
1888 write_anchors(state, state.out);
1889
1890 value_consumer values = include;
11fdf7f2
TL
1891 value include_doc_id =
1892 values.optional_consume(general_tags::include_id);
7c673cae
FG
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);
92f5a8d4 1898 QUICKBOOK_FOR (quickbook_path const& path, search) {
7c673cae 1899 try {
11fdf7f2
TL
1900 if (qbk_version_n >= 106) {
1901 if (state.imported &&
1902 include.get_tag() == block_tags::include)
7c673cae
FG
1903 return;
1904
11fdf7f2
TL
1905 std::string ext =
1906 path.file_path.extension().generic_string();
7c673cae 1907
11fdf7f2
TL
1908 if (ext == ".qbk" || ext == ".quickbook") {
1909 load_quickbook(
1910 state, path, include.get_tag(), include_doc_id);
7c673cae 1911 }
11fdf7f2
TL
1912 else {
1913 load_source_file(
1914 state, path, include.get_tag(), first,
1915 include_doc_id);
7c673cae
FG
1916 }
1917 }
11fdf7f2
TL
1918 else {
1919 if (include.get_tag() == block_tags::include) {
1920 load_quickbook(
1921 state, path, include.get_tag(), include_doc_id);
7c673cae 1922 }
11fdf7f2
TL
1923 else {
1924 load_source_file(
1925 state, path, include.get_tag(), first,
1926 include_doc_id);
7c673cae
FG
1927 }
1928 }
11fdf7f2 1929 } catch (load_error& e) {
7c673cae
FG
1930 ++state.error_count;
1931
1932 detail::outerr(state.current_file, first)
11fdf7f2 1933 << "Loading file " << path.file_path << ": " << e.what()
7c673cae
FG
1934 << std::endl;
1935 }
1936 }
1937 }
1938
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
11fdf7f2
TL
1948 void to_value_scoped_action::success(
1949 parse_iterator first, parse_iterator last)
7c673cae
FG
1950 {
1951 std::string value;
1952
11fdf7f2 1953 if (!state.out.str().empty()) {
7c673cae
FG
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 }
11fdf7f2 1959 else {
7c673cae
FG
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 }
11fdf7f2 1967
7c673cae
FG
1968 void to_value_scoped_action::cleanup()
1969 {
1970 state.pop_output();
1971 state.anchors.swap(saved_anchors);
1972 }
1973}