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