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