summaryrefslogtreecommitdiffstats
path: root/src/boost/tools/quickbook/src/actions.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/boost/tools/quickbook/src/actions.cpp')
-rw-r--r--src/boost/tools/quickbook/src/actions.cpp1973
1 files changed, 1973 insertions, 0 deletions
diff --git a/src/boost/tools/quickbook/src/actions.cpp b/src/boost/tools/quickbook/src/actions.cpp
new file mode 100644
index 000000000..a40741686
--- /dev/null
+++ b/src/boost/tools/quickbook/src/actions.cpp
@@ -0,0 +1,1973 @@
+/*=============================================================================
+ Copyright (c) 2002 2004 2006 Joel de Guzman
+ Copyright (c) 2004 Eric Niebler
+ Copyright (c) 2005 Thomas Guest
+ http://spirit.sourceforge.net/
+
+ Use, modification and distribution is subject to the Boost Software
+ License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+ http://www.boost.org/LICENSE_1_0.txt)
+=============================================================================*/
+#include "actions.hpp"
+#include <functional>
+#include <map>
+#include <numeric>
+#include <set>
+#include <vector>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/filesystem/convenience.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/next_prior.hpp>
+#include <boost/range/algorithm/replace.hpp>
+#include <boost/range/distance.hpp>
+#include "block_tags.hpp"
+#include "document_state.hpp"
+#include "files.hpp"
+#include "for.hpp"
+#include "grammar.hpp"
+#include "markups.hpp"
+#include "path.hpp"
+#include "phrase_tags.hpp"
+#include "quickbook.hpp"
+#include "state.hpp"
+#include "state_save.hpp"
+#include "stream.hpp"
+#include "syntax_highlight.hpp"
+#include "utils.hpp"
+
+namespace quickbook
+{
+ namespace
+ {
+ void write_anchors(quickbook::state& state, collector& tgt)
+ {
+ if (state.source_mode_next) {
+ detail::outwarn(
+ state.source_mode_next_pos.get_file(),
+ state.source_mode_next_pos.get_position())
+ << "Temporary source mode unsupported here." << std::endl;
+ state.source_mode_next = 0;
+ }
+
+ QUICKBOOK_FOR (auto const& anchor_id, state.anchors) {
+ tgt << "<anchor id=\"";
+ detail::print_string(anchor_id, tgt.get());
+ tgt << "\"/>";
+ }
+
+ state.anchors.clear();
+ }
+
+ std::string add_anchor(
+ quickbook::state& state,
+ quickbook::string_view id,
+ id_category::categories category = id_category::explicit_anchor_id)
+ {
+ std::string placeholder = state.document.add_anchor(id, category);
+ state.anchors.push_back(placeholder);
+ return placeholder;
+ }
+
+ std::string get_attribute_value(
+ quickbook::state& state, quickbook::value const& value)
+ {
+ std::string x = value.is_encoded() ? value.get_encoded()
+ : value.get_quickbook().to_s();
+
+ if (x.empty()) {
+ detail::outerr(value.get_file(), value.get_position())
+ << "Empty attribute value." << std::endl;
+ ++state.error_count;
+ x = "xxx";
+ }
+
+ return x;
+ }
+
+ std::string validate_id(
+ quickbook::state& state, quickbook::value const& id_value)
+ {
+ bool valid = true;
+ std::string id = get_attribute_value(state, id_value);
+
+ // Special case since I use dollar ids for id placeholders.
+ if (id[0] == '$') {
+ valid = false;
+ id[0] = '_';
+ }
+
+ if (qbk_version_n >= 107u) {
+ char const* allowed_punctuation = "_.-";
+
+ QUICKBOOK_FOR (char c, id) {
+ if (!std::isalnum(c) &&
+ !std::strchr(allowed_punctuation, c))
+ valid = false;
+ }
+ }
+
+ if (!valid) {
+ detail::outerr(id_value.get_file(), id_value.get_position())
+ << "Invalid id: " << (id_value.is_encoded()
+ ? id_value.get_encoded()
+ : id_value.get_quickbook().to_s())
+ << std::endl;
+ ++state.error_count;
+ }
+
+ return id;
+ }
+ }
+
+ bool quickbook_range::in_range() const
+ {
+ return qbk_version_n >= lower && qbk_version_n < upper;
+ }
+
+ bool quickbook_strict::is_strict_checking() const
+ {
+ return state.strict_mode;
+ }
+
+ void list_action(quickbook::state&, value);
+ void header_action(quickbook::state&, value);
+ void begin_section_action(quickbook::state&, value);
+ void end_section_action(quickbook::state&, value, string_iterator);
+ void block_action(quickbook::state&, value);
+ void block_empty_action(quickbook::state&, value);
+ void macro_definition_action(quickbook::state&, value);
+ void template_body_action(quickbook::state&, value);
+ void variable_list_action(quickbook::state&, value);
+ void table_action(quickbook::state&, value);
+ void xinclude_action(quickbook::state&, value);
+ void include_action(quickbook::state&, value, string_iterator);
+ void image_action(quickbook::state&, value);
+ void anchor_action(quickbook::state&, value);
+ void link_action(quickbook::state&, value);
+ void phrase_action(quickbook::state&, value);
+ void role_action(quickbook::state&, value);
+ void footnote_action(quickbook::state&, value);
+ void raw_phrase_action(quickbook::state&, value);
+ void source_mode_action(quickbook::state&, value);
+ void next_source_mode_action(quickbook::state&, value);
+ void code_action(quickbook::state&, value);
+ void do_template_action(quickbook::state&, value, string_iterator);
+
+ void element_action::operator()(parse_iterator first, parse_iterator) const
+ {
+ value_consumer values = state.values.release();
+ if (!values.check() || !state.conditional) return;
+ value v = values.consume();
+ values.finish();
+
+ switch (v.get_tag()) {
+ case block_tags::ordered_list:
+ case block_tags::itemized_list:
+ return list_action(state, v);
+ case block_tags::generic_heading:
+ case block_tags::heading1:
+ case block_tags::heading2:
+ case block_tags::heading3:
+ case block_tags::heading4:
+ case block_tags::heading5:
+ case block_tags::heading6:
+ return header_action(state, v);
+ case block_tags::begin_section:
+ return begin_section_action(state, v);
+ case block_tags::end_section:
+ return end_section_action(state, v, first.base());
+ case block_tags::blurb:
+ case block_tags::preformatted:
+ case block_tags::blockquote:
+ case block_tags::warning:
+ case block_tags::caution:
+ case block_tags::important:
+ case block_tags::note:
+ case block_tags::tip:
+ case block_tags::block:
+ return block_action(state, v);
+ case block_tags::hr:
+ return block_empty_action(state, v);
+ case block_tags::macro_definition:
+ return macro_definition_action(state, v);
+ case block_tags::template_definition:
+ return template_body_action(state, v);
+ case block_tags::variable_list:
+ return variable_list_action(state, v);
+ case block_tags::table:
+ return table_action(state, v);
+ case block_tags::xinclude:
+ return xinclude_action(state, v);
+ case block_tags::import:
+ case block_tags::include:
+ return include_action(state, v, first.base());
+ case phrase_tags::image:
+ return image_action(state, v);
+ case phrase_tags::anchor:
+ return anchor_action(state, v);
+ case phrase_tags::url:
+ case phrase_tags::link:
+ case phrase_tags::funcref:
+ case phrase_tags::classref:
+ case phrase_tags::memberref:
+ case phrase_tags::enumref:
+ case phrase_tags::macroref:
+ case phrase_tags::headerref:
+ case phrase_tags::conceptref:
+ case phrase_tags::globalref:
+ return link_action(state, v);
+ case phrase_tags::bold:
+ case phrase_tags::italic:
+ case phrase_tags::underline:
+ case phrase_tags::teletype:
+ case phrase_tags::strikethrough:
+ case phrase_tags::quote:
+ case phrase_tags::replaceable:
+ return phrase_action(state, v);
+ case phrase_tags::footnote:
+ return footnote_action(state, v);
+ case phrase_tags::escape:
+ return raw_phrase_action(state, v);
+ case phrase_tags::role:
+ return role_action(state, v);
+ case source_mode_tags::cpp:
+ case source_mode_tags::python:
+ case source_mode_tags::teletype:
+ return source_mode_action(state, v);
+ case code_tags::next_source_mode:
+ return next_source_mode_action(state, v);
+ case code_tags::code_block:
+ case code_tags::inline_code_block:
+ case code_tags::inline_code:
+ return code_action(state, v);
+ case template_tags::attribute_template:
+ case template_tags::template_:
+ return do_template_action(state, v, first.base());
+ default:
+ break;
+ }
+ }
+
+ void break_action::operator()(parse_iterator first, parse_iterator) const
+ {
+ write_anchors(state, state.phrase);
+
+ if (*first == '\\') {
+ detail::outwarn(state.current_file, first.base())
+ //<< "in column:" << pos.column << ", "
+ << "'\\n' is deprecated, pleases use '[br]' instead"
+ << ".\n";
+ }
+
+ if (!state.warned_about_breaks) {
+ detail::outwarn(state.current_file, first.base())
+ << "line breaks generate invalid boostbook "
+ "(will only note first occurrence).\n";
+
+ state.warned_about_breaks = true;
+ }
+
+ state.phrase << detail::get_markup(phrase_tags::break_mark).pre;
+ }
+
+ void error_message_action::operator()(
+ parse_iterator first, parse_iterator last) const
+ {
+ file_position const pos = state.current_file->position_of(first.base());
+
+ std::string value(first, last);
+ std::string formatted_message = message;
+ boost::replace_all(formatted_message, "%s", value);
+ boost::replace_all(
+ formatted_message, "%c",
+ boost::lexical_cast<std::string>(pos.column));
+
+ detail::outerr(state.current_file->path, pos.line)
+ << formatted_message << std::endl;
+ ++state.error_count;
+ }
+
+ void error_action::operator()(
+ parse_iterator first, parse_iterator /*last*/) const
+ {
+ file_position const pos = state.current_file->position_of(first.base());
+
+ detail::outerr(state.current_file->path, pos.line)
+ << "Syntax Error near column " << pos.column << ".\n";
+ ++state.error_count;
+ }
+
+ void block_action(quickbook::state& state, value block)
+ {
+ write_anchors(state, state.out);
+
+ detail::markup markup = detail::get_markup(block.get_tag());
+
+ value_consumer values = block;
+ state.out << markup.pre << values.consume().get_encoded()
+ << markup.post;
+ values.finish();
+ }
+
+ void block_empty_action(quickbook::state& state, value block)
+ {
+ write_anchors(state, state.out);
+
+ detail::markup markup = detail::get_markup(block.get_tag());
+ state.out << markup.pre;
+ }
+
+ void phrase_action(quickbook::state& state, value phrase)
+ {
+ write_anchors(state, state.phrase);
+
+ detail::markup markup = detail::get_markup(phrase.get_tag());
+
+ value_consumer values = phrase;
+ state.phrase << markup.pre << values.consume().get_encoded()
+ << markup.post;
+ values.finish();
+ }
+
+ void role_action(quickbook::state& state, value role_list)
+ {
+ write_anchors(state, state.phrase);
+
+ value_consumer values = role_list;
+ value role = values.consume();
+ value phrase = values.consume();
+ values.finish();
+
+ state.phrase << "<phrase role=\"";
+ detail::print_string(
+ get_attribute_value(state, role), state.phrase.get());
+ state.phrase << "\">" << phrase.get_encoded() << "</phrase>";
+ }
+
+ void footnote_action(quickbook::state& state, value phrase)
+ {
+ write_anchors(state, state.phrase);
+
+ value_consumer values = phrase;
+ state.phrase << "<footnote id=\""
+ << state.document.add_id("f", id_category::numbered)
+ << "\"><para>" << values.consume().get_encoded()
+ << "</para></footnote>";
+ values.finish();
+ }
+
+ void raw_phrase_action(quickbook::state& state, value phrase)
+ {
+ write_anchors(state, state.phrase);
+
+ detail::markup markup = detail::get_markup(phrase.get_tag());
+ state.phrase << markup.pre << phrase.get_quickbook() << markup.post;
+ }
+
+ void paragraph_action::operator()() const
+ {
+ std::string str;
+ state.phrase.swap(str);
+
+ std::string::const_iterator pos = str.begin(), end = str.end();
+
+ while (pos != end && cl::space_p.test(*pos))
+ ++pos;
+
+ if (pos != end) {
+ detail::markup markup =
+ state.in_list
+ ? detail::get_markup(block_tags::paragraph_in_list)
+ : detail::get_markup(block_tags::paragraph);
+ state.out << markup.pre << str;
+ write_anchors(state, state.out);
+ state.out << markup.post;
+ }
+ }
+
+ void explicit_list_action::operator()() const
+ {
+ state.explicit_list = true;
+ }
+
+ void phrase_end_action::operator()() const
+ {
+ write_anchors(state, state.phrase);
+ }
+
+ namespace
+ {
+ void write_bridgehead(
+ quickbook::state& state,
+ int level,
+ std::string const& str,
+ std::string const& id,
+ bool self_link)
+ {
+ if (self_link && !id.empty()) {
+ state.out << "<bridgehead renderas=\"sect" << level << "\"";
+ state.out << " id=\"";
+ state.out << state.document.add_id("h", id_category::numbered);
+ state.out << "\">";
+ state.out << "<phrase id=\"" << id << "\"/>";
+ state.out << "<link linkend=\"" << id << "\">";
+ state.out << str;
+ state.out << "</link>";
+ state.out << "</bridgehead>";
+ }
+ else {
+ state.out << "<bridgehead renderas=\"sect" << level << "\"";
+ if (!id.empty()) state.out << " id=\"" << id << "\"";
+ state.out << ">";
+ state.out << str;
+ state.out << "</bridgehead>";
+ }
+ }
+ }
+
+ void header_action(quickbook::state& state, value heading_list)
+ {
+ value_consumer values = heading_list;
+
+ bool generic = heading_list.get_tag() == block_tags::generic_heading;
+ value element_id = values.optional_consume(general_tags::element_id);
+ value content = values.consume();
+ values.finish();
+
+ int level;
+
+ if (generic) {
+ level = state.document.section_level() + 1;
+ // We need to use a heading which is one greater
+ // than the current.
+ if (level > 6) // The max is h6, clip it if it goes
+ level = 6; // further than that
+ }
+ else {
+ level = heading_list.get_tag() - block_tags::heading1 + 1;
+ }
+
+ write_anchors(state, state.out);
+
+ if (!element_id.empty()) {
+ // Use an explicit id.
+
+ std::string anchor = state.document.add_id(
+ validate_id(state, element_id), id_category::explicit_id);
+
+ write_bridgehead(
+ state, level, content.get_encoded(), anchor,
+ self_linked_headers);
+ }
+ else if (state.document.compatibility_version() >= 106u) {
+ // Generate ids for 1.6+
+
+ std::string anchor = state.document.add_id(
+ detail::make_identifier(content.get_quickbook()),
+ id_category::generated_heading);
+
+ write_bridgehead(
+ state, level, content.get_encoded(), anchor,
+ self_linked_headers);
+ }
+ else {
+ // Generate ids that are compatible with older versions of
+ // quickbook.
+
+ // Older versions of quickbook used the generated boostbook, but
+ // we only have an intermediate version which can contain id
+ // placeholders. So to generate the ids they must be replaced
+ // by the ids that the older versions would have used - i.e. the
+ // unresolved ids.
+ //
+ // Note that this doesn't affect the actual boostbook generated for
+ // the content, it's just used to generate this id.
+
+ std::string id = detail::make_identifier(
+ state.document.replace_placeholders_with_unresolved_ids(
+ content.get_encoded()));
+
+ if (generic || state.document.compatibility_version() >= 103) {
+ std::string anchor =
+ state.document.add_id(id, id_category::generated_heading);
+
+ write_bridgehead(
+ state, level, content.get_encoded(), anchor,
+ self_linked_headers);
+ }
+ else {
+ std::string anchor = state.document.old_style_id(
+ id, id_category::generated_heading);
+
+ write_bridgehead(
+ state, level, content.get_encoded(), anchor, false);
+ }
+ }
+ }
+
+ void simple_phrase_action::operator()(char mark) const
+ {
+ write_anchors(state, state.phrase);
+
+ int tag = mark == '*'
+ ? phrase_tags::bold
+ : mark == '/'
+ ? phrase_tags::italic
+ : mark == '_'
+ ? phrase_tags::underline
+ : mark == '=' ? phrase_tags::teletype : 0;
+
+ assert(tag != 0);
+ detail::markup markup = detail::get_markup(tag);
+
+ value_consumer values = state.values.release();
+ value content = values.consume();
+ values.finish();
+
+ state.phrase << markup.pre;
+ state.phrase << content.get_encoded();
+ state.phrase << markup.post;
+ }
+
+ bool cond_phrase_push::start()
+ {
+ value_consumer values = state.values.release();
+
+ saved_conditional = state.conditional;
+
+ if (saved_conditional) {
+ bool positive = values.consume().get_quickbook().empty();
+ quickbook::string_view macro1 = values.consume().get_quickbook();
+ std::string macro(macro1.begin(), macro1.end());
+
+ state.conditional =
+ (bool)find(state.macro, macro.c_str()) == positive;
+
+ if (!state.conditional) {
+ state.push_output();
+ state.anchors.swap(anchors);
+ }
+ }
+
+ return true;
+ }
+
+ void cond_phrase_push::cleanup()
+ {
+ if (saved_conditional && !state.conditional) {
+ state.pop_output();
+ state.anchors.swap(anchors);
+ }
+
+ state.conditional = saved_conditional;
+ }
+
+ void state::start_list(char mark)
+ {
+ push_tagged_source_mode(source_mode_next);
+ source_mode_next = 0;
+
+ write_anchors(*this, (in_list ? phrase : out));
+ assert(mark == '*' || mark == '#');
+ push_output();
+ out << ((mark == '#') ? "<orderedlist>\n" : "<itemizedlist>\n");
+ in_list = true;
+ }
+
+ void state::end_list(char mark)
+ {
+ write_anchors(*this, out);
+ assert(mark == '*' || mark == '#');
+ out << ((mark == '#') ? "\n</orderedlist>" : "\n</itemizedlist>");
+
+ std::string list_output;
+ out.swap(list_output);
+
+ pop_output();
+
+ (in_list ? phrase : out) << list_output;
+
+ pop_tagged_source_mode();
+ }
+
+ void state::start_list_item()
+ {
+ out << "<listitem>";
+ write_anchors(*this, phrase);
+ }
+
+ void state::end_list_item()
+ {
+ write_anchors(*this, phrase);
+ paragraph_action para(*this);
+ para();
+ out << "</listitem>";
+ }
+
+ namespace
+ {
+ bool parse_template(
+ value const&,
+ quickbook::state& state,
+ bool is_attribute_template = false);
+ }
+
+ void state::start_callouts() { ++callout_depth; }
+
+ std::string state::add_callout(value v)
+ {
+ std::string callout_id1 = document.add_id("c", id_category::numbered);
+ std::string callout_id2 = document.add_id("c", id_category::numbered);
+
+ callouts.insert(encoded_value(callout_id1));
+ callouts.insert(encoded_value(callout_id2));
+ callouts.insert(v);
+
+ std::string code;
+ code += "<co id=\"" + callout_id1 + "\" ";
+ code += "linkends=\"" + callout_id2 + "\" />";
+
+ return code;
+ }
+
+ std::string state::end_callouts()
+ {
+ assert(callout_depth > 0);
+ std::string block;
+
+ --callout_depth;
+ if (callout_depth > 0) return block;
+
+ value_consumer c = callouts.release();
+ if (!c.check()) return block;
+
+ block += "<calloutlist>";
+ while (c.check()) {
+ std::string callout_id1 = c.consume().get_encoded();
+ std::string callout_id2 = c.consume().get_encoded();
+ value callout_body = c.consume();
+
+ std::string callout_value;
+
+ {
+ state_save save(*this, state_save::scope_all);
+ ++template_depth;
+
+ bool r = parse_template(callout_body, *this);
+
+ if (!r) {
+ detail::outerr(
+ callout_body.get_file(), callout_body.get_position())
+ << "Expanding callout." << std::endl
+ << "------------------begin------------------"
+ << std::endl
+ << callout_body.get_quickbook() << std::endl
+ << "------------------end--------------------"
+ << std::endl;
+ ++error_count;
+ }
+
+ out.swap(callout_value);
+ }
+
+ block += "<callout arearefs=\"" + callout_id1 + "\" ";
+ block += "id=\"" + callout_id2 + "\">";
+ block += callout_value;
+ block += "</callout>";
+ }
+ block += "</calloutlist>";
+
+ return block;
+ }
+
+ void list_action(quickbook::state& state, value list)
+ {
+ write_anchors(state, state.out);
+
+ detail::markup markup = detail::get_markup(list.get_tag());
+
+ state.out << markup.pre;
+
+ QUICKBOOK_FOR (value item, list) {
+ state.out << "<listitem>";
+ state.out << item.get_encoded();
+ state.out << "</listitem>";
+ }
+
+ state.out << markup.post;
+ }
+
+ void anchor_action(quickbook::state& state, value anchor)
+ {
+ value_consumer values = anchor;
+ value anchor_id = values.consume();
+ // Note: anchor_id is never encoded as boostbook. If it
+ // is encoded, it's just things like escapes.
+ add_anchor(state, validate_id(state, anchor_id));
+ values.finish();
+ }
+
+ void do_macro_action::operator()(std::string const& str) const
+ {
+ write_anchors(state, state.phrase);
+
+ if (str == quickbook_get_date) {
+ char strdate[64];
+ strftime(strdate, sizeof(strdate), "%Y-%b-%d", current_time);
+ state.phrase << strdate;
+ }
+ else if (str == quickbook_get_time) {
+ char strdate[64];
+ strftime(strdate, sizeof(strdate), "%I:%M:%S %p", current_time);
+ state.phrase << strdate;
+ }
+ else {
+ state.phrase << str;
+ }
+ }
+
+ void raw_char_action::operator()(char ch) const { state.phrase << ch; }
+
+ void raw_char_action::operator()(
+ parse_iterator first, parse_iterator last) const
+ {
+ while (first != last)
+ state.phrase << *first++;
+ }
+
+ void source_mode_action(quickbook::state& state, value source_mode)
+ {
+ state.change_source_mode(source_mode.get_tag());
+ }
+
+ void next_source_mode_action(quickbook::state& state, value source_mode)
+ {
+ value_consumer values = source_mode;
+ state.source_mode_next_pos = values.consume();
+ state.source_mode_next = values.consume().get_int();
+ values.finish();
+ }
+
+ void code_action(quickbook::state& state, value code_block)
+ {
+ int code_tag = code_block.get_tag();
+
+ value_consumer values = code_block;
+ quickbook::string_view code_value = values.consume().get_quickbook();
+ values.finish();
+
+ bool inline_code =
+ code_tag == code_tags::inline_code ||
+ (code_tag == code_tags::inline_code_block && qbk_version_n < 106u);
+ bool block = code_tag != code_tags::inline_code;
+
+ source_mode_type source_mode =
+ state.source_mode_next ? state.source_mode_next
+ : state.current_source_mode().source_mode;
+ state.source_mode_next = 0;
+
+ if (inline_code) {
+ write_anchors(state, state.phrase);
+ }
+ else {
+ paragraph_action para(state);
+ para();
+ write_anchors(state, state.out);
+ }
+
+ if (block) {
+ // preprocess the code section to remove the initial indentation
+ mapped_file_builder mapped;
+ mapped.start(state.current_file);
+ mapped.unindent_and_add(code_value);
+
+ file_ptr f = mapped.release();
+
+ if (f->source().empty())
+ return; // Nothing left to do here. The program is empty.
+
+ if (qbk_version_n >= 107u) state.start_callouts();
+
+ parse_iterator first_(f->source().begin());
+ parse_iterator last_(f->source().end());
+
+ file_ptr saved_file = f;
+ boost::swap(state.current_file, saved_file);
+
+ // print the code with syntax coloring
+ //
+ // We must not place a \n after the <programlisting> tag
+ // otherwise PDF output starts code blocks with a blank line:
+ state.phrase << "<programlisting>";
+ syntax_highlight(first_, last_, state, source_mode, block);
+ state.phrase << "</programlisting>\n";
+
+ boost::swap(state.current_file, saved_file);
+
+ if (qbk_version_n >= 107u) state.phrase << state.end_callouts();
+
+ if (!inline_code) {
+ state.out << state.phrase.str();
+ state.phrase.clear();
+ }
+ }
+ else {
+ parse_iterator first_(code_value.begin());
+ parse_iterator last_(code_value.end());
+
+ state.phrase << "<code>";
+ syntax_highlight(first_, last_, state, source_mode, block);
+ state.phrase << "</code>";
+ }
+ }
+
+ void plain_char_action::operator()(char ch) const
+ {
+ write_anchors(state, state.phrase);
+
+ detail::print_char(ch, state.phrase.get());
+ }
+
+ void plain_char_action::operator()(
+ parse_iterator first, parse_iterator last) const
+ {
+ write_anchors(state, state.phrase);
+
+ while (first != last)
+ detail::print_char(*first++, state.phrase.get());
+ }
+
+ void escape_unicode_action::operator()(
+ parse_iterator first, parse_iterator last) const
+ {
+ write_anchors(state, state.phrase);
+
+ while (first != last && *first == '0')
+ ++first;
+
+ // Just ignore \u0000
+ // Maybe I should issue a warning?
+ if (first == last) return;
+
+ std::string hex_digits(first, last);
+
+ if (hex_digits.size() == 2 && *first > '0' && *first <= '7') {
+ using namespace std;
+ detail::print_char(
+ (char)strtol(hex_digits.c_str(), 0, 16), state.phrase.get());
+ }
+ else {
+ state.phrase << "&#x" << hex_digits << ";";
+ }
+ }
+
+ void write_plain_text(std::ostream& out, value const& v)
+ {
+ if (v.is_encoded()) {
+ detail::print_string(v.get_encoded(), out);
+ }
+ else {
+ quickbook::string_view value = v.get_quickbook();
+ for (string_iterator first = value.begin(), last = value.end();
+ first != last; ++first) {
+ if (*first == '\\' && ++first == last) break;
+ detail::print_char(*first, out);
+ }
+ }
+ }
+
+ void image_action(quickbook::state& state, value image)
+ {
+ write_anchors(state, state.phrase);
+
+ // Note: attributes are never encoded as boostbook, if they're
+ // encoded, it's just things like escapes.
+ typedef std::map<std::string, value> attribute_map;
+ attribute_map attributes;
+
+ value_consumer values = image;
+ attributes["fileref"] = values.consume();
+
+ QUICKBOOK_FOR (value pair_, values) {
+ value_consumer pair = pair_;
+ value name = pair.consume();
+ value value = pair.consume();
+ std::string name_str(
+ name.get_quickbook().begin(), name.get_quickbook().end());
+ pair.finish();
+ if (!attributes.insert(std::make_pair(name_str, value)).second) {
+ detail::outwarn(name.get_file(), name.get_position())
+ << "Duplicate image attribute: " << name.get_quickbook()
+ << std::endl;
+ }
+ }
+
+ values.finish();
+
+ // Find the file basename and extension.
+ //
+ // Not using Boost.Filesystem because I want to stay in UTF-8.
+ // Need to think about uri encoding.
+
+ std::string fileref =
+ attributes["fileref"].is_encoded()
+ ? attributes["fileref"].get_encoded()
+ : attributes["fileref"].get_quickbook().to_s();
+
+ // Check for windows paths, then convert.
+ // A bit crude, but there you go.
+
+ if (fileref.find('\\') != std::string::npos) {
+ (qbk_version_n >= 106u ? detail::outerr(
+ attributes["fileref"].get_file(),
+ attributes["fileref"].get_position())
+ : detail::outwarn(
+ attributes["fileref"].get_file(),
+ attributes["fileref"].get_position()))
+ << "Image path isn't portable: '" << fileref << "'"
+ << std::endl;
+ if (qbk_version_n >= 106u) ++state.error_count;
+ }
+
+ boost::replace(fileref, '\\', '/');
+
+ // Find the file basename and extension.
+ //
+ // Not using Boost.Filesystem because I want to stay in UTF-8.
+ // Need to think about uri encoding.
+
+ std::string::size_type pos;
+ std::string stem, extension;
+
+ pos = fileref.rfind('/');
+ stem = pos == std::string::npos ? fileref : fileref.substr(pos + 1);
+
+ pos = stem.rfind('.');
+ if (pos != std::string::npos) {
+ extension = stem.substr(pos + 1);
+ stem = stem.substr(0, pos);
+ }
+
+ // Extract the alt tag, to use as a text description.
+ // Or if there isn't one, use the stem of the file name.
+
+ attribute_map::iterator alt_pos = attributes.find("alt");
+ quickbook::value alt_text = alt_pos != attributes.end()
+ ? alt_pos->second
+ : qbk_version_n < 106u
+ ? encoded_value(stem)
+ : quickbook::value();
+ attributes.erase("alt");
+
+ if (extension == "svg") {
+ //
+ // SVG's need special handling:
+ //
+ // 1) We must set the "format" attribute, otherwise
+ // HTML generation produces code that will not display
+ // the image at all.
+ // 2) We need to set the "contentwidth" and "contentdepth"
+ // attributes, otherwise the image will be displayed inside
+ // a tiny box with scrollbars (Firefox), or else cropped to
+ // fit in a tiny box (IE7).
+ //
+
+ attributes.insert(
+ attribute_map::value_type("format", encoded_value("SVG")));
+
+ //
+ // Image paths are relative to the html subdirectory:
+ //
+ fs::path img = detail::generic_to_path(fileref);
+ if (!img.has_root_directory())
+ img = quickbook::image_location / img; // relative path
+
+ //
+ // Now load the SVG file:
+ //
+ std::string svg_text;
+ if (state.dependencies.add_dependency(img)) {
+ fs::ifstream fs(img);
+ std::stringstream buffer;
+ buffer << fs.rdbuf();
+ svg_text = buffer.str();
+ }
+
+ //
+ // Extract the svg header from the file:
+ //
+ std::string::size_type a, b;
+ a = svg_text.find("<svg");
+ b = svg_text.find('>', a);
+ svg_text =
+ (a == std::string::npos) ? "" : svg_text.substr(a, b - a);
+ //
+ // Now locate the "width" and "height" attributes
+ // and borrow their values:
+ //
+ a = svg_text.find("width");
+ a = svg_text.find('=', a);
+ a = svg_text.find('\"', a);
+ b = svg_text.find('\"', a + 1);
+ if (a != std::string::npos) {
+ attributes.insert(std::make_pair(
+ "contentwidth", encoded_value(std::string(
+ boost::next(svg_text.begin(), a + 1),
+ boost::next(svg_text.begin(), b)))));
+ }
+ a = svg_text.find("height");
+ a = svg_text.find('=', a);
+ a = svg_text.find('\"', a);
+ b = svg_text.find('\"', a + 1);
+ if (a != std::string::npos) {
+ attributes.insert(std::make_pair(
+ "contentdepth", encoded_value(std::string(
+ boost::next(svg_text.begin(), a + 1),
+ boost::next(svg_text.begin(), b)))));
+ }
+ }
+
+ state.phrase << "<inlinemediaobject>";
+
+ state.phrase << "<imageobject><imagedata";
+
+ QUICKBOOK_FOR (attribute_map::value_type const& attr, attributes) {
+ state.phrase << " " << attr.first << "=\"";
+ write_plain_text(state.phrase.get(), attr.second);
+ state.phrase << "\"";
+ }
+
+ state.phrase << "></imagedata></imageobject>";
+
+ // Add a textobject containing the alt tag from earlier.
+ // This will be used for the alt tag in html.
+ if (alt_text.check()) {
+ state.phrase << "<textobject><phrase>";
+ write_plain_text(state.phrase.get(), alt_text);
+ state.phrase << "</phrase></textobject>";
+ }
+
+ state.phrase << "</inlinemediaobject>";
+ }
+
+ void macro_definition_action(
+ quickbook::state& state, quickbook::value macro_definition)
+ {
+ value_consumer values = macro_definition;
+ std::string macro_id = values.consume().get_quickbook().to_s();
+ value phrase_value = values.optional_consume();
+ std::string phrase;
+ if (phrase_value.check()) phrase = phrase_value.get_encoded();
+ values.finish();
+
+ std::string* existing_macro =
+ boost::spirit::classic::find(state.macro, macro_id.c_str());
+ quickbook::ignore_variable(&existing_macro);
+
+ if (existing_macro) {
+ if (qbk_version_n < 106) return;
+
+ // Do this if you're using spirit's TST.
+ //
+ // *existing_macro = phrase;
+ // return;
+ }
+
+ state.macro.add(macro_id.begin(), macro_id.end(), phrase);
+ }
+
+ void template_body_action(
+ quickbook::state& state, quickbook::value template_definition)
+ {
+ value_consumer values = template_definition;
+ std::string identifier = values.consume().get_quickbook().to_s();
+
+ std::vector<std::string> template_values;
+ QUICKBOOK_FOR (value const& p, values.consume()) {
+ template_values.push_back(p.get_quickbook().to_s());
+ }
+
+ BOOST_ASSERT(
+ values.check(template_tags::block) ||
+ values.check(template_tags::phrase));
+ value body = values.consume();
+ BOOST_ASSERT(!values.check());
+
+ if (!state.templates.add(template_symbol(
+ identifier, template_values, body,
+ &state.templates.top_scope()))) {
+ detail::outwarn(body.get_file(), body.get_position())
+ << "Template Redefinition: " << identifier << std::endl;
+ ++state.error_count;
+ }
+ }
+
+ namespace
+ {
+ string_iterator find_first_seperator(
+ string_iterator begin, string_iterator end)
+ {
+ if (qbk_version_n < 105) {
+ for (; begin != end; ++begin) {
+ switch (*begin) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ return begin;
+ default:
+ break;
+ }
+ }
+ }
+ else {
+ unsigned int depth = 0;
+
+ for (; begin != end; ++begin) {
+ switch (*begin) {
+ case '[':
+ ++depth;
+ break;
+ case '\\':
+ if (++begin == end) return begin;
+ break;
+ case ']':
+ if (depth > 0) --depth;
+ break;
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ if (depth == 0) return begin;
+ default:
+ break;
+ }
+ }
+ }
+
+ return begin;
+ }
+
+ std::pair<string_iterator, string_iterator> find_seperator(
+ string_iterator begin, string_iterator end)
+ {
+ string_iterator first = begin = find_first_seperator(begin, end);
+
+ for (; begin != end; ++begin) {
+ switch (*begin) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ break;
+ default:
+ return std::make_pair(first, begin);
+ }
+ }
+
+ return std::make_pair(first, begin);
+ }
+
+ void break_arguments(
+ std::vector<value>& args,
+ std::vector<std::string> const& params,
+ fs::path const& /* filename */
+ )
+ {
+ // Quickbook 1.4-: If there aren't enough parameters seperated by
+ // '..' then seperate the last parameter using
+ // whitespace.
+ // Quickbook 1.5+: If '..' isn't used to seperate the parameters
+ // then use whitespace to separate them
+ // (2 = template name + argument).
+
+ if (qbk_version_n < 105 ? args.size() : args.size() == 1) {
+
+ while (args.size() < params.size()) {
+ // Try to break the last argument at the first space found
+ // and push it into the back of args. Do this
+ // recursively until we have all the expected number of
+ // arguments, or if there are no more spaces left.
+
+ value last_arg = args.back();
+ string_iterator begin = last_arg.get_quickbook().begin();
+ string_iterator end = last_arg.get_quickbook().end();
+
+ std::pair<string_iterator, string_iterator> pos =
+ find_seperator(begin, end);
+ if (pos.second == end) break;
+ value new_arg(qbk_value(
+ last_arg.get_file(), pos.second, end,
+ template_tags::phrase));
+
+ args.back() = qbk_value(
+ last_arg.get_file(), begin, pos.first,
+ last_arg.get_tag());
+ args.push_back(new_arg);
+ }
+ }
+ }
+
+ std::pair<bool, std::vector<std::string>::const_iterator> get_arguments(
+ std::vector<value> const& args,
+ std::vector<std::string> const& params,
+ template_scope const& scope,
+ string_iterator first,
+ quickbook::state& state)
+ {
+ std::vector<value>::const_iterator arg = args.begin();
+ std::vector<std::string>::const_iterator tpl = params.begin();
+ std::vector<std::string> empty_params;
+
+ // Store each of the argument passed in as local templates:
+ while (arg != args.end()) {
+ if (!state.templates.add(
+ template_symbol(*tpl, empty_params, *arg, &scope))) {
+ detail::outerr(state.current_file, first)
+ << "Duplicate Symbol Found" << std::endl;
+ ++state.error_count;
+ return std::make_pair(false, tpl);
+ }
+ ++arg;
+ ++tpl;
+ }
+ return std::make_pair(true, tpl);
+ }
+
+ bool parse_template(
+ value const& content,
+ quickbook::state& state,
+ bool is_attribute_template)
+ {
+ file_ptr saved_current_file = state.current_file;
+
+ state.current_file = content.get_file();
+ quickbook::string_view source = content.get_quickbook();
+
+ parse_iterator first(source.begin());
+ parse_iterator last(source.end());
+
+ bool r = cl::parse(
+ first, last,
+ is_attribute_template
+ ? state.grammar().attribute_template_body
+ : content.get_tag() == template_tags::phrase
+ ? state.grammar().inline_phrase
+ : state.grammar().block_start)
+ .full;
+
+ boost::swap(state.current_file, saved_current_file);
+
+ return r;
+ }
+ }
+
+ void call_template(
+ quickbook::state& state,
+ template_symbol const* symbol,
+ std::vector<value> const& args,
+ string_iterator first,
+ bool is_attribute_template = false)
+ {
+ bool is_block = symbol->content.get_tag() != template_tags::phrase;
+ assert(!(is_attribute_template && is_block));
+
+ quickbook::paragraph_action paragraph_action(state);
+
+ // Finish off any existing paragraphs.
+ if (is_block) paragraph_action();
+
+ // If this template contains already encoded text, then just
+ // write it out, without going through any of the rigamarole.
+
+ if (symbol->content.is_encoded()) {
+ (is_block ? state.out : state.phrase)
+ << symbol->content.get_encoded();
+ return;
+ }
+
+ // The template arguments should have the scope that the template was
+ // called from, not the template's own scope.
+ //
+ // Note that for quickbook 1.4- this value is just ignored when the
+ // arguments are expanded.
+ template_scope const& call_scope = state.templates.top_scope();
+
+ {
+ state_save save(state, state_save::scope_callables);
+ std::string save_block;
+ std::string save_phrase;
+
+ state.templates.start_template(symbol);
+
+ qbk_version_n = symbol->content.get_file()->version();
+
+ ++state.template_depth;
+ if (state.template_depth > state.max_template_depth) {
+ detail::outerr(state.current_file, first)
+ << "Infinite loop detected" << std::endl;
+ ++state.error_count;
+ return;
+ }
+
+ // Store the current section level so that we can ensure that
+ // [section] and [endsect] tags in the template are balanced.
+ state.min_section_level = state.document.section_level();
+
+ ///////////////////////////////////
+ // Prepare the arguments as local templates
+ bool get_arg_result;
+ std::vector<std::string>::const_iterator tpl;
+ boost::tie(get_arg_result, tpl) =
+ get_arguments(args, symbol->params, call_scope, first, state);
+
+ if (!get_arg_result) {
+ return;
+ }
+
+ ///////////////////////////////////
+ // parse the template body:
+
+ if (symbol->content.get_file()->version() < 107u) {
+ state.out.swap(save_block);
+ state.phrase.swap(save_phrase);
+ }
+
+ if (!parse_template(
+ symbol->content, state, is_attribute_template)) {
+ detail::outerr(state.current_file, first)
+ << "Expanding " << (is_block ? "block" : "phrase")
+ << " template: " << symbol->identifier << "\n\n"
+ << "------------------begin------------------\n"
+ << symbol->content.get_quickbook()
+ << "------------------end--------------------\n"
+ << std::endl;
+ ++state.error_count;
+ return;
+ }
+
+ if (state.document.section_level() != state.min_section_level) {
+ detail::outerr(state.current_file, first)
+ << "Mismatched sections in template " << symbol->identifier
+ << std::endl;
+ ++state.error_count;
+ return;
+ }
+
+ if (symbol->content.get_file()->version() < 107u) {
+ state.out.swap(save_block);
+ state.phrase.swap(save_phrase);
+
+ if (is_block || !save_block.empty()) {
+ paragraph_action();
+ state.out << save_block;
+ state.phrase << save_phrase;
+ paragraph_action();
+ }
+ else {
+ state.phrase << save_phrase;
+ }
+ }
+ else {
+ if (is_block) paragraph_action();
+ }
+ }
+ }
+
+ void call_code_snippet(
+ quickbook::state& state,
+ template_symbol const* symbol,
+ string_iterator first)
+ {
+ assert(symbol->params.size() == 0);
+ std::vector<value> args;
+
+ // Create a fake symbol for call_template
+ template_symbol t(
+ symbol->identifier, symbol->params, symbol->content,
+ symbol->lexical_parent);
+
+ state.start_callouts();
+ call_template(state, &t, args, first);
+ state.out << state.end_callouts();
+ }
+
+ void do_template_action(
+ quickbook::state& state, value template_list, string_iterator first)
+ {
+ bool const is_attribute_template =
+ template_list.get_tag() == template_tags::attribute_template;
+
+ // Get the arguments
+ value_consumer values = template_list;
+
+ bool template_escape = values.check(template_tags::escape);
+ if (template_escape) values.consume();
+
+ std::string identifier =
+ values.consume(template_tags::identifier).get_quickbook().to_s();
+
+ std::vector<value> args;
+
+ QUICKBOOK_FOR (value arg, values) {
+ args.push_back(arg);
+ }
+
+ values.finish();
+
+ template_symbol const* symbol = state.templates.find(identifier);
+ BOOST_ASSERT(symbol);
+
+ // Deal with escaped templates.
+
+ if (template_escape) {
+ if (!args.empty()) {
+ detail::outerr(state.current_file, first)
+ << "Arguments for escaped template." << std::endl;
+ ++state.error_count;
+ }
+
+ if (symbol->content.is_encoded()) {
+ state.phrase << symbol->content.get_encoded();
+ }
+ else {
+ state.phrase << symbol->content.get_quickbook();
+
+ /*
+
+ This would surround the escaped template in escape
+ comments to indicate to the post-processor that it
+ isn't quickbook generated markup. But I'm not sure if
+ it would work.
+
+ quickbook::detail::markup escape_markup
+ = detail::get_markup(phrase_tags::escape);
+
+ state.phrase
+ << escape_markup.pre
+ << symbol->content.get_quickbook()
+ << escape_markup.post
+ ;
+ */
+ }
+
+ return;
+ }
+
+ ///////////////////////////////////
+ // Check that attribute templates are phrase templates
+
+ if (is_attribute_template &&
+ symbol->content.get_tag() != template_tags::phrase) {
+ detail::outerr(state.current_file, first)
+ << "Only phrase templates can be used in attribute values."
+ << std::endl;
+
+ ++state.error_count;
+ return;
+ }
+
+ ///////////////////////////////////
+ // Initialise the arguments
+
+ switch (symbol->content.get_tag()) {
+ case template_tags::block:
+ case template_tags::phrase:
+ // Break the arguments for a template
+
+ break_arguments(args, symbol->params, state.current_file->path);
+
+ if (args.size() != symbol->params.size()) {
+ detail::outerr(state.current_file, first)
+ << "Invalid number of arguments passed. Expecting: "
+ << symbol->params.size()
+ << " argument(s), got: " << args.size()
+ << " argument(s) instead." << std::endl;
+
+ ++state.error_count;
+ return;
+ }
+
+ call_template(state, symbol, args, first, is_attribute_template);
+ break;
+
+ case template_tags::snippet:
+
+ if (!args.empty()) {
+ detail::outerr(state.current_file, first)
+ << "Arguments for code snippet." << std::endl;
+ ++state.error_count;
+
+ args.clear();
+ }
+
+ call_code_snippet(state, symbol, first);
+ break;
+
+ default:
+ assert(0);
+ }
+ }
+
+ void link_action(quickbook::state& state, value link)
+ {
+ write_anchors(state, state.phrase);
+
+ detail::markup markup = detail::get_markup(link.get_tag());
+
+ value_consumer values = link;
+ value dst_value = values.consume();
+ value content = values.consume();
+ values.finish();
+
+ std::string dst;
+
+ if (link.get_tag() == phrase_tags::link) {
+ dst = validate_id(state, dst_value);
+ }
+ else {
+ dst = get_attribute_value(state, dst_value);
+
+ if (link.get_tag() == phrase_tags::url) {
+ dst = detail::partially_escape_uri(dst);
+ }
+ }
+
+ state.phrase << markup.pre;
+ detail::print_string(dst, state.phrase.get());
+ state.phrase << "\">";
+
+ if (content.empty())
+ detail::print_string(dst, state.phrase.get());
+ else
+ state.phrase << content.get_encoded();
+
+ state.phrase << markup.post;
+ }
+
+ void variable_list_action(quickbook::state& state, value variable_list)
+ {
+ write_anchors(state, state.out);
+
+ value_consumer values = variable_list;
+ std::string title =
+ values.consume(table_tags::title).get_quickbook().to_s();
+
+ state.out << "<variablelist>\n";
+
+ state.out << "<title>";
+ detail::print_string(title, state.out.get());
+ state.out << "</title>\n";
+
+ QUICKBOOK_FOR (value_consumer entry, values) {
+ state.out << "<varlistentry>";
+
+ if (entry.check()) {
+ state.out << "<term>";
+ state.out << entry.consume().get_encoded();
+ state.out << "</term>";
+ }
+
+ if (entry.check()) {
+ state.out << "<listitem>";
+ QUICKBOOK_FOR (value phrase, entry)
+ state.out << phrase.get_encoded();
+ state.out << "</listitem>";
+ }
+
+ state.out << "</varlistentry>\n";
+ }
+
+ state.out << "</variablelist>\n";
+
+ values.finish();
+ }
+
+ void table_action(quickbook::state& state, value table)
+ {
+ write_anchors(state, state.out);
+
+ value_consumer values = table;
+
+ std::string element_id;
+ if (values.check(general_tags::element_id)) {
+ element_id = validate_id(state, values.consume());
+ }
+
+ value title = values.consume(table_tags::title);
+ bool has_title = !title.empty();
+
+ std::string table_id;
+
+ if (!element_id.empty()) {
+ table_id =
+ state.document.add_id(element_id, id_category::explicit_id);
+ }
+ else if (has_title) {
+ if (state.document.compatibility_version() >= 105) {
+ table_id = state.document.add_id(
+ detail::make_identifier(title.get_quickbook()),
+ id_category::generated);
+ }
+ else {
+ table_id = state.document.add_id("t", id_category::numbered);
+ }
+ }
+
+ // Emulating the old behaviour which used the width of the final
+ // row for span_count.
+ int row_count = 0;
+ int span_count = 0;
+
+ value_consumer lookahead = values;
+ QUICKBOOK_FOR (value row, lookahead) {
+ ++row_count;
+ span_count = boost::distance(row);
+ }
+ lookahead.finish();
+
+ if (has_title) {
+ state.out << "<table frame=\"all\"";
+ if (!table_id.empty()) state.out << " id=\"" << table_id << "\"";
+ state.out << ">\n";
+ state.out << "<title>";
+ if (qbk_version_n < 106u) {
+ detail::print_string(title.get_quickbook(), state.out.get());
+ }
+ else {
+ state.out << title.get_encoded();
+ }
+ state.out << "</title>";
+ }
+ else {
+ state.out << "<informaltable frame=\"all\"";
+ if (!table_id.empty()) state.out << " id=\"" << table_id << "\"";
+ state.out << ">\n";
+ }
+
+ state.out << "<tgroup cols=\"" << span_count << "\">\n";
+
+ if (row_count > 1) {
+ state.out << "<thead>"
+ << "<row>";
+ QUICKBOOK_FOR (value cell, values.consume()) {
+ state.out << "<entry>" << cell.get_encoded() << "</entry>";
+ }
+ state.out << "</row>\n"
+ << "</thead>\n";
+ }
+
+ state.out << "<tbody>\n";
+
+ QUICKBOOK_FOR (value row, values) {
+ state.out << "<row>";
+ QUICKBOOK_FOR (value cell, row) {
+ state.out << "<entry>" << cell.get_encoded() << "</entry>";
+ }
+ state.out << "</row>\n";
+ }
+
+ values.finish();
+
+ state.out << "</tbody>\n"
+ << "</tgroup>\n";
+
+ if (has_title) {
+ state.out << "</table>\n";
+ }
+ else {
+ state.out << "</informaltable>\n";
+ }
+ }
+
+ void begin_section_action(quickbook::state& state, value begin_section_list)
+ {
+ value_consumer values = begin_section_list;
+
+ value element_id = values.optional_consume(general_tags::element_id);
+ value content = values.consume();
+ values.finish();
+
+ std::string full_id = state.document.begin_section(
+ element_id,
+ element_id.empty()
+ ? detail::make_identifier(content.get_quickbook())
+ : validate_id(state, element_id),
+ element_id.empty() ? id_category::generated_section
+ : id_category::explicit_section_id,
+ state.current_source_mode());
+
+ state.out << "\n<section id=\"" << full_id << "\">\n";
+
+ std::string title = content.get_encoded();
+
+ if (!title.empty()) {
+ state.out << "<title>";
+
+ write_anchors(state, state.out);
+
+ if (self_linked_headers &&
+ state.document.compatibility_version() >= 103) {
+ state.out << quickbook::detail::linkify(title, full_id);
+ }
+ else {
+ state.out << title;
+ }
+
+ state.out << "</title>\n";
+ }
+ }
+
+ void end_section_action(
+ quickbook::state& state, value end_section_list, string_iterator first)
+ {
+ value_consumer values = end_section_list;
+ value element_id = values.optional_consume(general_tags::element_id);
+ values.finish();
+
+ write_anchors(state, state.out);
+
+ if (state.document.section_level() <= state.min_section_level) {
+ file_position const pos = state.current_file->position_of(first);
+
+ detail::outerr(state.current_file->path, pos.line)
+ << "Mismatched [endsect] near column " << pos.column << ".\n";
+ ++state.error_count;
+
+ return;
+ }
+
+ if (!element_id.empty() &&
+ !(element_id == state.document.explicit_id())) {
+ file_position const pos = state.current_file->position_of(first);
+ value section_element_id = state.document.explicit_id();
+
+ if (section_element_id.empty()) {
+ detail::outerr(state.current_file->path, pos.line)
+ << "Endsect has unexpected id '"
+ << element_id.get_quickbook()
+ << "' in section with no explicit id, near column "
+ << pos.column << ".\n";
+ }
+ else {
+ detail::outerr(state.current_file->path, pos.line)
+ << "Endsect has incorrect id '"
+ << element_id.get_quickbook() << "', expected '"
+ << state.document.explicit_id().get_quickbook()
+ << "', near column " << pos.column << ".\n";
+ }
+ ++state.error_count;
+ }
+
+ state.out << "</section>";
+ state.document.end_section();
+ }
+
+ void element_id_warning_action::operator()(
+ parse_iterator first, parse_iterator) const
+ {
+ detail::outwarn(state.current_file, first.base()) << "Empty id.\n";
+ }
+
+ void xinclude_action(quickbook::state& state, value xinclude)
+ {
+ write_anchors(state, state.out);
+
+ value_consumer values = xinclude;
+ path_parameter x = check_xinclude_path(values.consume(), state);
+ values.finish();
+
+ if (x.type == path_parameter::path) {
+ quickbook_path path = resolve_xinclude_path(x.value, state, true);
+
+ state.out << "\n<xi:include href=\"";
+ detail::print_string(
+ file_path_to_url(path.abstract_file_path), state.out.get());
+ state.out << "\" />\n";
+ }
+ }
+
+ void load_quickbook(
+ quickbook::state& state,
+ quickbook_path const& path,
+ value::tag_type load_type,
+ value const& include_doc_id = value())
+ {
+ assert(
+ load_type == block_tags::include ||
+ load_type == block_tags::import);
+
+ // Check this before qbk_version_n gets changed by the inner file.
+ bool keep_inner_source_mode = (qbk_version_n < 106);
+
+ {
+ // When importing, state doesn't scope templates and macros so that
+ // they're added to the existing scope. It might be better to add
+ // them to a new scope then explicitly import them into the
+ // existing scope.
+ //
+ // For old versions of quickbook, templates aren't scoped by the
+ // file.
+ state_save save(
+ state,
+ load_type == block_tags::import
+ ? state_save::scope_output
+ : qbk_version_n >= 106u ? state_save::scope_callables
+ : state_save::scope_macros);
+
+ state.current_file = load(path.file_path); // Throws load_error
+ state.current_path = path;
+ state.imported = (load_type == block_tags::import);
+
+ // update the __FILENAME__ macro
+ state.update_filename_macro();
+
+ // parse the file
+ quickbook::parse_file(state, include_doc_id, true);
+
+ // Don't restore source_mode on older versions.
+ if (keep_inner_source_mode) save.source_mode = state.source_mode;
+ }
+
+ // restore the __FILENAME__ macro
+ state.update_filename_macro();
+ }
+
+ void load_source_file(
+ quickbook::state& state,
+ quickbook_path const& path,
+ value::tag_type load_type,
+ string_iterator first,
+ value const& /* include_doc_id */ = value())
+ {
+ assert(
+ load_type == block_tags::include ||
+ load_type == block_tags::import);
+
+ std::string ext = path.file_path.extension().generic_string();
+ std::vector<template_symbol> storage;
+ // Throws load_error
+ state.error_count +=
+ load_snippets(path.file_path, storage, ext, load_type);
+
+ if (load_type == block_tags::include) {
+ state.templates.push();
+ }
+
+ QUICKBOOK_FOR (template_symbol& ts, storage) {
+ std::string tname = ts.identifier;
+ if (tname != "!") {
+ ts.lexical_parent = &state.templates.top_scope();
+ if (!state.templates.add(ts)) {
+ detail::outerr(
+ ts.content.get_file(), ts.content.get_position())
+ << "Template Redefinition: " << tname << std::endl;
+ ++state.error_count;
+ }
+ }
+ }
+
+ if (load_type == block_tags::include) {
+ QUICKBOOK_FOR (template_symbol& ts, storage) {
+ std::string tname = ts.identifier;
+
+ if (tname == "!") {
+ ts.lexical_parent = &state.templates.top_scope();
+ call_code_snippet(state, &ts, first);
+ }
+ }
+
+ state.templates.pop();
+ }
+ }
+
+ void include_action(
+ quickbook::state& state, value include, string_iterator first)
+ {
+ write_anchors(state, state.out);
+
+ value_consumer values = include;
+ value include_doc_id =
+ values.optional_consume(general_tags::include_id);
+ path_parameter parameter = check_path(values.consume(), state);
+ values.finish();
+
+ std::set<quickbook_path> search =
+ include_search(parameter, state, first);
+ QUICKBOOK_FOR (quickbook_path const& path, search) {
+ try {
+ if (qbk_version_n >= 106) {
+ if (state.imported &&
+ include.get_tag() == block_tags::include)
+ return;
+
+ std::string ext =
+ path.file_path.extension().generic_string();
+
+ if (ext == ".qbk" || ext == ".quickbook") {
+ load_quickbook(
+ state, path, include.get_tag(), include_doc_id);
+ }
+ else {
+ load_source_file(
+ state, path, include.get_tag(), first,
+ include_doc_id);
+ }
+ }
+ else {
+ if (include.get_tag() == block_tags::include) {
+ load_quickbook(
+ state, path, include.get_tag(), include_doc_id);
+ }
+ else {
+ load_source_file(
+ state, path, include.get_tag(), first,
+ include_doc_id);
+ }
+ }
+ } catch (load_error& e) {
+ ++state.error_count;
+
+ detail::outerr(state.current_file, first)
+ << "Loading file " << path.file_path << ": " << e.what()
+ << std::endl;
+ }
+ }
+ }
+
+ bool to_value_scoped_action::start(value::tag_type t)
+ {
+ state.push_output();
+ state.anchors.swap(saved_anchors);
+ tag = t;
+
+ return true;
+ }
+
+ void to_value_scoped_action::success(
+ parse_iterator first, parse_iterator last)
+ {
+ std::string value;
+
+ if (!state.out.str().empty()) {
+ paragraph_action para(state);
+ para(); // For paragraphs before the template call.
+ write_anchors(state, state.out);
+ state.out.swap(value);
+ }
+ else {
+ write_anchors(state, state.phrase);
+ state.phrase.swap(value);
+ }
+
+ state.values.builder.insert(encoded_qbk_value(
+ state.current_file, first.base(), last.base(), value, tag));
+ }
+
+ void to_value_scoped_action::cleanup()
+ {
+ state.pop_output();
+ state.anchors.swap(saved_anchors);
+ }
+}