1 /*============================================================================= 2 Copyright (c) 2011, 2013 Daniel James 3 4 Use, modification and distribution is subject to the Boost Software 5 License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at 6 http://www.boost.org/LICENSE_1_0.txt) 7 =============================================================================*/ 8 9 #include <cctype> 10 #include <boost/lexical_cast.hpp> 11 #include <boost/make_shared.hpp> 12 #include <boost/range/algorithm/count.hpp> 13 #include "document_state_impl.hpp" 14 #include "utils.hpp" 15 16 namespace quickbook 17 { 18 struct file_info 19 { 20 boost::shared_ptr<file_info> const parent; 21 boost::shared_ptr<doc_info> const document; 22 23 unsigned const compatibility_version; 24 unsigned const depth; 25 unsigned const override_depth; 26 id_placeholder const* const override_id; 27 28 // The 1.1-1.5 document id would actually change per file due to 29 // explicit ids in includes and a bug which would sometimes use the 30 // document title instead of the id. 31 std::string const doc_id_1_1; 32 33 // Constructor for files that aren't the root of a document. file_infoquickbook::file_info34 explicit file_info( 35 boost::shared_ptr<file_info> const& parent_, 36 unsigned compatibility_version_, 37 quickbook::string_view doc_id_1_1_, 38 id_placeholder const* override_id_) 39 : parent(parent_) 40 , document(parent->document) 41 , compatibility_version(compatibility_version_) 42 , depth(parent->depth + 1) 43 , override_depth(override_id_ ? depth : parent->override_depth) 44 , override_id(override_id_ ? override_id_ : parent->override_id) 45 , doc_id_1_1(doc_id_1_1_.to_s()) 46 { 47 } 48 49 // Constructor for files that are the root of a document. file_infoquickbook::file_info50 explicit file_info( 51 boost::shared_ptr<file_info> const& parent_, 52 boost::shared_ptr<doc_info> const& document_, 53 unsigned compatibility_version_, 54 quickbook::string_view doc_id_1_1_) 55 : parent(parent_) 56 , document(document_) 57 , compatibility_version(compatibility_version_) 58 , depth(0) 59 , override_depth(0) 60 , override_id(0) 61 , doc_id_1_1(doc_id_1_1_.to_s()) 62 { 63 } 64 }; 65 66 struct doc_info 67 { 68 boost::shared_ptr<section_info> current_section; 69 70 // Note: these are mutable to remain bug compatible with old versions 71 // of quickbook. They would set these values at the start of new files 72 // and sections and then not restore them at the end. 73 std::string last_title_1_1; 74 std::string section_id_1_1; 75 }; 76 77 struct section_info 78 { 79 boost::shared_ptr<section_info> const parent; 80 unsigned const compatibility_version; 81 unsigned const file_depth; 82 unsigned const level; 83 84 value const explicit_id; 85 std::string const id_1_1; 86 id_placeholder const* const placeholder_1_6; 87 source_mode_info const source_mode; 88 section_infoquickbook::section_info89 explicit section_info( 90 boost::shared_ptr<section_info> const& parent_, 91 file_info const* current_file_, 92 value const& explicit_id_, 93 quickbook::string_view id_1_1_, 94 id_placeholder const* placeholder_1_6_, 95 source_mode_info const& source_mode_) 96 : parent(parent_) 97 , compatibility_version(current_file_->compatibility_version) 98 , file_depth(current_file_->depth) 99 , level(parent ? parent->level + 1 : 1) 100 , explicit_id(explicit_id_) 101 , id_1_1(id_1_1_.to_s()) 102 , placeholder_1_6(placeholder_1_6_) 103 , source_mode(source_mode_) 104 { 105 } 106 }; 107 108 // 109 // document_state 110 // 111 document_state()112 document_state::document_state() : state(new document_state_impl) {} 113 ~document_state()114 document_state::~document_state() {} 115 start_file(unsigned compatibility_version_,quickbook::string_view include_doc_id,quickbook::string_view id,value const & title_)116 void document_state::start_file( 117 unsigned compatibility_version_, 118 quickbook::string_view include_doc_id, 119 quickbook::string_view id, 120 value const& title_) 121 { 122 state->start_file( 123 compatibility_version_, false, include_doc_id, id, title_); 124 } 125 start_file_with_docinfo(unsigned compatibility_version_,quickbook::string_view include_doc_id,quickbook::string_view id,value const & title_)126 std::string document_state::start_file_with_docinfo( 127 unsigned compatibility_version_, 128 quickbook::string_view include_doc_id, 129 quickbook::string_view id, 130 value const& title_) 131 { 132 return state 133 ->start_file( 134 compatibility_version_, true, include_doc_id, id, title_) 135 ->to_string(); 136 } 137 end_file()138 void document_state::end_file() { state->end_file(); } 139 begin_section(value const & explicit_id_,quickbook::string_view id,id_category category,source_mode_info const & source_mode)140 std::string document_state::begin_section( 141 value const& explicit_id_, 142 quickbook::string_view id, 143 id_category category, 144 source_mode_info const& source_mode) 145 { 146 return state->begin_section(explicit_id_, id, category, source_mode) 147 ->to_string(); 148 } 149 end_section()150 void document_state::end_section() { return state->end_section(); } 151 section_level() const152 int document_state::section_level() const 153 { 154 return state->current_file->document->current_section->level; 155 } 156 explicit_id() const157 value const& document_state::explicit_id() const 158 { 159 return state->current_file->document->current_section->explicit_id; 160 } 161 section_source_mode() const162 source_mode_info document_state::section_source_mode() const 163 { 164 return state->current_file 165 ? state->current_file->document->current_section->source_mode 166 : source_mode_info(); 167 } 168 old_style_id(quickbook::string_view id,id_category category)169 std::string document_state::old_style_id( 170 quickbook::string_view id, id_category category) 171 { 172 return state->old_style_id(id, category)->to_string(); 173 } 174 add_id(quickbook::string_view id,id_category category)175 std::string document_state::add_id( 176 quickbook::string_view id, id_category category) 177 { 178 return state->add_id(id, category)->to_string(); 179 } 180 add_anchor(quickbook::string_view id,id_category category)181 std::string document_state::add_anchor( 182 quickbook::string_view id, id_category category) 183 { 184 return state->add_placeholder(id, category)->to_string(); 185 } 186 replace_placeholders_with_unresolved_ids(quickbook::string_view xml) const187 std::string document_state::replace_placeholders_with_unresolved_ids( 188 quickbook::string_view xml) const 189 { 190 return replace_ids(*state, xml); 191 } 192 replace_placeholders(quickbook::string_view xml) const193 std::string document_state::replace_placeholders( 194 quickbook::string_view xml) const 195 { 196 assert(!state->current_file); 197 std::vector<std::string> ids = generate_ids(*state, xml); 198 return replace_ids(*state, xml, &ids); 199 } 200 compatibility_version() const201 unsigned document_state::compatibility_version() const 202 { 203 return state->current_file->compatibility_version; 204 } 205 206 // 207 // id_placeholder 208 // 209 id_placeholder(std::size_t index_,quickbook::string_view id_,id_category category_,id_placeholder const * parent_)210 id_placeholder::id_placeholder( 211 std::size_t index_, 212 quickbook::string_view id_, 213 id_category category_, 214 id_placeholder const* parent_) 215 : index(index_) 216 , id(id_.begin(), id_.end()) 217 , unresolved_id(parent_ ? parent_->unresolved_id + '.' + id : id) 218 , parent(parent_) 219 , category(category_) 220 , num_dots( 221 boost::range::count(id, '.') + 222 (parent_ ? parent_->num_dots + 1 : 0)) 223 { 224 } 225 to_string() const226 std::string id_placeholder::to_string() const 227 { 228 return '$' + boost::lexical_cast<std::string>(index); 229 } 230 231 // 232 // document_state_impl 233 // 234 add_placeholder(quickbook::string_view id,id_category category,id_placeholder const * parent)235 id_placeholder const* document_state_impl::add_placeholder( 236 quickbook::string_view id, 237 id_category category, 238 id_placeholder const* parent) 239 { 240 placeholders.push_back( 241 id_placeholder(placeholders.size(), id, category, parent)); 242 return &placeholders.back(); 243 } 244 get_placeholder(quickbook::string_view value) const245 id_placeholder const* document_state_impl::get_placeholder( 246 quickbook::string_view value) const 247 { 248 // If this isn't a placeholder id. 249 if (value.size() <= 1 || *value.begin() != '$') return 0; 250 251 unsigned index = boost::lexical_cast<unsigned>( 252 std::string(value.begin() + 1, value.end())); 253 254 return &placeholders.at(index); 255 } 256 get_id_placeholder(boost::shared_ptr<section_info> const & section) const257 id_placeholder const* document_state_impl::get_id_placeholder( 258 boost::shared_ptr<section_info> const& section) const 259 { 260 return !section ? 0 261 : section->file_depth < current_file->override_depth 262 ? current_file->override_id 263 : section->placeholder_1_6; 264 } 265 start_file(unsigned compatibility_version,bool document_root,quickbook::string_view include_doc_id,quickbook::string_view id,value const & title)266 id_placeholder const* document_state_impl::start_file( 267 unsigned compatibility_version, 268 bool document_root, 269 quickbook::string_view include_doc_id, 270 quickbook::string_view id, 271 value const& title) 272 { 273 boost::shared_ptr<file_info> parent = current_file; 274 assert(parent || document_root); 275 276 boost::shared_ptr<doc_info> document = 277 document_root ? boost::make_shared<doc_info>() : parent->document; 278 279 // Choose specified id to use. Prefer 'include_doc_id' (the id 280 // specified in an 'include' element) unless backwards compatibility 281 // is required. 282 283 quickbook::string_view initial_doc_id; 284 285 if (document_root || compatibility_version >= 106u || 286 parent->compatibility_version >= 106u) { 287 initial_doc_id = !include_doc_id.empty() ? include_doc_id : id; 288 } 289 else { 290 initial_doc_id = !id.empty() ? id : include_doc_id; 291 } 292 293 // Work out this file's doc_id for older versions of quickbook. 294 // A bug meant that this need to be done per file, not per 295 // document. 296 297 std::string doc_id_1_1; 298 299 if (document_root || compatibility_version < 106u) { 300 if (title.check()) 301 document->last_title_1_1 = title.get_quickbook().to_s(); 302 303 doc_id_1_1 = 304 !initial_doc_id.empty() 305 ? initial_doc_id.to_s() 306 : detail::make_identifier(document->last_title_1_1); 307 } 308 else if (parent) { 309 doc_id_1_1 = parent->doc_id_1_1; 310 } 311 312 if (document_root) { 313 // Create new file 314 315 current_file = boost::make_shared<file_info>( 316 parent, document, compatibility_version, doc_id_1_1); 317 318 // Create a section for the new document. 319 320 source_mode_info default_source_mode; 321 322 if (!initial_doc_id.empty()) { 323 return create_new_section( 324 empty_value(), id, id_category::explicit_section_id, 325 default_source_mode); 326 } 327 else if (!title.empty()) { 328 return create_new_section( 329 empty_value(), 330 detail::make_identifier(title.get_quickbook()), 331 id_category::generated_doc, default_source_mode); 332 } 333 else if (compatibility_version >= 106u) { 334 return create_new_section( 335 empty_value(), "doc", id_category::numbered, 336 default_source_mode); 337 } 338 else { 339 return create_new_section( 340 empty_value(), "", id_category::generated_doc, 341 default_source_mode); 342 } 343 } 344 else { 345 // If an id was set for the file, then the file overrides the 346 // current section's id with this id. 347 // 348 // Don't do this for document_root as it will create a section 349 // for the document. 350 // 351 // Don't do this for older versions, as they use a different 352 // backwards compatible mechanism to handle file ids. 353 354 id_placeholder const* override_id = 0; 355 356 if (!initial_doc_id.empty() && compatibility_version >= 106u) { 357 boost::shared_ptr<section_info> null_section; 358 359 override_id = add_id_to_section( 360 initial_doc_id, id_category::explicit_section_id, 361 null_section); 362 } 363 364 // Create new file 365 366 current_file = boost::make_shared<file_info>( 367 parent, compatibility_version, doc_id_1_1, override_id); 368 369 return 0; 370 } 371 } 372 end_file()373 void document_state_impl::end_file() 374 { 375 current_file = current_file->parent; 376 } 377 add_id(quickbook::string_view id,id_category category)378 id_placeholder const* document_state_impl::add_id( 379 quickbook::string_view id, id_category category) 380 { 381 return add_id_to_section( 382 id, category, current_file->document->current_section); 383 } 384 add_id_to_section(quickbook::string_view id,id_category category,boost::shared_ptr<section_info> const & section)385 id_placeholder const* document_state_impl::add_id_to_section( 386 quickbook::string_view id, 387 id_category category, 388 boost::shared_ptr<section_info> const& section) 389 { 390 std::string id_part(id.begin(), id.end()); 391 392 // Note: Normalizing id according to file compatibility version, but 393 // adding to section according to section compatibility version. 394 395 if (current_file->compatibility_version >= 106u && 396 category.c < id_category::explicit_id) { 397 id_part = normalize_id(id); 398 } 399 400 id_placeholder const* placeholder_1_6 = get_id_placeholder(section); 401 402 if (!section || section->compatibility_version >= 106u) { 403 return add_placeholder(id_part, category, placeholder_1_6); 404 } 405 else { 406 std::string const& qualified_id = section->id_1_1; 407 408 std::string new_id; 409 if (!placeholder_1_6) new_id = current_file->doc_id_1_1; 410 if (!new_id.empty() && !qualified_id.empty()) new_id += '.'; 411 new_id += qualified_id; 412 if (!new_id.empty() && !id_part.empty()) new_id += '.'; 413 new_id += id_part; 414 415 return add_placeholder(new_id, category, placeholder_1_6); 416 } 417 } 418 old_style_id(quickbook::string_view id,id_category category)419 id_placeholder const* document_state_impl::old_style_id( 420 quickbook::string_view id, id_category category) 421 { 422 return current_file->compatibility_version < 103u 423 ? add_placeholder( 424 current_file->document->section_id_1_1 + "." + 425 id.to_s(), 426 category) 427 : add_id(id, category); 428 } 429 begin_section(value const & explicit_id,quickbook::string_view id,id_category category,source_mode_info const & source_mode)430 id_placeholder const* document_state_impl::begin_section( 431 value const& explicit_id, 432 quickbook::string_view id, 433 id_category category, 434 source_mode_info const& source_mode) 435 { 436 current_file->document->section_id_1_1 = id.to_s(); 437 return create_new_section(explicit_id, id, category, source_mode); 438 } 439 create_new_section(value const & explicit_id,quickbook::string_view id,id_category category,source_mode_info const & source_mode)440 id_placeholder const* document_state_impl::create_new_section( 441 value const& explicit_id, 442 quickbook::string_view id, 443 id_category category, 444 source_mode_info const& source_mode) 445 { 446 boost::shared_ptr<section_info> parent = 447 current_file->document->current_section; 448 449 id_placeholder const* p = 0; 450 id_placeholder const* placeholder_1_6 = 0; 451 452 std::string id_1_1; 453 454 if (parent && current_file->compatibility_version < 106u) { 455 id_1_1 = parent->id_1_1; 456 if (!id_1_1.empty() && !id.empty()) id_1_1 += "."; 457 id_1_1.append(id.begin(), id.end()); 458 } 459 460 if (current_file->compatibility_version >= 106u) { 461 p = placeholder_1_6 = add_id_to_section(id, category, parent); 462 } 463 else if (current_file->compatibility_version >= 103u) { 464 placeholder_1_6 = get_id_placeholder(parent); 465 466 std::string new_id; 467 if (!placeholder_1_6) { 468 new_id = current_file->doc_id_1_1; 469 if (!id_1_1.empty()) new_id += '.'; 470 } 471 new_id += id_1_1; 472 473 p = add_placeholder(new_id, category, placeholder_1_6); 474 } 475 else { 476 placeholder_1_6 = get_id_placeholder(parent); 477 478 std::string new_id; 479 if (parent && !placeholder_1_6) 480 new_id = current_file->doc_id_1_1 + '.'; 481 482 new_id += id.to_s(); 483 484 p = add_placeholder(new_id, category, placeholder_1_6); 485 } 486 487 current_file->document->current_section = 488 boost::make_shared<section_info>( 489 parent, current_file.get(), explicit_id, id_1_1, 490 placeholder_1_6, source_mode); 491 492 return p; 493 } 494 end_section()495 void document_state_impl::end_section() 496 { 497 current_file->document->current_section = 498 current_file->document->current_section->parent; 499 } 500 } 501