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