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