| /*============================================================================= |
| 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 <vector> |
| #include <map> |
| #include <boost/filesystem/v3/convenience.hpp> |
| #include <boost/filesystem/v3/fstream.hpp> |
| #include <boost/range/distance.hpp> |
| #include <boost/range/algorithm/replace.hpp> |
| #include <boost/lexical_cast.hpp> |
| #include <boost/algorithm/string/replace.hpp> |
| #include <boost/next_prior.hpp> |
| #include <boost/foreach.hpp> |
| #include "quickbook.hpp" |
| #include "actions.hpp" |
| #include "utils.hpp" |
| #include "markups.hpp" |
| #include "actions_class.hpp" |
| #include "grammar.hpp" |
| #include "input_path.hpp" |
| #include "block_tags.hpp" |
| #include "phrase_tags.hpp" |
| |
| namespace quickbook |
| { |
| char const* quickbook_get_date = "__quickbook_get_date__"; |
| char const* quickbook_get_time = "__quickbook_get_time__"; |
| |
| 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; |
| } |
| |
| void write_anchors(quickbook::actions& actions, collector& tgt) |
| { |
| for(quickbook::actions::string_list::iterator |
| it = actions.anchors.begin(), |
| end = actions.anchors.end(); |
| it != end; ++it) |
| { |
| tgt << "<anchor id=\""; |
| detail::print_string(*it, tgt.get()); |
| tgt << "\"/>"; |
| } |
| |
| actions.anchors.clear(); |
| } |
| } |
| |
| void list_action(quickbook::actions&, value); |
| void explicit_list_action(quickbook::actions&, value); |
| void header_action(quickbook::actions&, value); |
| void begin_section_action(quickbook::actions&, value); |
| void end_section_action(quickbook::actions&, value, file_position); |
| void block_action(quickbook::actions&, value); |
| void block_empty_action(quickbook::actions&, value); |
| void macro_definition_action(quickbook::actions&, value); |
| void template_body_action(quickbook::actions&, value); |
| void variable_list_action(quickbook::actions&, value); |
| void table_action(quickbook::actions&, value); |
| void xinclude_action(quickbook::actions&, value); |
| void import_action(quickbook::actions&, value); |
| void include_action(quickbook::actions&, value); |
| void image_action(quickbook::actions&, value); |
| void anchor_action(quickbook::actions&, value); |
| void link_action(quickbook::actions&, value); |
| void phrase_action(quickbook::actions&, value); |
| void raw_phrase_action(quickbook::actions&, value); |
| void source_mode_action(quickbook::actions&, value); |
| void do_template_action(quickbook::actions&, value, file_position); |
| |
| void element_action::operator()(iterator first, iterator) const |
| { |
| value_consumer values = actions.values.release(); |
| if(!values.check()) return; |
| value v = values.consume(); |
| if(values.check()) return; |
| |
| switch(v.get_tag()) |
| { |
| case block_tags::list: |
| return list_action(actions, v); |
| case block_tags::ordered_list: |
| case block_tags::itemized_list: |
| return explicit_list_action(actions, 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(actions, v); |
| case block_tags::begin_section: |
| return begin_section_action(actions, v); |
| case block_tags::end_section: |
| return end_section_action(actions, v, first.get_position()); |
| 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: |
| return block_action(actions,v); |
| case block_tags::hr: |
| return block_empty_action(actions,v); |
| case block_tags::macro_definition: |
| return macro_definition_action(actions,v); |
| case block_tags::template_definition: |
| return template_body_action(actions,v); |
| case block_tags::variable_list: |
| return variable_list_action(actions, v); |
| case block_tags::table: |
| return table_action(actions, v); |
| case block_tags::xinclude: |
| return xinclude_action(actions, v); |
| case block_tags::import: |
| return import_action(actions, v); |
| case block_tags::include: |
| return include_action(actions, v); |
| case phrase_tags::image: |
| return image_action(actions, v); |
| case phrase_tags::anchor: |
| return anchor_action(actions, 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(actions, 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: |
| case phrase_tags::footnote: |
| return phrase_action(actions, v); |
| case phrase_tags::escape: |
| return raw_phrase_action(actions, v); |
| case source_mode_tags::cpp: |
| case source_mode_tags::python: |
| case source_mode_tags::teletype: |
| return source_mode_action(actions, v); |
| case template_tags::template_: |
| return do_template_action(actions, v, first.get_position()); |
| default: |
| break; |
| } |
| } |
| |
| // Handles line-breaks (DEPRECATED!!!) |
| void break_action::operator()(iterator first, iterator) const |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, phrase); |
| |
| file_position const pos = first.get_position(); |
| if(*first == '\\') |
| { |
| detail::outwarn(actions.filename, pos.line) |
| << "in column:" << pos.column << ", " |
| << "'\\n' is deprecated, pleases use '[br]' instead" << ".\n"; |
| } |
| |
| if(!actions.warned_about_breaks) |
| { |
| detail::outwarn(actions.filename, pos.line) |
| << "line breaks generate invalid boostbook " |
| "(will only note first occurrence).\n"; |
| |
| actions.warned_about_breaks = true; |
| } |
| |
| phrase << detail::get_markup(phrase_tags::break_mark).pre; |
| } |
| |
| void error_message_action::operator()(iterator first, iterator last) const |
| { |
| file_position const pos = first.get_position(); |
| |
| 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(actions.filename, pos.line) |
| << detail::utf8(formatted_message) << std::endl; |
| ++actions.error_count; |
| } |
| |
| void error_action::operator()(iterator first, iterator /*last*/) const |
| { |
| file_position const pos = first.get_position(); |
| detail::outerr(actions.filename, pos.line) |
| << "Syntax Error near column " << pos.column << ".\n"; |
| ++actions.error_count; |
| } |
| |
| void block_action(quickbook::actions& actions, value block) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.out); |
| |
| detail::markup markup = detail::get_markup(block.get_tag()); |
| |
| value_consumer values = block; |
| actions.out << markup.pre << values.consume().get_boostbook() << markup.post; |
| values.finish(); |
| } |
| |
| void block_empty_action(quickbook::actions& actions, value block) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.out); |
| |
| detail::markup markup = detail::get_markup(block.get_tag()); |
| actions.out << markup.pre; |
| } |
| |
| void phrase_action(quickbook::actions& actions, value phrase) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.phrase); |
| |
| detail::markup markup = detail::get_markup(phrase.get_tag()); |
| |
| value_consumer values = phrase; |
| actions.phrase << markup.pre << values.consume().get_boostbook() << markup.post; |
| values.finish(); |
| } |
| |
| void raw_phrase_action(quickbook::actions& actions, value phrase) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.phrase); |
| |
| detail::markup markup = detail::get_markup(phrase.get_tag()); |
| actions.phrase << markup.pre << phrase.get_quickbook() << markup.post; |
| } |
| |
| void paragraph_action::operator()() const |
| { |
| if(actions.suppress) return; |
| |
| std::string str; |
| actions.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 = detail::get_markup(block_tags::paragraph); |
| actions.out << markup.pre << str; |
| write_anchors(actions, actions.out); |
| actions.out << markup.post; |
| } |
| } |
| |
| namespace { |
| void write_bridgehead(collector& out, int level, |
| std::string const& str, std::string const& id, std::string const& linkend) |
| { |
| out << "<bridgehead renderas=\"sect" << level << "\""; |
| if(!id.empty()) out << " id=\"" << id << "\""; |
| out << ">"; |
| if(!linkend.empty()) out << "<link linkend=\"" << linkend << "\">"; |
| out << str; |
| if(!linkend.empty()) out << "</link>"; |
| out << "</bridgehead>"; |
| } |
| } |
| |
| void header_action(quickbook::actions& actions, value heading_list) |
| { |
| if(actions.suppress) return; |
| |
| 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 = actions.section_level + 2; |
| // section_level is zero-based. We need to use a |
| // one-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 |
| } |
| else |
| { |
| level = heading_list.get_tag() - block_tags::heading1 + 1; |
| } |
| |
| std::string anchor; |
| std::string linkend; |
| |
| if (!generic && qbk_version_n < 103) // version 1.2 and below |
| { |
| anchor = actions.section_id + '.' + |
| detail::make_identifier(content.get_boostbook()); |
| } |
| else |
| { |
| std::string id = |
| !element_id.empty() ? |
| element_id.get_quickbook() : |
| detail::make_identifier( |
| qbk_version_n >= 106 ? |
| content.get_quickbook() : |
| content.get_boostbook() |
| ); |
| |
| linkend = anchor = |
| fully_qualified_id(actions.doc_id, actions.qualified_section_id, id); |
| } |
| |
| actions.anchors.push_back(anchor); |
| write_anchors(actions, actions.out); |
| |
| write_bridgehead(actions.out, level, |
| content.get_boostbook(), anchor + "-heading", linkend); |
| } |
| |
| void simple_phrase_action::operator()(char mark) const |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, out); |
| |
| 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 = actions.values.release(); |
| value content = values.consume(); |
| values.finish(); |
| |
| out << markup.pre; |
| out << content.get_boostbook(); |
| out << markup.post; |
| } |
| |
| bool cond_phrase_push::start() |
| { |
| saved_suppress = actions.suppress; |
| |
| value_consumer values = actions.values.release(); |
| bool condition = find(actions.macro, |
| values.consume().get_quickbook().c_str()); |
| |
| actions.suppress = actions.suppress || !condition; |
| |
| return true; |
| } |
| |
| void cond_phrase_push::cleanup() |
| { |
| actions.suppress = saved_suppress; |
| } |
| |
| namespace { |
| int indent_length(std::string const& indent) |
| { |
| int length = 0; |
| for(std::string::const_iterator |
| first = indent.begin(), end = indent.end(); first != end; ++first) |
| { |
| switch(*first) { |
| case ' ': ++length; break; |
| // hardcoded tab to 4 for now |
| case '\t': length = ((length + 4) / 4) * 4; break; |
| default: BOOST_ASSERT(false); |
| } |
| } |
| |
| return length; |
| } |
| } |
| |
| void list_action(quickbook::actions& actions, value list) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.out); |
| |
| typedef std::pair<char, int> mark_type; |
| std::stack<mark_type> list_marks; |
| int list_indent = -1; |
| |
| BOOST_FOREACH(value_consumer values, list) |
| { |
| int new_indent = indent_length( |
| values.consume(general_tags::list_indent).get_quickbook()); |
| value mark_value = values.consume(general_tags::list_mark); |
| std::string content = values.consume().get_boostbook(); |
| values.finish(); |
| |
| char mark = mark_value.get_quickbook()[0]; |
| assert(mark == '*' || mark == '#'); |
| |
| if(list_indent == -1) { |
| assert(new_indent == 0); |
| } |
| |
| if(new_indent > list_indent) |
| { |
| list_indent = new_indent; |
| list_marks.push(mark_type(mark, list_indent)); |
| |
| actions.out << ((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(); |
| actions.out << "</simpara></listitem>"; |
| actions.out << ((mark == '#') ? "\n</orderedlist>" : "\n</itemizedlist>"); |
| } |
| actions.out << "</simpara></listitem>"; |
| } |
| else |
| { |
| actions.out << "</simpara></listitem>"; |
| } |
| |
| if (mark != list_marks.top().first) // new_indent == list_indent |
| { |
| file_position const pos = mark_value.get_position(); |
| detail::outerr(actions.filename, pos.line) |
| << "Illegal change of list style near column " << pos.column << ".\n"; |
| detail::outwarn(actions.filename, pos.line) |
| << "Ignoring change of list style" << std::endl; |
| ++actions.error_count; |
| } |
| |
| actions.out << "<listitem><simpara>"; |
| actions.out << content; |
| } |
| |
| assert(!list_marks.empty()); |
| while (!list_marks.empty()) |
| { |
| char mark = list_marks.top().first; |
| list_marks.pop(); |
| actions.out << "</simpara></listitem>"; |
| actions.out << ((mark == '#') ? "\n</orderedlist>" : "\n</itemizedlist>"); |
| } |
| } |
| |
| void explicit_list_action(quickbook::actions& actions, value list) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.out); |
| |
| detail::markup markup = detail::get_markup(list.get_tag()); |
| |
| actions.out << markup.pre; |
| |
| BOOST_FOREACH(value item, list) |
| { |
| actions.out << "<listitem>"; |
| actions.out << item.get_boostbook(); |
| actions.out << "</listitem>"; |
| } |
| |
| actions.out << markup.post; |
| } |
| |
| // TODO: No need to check suppress since this is only used in the syntax |
| // highlighter. I should move this or something. |
| void span::operator()(iterator first, iterator last) const |
| { |
| if (name) out << "<phrase role=\"" << name << "\">"; |
| while (first != last) |
| detail::print_char(*first++, out.get()); |
| if (name) out << "</phrase>"; |
| } |
| |
| void span_start::operator()(iterator first, iterator last) const |
| { |
| out << "<phrase role=\"" << name << "\">"; |
| while (first != last) |
| detail::print_char(*first++, out.get()); |
| } |
| |
| void span_end::operator()(iterator first, iterator last) const |
| { |
| while (first != last) |
| detail::print_char(*first++, out.get()); |
| out << "</phrase>"; |
| } |
| |
| void unexpected_char::operator()(iterator first, iterator last) const |
| { |
| file_position const pos = first.get_position(); |
| |
| detail::outwarn(actions.filename, pos.line) |
| << "in column:" << pos.column |
| << ", unexpected character: " << detail::utf8(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(quickbook::actions& actions, value anchor) |
| { |
| if(actions.suppress) return; |
| |
| value_consumer values = anchor; |
| actions.anchors.push_back(values.consume().get_quickbook()); |
| values.finish(); |
| } |
| |
| void do_macro_action::operator()(std::string const& str) const |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, phrase); |
| |
| 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, iterator) const |
| { |
| escape_actions.phrase.push(); // save the stream |
| } |
| |
| void post_escape_back::operator()(iterator, iterator) const |
| { |
| write_anchors(escape_actions, escape_actions.phrase); |
| out << escape_actions.phrase.str(); |
| escape_actions.phrase.pop(); // restore the stream |
| } |
| |
| void source_mode_action(quickbook::actions& actions, value source_mode) |
| { |
| actions.source_mode = source_mode_tags::name(source_mode.get_tag()); |
| } |
| |
| void code_action::operator()(iterator first, iterator last) const |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, out); |
| |
| // 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(), first.get_position()); |
| iterator last_(program.end()); |
| |
| // TODO: Shouldn't phrase be empty here? Why would it be output |
| // after the code block? |
| 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 |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, out); |
| |
| 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 |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, phrase); |
| |
| phrase << ch; |
| } |
| |
| void raw_char_action::operator()(iterator first, iterator /*last*/) const |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, phrase); |
| |
| phrase << *first; |
| } |
| |
| void plain_char_action::operator()(char ch) const |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, phrase); |
| |
| detail::print_char(ch, phrase.get()); |
| } |
| |
| void plain_char_action::operator()(iterator first, iterator /*last*/) const |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, phrase); |
| |
| detail::print_char(*first, phrase.get()); |
| } |
| |
| void escape_unicode_action::operator()(iterator first, iterator last) const |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, 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(strtol(hex_digits.c_str(), 0, 16), phrase.get()); |
| } |
| else { |
| phrase << "&#x" << hex_digits << ";"; |
| } |
| } |
| |
| void image_action(quickbook::actions& actions, value image) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.phrase); |
| |
| typedef std::map<std::string, value> attribute_map; |
| attribute_map attributes; |
| |
| value_consumer values = image; |
| attributes["fileref"] = values.consume(); |
| |
| BOOST_FOREACH(value pair_, values) |
| { |
| value_consumer pair = pair_; |
| value name = pair.consume(); |
| value value = pair.consume(); |
| pair.finish(); |
| if(!attributes.insert(std::make_pair(name.get_quickbook(), value)).second) |
| { |
| detail::outwarn(actions.filename, name.get_position().line) |
| << "Duplicate image attribute: " |
| << detail::utf8(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"].get_quickbook(); |
| |
| // Check for windows paths, then convert. |
| // A bit crude, but there you go. |
| |
| if(fileref.find('\\') != std::string::npos) |
| { |
| detail::outwarn(actions.filename, attributes["fileref"].get_position().line) |
| << "Image path isn't portable: '" |
| << detail::utf8(fileref) |
| << "'" |
| << std::endl; |
| } |
| |
| 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. |
| // TODO: IMO if there isn't an alt tag, then the description should |
| // be empty or missing. |
| |
| attribute_map::iterator alt_pos = attributes.find("alt"); |
| std::string alt_text = alt_pos != attributes.end() ? |
| alt_pos->second.get_quickbook() : stem; |
| 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", qbk_value("SVG"))); |
| |
| // |
| // Image paths are relative to the html subdirectory: |
| // |
| // TODO: This seems wrong to me. |
| // |
| fs::path img = detail::generic_to_path(fileref); |
| if(img.root_path().empty()) |
| img = "html" / img; // relative 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(std::make_pair( |
| "contentwidth", qbk_value(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(std::make_pair( |
| "contentdepth", qbk_value(std::string( |
| svg_text.begin() + a + 1, svg_text.begin() + b)) |
| )); |
| } |
| } |
| |
| actions.phrase << "<inlinemediaobject>"; |
| |
| actions.phrase << "<imageobject><imagedata"; |
| |
| BOOST_FOREACH(attribute_map::value_type const& attr, attributes) |
| { |
| actions.phrase << " " << attr.first << "=\""; |
| |
| std::string value = attr.second.get_quickbook(); |
| for(std::string::const_iterator |
| first = value.begin(), last = value.end(); |
| first != last; ++first) |
| { |
| if (*first == '\\' && ++first == last) break; |
| detail::print_char(*first, actions.phrase.get()); |
| } |
| |
| actions.phrase << "\""; |
| } |
| |
| actions.phrase << "></imagedata></imageobject>"; |
| |
| // Add a textobject containing the alt tag from earlier. |
| // This will be used for the alt tag in html. |
| actions.phrase << "<textobject><phrase>"; |
| detail::print_string(alt_text, actions.phrase.get()); |
| actions.phrase << "</phrase></textobject>"; |
| |
| actions.phrase << "</inlinemediaobject>"; |
| } |
| |
| void macro_definition_action(quickbook::actions& actions, quickbook::value macro_definition) |
| { |
| if(actions.suppress) return; |
| |
| value_consumer values = macro_definition; |
| std::string macro_id = values.consume().get_quickbook(); |
| std::string phrase = values.consume().get_boostbook(); |
| values.finish(); |
| |
| actions.copy_macros_for_write(); |
| actions.macro.add( |
| macro_id.begin() |
| , macro_id.end() |
| , phrase); |
| } |
| |
| void template_body_action(quickbook::actions& actions, quickbook::value template_definition) |
| { |
| if(actions.suppress) return; |
| |
| value_consumer values = template_definition; |
| std::string identifier = values.consume().get_quickbook(); |
| |
| std::vector<std::string> template_values; |
| BOOST_FOREACH(value const& p, values.consume()) { |
| template_values.push_back(p.get_quickbook()); |
| } |
| |
| BOOST_ASSERT(values.check(template_tags::block) || values.check(template_tags::phrase)); |
| value body = values.consume(); |
| BOOST_ASSERT(!values.check()); |
| |
| if (!actions.templates.add( |
| template_symbol( |
| identifier, |
| template_values, |
| body, |
| actions.filename, |
| &actions.templates.top_scope()))) |
| { |
| file_position const pos = body.get_position(); |
| detail::outerr(actions.filename, pos.line) |
| << "Template Redefinition: " << detail::utf8(identifier) << std::endl; |
| ++actions.error_count; |
| } |
| } |
| |
| namespace |
| { |
| iterator find_first_seperator(iterator begin, 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<iterator, iterator> find_seperator(iterator begin, iterator end) |
| { |
| 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); |
| } |
| |
| bool break_arguments( |
| std::vector<template_body>& args |
| , std::vector<std::string> const& params |
| , fs::path const& filename |
| , file_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.get_quickbook_range().begin(); |
| iterator end = body.content.get_quickbook_range().end(); |
| |
| std::pair<iterator, iterator> pos = |
| find_seperator(begin, end); |
| if (pos.second == end) break; |
| template_body second( |
| qbk_value(pos.second, end, template_tags::phrase), |
| body.filename); |
| |
| body.content = qbk_value(begin, pos.first, |
| body.content.get_tag()); |
| args.push_back(second); |
| } |
| } |
| |
| if (args.size() != params.size()) |
| { |
| detail::outerr(filename, 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 |
| , file_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->filename, &scope))) |
| { |
| detail::outerr(actions.filename, 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.get_quickbook(); |
| return true; |
| } |
| else |
| { |
| if (!body.is_block()) |
| { |
| // do a phrase level parse |
| actions.filename = body.filename; |
| iterator first = body.content.get_quickbook_range().begin(); |
| iterator last = body.content.get_quickbook_range().end(); |
| |
| return cl::parse(first, last, actions.grammar().simple_phrase).full; |
| } |
| else |
| { |
| // 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. |
| |
| actions.filename = body.filename; |
| std::string content = body.content.get_quickbook() + "\n\n"; |
| iterator first(content.begin(), body.content.get_position()); |
| iterator last(content.end()); |
| |
| return cl::parse(first, last, actions.grammar().block).full; |
| } |
| } |
| } |
| } |
| |
| namespace detail |
| { |
| int callout_id = 0; |
| } |
| |
| void do_template_action(quickbook::actions& actions, value template_list, |
| file_position pos) |
| { |
| if(actions.suppress) return; |
| |
| // 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(); |
| |
| std::vector<template_body> args; |
| |
| BOOST_FOREACH(value arg, values) |
| { |
| args.push_back(template_body(arg, actions.filename)); |
| } |
| |
| values.finish(); |
| |
| ++actions.template_depth; |
| if (actions.template_depth > actions.max_template_depth) |
| { |
| detail::outerr(actions.filename, 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->callouts.check()) |
| { |
| // Break the arguments for a template |
| |
| if (!break_arguments(args, symbol->params, actions.filename, pos)) |
| { |
| actions.pop(); // restore the actions' states |
| --actions.template_depth; |
| ++actions.error_count; |
| return; |
| } |
| } |
| else |
| { |
| if (!args.empty()) |
| { |
| detail::outerr(actions.filename, 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( |
| qbk_value(code, pos, template_tags::phrase), |
| actions.filename)); |
| } |
| } |
| |
| /////////////////////////////////// |
| // 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, template_escape, actions)) |
| { |
| detail::outerr(actions.filename, pos.line) |
| << "Expanding " |
| << (symbol->body.is_block() ? "block" : "phrase") |
| << " template: " << detail::utf8(symbol->identifier) << std::endl |
| << std::endl |
| << "------------------begin------------------" << std::endl |
| << detail::utf8(symbol->body.content.get_quickbook()) |
| << "------------------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) |
| { |
| detail::outerr(actions.filename, pos.line) |
| << "Mismatched sections in template " << detail::utf8(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->callouts.empty()) |
| { |
| BOOST_ASSERT(phrase.empty()); |
| block += "<calloutlist>"; |
| BOOST_FOREACH(value 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( |
| template_body(c, symbol->body.filename), false, actions); |
| actions.out.swap(callout_value); |
| actions.pop(); |
| |
| if(!r) |
| { |
| detail::outerr(symbol->body.filename, c.get_position().line) |
| << "Expanding callout." << std::endl |
| << "------------------begin------------------" << std::endl |
| << detail::utf8(c.get_quickbook()) |
| << 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.paragraph(); // For paragraphs before the template call. |
| actions.out << block; |
| actions.phrase << phrase; |
| actions.paragraph(); |
| } |
| else { |
| actions.phrase << phrase; |
| } |
| --actions.template_depth; |
| } |
| |
| void link_action(quickbook::actions& actions, value link) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.phrase); |
| |
| detail::markup markup = detail::get_markup(link.get_tag()); |
| |
| value_consumer values = link; |
| value dst = values.consume(); |
| value content = values.consume(); |
| values.finish(); |
| |
| actions.phrase << markup.pre; |
| detail::print_string(dst.get_quickbook(), actions.phrase.get()); |
| actions.phrase << "\">"; |
| |
| if (content.empty()) |
| detail::print_string(dst.get_quickbook(), actions.phrase.get()); |
| else |
| actions.phrase << content.get_boostbook(); |
| |
| actions.phrase << markup.post; |
| } |
| |
| void variable_list_action(quickbook::actions& actions, value variable_list) |
| { |
| if(actions.suppress) return; |
| write_anchors(actions, actions.out); |
| |
| value_consumer values = variable_list; |
| std::string title = values.consume(table_tags::title).get_quickbook(); |
| |
| actions.out << "<variablelist>\n"; |
| |
| actions.out << "<title>"; |
| detail::print_string(title, actions.out.get()); |
| actions.out << "</title>\n"; |
| |
| BOOST_FOREACH(value_consumer entry, values) { |
| actions.out << "<varlistentry>"; |
| |
| if(entry.check()) { |
| actions.out << "<term>"; |
| actions.out << entry.consume().get_boostbook(); |
| actions.out << "</term>"; |
| } |
| |
| if(entry.check()) { |
| actions.out << "<listitem>"; |
| BOOST_FOREACH(value phrase, entry) actions.out << phrase.get_boostbook(); |
| actions.out << "</listitem>"; |
| } |
| |
| actions.out << "</varlistentry>\n"; |
| } |
| |
| actions.out << "</variablelist>\n"; |
| |
| values.finish(); |
| } |
| |
| void table_action(quickbook::actions& actions, value table) |
| { |
| if(actions.suppress) return; |
| write_anchors(actions, actions.out); |
| |
| value_consumer values = table; |
| |
| std::string element_id; |
| if(values.check(general_tags::element_id)) |
| element_id = values.consume().get_quickbook(); |
| |
| std::string title = values.consume(table_tags::title).get_quickbook(); |
| bool has_title = !title.empty(); |
| |
| std::string table_id; |
| if(qbk_version_n >= 105) { |
| if(!element_id.empty()) { |
| table_id = fully_qualified_id(actions.doc_id, |
| actions.qualified_section_id, element_id); |
| } |
| else if(has_title) { |
| table_id = fully_qualified_id(actions.doc_id, |
| actions.qualified_section_id, |
| detail::make_identifier(title)); |
| } |
| } |
| |
| // 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; |
| BOOST_FOREACH(value row, lookahead) { |
| ++row_count; |
| span_count = boost::distance(row); |
| } |
| lookahead.finish(); |
| |
| if (has_title) |
| { |
| actions.out << "<table frame=\"all\""; |
| if(!table_id.empty()) |
| actions.out << " id=\"" << table_id << "\""; |
| actions.out << ">\n"; |
| actions.out << "<title>"; |
| detail::print_string(title, 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=\"" << span_count << "\">\n"; |
| |
| if (row_count > 1) |
| { |
| actions.out << "<thead>" << "<row>"; |
| BOOST_FOREACH(value cell, values.consume()) { |
| actions.out << "<entry>" << cell.get_boostbook() << "</entry>"; |
| } |
| actions.out << "</row>\n" << "</thead>\n"; |
| } |
| |
| actions.out << "<tbody>\n"; |
| |
| BOOST_FOREACH(value row, values) { |
| actions.out << "<row>"; |
| BOOST_FOREACH(value cell, row) { |
| actions.out << "<entry>" << cell.get_boostbook() << "</entry>"; |
| } |
| actions.out << "</row>\n"; |
| } |
| |
| values.finish(); |
| |
| actions.out << "</tbody>\n" |
| << "</tgroup>\n"; |
| |
| if (has_title) |
| { |
| actions.out << "</table>\n"; |
| } |
| else |
| { |
| actions.out << "</informaltable>\n"; |
| } |
| } |
| |
| void begin_section_action(quickbook::actions& actions, value begin_section_list) |
| { |
| if(actions.suppress) return; |
| |
| value_consumer values = begin_section_list; |
| |
| value element_id = values.optional_consume(general_tags::element_id); |
| value content = values.consume(); |
| values.finish(); |
| |
| actions.section_id = !element_id.empty() ? |
| element_id.get_quickbook() : |
| detail::make_identifier(content.get_quickbook()); |
| |
| if (actions.section_level != 0) |
| actions.qualified_section_id += '.'; |
| else |
| BOOST_ASSERT(actions.qualified_section_id.empty()); |
| |
| actions.qualified_section_id += actions.section_id; |
| ++actions.section_level; |
| |
| actions::string_list saved_anchors; |
| saved_anchors.swap(actions.anchors); |
| |
| if (qbk_version_n < 103) // version 1.2 and below |
| { |
| actions.out << "\n<section id=\"" |
| << actions.doc_id << "." << actions.section_id << "\">\n"; |
| } |
| else // version 1.3 and above |
| { |
| actions.out << "\n<section id=\"" << actions.doc_id |
| << "." << actions.qualified_section_id << "\">\n"; |
| } |
| |
| actions.out << "<title>"; |
| |
| actions.anchors.swap(saved_anchors); |
| write_anchors(actions, actions.out); |
| |
| if (qbk_version_n < 103) // version 1.2 and below |
| { |
| actions.out << content.get_boostbook(); |
| } |
| else // version 1.3 and above |
| { |
| actions.out << "<link linkend=\"" << actions.doc_id |
| << "." << actions.qualified_section_id << "\">" |
| << content.get_boostbook() |
| << "</link>" |
| ; |
| } |
| |
| actions.out << "</title>\n"; |
| } |
| |
| void end_section_action(quickbook::actions& actions, value end_section, file_position pos) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.out); |
| |
| if (actions.section_level <= actions.min_section_level) |
| { |
| detail::outerr(actions.filename, pos.line) |
| << "Mismatched [endsect] near column " << pos.column << ".\n"; |
| ++actions.error_count; |
| |
| return; |
| } |
| |
| --actions.section_level; |
| actions.out << "</section>"; |
| |
| if (actions.section_level == 0) |
| { |
| actions.qualified_section_id.clear(); |
| } |
| else |
| { |
| std::string::size_type const n = |
| actions.qualified_section_id.find_last_of('.'); |
| actions.qualified_section_id.erase(n, std::string::npos); |
| } |
| } |
| |
| void element_id_warning_action::operator()(iterator first, iterator) const |
| { |
| file_position const pos = first.get_position(); |
| detail::outwarn(actions.filename, pos.line) << "Empty id.\n"; |
| } |
| |
| // Not a general purpose normalization function, just |
| // from paths from the root directory. It strips the excess |
| // ".." parts from a path like: "x/../../y", leaving "y". |
| std::vector<fs::path> normalize_path_from_root(fs::path const& path) |
| { |
| assert(!path.has_root_directory() && !path.has_root_name()); |
| |
| std::vector<fs::path> parts; |
| |
| BOOST_FOREACH(fs::path const& part, path) |
| { |
| if (part.empty() || part == ".") { |
| } |
| else if (part == "..") { |
| if (!parts.empty()) parts.pop_back(); |
| } |
| else { |
| parts.push_back(part); |
| } |
| } |
| |
| return parts; |
| } |
| |
| // The relative path from base to path |
| fs::path path_difference(fs::path const& base, fs::path const& path) |
| { |
| fs::path |
| absolute_base = fs::absolute(base), |
| absolute_path = fs::absolute(path); |
| |
| // Remove '.', '..' and empty parts from the remaining path |
| std::vector<fs::path> |
| base_parts = normalize_path_from_root(absolute_base.relative_path()), |
| path_parts = normalize_path_from_root(absolute_path.relative_path()); |
| |
| std::vector<fs::path>::iterator |
| base_it = base_parts.begin(), |
| base_end = base_parts.end(), |
| path_it = path_parts.begin(), |
| path_end = path_parts.end(); |
| |
| // Build up the two paths in these variables, checking for the first |
| // difference. |
| fs::path |
| base_tmp = absolute_base.root_path(), |
| path_tmp = absolute_path.root_path(); |
| |
| fs::path result; |
| |
| // If they have different roots then there's no relative path so |
| // just build an absolute path. |
| if (!fs::equivalent(base_tmp, path_tmp)) |
| { |
| result = path_tmp; |
| } |
| else |
| { |
| // Find the point at which the paths differ |
| for(; base_it != base_end && path_it != path_end; ++base_it, ++path_it) |
| { |
| if(!fs::equivalent(base_tmp /= *base_it, path_tmp /= *path_it)) |
| break; |
| } |
| |
| // Build a relative path to that point |
| for(; base_it != base_end; ++base_it) result /= ".."; |
| } |
| |
| // Build the rest of our path |
| for(; path_it != path_end; ++path_it) result /= *path_it; |
| |
| return result; |
| } |
| |
| std::string check_path(value const& path, quickbook::actions& actions) |
| { |
| std::string path_text = path.get_quickbook(); |
| |
| if(path_text.find('\\') != std::string::npos) |
| { |
| detail::outwarn(actions.filename, path.get_position().line) |
| << "Path isn't portable: " |
| << detail::utf8(path_text) |
| << std::endl; |
| } |
| |
| boost::replace(path_text, '\\', '/'); |
| |
| return path_text; |
| } |
| |
| fs::path calculate_relative_path(std::string const& name, 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 = detail::generic_to_path(name); |
| if (path.has_root_directory()) |
| { |
| return path; |
| } |
| else |
| { |
| return path_difference( |
| actions.xinclude_base, |
| actions.filename.parent_path() / path); |
| |
| } |
| } |
| |
| void xinclude_action(quickbook::actions& actions, value xinclude) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.out); |
| |
| value_consumer values = xinclude; |
| fs::path path = calculate_relative_path( |
| check_path(values.consume(), actions), actions); |
| values.finish(); |
| |
| actions.out << "\n<xi:include href=\""; |
| detail::print_string(detail::escape_uri(path.generic_string()), actions.out.get()); |
| actions.out << "\" />\n"; |
| } |
| |
| namespace |
| { |
| struct include_search_return |
| { |
| include_search_return(fs::path const& x, fs::path const& y) |
| : filename(x), filename_relative(y) {} |
| |
| fs::path filename; |
| fs::path filename_relative; |
| }; |
| |
| include_search_return include_search(std::string const & name, |
| quickbook::actions const& actions) |
| { |
| fs::path current = actions.filename.parent_path(); |
| fs::path path(name); |
| |
| // If the path is relative, try and resolve it. |
| if (!path.has_root_directory() && !path.has_root_name()) |
| { |
| // See if it can be found locally first. |
| if (fs::exists(current / path)) |
| { |
| return include_search_return( |
| current / path, |
| actions.filename_relative.parent_path() / path); |
| } |
| |
| // Search in each of the include path locations. |
| BOOST_FOREACH(fs::path full, include_path) |
| { |
| full /= path; |
| if (fs::exists(full)) |
| { |
| return include_search_return(full, path); |
| } |
| } |
| } |
| |
| return include_search_return(path, |
| actions.filename_relative.parent_path() / path); |
| } |
| } |
| |
| void import_action(quickbook::actions& actions, value import) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.out); |
| |
| value_consumer values = import; |
| include_search_return paths = include_search( |
| check_path(values.consume(), actions), actions); |
| values.finish(); |
| |
| std::string ext = paths.filename.extension().generic_string(); |
| std::vector<template_symbol> storage; |
| actions.error_count += |
| load_snippets(paths.filename, 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)) |
| { |
| detail::outerr(ts.body.filename, ts.body.content.get_position().line) |
| << "Template Redefinition: " << detail::utf8(tname) << std::endl; |
| ++actions.error_count; |
| } |
| } |
| } |
| |
| void include_action(quickbook::actions& actions, value include) |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.out); |
| |
| value_consumer values = include; |
| value include_doc_id = values.optional_consume(general_tags::include_id); |
| include_search_return filein = include_search( |
| check_path(values.consume(), actions), actions); |
| values.finish(); |
| |
| std::string doc_type, doc_id; |
| |
| // swap the filenames |
| std::swap(actions.filename, filein.filename); |
| std::swap(actions.filename_relative, filein.filename_relative); |
| |
| // save the doc info strings and source mode |
| if(qbk_version_n >= 106) { |
| doc_type = actions.doc_type; |
| doc_id = actions.doc_id; |
| } |
| else { |
| actions.doc_type.swap(doc_type); |
| actions.doc_id.swap(doc_id); |
| } |
| |
| // save the source mode and version info (only restored for 1.6+) |
| std::string source_mode = actions.source_mode; |
| 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 (!include_doc_id.empty()) |
| actions.doc_id = include_doc_id.get_quickbook(); |
| |
| // update the __FILENAME__ macro |
| *boost::spirit::classic::find(actions.macro, "__FILENAME__") |
| = detail::path_to_generic(actions.filename_relative); |
| |
| // save values |
| actions.values.builder.save(); |
| |
| // parse the file |
| quickbook::parse_file(actions.filename, actions, true); |
| |
| // restore the values |
| actions.values.builder.restore(); |
| |
| std::swap(actions.filename, filein.filename); |
| std::swap(actions.filename_relative, filein.filename_relative); |
| |
| actions.doc_type.swap(doc_type); |
| actions.doc_id.swap(doc_id); |
| |
| if(qbk_version_n >= 106 || qbk_version_n_store >= 106) |
| { |
| actions.source_mode = source_mode; |
| |
| 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_docinfo_action_impl::operator()(iterator first, iterator last, |
| value::tag_type tag) const |
| { |
| if (actions.suppress) return; |
| write_anchors(actions, actions.phrase); |
| |
| std::string encoded; |
| actions.phrase.swap(encoded); |
| actions.values.builder.insert( |
| qbk_bbk_value(first, last, encoded, tag)); |
| } |
| |
| void phrase_to_docinfo_action_impl::operator()(iterator first, iterator last) const |
| { |
| return (*this)(first, last, value::default_tag); |
| } |
| |
| void to_value_action::operator()(iterator, iterator) const |
| { |
| if (actions.suppress) return; |
| |
| std::string value; |
| |
| if (!actions.out.str().empty()) |
| { |
| actions.paragraph(); |
| write_anchors(actions, actions.out); |
| actions.out.swap(value); |
| } |
| else |
| { |
| write_anchors(actions, actions.phrase); |
| actions.phrase.swap(value); |
| } |
| |
| actions.values.builder.insert(bbk_value(value, value::default_tag)); |
| } |
| |
| bool scoped_output_push::start() |
| { |
| actions.out.push(); |
| actions.phrase.push(); |
| actions.anchors.swap(saved_anchors); |
| |
| return true; |
| } |
| |
| void scoped_output_push::cleanup() |
| { |
| actions.phrase.pop(); |
| actions.out.pop(); |
| actions.anchors.swap(saved_anchors); |
| } |
| |
| bool set_no_eols_scoped::start() |
| { |
| saved_no_eols = actions.no_eols; |
| actions.no_eols = false; |
| |
| return true; |
| } |
| |
| void set_no_eols_scoped::cleanup() |
| { |
| actions.no_eols = saved_no_eols; |
| } |
| |
| bool scoped_context_impl::start(int new_context) |
| { |
| saved_context_ = actions_.context; |
| actions_.context = new_context; |
| |
| return true; |
| } |
| |
| void scoped_context_impl::cleanup() |
| { |
| actions_.context = saved_context_; |
| } |
| } |