blob: 8c51b664581556c5c086855d36c9d4fe4ba3009d [file] [log] [blame]
/*=============================================================================
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 <numeric>
#include <functional>
#include <algorithm>
#include <boost/filesystem/v2/convenience.hpp>
#include <boost/filesystem/v2/fstream.hpp>
#include <boost/lexical_cast.hpp>
#include "quickbook.hpp"
#include "actions.hpp"
#include "utils.hpp"
#include "markups.hpp"
#include "actions_class.hpp"
#include "grammar.hpp"
namespace quickbook
{
char const* quickbook_get_date = "__quickbook_get_date__";
char const* quickbook_get_time = "__quickbook_get_time__";
int qbk_major_version = -1;
int qbk_minor_version = -1;
unsigned qbk_version_n = 0; // qbk_major_version * 100 + qbk_minor_version
namespace {
std::string fully_qualified_id(std::string const& library_id,
std::string const& qualified_section_id,
std::string const& section_id)
{
std::string id = library_id;
if(!id.empty() && !qualified_section_id.empty()) id += '.';
id += qualified_section_id;
if(!id.empty() && !section_id.empty()) id += '.';
id += section_id;
return id;
}
}
// Handles line-breaks (DEPRECATED!!!)
void break_action::operator()(iterator first, iterator) const
{
position const pos = first.get_position();
detail::outwarn(pos.file,pos.line) << "in column:" << pos.column << ", "
<< "[br] and \\n are deprecated" << ".\n";
phrase << break_mark;
}
void error_action::operator()(iterator first, iterator /*last*/) const
{
position const pos = first.get_position();
detail::outerr(pos.file,pos.line)
<< "Syntax Error near column " << pos.column << ".\n";
++error_count;
}
void tagged_action::operator()(std::string const& str) const
{
out << pre << str << post;
}
void phrase_action::operator()(iterator first, iterator last) const
{
std::string str;
phrase.swap(str);
out << pre << str << post;
}
void implicit_paragraph_action::operator()() const
{
std::string str;
phrase.swap(str);
// TODO: Use spirit to do this?
std::string::const_iterator
pos = str.begin(),
end = str.end();
while(pos != end && (
*pos == ' ' || *pos == '\t' || *pos == '\n' || *pos == '\r'))
{
++pos;
}
if(pos != end) {
out << pre << str << post;
}
}
void header_action::operator()(iterator first, iterator last) const
{
std::string str;
phrase.swap(str);
if (qbk_version_n < 103) // version 1.2 and below
{
out << "<anchor id=\""
<< section_id << '.'
<< detail::make_identifier(str.begin(), str.end())
<< "\" />"
<< pre << str << post
;
}
else // version 1.3 and above
{
std::string id =
!element_id.empty() ? element_id :
qbk_version_n >= 106 ? detail::make_identifier(first, last) :
detail::make_identifier(str.begin(), str.end());
std::string anchor =
fully_qualified_id(library_id, qualified_section_id, id);
out << "<anchor id=\"" << anchor << "\"/>"
<< pre
<< "<link linkend=\"" << anchor << "\">"
<< str
<< "</link>"
<< post
;
}
}
void generic_header_action::operator()(iterator first, iterator last) const
{
int level_ = section_level + 2; // section_level is zero-based. We need to use a
// 0ne-based heading which is one greater
// than the current. Thus: section_level + 2.
if (level_ > 6) // The max is h6, clip it if it goes
level_ = 6; // further than that
std::string str;
phrase.swap(str);
std::string id =
!element_id.empty() ? element_id :
qbk_version_n >= 106 ? detail::make_identifier(first, last) :
detail::make_identifier(str.begin(), str.end());
std::string anchor =
fully_qualified_id(library_id, qualified_section_id, id);
out
<< "<anchor id=\"" << anchor << "\"/>"
<< "<bridgehead renderas=\"sect" << level_ << "\">"
<< "<link linkend=\"" << anchor << "\">"
<< str
<< "</link>"
<< "</bridgehead>"
;
}
void simple_phrase_action::operator()(iterator first, iterator last) const
{
out << pre;
std::string str(first, last);
if (std::string const* ptr = find(macro, str.c_str()))
{
out << *ptr;
}
else
{
while (first != last)
detail::print_char(*first++, out.get());
}
out << post;
}
void cond_phrase_action_pre::operator()(iterator first, iterator last) const
{
std::string str(first, last);
conditions.push_back(find(macro, str.c_str()));
out.push(); // save the stream
}
void cond_phrase_action_post::operator()(iterator first, iterator last) const
{
bool symbol_found = conditions.back();
conditions.pop_back();
if (first == last || !symbol_found)
{
out.pop(); // restore the stream
}
else
{
std::string save;
out.swap(save);
out.pop(); // restore the stream
out << save; // print the body
}
}
void list_action::operator()(iterator first, iterator last) const
{
BOOST_ASSERT(!list_marks.empty()); // there must be at least one item in the stack
out << list_buffer.str();
list_buffer.clear();
while (!list_marks.empty())
{
char mark = list_marks.top().first;
list_marks.pop();
out << std::string((mark == '#') ? "\n</orderedlist>" : "\n</itemizedlist>");
if (list_marks.size() >= 1)
out << "</listitem>";
}
list_indent = -1; // reset
}
void list_format_action::operator()(iterator first, iterator last) const
{
int new_indent = 0;
while (first != last && (*first == ' ' || *first == '\t'))
{
char mark = *first++;
if (mark == ' ')
{
++new_indent;
}
else // must be a tab
{
BOOST_ASSERT(mark == '\t');
// hardcoded tab to 4 for now
new_indent = ((new_indent + 4) / 4) * 4;
}
}
char mark = *first;
BOOST_ASSERT(mark == '#' || mark == '*'); // expecting a mark
if (list_indent == -1) // the very start
{
BOOST_ASSERT(new_indent == 0);
}
if (new_indent > list_indent)
{
list_indent = new_indent;
list_marks.push(mark_type(mark, list_indent));
if (list_marks.size() > 1)
{
// Make this new list a child of the previous list.
// The previous listelem has already ended so we erase
// "</listitem>" to accomodate this sub-list. We'll close
// the listelem later.
std::string str;
out.swap(str);
std::string::size_type pos = str.rfind("</listitem>");
BOOST_ASSERT(pos <= str.size());
str.erase(str.begin()+pos, str.end());
out << str;
}
out << std::string((mark == '#') ? "<orderedlist>\n" : "<itemizedlist>\n");
}
else if (new_indent < list_indent)
{
BOOST_ASSERT(!list_marks.empty());
list_indent = new_indent;
while (!list_marks.empty() && (list_indent < list_marks.top().second))
{
char mark = list_marks.top().first;
list_marks.pop();
out << std::string((mark == '#') ? "\n</orderedlist>" : "\n</itemizedlist>");
if (list_marks.size() >= 1)
out << "</listitem>";
}
}
if (mark != list_marks.top().first) // new_indent == list_indent
{
position const pos = first.get_position();
detail::outerr(pos.file,pos.line)
<< "Illegal change of list style near column " << pos.column << ".\n";
detail::outwarn(pos.file,pos.line)
<< "Ignoring change of list style" << std::endl;
++error_count;
}
}
void span::operator()(iterator first, iterator last) const
{
out << "<phrase role=\"" << name << "\">";
while (first != last)
detail::print_char(*first++, out.get());
out << "</phrase>";
}
void unexpected_char::operator()(iterator first, iterator last) const
{
position const pos = first.get_position();
detail::outwarn(pos.file, pos.line)
<< "in column:" << pos.column
<< ", unexpected character: " << std::string(first, last)
<< "\n";
// print out an unexpected character
out << "<phrase role=\"error\">";
while (first != last)
detail::print_char(*first++, out.get());
out << "</phrase>";
}
void anchor_action::operator()(iterator first, iterator last) const
{
out << "<anchor id=\"";
while (first != last)
detail::print_char(*first++, out.get());
out << "\" />\n";
}
void do_macro_action::operator()(std::string const& str) const
{
if (str == quickbook_get_date)
{
char strdate[64];
strftime(strdate, sizeof(strdate), "%Y-%b-%d", current_time);
phrase << strdate;
}
else if (str == quickbook_get_time)
{
char strdate[64];
strftime(strdate, sizeof(strdate), "%I:%M:%S %p", current_time);
phrase << strdate;
}
else
{
phrase << str;
}
}
void space::operator()(char ch) const
{
detail::print_space(ch, out.get());
}
void space::operator()(iterator first, iterator last) const
{
while (first != last)
detail::print_space(*first++, out.get());
}
void pre_escape_back::operator()(iterator first, iterator last) const
{
escape_actions.phrase.push(); // save the stream
}
void post_escape_back::operator()(iterator first, iterator last) const
{
out << escape_actions.phrase.str();
escape_actions.phrase.pop(); // restore the stream
}
void code_action::operator()(iterator first, iterator last) const
{
// preprocess the code section to remove the initial indentation
std::string program(first, last);
detail::unindent(program);
if (program.size() == 0)
return; // Nothing left to do here. The program is empty.
iterator first_(program.begin(), program.end());
iterator last_(program.end(), program.end());
first_.set_position(first.get_position());
std::string save;
phrase.swap(save);
// print the code with syntax coloring
std::string str = syntax_highlight(first_, last_, actions, actions.source_mode);
phrase.swap(save);
//
// We must not place a \n after the <programlisting> tag
// otherwise PDF output starts code blocks with a blank line:
//
out << "<programlisting>";
out << str;
out << "</programlisting>\n";
}
void inline_code_action::operator()(iterator first, iterator last) const
{
std::string save;
out.swap(save);
// print the code with syntax coloring
std::string str = syntax_highlight(first, last, actions, actions.source_mode);
out.swap(save);
out << "<code>";
out << str;
out << "</code>";
}
void raw_char_action::operator()(char ch) const
{
phrase << ch;
}
void raw_char_action::operator()(iterator first, iterator /*last*/) const
{
phrase << *first;
}
void plain_char_action::operator()(char ch) const
{
detail::print_char(ch, phrase.get());
}
void plain_char_action::operator()(iterator first, iterator /*last*/) const
{
detail::print_char(*first, phrase.get());
}
void escape_unicode_action::operator()(iterator first, iterator last) const
{
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(strtol(hex_digits.c_str(), 0, 16), phrase.get());
}
else {
phrase << "&#x" << hex_digits << ";";
}
}
void attribute_action::operator()(iterator first, iterator last) const
{
position const pos = first.get_position();
if (!attributes.insert(
attribute_map::value_type(attribute_name, std::string(first, last))
).second)
{
detail::outwarn(pos.file,pos.line)
<< "Repeated attribute: " << attribute_name << ".\n";
}
}
void image_action::operator()(iterator, iterator) const
{
fs::path const img_path(image_fileref);
attribute_map::iterator it = attributes.find("alt");
std::string alt_text = it != attributes.end() ? it->second : img_path.stem();
attributes.erase("alt");
attributes.insert(attribute_map::value_type("fileref", image_fileref));
if(img_path.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", "SVG"));
//
// Image paths are relative to the html subdirectory:
//
fs::path img;
if(img_path.root_path().empty())
img = "html" / img_path; // relative path
else
img = img_path; // absolute path
//
// Now load the SVG file:
//
std::string svg_text;
fs::ifstream fs(img);
char c;
while(fs.get(c) && fs.good())
svg_text.push_back(c);
//
// 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(attribute_map::value_type("contentwidth",
std::string(svg_text.begin() + a + 1, 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(attribute_map::value_type("contentdepth",
std::string(svg_text.begin() + a + 1, svg_text.begin() + b)));
}
}
phrase << "<inlinemediaobject>";
phrase << "<imageobject><imagedata";
for(attribute_map::const_iterator
attr_first = attributes.begin(), attr_last = attributes.end();
attr_first != attr_last; ++attr_first)
{
phrase << " " << attr_first->first << "=\"";
for(std::string::const_iterator
first = attr_first->second.begin(),
last = attr_first->second.end();
first != last; ++first)
{
if (*first == '\\' && ++first == last) break;
detail::print_char(*first, phrase.get());
}
phrase << "\"";
}
phrase << "></imagedata></imageobject>";
// Also add a textobject -- use the basename of the image file.
// This will mean we get "alt" attributes of the HTML img.
phrase << "<textobject><phrase>";
detail::print_string(alt_text, phrase.get());
phrase << "</phrase></textobject>";
phrase << "</inlinemediaobject>";
}
void macro_identifier_action::operator()(iterator first, iterator last) const
{
actions.macro_id.assign(first, last);
actions.phrase.push(); // save the phrase
}
void macro_definition_action::operator()(iterator first, iterator last) const
{
actions.copy_macros_for_write();
actions.macro.add(
actions.macro_id.begin()
, actions.macro_id.end()
, actions.phrase.str());
actions.phrase.pop(); // restore the phrase
}
void template_body_action::operator()(iterator first, iterator last) const
{
if (!actions.templates.add(
template_symbol(actions.template_identifier, actions.template_info,
std::string(first, last), first.get_position(),
actions.template_block, &actions.templates.top_scope())))
{
position const pos = first.get_position();
detail::outerr(pos.file,pos.line)
<< "Template Redefinition: " << actions.template_identifier << std::endl;
++actions.error_count;
}
actions.template_identifier.clear();
actions.template_info.clear();
}
namespace
{
iterator find_bracket_end(iterator begin, iterator const& end)
{
unsigned int depth = 1;
while(depth > 0) {
char const* search_chars = "[]\\";
begin = std::find_first_of(begin, end, search_chars, search_chars + 3);
if(begin == end) return begin;
if(*begin == '\\')
{
if(++begin == end) return begin;
++begin;
}
else
{
depth += (*begin == '[') ? 1 : -1;
++begin;
}
}
return begin;
}
iterator find_first_seperator(iterator const& begin, iterator const& end)
{
if(qbk_version_n < 105) {
char const* whitespace = " \t\r\n";
return std::find_first_of(begin, end, whitespace, whitespace + 4);
}
else {
iterator pos = begin;
while(true)
{
char const* search_chars = " \t\r\n\\[";
pos = std::find_first_of(pos, end, search_chars, search_chars + 6);
if(pos == end) return pos;
switch(*pos)
{
case '[':
pos = find_bracket_end(++pos, end);
break;
case '\\':
if(++pos == end) return pos;
++pos;
break;
default:
return pos;
}
}
}
}
bool break_arguments(
std::vector<template_body>& args
, std::vector<std::string> const& params
, position const& pos
)
{
// 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() == 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.
template_body& body = args.back();
iterator begin(body.content.begin(), body.content.end(),
position(body.position.file.c_str(), body.position.line, body.position.column));
iterator end(body.content.end(), body.content.end());
iterator l_pos = find_first_seperator(begin, end);
if (l_pos == end)
break;
char const* whitespace = " \t\r\n";
char const* whitespace_end = whitespace + 4;
iterator r_pos = l_pos;
while(r_pos != end && std::find(whitespace, whitespace_end, *r_pos) != whitespace_end) ++r_pos;
if (r_pos == end)
break;
template_body second(std::string(r_pos, end), r_pos.get_position(), false);
body.content = std::string(begin, l_pos);
args.push_back(second);
}
}
if (args.size() != params.size())
{
detail::outerr(pos.file, pos.line)
<< "Invalid number of arguments passed. Expecting: "
<< params.size()
<< " argument(s), got: "
<< args.size()
<< " argument(s) instead."
<< std::endl;
return false;
}
return true;
}
std::pair<bool, std::vector<std::string>::const_iterator>
get_arguments(
std::vector<template_body>& args
, std::vector<std::string> const& params
, template_scope const& scope
, position const& pos
, quickbook::actions& actions
)
{
std::vector<template_body>::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 (!actions.templates.add(
template_symbol(*tpl, empty_params, arg->content,
arg->position, arg->is_block, &scope)))
{
detail::outerr(pos.file,pos.line)
<< "Duplicate Symbol Found" << std::endl;
++actions.error_count;
return std::make_pair(false, tpl);
}
++arg; ++tpl;
}
return std::make_pair(true, tpl);
}
bool parse_template(
template_body const& body
, bool escape
, quickbook::actions& actions
)
{
// How do we know if we are to parse the template as a block or
// a phrase? We apply a simple heuristic: if the body starts with
// a newline, then we regard it as a block, otherwise, we parse
// it as a phrase.
//
// Note: this is now done in the grammar.
if (escape)
{
// escape the body of the template
// we just copy out the literal body
(body.is_block ? actions.out : actions.phrase) << body.content;
return true;
}
else if (!body.is_block)
{
simple_phrase_grammar phrase_p(actions);
// do a phrase level parse
iterator first(body.content.begin(), body.content.end(),
position(body.position.file.c_str(), body.position.line, body.position.column));
iterator last(body.content.end(), body.content.end());
return call_parse(first, last, phrase_p).full;
}
else
{
block_grammar block_p(actions, true);
// do a block level parse
// ensure that we have enough trailing newlines to eliminate
// the need to check for end of file in the grammar.
std::string content = body.content + "\n\n";
iterator first(content.begin(), content.end(),
position(body.position.file.c_str(), body.position.line, body.position.column));
iterator last(content.end(), content.end());
return call_parse(first, last, block_p).full;
}
}
}
namespace detail
{
int callout_id = 0;
}
void template_arg_action::operator()(iterator first, iterator last) const
{
actions.template_args.push_back(
template_body(
std::string(first, last),
first.get_position(),
actions.template_block));
}
void do_template_action::operator()(iterator first, iterator) const
{
// Get the arguments and clear values stored in action.
std::vector<template_body> args;
std::string identifier;
std::swap(args, actions.template_args);
std::swap(identifier, actions.template_identifier);
position const pos = first.get_position();
++actions.template_depth;
if (actions.template_depth > actions.max_template_depth)
{
detail::outerr(pos.file,pos.line)
<< "Infinite loop detected" << std::endl;
--actions.template_depth;
++actions.error_count;
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 = actions.templates.top_scope();
template_symbol const* symbol = actions.templates.find(identifier);
BOOST_ASSERT(symbol);
std::string block;
std::string phrase;
actions.push(); // scope the actions' states
{
// Store the current section level so that we can ensure that
// [section] and [endsect] tags in the template are balanced.
actions.min_section_level = actions.section_level;
// Quickbook 1.4-: When expanding the tempalte continue to use the
// current scope (the dynamic scope).
// Quickbook 1.5+: Use the scope the template was defined in
// (the static scope).
if (qbk_version_n >= 105)
actions.templates.set_parent_scope(*symbol->parent);
///////////////////////////////////
// Initialise the arguments
if (!symbol->callout)
{
// Break the arguments for a template
if (!break_arguments(args, symbol->params, pos))
{
actions.pop(); // restore the actions' states
--actions.template_depth;
++actions.error_count;
return;
}
}
else
{
if (!args.empty())
{
detail::outerr(pos.file, pos.line)
<< "Arguments for code snippet."
<<std::endl;
++actions.error_count;
args.clear();
}
unsigned int size = symbol->params.size();
for(unsigned int i = 0; i < size; ++i)
{
std::string callout_id = actions.doc_id +
boost::lexical_cast<std::string>(detail::callout_id + i);
std::string code;
code += "'''";
code += "<co id=\"" + callout_id + "co\" ";
code += "linkends=\"" + callout_id + "\" />";
code += "'''";
args.push_back(template_body(code, first.get_position(), false));
}
}
///////////////////////////////////
// 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, pos, actions);
if (!get_arg_result)
{
actions.pop(); // restore the actions' states
--actions.template_depth;
return;
}
///////////////////////////////////
// parse the template body:
if (!parse_template(symbol->body, actions.template_escape, actions))
{
position const pos = first.get_position();
detail::outerr(pos.file,pos.line)
<< "Expanding "
<< (symbol->body.is_block ? "block" : "phrase")
<< " template: " << symbol->identifier << std::endl
<< std::endl
<< "------------------begin------------------" << std::endl
<< symbol->body.content
<< "------------------end--------------------" << std::endl
<< std::endl;
actions.pop(); // restore the actions' states
--actions.template_depth;
++actions.error_count;
return;
}
if (actions.section_level != actions.min_section_level)
{
position const pos = first.get_position();
detail::outerr(pos.file,pos.line)
<< "Mismatched sections in template " << identifier << std::endl;
actions.pop(); // restore the actions' states
--actions.template_depth;
++actions.error_count;
return;
}
}
actions.out.swap(block);
actions.phrase.swap(phrase);
actions.pop(); // restore the actions' states
if(symbol->callout && symbol->callouts.size() > 0)
{
BOOST_ASSERT(phrase.empty());
block += "<calloutlist>";
BOOST_FOREACH(template_body const& c, symbol->callouts)
{
std::string callout_id = actions.doc_id +
boost::lexical_cast<std::string>(detail::callout_id++);
std::string callout_value;
actions.push();
bool r = parse_template(c, false, actions);
actions.out.swap(callout_value);
actions.pop();
if(!r)
{
detail::outerr(c.position.file, c.position.line)
<< "Expanding callout." << std::endl
<< "------------------begin------------------" << std::endl
<< c.content
<< std::endl
<< "------------------end--------------------" << std::endl
;
++actions.error_count;
return;
}
block += "<callout arearefs=\"" + callout_id + "co\" ";
block += "id=\"" + callout_id + "\">";
block += callout_value;
block += "</callout>";
}
block += "</calloutlist>";
}
if(symbol->body.is_block || !block.empty()) {
actions.inside_paragraph();
actions.out << block;
actions.phrase << phrase;
}
else {
actions.phrase << phrase;
}
--actions.template_depth;
}
void link_action::operator()(iterator first, iterator last) const
{
iterator save = first;
phrase << tag;
while (first != last)
detail::print_char(*first++, phrase.get());
phrase << "\">";
// Yes, it is safe to dereference last here. When we
// reach here, *last is certainly valid. We test if
// *last == ']'. In which case, the url is the text.
// Example: [@http://spirit.sourceforge.net/]
if (*last == ']')
{
first = save;
while (first != last)
detail::print_char(*first++, phrase.get());
}
}
void variablelist_action::operator()(iterator, iterator) const
{
actions.out << "<variablelist>\n";
actions.out << "<title>";
std::string::iterator first = actions.table_title.begin();
std::string::iterator last = actions.table_title.end();
while (first != last)
detail::print_char(*first++, actions.out.get());
actions.out << "</title>\n";
std::string str;
actions.phrase.swap(str);
actions.out << str;
actions.out << "</variablelist>\n";
actions.table_span = 0;
actions.table_header.clear();
actions.table_title.clear();
}
void table_action::operator()(iterator, iterator) const
{
std::string::iterator first = actions.table_title.begin();
std::string::iterator last = actions.table_title.end();
bool has_title = first != last;
std::string table_id;
if(qbk_version_n >= 105) {
if(!actions.element_id.empty()) {
table_id = fully_qualified_id(actions.doc_id,
actions.qualified_section_id, actions.element_id);
}
else if(has_title) {
table_id = fully_qualified_id(actions.doc_id,
actions.qualified_section_id,
detail::make_identifier(first, last));
}
}
if (has_title)
{
actions.out << "<table frame=\"all\"";
if(!table_id.empty())
actions.out << " id=\"" << table_id << "\"";
actions.out << ">\n";
actions.out << "<title>";
while (first != last)
detail::print_char(*first++, actions.out.get());
actions.out << "</title>";
}
else
{
actions.out << "<informaltable frame=\"all\"";
if(!table_id.empty())
actions.out << " id=\"" << table_id << "\"";
actions.out << ">\n";
}
actions.out << "<tgroup cols=\"" << actions.table_span << "\">\n";
if (!actions.table_header.empty())
{
actions.out << "<thead>" << actions.table_header << "</thead>\n";
}
actions.out << "<tbody>\n";
std::string str;
actions.phrase.swap(str);
actions.out << str;
actions.out << "</tbody>\n"
<< "</tgroup>\n";
if (has_title)
{
actions.out << "</table>\n";
}
else
{
actions.out << "</informaltable>\n";
}
actions.table_span = 0;
actions.table_header.clear();
actions.table_title.clear();
}
void start_row_action::operator()(char) const
{
// the first row is the header
if (header.empty() && !phrase.str().empty())
{
phrase.swap(header);
}
phrase << start_row_;
span = 0;
}
void start_row_action::operator()(iterator f, iterator) const
{
(*this)(*f);
}
void col_action::operator()(std::string const& contents) const
{
phrase << start_cell_ << contents << end_cell_;
++span;
}
void begin_section_action::operator()(iterator first, iterator last) const
{
section_id = element_id.empty() ?
detail::make_identifier(first, last) :
element_id;
if (section_level != 0)
qualified_section_id += '.';
else
BOOST_ASSERT(qualified_section_id.empty());
qualified_section_id += section_id;
++section_level;
if (qbk_version_n < 103) // version 1.2 and below
{
out << "\n<section id=\""
<< library_id << "." << section_id << "\">\n";
}
else // version 1.3 and above
{
out << "\n<section id=\"" << library_id
<< "." << qualified_section_id << "\">\n";
}
std::string str;
phrase.swap(str);
if (qbk_version_n < 103) // version 1.2 and below
{
out << "<title>" << str << "</title>\n";
}
else // version 1.3 and above
{
out << "<title>"
<< "<link linkend=\"" << library_id
<< "." << qualified_section_id << "\">"
<< str
<< "</link>"
<< "</title>\n"
;
}
}
void end_section_action::operator()(iterator first, iterator last) const
{
if (section_level <= min_section_level)
{
position const pos = first.get_position();
detail::outerr(pos.file,pos.line)
<< "Mismatched [endsect] near column " << pos.column << ".\n";
++error_count;
return;
}
--section_level;
out << "</section>";
if (section_level == 0)
{
qualified_section_id.clear();
}
else
{
std::string::size_type const n =
qualified_section_id.find_last_of('.');
qualified_section_id.erase(n, std::string::npos);
}
}
void element_id_warning_action::operator()(iterator first, iterator) const
{
position const pos = first.get_position();
detail::outwarn(pos.file,pos.line) << "Empty id.\n";
}
fs::path path_difference(fs::path const& outdir, fs::path const& path)
{
fs::path outtmp, temp;
fs::path::iterator out = outdir.begin(), file = path.begin();
for(; out != outdir.end() && file != path.end(); ++out, ++file)
{
if(!fs::equivalent(outtmp /= *out, temp /= *file))
break;
}
out = (out == outdir.begin()) ? outdir.end() : out;
fs::path result = fs::path();
for(; out != outdir.end(); ++out)
if(*out != ".") result /= "..";
std::divides<fs::path> concat;
return std::accumulate(file, path.end(), result, concat);
}
fs::path calculate_relative_path(
iterator first, iterator last, quickbook::actions& actions)
{
// Given a source file and the current filename, calculate the
// path to the source file relative to the output directory.
fs::path path(std::string(first, last));
if (!path.is_complete())
{
fs::path infile = fs::complete(actions.filename).normalize();
path = (infile.parent_path() / path).normalize();
fs::path outdir = fs::complete(actions.outdir).normalize();
path = path_difference(outdir, path);
}
return path;
}
void xinclude_action::operator()(iterator first, iterator last) const
{
fs::path path = calculate_relative_path(first, last, actions);
out << "\n<xi:include href=\"";
detail::print_string(detail::escape_uri(path.string()), out.get());
out << "\" />\n";
}
namespace
{
fs::path include_search(fs::path const & current, std::string const & name)
{
fs::path path(name);
// If the path is relative, try and resolve it.
if (!path.is_complete())
{
// See if it can be found locally first.
if (fs::exists(current / path))
{
return current / path;
}
// Search in each of the include path locations.
BOOST_FOREACH(std::string const & p, include_path)
{
fs::path full(p);
full /= path;
if (fs::exists(full))
{
return full;
}
}
}
return path;
}
}
void import_action::operator()(iterator first, iterator last) const
{
fs::path path = include_search(actions.filename.parent_path(), std::string(first,last));
std::string ext = path.extension();
std::vector<template_symbol> storage;
actions.error_count +=
load_snippets(path.string(), storage, ext, actions.doc_id);
BOOST_FOREACH(template_symbol& ts, storage)
{
std::string tname = ts.identifier;
ts.parent = &actions.templates.top_scope();
if (!actions.templates.add(ts))
{
cl::file_position const pos = ts.body.position;
detail::outerr(pos.file, pos.line)
<< "Template Redefinition: " << tname << std::endl;
++actions.error_count;
}
}
}
void include_action::operator()(iterator first, iterator last) const
{
fs::path filein = include_search(actions.filename.parent_path(), std::string(first,last));
std::string doc_type, doc_id;
docinfo_string doc_dirname, doc_last_revision;
// swap the filenames
std::swap(actions.filename, filein);
// save the doc info strings
if(qbk_version_n >= 106) {
doc_type = actions.doc_type;
doc_id = actions.doc_id;
doc_dirname = actions.doc_dirname;
doc_last_revision = actions.doc_last_revision;
}
else {
actions.doc_type.swap(doc_type);
actions.doc_id.swap(doc_id);
actions.doc_dirname.swap(doc_dirname);
actions.doc_last_revision.swap(doc_last_revision);
}
// save the version info
unsigned qbk_major_version_store = qbk_major_version;
unsigned qbk_minor_version_store = qbk_minor_version;
unsigned qbk_version_n_store = qbk_version_n;
// scope the macros
string_symbols macro = actions.macro;
std::size_t macro_change_depth = actions.macro_change_depth;
// scope the templates
//~ template_symbols templates = actions.templates; $$$ fixme $$$
// if an id is specified in this include (as in [include:id foo.qbk])
// then use it as the doc_id.
if (!actions.include_doc_id.empty())
{
actions.doc_id = actions.include_doc_id;
actions.include_doc_id.clear();
}
// update the __FILENAME__ macro
*boost::spirit::classic::find(actions.macro, "__FILENAME__") = actions.filename.file_string();
// parse the file
quickbook::parse_file(actions.filename.file_string().c_str(), actions, true);
// restore the values
std::swap(actions.filename, filein);
actions.doc_type.swap(doc_type);
actions.doc_id.swap(doc_id);
actions.doc_dirname.swap(doc_dirname);
actions.doc_last_revision.swap(doc_last_revision);
if(qbk_version_n >= 106 || qbk_version_n_store >= 106)
{
qbk_major_version = qbk_major_version_store;
qbk_minor_version = qbk_minor_version_store;
qbk_version_n = qbk_version_n_store;
}
// restore the macros
actions.macro = macro;
actions.macro_change_depth = macro_change_depth;
// restore the templates
//~ actions.templates = templates; $$$ fixme $$$
}
void phrase_to_string_action::operator()(iterator first, iterator last) const
{
out.clear();
phrase.swap(out);
}
void phrase_to_docinfo_action::operator()(iterator first, iterator last) const
{
out.encoded.clear();
phrase.swap(out.encoded);
out.raw = std::string(first, last);
}
}