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/
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 =============================================================================*/
12 #include <boost/bind.hpp>
13 #include <boost/algorithm/string/join.hpp>
14 #include <boost/foreach.hpp>
15 #include <boost/filesystem/operations.hpp>
16 #include "quickbook.hpp"
19 #include "native_text.hpp"
21 #include "doc_info_tags.hpp"
22 #include "document_state.hpp"
23 #include "include_paths.hpp"
27 static void write_document_title(collector
& out
, value
const& title
, value
const& version
);
29 static std::string
doc_info_output(value
const& p
, unsigned version
)
31 if (qbk_version_n
< version
) {
32 std::string value
= detail::to_s(p
.get_quickbook());
33 value
.erase(value
.find_last_not_of(" \t") + 1);
37 return p
.get_encoded();
41 // Each docinfo attribute is stored in a value list, these are then stored
42 // in a sorted value list. The following convenience methods extract all the
43 // values for an attribute tag.
45 // Expecting at most one attribute, with several values in the list.
46 value
consume_list(value_consumer
& c
, value::tag_type tag
,
47 std::vector
<std::string
>* duplicates
)
57 if(count
> 1) duplicates
->push_back(doc_info_attributes::name(tag
));
62 // Expecting at most one attribute, with a single value, so extract that
64 value
consume_value_in_list(value_consumer
& c
, value::tag_type tag
,
65 std::vector
<std::string
>* duplicates
)
67 value l
= consume_list(c
, tag
, duplicates
);
68 if(l
.empty()) return l
;
71 value_consumer c2
= l
;
72 value p
= c2
.consume();
78 // Any number of attributes, so stuff them into a vector.
79 std::vector
<value
> consume_multiple_values(value_consumer
& c
, value::tag_type tag
)
81 std::vector
<value
> values
;
84 values
.push_back(c
.consume());
90 unsigned get_version(quickbook::state
& state
, bool using_docinfo
,
95 if (!version
.empty()) {
96 value_consumer
version_values(version
);
97 bool before_docinfo
= version_values
.optional_consume(
98 doc_info_tags::before_docinfo
).check();
99 int major_verison
= version_values
.consume().get_int();
100 int minor_verison
= version_values
.consume().get_int();
101 version_values
.finish();
103 if (before_docinfo
|| using_docinfo
) {
104 result
= ((unsigned) major_verison
* 100) +
105 (unsigned) minor_verison
;
107 if(result
< 100 || result
> 107)
109 detail::outerr(state
.current_file
->path
)
110 << "Unknown version: "
123 std::string
pre(quickbook::state
& state
, parse_iterator pos
,
124 value include_doc_id
, bool nested_file
)
126 // The doc_info in the file has been parsed. Here's what we'll do
127 // *before* anything else.
129 // If there isn't a doc info block, then values will be empty, so most
130 // of the following code won't actually do anything.
132 value_consumer values
= state
.values
.release();
134 // Skip over invalid attributes
136 while (values
.check(value::default_tag
)) values
.consume();
138 bool use_doc_info
= false;
139 std::string doc_type
;
142 if (values
.check(doc_info_tags::type
))
144 doc_type
= detail::to_s(values
.consume(doc_info_tags::type
).get_quickbook());
145 doc_title
= values
.consume(doc_info_tags::title
);
146 use_doc_info
= !nested_file
|| qbk_version_n
>= 106u;
152 detail::outerr(state
.current_file
, pos
.base())
153 << "No doc_info block."
158 // Create a fake document info block in order to continue.
159 doc_type
= "article";
160 doc_title
= qbk_value(state
.current_file
,
161 pos
.base(), pos
.base(),
162 doc_info_tags::type
);
167 std::vector
<std::string
> duplicates
;
169 std::vector
<value
> escaped_attributes
= consume_multiple_values(values
, doc_info_tags::escaped_attribute
);
171 value qbk_version
= consume_list(values
, doc_attributes::qbk_version
, &duplicates
);
172 value compatibility_mode
= consume_list(values
, doc_attributes::compatibility_mode
, &duplicates
);
173 consume_multiple_values(values
, doc_attributes::source_mode
);
175 value id
= consume_value_in_list(values
, doc_info_attributes::id
, &duplicates
);
176 value dirname
= consume_value_in_list(values
, doc_info_attributes::dirname
, &duplicates
);
177 value last_revision
= consume_value_in_list(values
, doc_info_attributes::last_revision
, &duplicates
);
178 value purpose
= consume_value_in_list(values
, doc_info_attributes::purpose
, &duplicates
);
179 std::vector
<value
> categories
= consume_multiple_values(values
, doc_info_attributes::category
);
180 value lang
= consume_value_in_list(values
, doc_info_attributes::lang
, &duplicates
);
181 value version
= consume_value_in_list(values
, doc_info_attributes::version
, &duplicates
);
182 std::vector
<value
> authors
= consume_multiple_values(values
, doc_info_attributes::authors
);
183 std::vector
<value
> copyrights
= consume_multiple_values(values
, doc_info_attributes::copyright
);
184 value license
= consume_value_in_list(values
, doc_info_attributes::license
, &duplicates
);
185 std::vector
<value
> biblioids
= consume_multiple_values(values
, doc_info_attributes::biblioid
);
186 value xmlbase
= consume_value_in_list(values
, doc_info_attributes::xmlbase
, &duplicates
);
190 if(!duplicates
.empty())
192 detail::outwarn(state
.current_file
->path
)
193 << (duplicates
.size() > 1 ?
194 "Duplicate attributes" : "Duplicate attribute")
195 << ":" << boost::algorithm::join(duplicates
, ", ")
200 std::string include_doc_id_
, id_
;
202 if (!include_doc_id
.empty())
203 include_doc_id_
= detail::to_s(include_doc_id
.get_quickbook());
205 id_
= detail::to_s(id
.get_quickbook());
209 unsigned new_version
= get_version(state
, use_doc_info
, qbk_version
);
211 if (new_version
!= qbk_version_n
)
213 if (new_version
>= 107u)
215 detail::outwarn(state
.current_file
->path
)
216 << "Quickbook " << (new_version
/ 100) << "." << (new_version
% 100)
217 << " is still under development and is "
218 "likely to change in the future." << std::endl
;
223 qbk_version_n
= new_version
;
225 else if (use_doc_info
) {
226 // hard code quickbook version to v1.1
228 detail::outwarn(state
.current_file
, pos
.base())
229 << "Quickbook version undefined. "
230 "Version 1.1 is assumed" << std::endl
;
233 state
.current_file
->version(qbk_version_n
);
235 // Compatibility Version
237 unsigned compatibility_version
=
238 get_version(state
, use_doc_info
, compatibility_mode
);
240 if (!compatibility_version
) {
241 compatibility_version
= use_doc_info
?
242 qbk_version_n
: state
.document
.compatibility_version();
245 // Start file, finish here if not generating document info.
249 state
.document
.start_file(compatibility_version
, include_doc_id_
, id_
,
254 std::string id_placeholder
=
255 state
.document
.start_file_with_docinfo(
256 compatibility_version
, include_doc_id_
, id_
, doc_title
);
258 // Make sure we really did have a document info block.
260 assert(doc_title
.check() && !doc_type
.empty());
264 std::string xmlbase_value
;
266 if (!xmlbase
.empty())
268 path_parameter x
= check_xinclude_path(xmlbase
, state
);
270 if (x
.type
== path_parameter::path
)
272 quickbook_path path
= resolve_xinclude_path(x
.value
, state
);
274 if (!fs::is_directory(path
.file_path
))
276 detail::outerr(xmlbase
.get_file(), xmlbase
.get_position())
278 << xmlbase
.get_quickbook()
279 << "\" isn't a directory."
286 xmlbase_value
= dir_path_to_url(path
.abstract_file_path
);
287 state
.xinclude_base
= path
.file_path
;
292 // Warn about invalid fields
294 if (doc_type
!= "library")
296 std::vector
<std::string
> invalid_attributes
;
298 if (!purpose
.empty())
299 invalid_attributes
.push_back("purpose");
301 if (!categories
.empty())
302 invalid_attributes
.push_back("category");
304 if (!dirname
.empty())
305 invalid_attributes
.push_back("dirname");
307 if(!invalid_attributes
.empty())
309 detail::outwarn(state
.current_file
->path
)
310 << (invalid_attributes
.size() > 1 ?
311 "Invalid attributes" : "Invalid attribute")
312 << " for '" << doc_type
<< " document info': "
313 << boost::algorithm::join(invalid_attributes
, ", ")
323 state
.out
<< "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
326 << " PUBLIC \"-//Boost//DTD BoostBook XML V1.0//EN\"\n"
327 << " \"http://www.boost.org/tools/boostbook/dtd/boostbook.dtd\">\n"
331 state
.out
<< '<' << doc_type
<< "\n"
338 state
.out
<< " lang=\""
339 << doc_info_output(lang
, 106)
343 if(doc_type
== "library" && !doc_title
.empty())
345 state
.out
<< " name=\"" << doc_info_output(doc_title
, 106) << "\"\n";
348 // Set defaults for dirname + last_revision
350 if (!dirname
.empty() || doc_type
== "library")
352 state
.out
<< " dirname=\"";
353 if (!dirname
.empty()) {
354 state
.out
<< doc_info_output(dirname
, 106);
356 else if (!id_
.empty()) {
359 else if (!include_doc_id_
.empty()) {
360 state
.out
<< include_doc_id_
;
362 else if (!doc_title
.empty()) {
363 state
.out
<< detail::make_identifier(doc_title
.get_quickbook());
366 state
.out
<< "library";
372 state
.out
<< " last-revision=\"";
373 if (!last_revision
.empty())
375 state
.out
<< doc_info_output(last_revision
, 106);
379 // default value for last-revision is now
383 strdate
, sizeof(strdate
),
385 "DEBUG MODE Date: %Y/%m/%d %H:%M:%S $" :
386 "$" /* prevent CVS substitution */ "Date: %Y/%m/%d %H:%M:%S $"),
390 state
.out
<< strdate
;
393 state
.out
<< "\" \n";
395 if (!xmlbase
.empty())
397 state
.out
<< " xml:base=\""
402 state
.out
<< " xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n";
404 std::ostringstream tmp
;
408 tmp
<< " <authorgroup>\n";
409 BOOST_FOREACH(value_consumer author_values
, authors
)
411 while (author_values
.check()) {
412 value surname
= author_values
.consume(doc_info_tags::author_surname
);
413 value first
= author_values
.consume(doc_info_tags::author_first
);
417 << doc_info_output(first
, 106)
420 << doc_info_output(surname
, 106)
425 tmp
<< " </authorgroup>\n";
428 BOOST_FOREACH(value_consumer copyright
, copyrights
)
430 while(copyright
.check())
432 tmp
<< "\n" << " <copyright>\n";
434 while(copyright
.check(doc_info_tags::copyright_year
))
436 value year_start_value
= copyright
.consume();
437 int year_start
= year_start_value
.get_int();
439 copyright
.check(doc_info_tags::copyright_year_end
) ?
440 copyright
.consume().get_int() :
443 if (year_end
< year_start
) {
446 detail::outerr(state
.current_file
, copyright
.begin()->get_position())
447 << "Invalid year range: "
455 for(; year_start
<= year_end
; ++year_start
)
456 tmp
<< " <year>" << year_start
<< "</year>\n";
460 << doc_info_output(copyright
.consume(doc_info_tags::copyright_name
), 106)
468 if (!license
.empty())
470 tmp
<< " <legalnotice id=\""
471 << state
.document
.add_id("legal", id_category::generated
)
474 << " " << doc_info_output(license
, 103) << "\n"
476 << " </legalnotice>\n"
481 if (!purpose
.empty())
483 tmp
<< " <" << doc_type
<< "purpose>\n"
484 << " " << doc_info_output(purpose
, 103)
485 << " </" << doc_type
<< "purpose>\n"
490 BOOST_FOREACH(value_consumer values
, categories
) {
491 value category
= values
.optional_consume();
492 if(!category
.empty()) {
493 tmp
<< " <" << doc_type
<< "category name=\"category:"
494 << doc_info_output(category
, 106)
495 << "\"></" << doc_type
<< "category>\n"
502 BOOST_FOREACH(value_consumer biblioid
, biblioids
)
504 value class_
= biblioid
.consume(doc_info_tags::biblioid_class
);
505 value value_
= biblioid
.consume(doc_info_tags::biblioid_value
);
507 tmp
<< " <biblioid class=\""
508 << class_
.get_quickbook()
510 << doc_info_output(value_
, 106)
517 BOOST_FOREACH(value escaped
, escaped_attributes
)
519 tmp
<< "<!--quickbook-escape-prefix-->"
520 << escaped
.get_quickbook()
521 << "<!--quickbook-escape-postfix-->"
525 if(doc_type
!= "library") {
526 write_document_title(state
.out
, doc_title
, version
);
529 std::string docinfo
= tmp
.str();
532 state
.out
<< " <" << doc_type
<< "info>\n"
534 << " </" << doc_type
<< "info>\n"
539 if(doc_type
== "library") {
540 write_document_title(state
.out
, doc_title
, version
);
546 void post(quickbook::state
& state
, std::string
const& doc_type
)
548 // We've finished generating our output. Here's what we'll do
549 // *after* everything else.
551 // Close any open sections.
552 if (!doc_type
.empty() && state
.document
.section_level() > 1) {
553 detail::outwarn(state
.current_file
->path
)
554 << "Missing [endsect] detected at end of file."
557 while(state
.document
.section_level() > 1) {
558 state
.out
<< "</section>";
559 state
.document
.end_section();
563 state
.document
.end_file();
564 if (!doc_type
.empty()) state
.out
<< "\n</" << doc_type
<< ">\n\n";
567 static void write_document_title(collector
& out
, value
const& title
, value
const& version
)
572 << doc_info_output(title
, 106);
573 if (!version
.empty()) {
574 out
<< ' ' << doc_info_output(version
, 106);
576 out
<< "</title>\n\n\n";