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