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