mirror of
https://github.com/boostorg/quickbook.git
synced 2026-01-19 16:32:16 +00:00
1973 lines
65 KiB
C++
1973 lines
65 KiB
C++
/*=============================================================================
|
|
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/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::core::invoke_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::core::invoke_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::core::invoke_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);
|
|
}
|
|
}
|