]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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/ | |
6 | ||
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 | =============================================================================*/ | |
11fdf7f2 | 11 | #include "actions.hpp" |
7c673cae | 12 | #include <functional> |
7c673cae | 13 | #include <map> |
11fdf7f2 | 14 | #include <numeric> |
7c673cae | 15 | #include <set> |
11fdf7f2 TL |
16 | #include <vector> |
17 | #include <boost/algorithm/string/replace.hpp> | |
7c673cae FG |
18 | #include <boost/filesystem/convenience.hpp> |
19 | #include <boost/filesystem/fstream.hpp> | |
7c673cae | 20 | #include <boost/lexical_cast.hpp> |
7c673cae | 21 | #include <boost/next_prior.hpp> |
11fdf7f2 TL |
22 | #include <boost/range/algorithm/replace.hpp> |
23 | #include <boost/range/distance.hpp> | |
24 | #include "block_tags.hpp" | |
25 | #include "document_state.hpp" | |
7c673cae | 26 | #include "files.hpp" |
92f5a8d4 | 27 | #include "for.hpp" |
11fdf7f2 | 28 | #include "grammar.hpp" |
7c673cae | 29 | #include "markups.hpp" |
11fdf7f2 TL |
30 | #include "path.hpp" |
31 | #include "phrase_tags.hpp" | |
32 | #include "quickbook.hpp" | |
7c673cae FG |
33 | #include "state.hpp" |
34 | #include "state_save.hpp" | |
b32b8144 | 35 | #include "stream.hpp" |
11fdf7f2 TL |
36 | #include "syntax_highlight.hpp" |
37 | #include "utils.hpp" | |
7c673cae FG |
38 | |
39 | namespace quickbook | |
40 | { | |
11fdf7f2 TL |
41 | namespace |
42 | { | |
7c673cae FG |
43 | void write_anchors(quickbook::state& state, collector& tgt) |
44 | { | |
7c673cae | 45 | if (state.source_mode_next) { |
11fdf7f2 TL |
46 | detail::outwarn( |
47 | state.source_mode_next_pos.get_file(), | |
7c673cae | 48 | state.source_mode_next_pos.get_position()) |
11fdf7f2 | 49 | << "Temporary source mode unsupported here." << std::endl; |
7c673cae FG |
50 | state.source_mode_next = 0; |
51 | } | |
52 | ||
92f5a8d4 | 53 | QUICKBOOK_FOR (auto const& anchor_id, state.anchors) { |
7c673cae | 54 | tgt << "<anchor id=\""; |
92f5a8d4 | 55 | detail::print_string(anchor_id, tgt.get()); |
7c673cae FG |
56 | tgt << "\"/>"; |
57 | } | |
11fdf7f2 | 58 | |
7c673cae FG |
59 | state.anchors.clear(); |
60 | } | |
11fdf7f2 TL |
61 | |
62 | std::string add_anchor( | |
63 | quickbook::state& state, | |
64 | quickbook::string_view id, | |
65 | id_category::categories category = id_category::explicit_anchor_id) | |
7c673cae FG |
66 | { |
67 | std::string placeholder = state.document.add_anchor(id, category); | |
68 | state.anchors.push_back(placeholder); | |
69 | return placeholder; | |
70 | } | |
71 | ||
11fdf7f2 TL |
72 | std::string get_attribute_value( |
73 | quickbook::state& state, quickbook::value const& value) | |
7c673cae | 74 | { |
11fdf7f2 TL |
75 | std::string x = value.is_encoded() ? value.get_encoded() |
76 | : value.get_quickbook().to_s(); | |
7c673cae FG |
77 | |
78 | if (x.empty()) { | |
79 | detail::outerr(value.get_file(), value.get_position()) | |
11fdf7f2 | 80 | << "Empty attribute value." << std::endl; |
7c673cae FG |
81 | ++state.error_count; |
82 | x = "xxx"; | |
83 | } | |
84 | ||
85 | return x; | |
86 | } | |
87 | ||
11fdf7f2 TL |
88 | std::string validate_id( |
89 | quickbook::state& state, quickbook::value const& id_value) | |
7c673cae FG |
90 | { |
91 | bool valid = true; | |
92 | std::string id = get_attribute_value(state, id_value); | |
93 | ||
94 | // Special case since I use dollar ids for id placeholders. | |
11fdf7f2 TL |
95 | if (id[0] == '$') { |
96 | valid = false; | |
97 | id[0] = '_'; | |
98 | } | |
7c673cae FG |
99 | |
100 | if (qbk_version_n >= 107u) { | |
101 | char const* allowed_punctuation = "_.-"; | |
102 | ||
92f5a8d4 | 103 | QUICKBOOK_FOR (char c, id) { |
7c673cae | 104 | if (!std::isalnum(c) && |
11fdf7f2 | 105 | !std::strchr(allowed_punctuation, c)) |
7c673cae FG |
106 | valid = false; |
107 | } | |
108 | } | |
109 | ||
110 | if (!valid) { | |
111 | detail::outerr(id_value.get_file(), id_value.get_position()) | |
11fdf7f2 TL |
112 | << "Invalid id: " << (id_value.is_encoded() |
113 | ? id_value.get_encoded() | |
114 | : id_value.get_quickbook().to_s()) | |
7c673cae FG |
115 | << std::endl; |
116 | ++state.error_count; | |
117 | } | |
118 | ||
119 | return id; | |
120 | } | |
121 | } | |
122 | ||
11fdf7f2 TL |
123 | bool quickbook_range::in_range() const |
124 | { | |
7c673cae FG |
125 | return qbk_version_n >= lower && qbk_version_n < upper; |
126 | } | |
127 | ||
11fdf7f2 TL |
128 | bool quickbook_strict::is_strict_checking() const |
129 | { | |
b32b8144 FG |
130 | return state.strict_mode; |
131 | } | |
132 | ||
133 | void list_action(quickbook::state&, value); | |
7c673cae FG |
134 | void header_action(quickbook::state&, value); |
135 | void begin_section_action(quickbook::state&, value); | |
136 | void end_section_action(quickbook::state&, value, string_iterator); | |
137 | void block_action(quickbook::state&, value); | |
138 | void block_empty_action(quickbook::state&, value); | |
139 | void macro_definition_action(quickbook::state&, value); | |
140 | void template_body_action(quickbook::state&, value); | |
141 | void variable_list_action(quickbook::state&, value); | |
142 | void table_action(quickbook::state&, value); | |
143 | void xinclude_action(quickbook::state&, value); | |
144 | void include_action(quickbook::state&, value, string_iterator); | |
145 | void image_action(quickbook::state&, value); | |
146 | void anchor_action(quickbook::state&, value); | |
147 | void link_action(quickbook::state&, value); | |
148 | void phrase_action(quickbook::state&, value); | |
149 | void role_action(quickbook::state&, value); | |
150 | void footnote_action(quickbook::state&, value); | |
151 | void raw_phrase_action(quickbook::state&, value); | |
152 | void source_mode_action(quickbook::state&, value); | |
153 | void next_source_mode_action(quickbook::state&, value); | |
154 | void code_action(quickbook::state&, value); | |
155 | void do_template_action(quickbook::state&, value, string_iterator); | |
11fdf7f2 | 156 | |
7c673cae FG |
157 | void element_action::operator()(parse_iterator first, parse_iterator) const |
158 | { | |
159 | value_consumer values = state.values.release(); | |
11fdf7f2 | 160 | if (!values.check() || !state.conditional) return; |
7c673cae FG |
161 | value v = values.consume(); |
162 | values.finish(); | |
11fdf7f2 TL |
163 | |
164 | switch (v.get_tag()) { | |
7c673cae FG |
165 | case block_tags::ordered_list: |
166 | case block_tags::itemized_list: | |
b32b8144 | 167 | return list_action(state, v); |
7c673cae FG |
168 | case block_tags::generic_heading: |
169 | case block_tags::heading1: | |
170 | case block_tags::heading2: | |
171 | case block_tags::heading3: | |
172 | case block_tags::heading4: | |
173 | case block_tags::heading5: | |
174 | case block_tags::heading6: | |
175 | return header_action(state, v); | |
176 | case block_tags::begin_section: | |
177 | return begin_section_action(state, v); | |
178 | case block_tags::end_section: | |
179 | return end_section_action(state, v, first.base()); | |
180 | case block_tags::blurb: | |
181 | case block_tags::preformatted: | |
182 | case block_tags::blockquote: | |
183 | case block_tags::warning: | |
184 | case block_tags::caution: | |
185 | case block_tags::important: | |
186 | case block_tags::note: | |
187 | case block_tags::tip: | |
188 | case block_tags::block: | |
11fdf7f2 | 189 | return block_action(state, v); |
7c673cae | 190 | case block_tags::hr: |
11fdf7f2 | 191 | return block_empty_action(state, v); |
7c673cae | 192 | case block_tags::macro_definition: |
11fdf7f2 | 193 | return macro_definition_action(state, v); |
7c673cae | 194 | case block_tags::template_definition: |
11fdf7f2 | 195 | return template_body_action(state, v); |
7c673cae FG |
196 | case block_tags::variable_list: |
197 | return variable_list_action(state, v); | |
198 | case block_tags::table: | |
199 | return table_action(state, v); | |
200 | case block_tags::xinclude: | |
201 | return xinclude_action(state, v); | |
202 | case block_tags::import: | |
203 | case block_tags::include: | |
204 | return include_action(state, v, first.base()); | |
205 | case phrase_tags::image: | |
206 | return image_action(state, v); | |
207 | case phrase_tags::anchor: | |
208 | return anchor_action(state, v); | |
209 | case phrase_tags::url: | |
210 | case phrase_tags::link: | |
211 | case phrase_tags::funcref: | |
212 | case phrase_tags::classref: | |
213 | case phrase_tags::memberref: | |
214 | case phrase_tags::enumref: | |
215 | case phrase_tags::macroref: | |
216 | case phrase_tags::headerref: | |
217 | case phrase_tags::conceptref: | |
218 | case phrase_tags::globalref: | |
219 | return link_action(state, v); | |
220 | case phrase_tags::bold: | |
221 | case phrase_tags::italic: | |
222 | case phrase_tags::underline: | |
223 | case phrase_tags::teletype: | |
224 | case phrase_tags::strikethrough: | |
225 | case phrase_tags::quote: | |
226 | case phrase_tags::replaceable: | |
227 | return phrase_action(state, v); | |
228 | case phrase_tags::footnote: | |
229 | return footnote_action(state, v); | |
230 | case phrase_tags::escape: | |
231 | return raw_phrase_action(state, v); | |
232 | case phrase_tags::role: | |
233 | return role_action(state, v); | |
234 | case source_mode_tags::cpp: | |
235 | case source_mode_tags::python: | |
236 | case source_mode_tags::teletype: | |
237 | return source_mode_action(state, v); | |
238 | case code_tags::next_source_mode: | |
239 | return next_source_mode_action(state, v); | |
240 | case code_tags::code_block: | |
241 | case code_tags::inline_code_block: | |
242 | case code_tags::inline_code: | |
243 | return code_action(state, v); | |
244 | case template_tags::attribute_template: | |
245 | case template_tags::template_: | |
246 | return do_template_action(state, v, first.base()); | |
247 | default: | |
248 | break; | |
249 | } | |
250 | } | |
251 | ||
252 | void break_action::operator()(parse_iterator first, parse_iterator) const | |
253 | { | |
254 | write_anchors(state, state.phrase); | |
255 | ||
11fdf7f2 | 256 | if (*first == '\\') { |
7c673cae FG |
257 | detail::outwarn(state.current_file, first.base()) |
258 | //<< "in column:" << pos.column << ", " | |
11fdf7f2 TL |
259 | << "'\\n' is deprecated, pleases use '[br]' instead" |
260 | << ".\n"; | |
7c673cae FG |
261 | } |
262 | ||
11fdf7f2 | 263 | if (!state.warned_about_breaks) { |
7c673cae FG |
264 | detail::outwarn(state.current_file, first.base()) |
265 | << "line breaks generate invalid boostbook " | |
266 | "(will only note first occurrence).\n"; | |
267 | ||
268 | state.warned_about_breaks = true; | |
269 | } | |
11fdf7f2 | 270 | |
7c673cae FG |
271 | state.phrase << detail::get_markup(phrase_tags::break_mark).pre; |
272 | } | |
273 | ||
11fdf7f2 TL |
274 | void error_message_action::operator()( |
275 | parse_iterator first, parse_iterator last) const | |
7c673cae FG |
276 | { |
277 | file_position const pos = state.current_file->position_of(first.base()); | |
278 | ||
279 | std::string value(first, last); | |
280 | std::string formatted_message = message; | |
281 | boost::replace_all(formatted_message, "%s", value); | |
11fdf7f2 TL |
282 | boost::replace_all( |
283 | formatted_message, "%c", | |
7c673cae FG |
284 | boost::lexical_cast<std::string>(pos.column)); |
285 | ||
286 | detail::outerr(state.current_file->path, pos.line) | |
287 | << formatted_message << std::endl; | |
288 | ++state.error_count; | |
289 | } | |
290 | ||
11fdf7f2 TL |
291 | void error_action::operator()( |
292 | parse_iterator first, parse_iterator /*last*/) const | |
7c673cae FG |
293 | { |
294 | file_position const pos = state.current_file->position_of(first.base()); | |
295 | ||
296 | detail::outerr(state.current_file->path, pos.line) | |
297 | << "Syntax Error near column " << pos.column << ".\n"; | |
298 | ++state.error_count; | |
299 | } | |
300 | ||
301 | void block_action(quickbook::state& state, value block) | |
302 | { | |
303 | write_anchors(state, state.out); | |
304 | ||
305 | detail::markup markup = detail::get_markup(block.get_tag()); | |
306 | ||
307 | value_consumer values = block; | |
11fdf7f2 TL |
308 | state.out << markup.pre << values.consume().get_encoded() |
309 | << markup.post; | |
7c673cae FG |
310 | values.finish(); |
311 | } | |
312 | ||
313 | void block_empty_action(quickbook::state& state, value block) | |
314 | { | |
315 | write_anchors(state, state.out); | |
316 | ||
317 | detail::markup markup = detail::get_markup(block.get_tag()); | |
318 | state.out << markup.pre; | |
319 | } | |
320 | ||
321 | void phrase_action(quickbook::state& state, value phrase) | |
322 | { | |
323 | write_anchors(state, state.phrase); | |
324 | ||
325 | detail::markup markup = detail::get_markup(phrase.get_tag()); | |
326 | ||
327 | value_consumer values = phrase; | |
11fdf7f2 TL |
328 | state.phrase << markup.pre << values.consume().get_encoded() |
329 | << markup.post; | |
7c673cae FG |
330 | values.finish(); |
331 | } | |
332 | ||
333 | void role_action(quickbook::state& state, value role_list) | |
334 | { | |
335 | write_anchors(state, state.phrase); | |
336 | ||
337 | value_consumer values = role_list; | |
338 | value role = values.consume(); | |
339 | value phrase = values.consume(); | |
340 | values.finish(); | |
341 | ||
11fdf7f2 TL |
342 | state.phrase << "<phrase role=\""; |
343 | detail::print_string( | |
344 | get_attribute_value(state, role), state.phrase.get()); | |
345 | state.phrase << "\">" << phrase.get_encoded() << "</phrase>"; | |
7c673cae FG |
346 | } |
347 | ||
348 | void footnote_action(quickbook::state& state, value phrase) | |
349 | { | |
350 | write_anchors(state, state.phrase); | |
351 | ||
352 | value_consumer values = phrase; | |
11fdf7f2 TL |
353 | state.phrase << "<footnote id=\"" |
354 | << state.document.add_id("f", id_category::numbered) | |
355 | << "\"><para>" << values.consume().get_encoded() | |
356 | << "</para></footnote>"; | |
7c673cae FG |
357 | values.finish(); |
358 | } | |
359 | ||
360 | void raw_phrase_action(quickbook::state& state, value phrase) | |
361 | { | |
362 | write_anchors(state, state.phrase); | |
363 | ||
364 | detail::markup markup = detail::get_markup(phrase.get_tag()); | |
365 | state.phrase << markup.pre << phrase.get_quickbook() << markup.post; | |
366 | } | |
367 | ||
368 | void paragraph_action::operator()() const | |
369 | { | |
370 | std::string str; | |
371 | state.phrase.swap(str); | |
372 | ||
11fdf7f2 | 373 | std::string::const_iterator pos = str.begin(), end = str.end(); |
7c673cae | 374 | |
11fdf7f2 TL |
375 | while (pos != end && cl::space_p.test(*pos)) |
376 | ++pos; | |
7c673cae | 377 | |
11fdf7f2 TL |
378 | if (pos != end) { |
379 | detail::markup markup = | |
380 | state.in_list | |
381 | ? detail::get_markup(block_tags::paragraph_in_list) | |
382 | : detail::get_markup(block_tags::paragraph); | |
7c673cae FG |
383 | state.out << markup.pre << str; |
384 | write_anchors(state, state.out); | |
385 | state.out << markup.post; | |
386 | } | |
387 | } | |
388 | ||
389 | void explicit_list_action::operator()() const | |
390 | { | |
391 | state.explicit_list = true; | |
392 | } | |
393 | ||
394 | void phrase_end_action::operator()() const | |
395 | { | |
396 | write_anchors(state, state.phrase); | |
397 | } | |
11fdf7f2 TL |
398 | |
399 | namespace | |
400 | { | |
401 | void write_bridgehead( | |
402 | quickbook::state& state, | |
403 | int level, | |
404 | std::string const& str, | |
405 | std::string const& id, | |
406 | bool self_link) | |
7c673cae | 407 | { |
11fdf7f2 | 408 | if (self_link && !id.empty()) { |
7c673cae FG |
409 | state.out << "<bridgehead renderas=\"sect" << level << "\""; |
410 | state.out << " id=\""; | |
411 | state.out << state.document.add_id("h", id_category::numbered); | |
412 | state.out << "\">"; | |
413 | state.out << "<phrase id=\"" << id << "\"/>"; | |
414 | state.out << "<link linkend=\"" << id << "\">"; | |
415 | state.out << str; | |
416 | state.out << "</link>"; | |
417 | state.out << "</bridgehead>"; | |
418 | } | |
11fdf7f2 | 419 | else { |
7c673cae | 420 | state.out << "<bridgehead renderas=\"sect" << level << "\""; |
11fdf7f2 | 421 | if (!id.empty()) state.out << " id=\"" << id << "\""; |
7c673cae FG |
422 | state.out << ">"; |
423 | state.out << str; | |
424 | state.out << "</bridgehead>"; | |
425 | } | |
426 | } | |
427 | } | |
428 | ||
429 | void header_action(quickbook::state& state, value heading_list) | |
430 | { | |
431 | value_consumer values = heading_list; | |
432 | ||
433 | bool generic = heading_list.get_tag() == block_tags::generic_heading; | |
434 | value element_id = values.optional_consume(general_tags::element_id); | |
435 | value content = values.consume(); | |
436 | values.finish(); | |
437 | ||
438 | int level; | |
439 | ||
11fdf7f2 | 440 | if (generic) { |
7c673cae | 441 | level = state.document.section_level() + 1; |
11fdf7f2 TL |
442 | // We need to use a heading which is one greater |
443 | // than the current. | |
444 | if (level > 6) // The max is h6, clip it if it goes | |
445 | level = 6; // further than that | |
7c673cae | 446 | } |
11fdf7f2 | 447 | else { |
7c673cae FG |
448 | level = heading_list.get_tag() - block_tags::heading1 + 1; |
449 | } | |
450 | ||
451 | write_anchors(state, state.out); | |
452 | ||
11fdf7f2 | 453 | if (!element_id.empty()) { |
7c673cae FG |
454 | // Use an explicit id. |
455 | ||
456 | std::string anchor = state.document.add_id( | |
11fdf7f2 | 457 | validate_id(state, element_id), id_category::explicit_id); |
7c673cae | 458 | |
11fdf7f2 TL |
459 | write_bridgehead( |
460 | state, level, content.get_encoded(), anchor, | |
461 | self_linked_headers); | |
7c673cae | 462 | } |
11fdf7f2 | 463 | else if (state.document.compatibility_version() >= 106u) { |
7c673cae FG |
464 | // Generate ids for 1.6+ |
465 | ||
466 | std::string anchor = state.document.add_id( | |
467 | detail::make_identifier(content.get_quickbook()), | |
468 | id_category::generated_heading); | |
469 | ||
11fdf7f2 TL |
470 | write_bridgehead( |
471 | state, level, content.get_encoded(), anchor, | |
472 | self_linked_headers); | |
7c673cae | 473 | } |
11fdf7f2 TL |
474 | else { |
475 | // Generate ids that are compatible with older versions of | |
476 | // quickbook. | |
7c673cae FG |
477 | |
478 | // Older versions of quickbook used the generated boostbook, but | |
479 | // we only have an intermediate version which can contain id | |
480 | // placeholders. So to generate the ids they must be replaced | |
481 | // by the ids that the older versions would have used - i.e. the | |
482 | // unresolved ids. | |
483 | // | |
484 | // Note that this doesn't affect the actual boostbook generated for | |
485 | // the content, it's just used to generate this id. | |
486 | ||
487 | std::string id = detail::make_identifier( | |
11fdf7f2 TL |
488 | state.document.replace_placeholders_with_unresolved_ids( |
489 | content.get_encoded())); | |
7c673cae FG |
490 | |
491 | if (generic || state.document.compatibility_version() >= 103) { | |
492 | std::string anchor = | |
493 | state.document.add_id(id, id_category::generated_heading); | |
494 | ||
11fdf7f2 TL |
495 | write_bridgehead( |
496 | state, level, content.get_encoded(), anchor, | |
497 | self_linked_headers); | |
7c673cae FG |
498 | } |
499 | else { | |
11fdf7f2 TL |
500 | std::string anchor = state.document.old_style_id( |
501 | id, id_category::generated_heading); | |
7c673cae | 502 | |
11fdf7f2 TL |
503 | write_bridgehead( |
504 | state, level, content.get_encoded(), anchor, false); | |
7c673cae FG |
505 | } |
506 | } | |
507 | } | |
508 | ||
509 | void simple_phrase_action::operator()(char mark) const | |
510 | { | |
511 | write_anchors(state, state.phrase); | |
512 | ||
11fdf7f2 TL |
513 | int tag = mark == '*' |
514 | ? phrase_tags::bold | |
515 | : mark == '/' | |
516 | ? phrase_tags::italic | |
517 | : mark == '_' | |
518 | ? phrase_tags::underline | |
519 | : mark == '=' ? phrase_tags::teletype : 0; | |
520 | ||
7c673cae FG |
521 | assert(tag != 0); |
522 | detail::markup markup = detail::get_markup(tag); | |
523 | ||
524 | value_consumer values = state.values.release(); | |
525 | value content = values.consume(); | |
526 | values.finish(); | |
527 | ||
528 | state.phrase << markup.pre; | |
529 | state.phrase << content.get_encoded(); | |
530 | state.phrase << markup.post; | |
531 | } | |
532 | ||
533 | bool cond_phrase_push::start() | |
534 | { | |
535 | value_consumer values = state.values.release(); | |
536 | ||
537 | saved_conditional = state.conditional; | |
538 | ||
11fdf7f2 | 539 | if (saved_conditional) { |
b32b8144 FG |
540 | bool positive = values.consume().get_quickbook().empty(); |
541 | quickbook::string_view macro1 = values.consume().get_quickbook(); | |
7c673cae FG |
542 | std::string macro(macro1.begin(), macro1.end()); |
543 | ||
b32b8144 FG |
544 | state.conditional = |
545 | (bool)find(state.macro, macro.c_str()) == positive; | |
7c673cae FG |
546 | |
547 | if (!state.conditional) { | |
548 | state.push_output(); | |
549 | state.anchors.swap(anchors); | |
550 | } | |
551 | } | |
552 | ||
553 | return true; | |
554 | } | |
11fdf7f2 | 555 | |
7c673cae FG |
556 | void cond_phrase_push::cleanup() |
557 | { | |
11fdf7f2 | 558 | if (saved_conditional && !state.conditional) { |
7c673cae FG |
559 | state.pop_output(); |
560 | state.anchors.swap(anchors); | |
561 | } | |
562 | ||
563 | state.conditional = saved_conditional; | |
564 | } | |
565 | ||
566 | void state::start_list(char mark) | |
567 | { | |
568 | push_tagged_source_mode(source_mode_next); | |
569 | source_mode_next = 0; | |
570 | ||
571 | write_anchors(*this, (in_list ? phrase : out)); | |
572 | assert(mark == '*' || mark == '#'); | |
573 | push_output(); | |
574 | out << ((mark == '#') ? "<orderedlist>\n" : "<itemizedlist>\n"); | |
575 | in_list = true; | |
576 | } | |
577 | ||
578 | void state::end_list(char mark) | |
579 | { | |
580 | write_anchors(*this, out); | |
581 | assert(mark == '*' || mark == '#'); | |
582 | out << ((mark == '#') ? "\n</orderedlist>" : "\n</itemizedlist>"); | |
583 | ||
584 | std::string list_output; | |
585 | out.swap(list_output); | |
586 | ||
587 | pop_output(); | |
588 | ||
589 | (in_list ? phrase : out) << list_output; | |
590 | ||
591 | pop_tagged_source_mode(); | |
592 | } | |
593 | ||
594 | void state::start_list_item() | |
595 | { | |
596 | out << "<listitem>"; | |
597 | write_anchors(*this, phrase); | |
598 | } | |
599 | ||
600 | void state::end_list_item() | |
601 | { | |
602 | write_anchors(*this, phrase); | |
603 | paragraph_action para(*this); | |
604 | para(); | |
605 | out << "</listitem>"; | |
606 | } | |
607 | ||
608 | namespace | |
609 | { | |
11fdf7f2 TL |
610 | bool parse_template( |
611 | value const&, | |
612 | quickbook::state& state, | |
613 | bool is_attribute_template = false); | |
7c673cae FG |
614 | } |
615 | ||
11fdf7f2 | 616 | void state::start_callouts() { ++callout_depth; } |
7c673cae FG |
617 | |
618 | std::string state::add_callout(value v) | |
619 | { | |
620 | std::string callout_id1 = document.add_id("c", id_category::numbered); | |
621 | std::string callout_id2 = document.add_id("c", id_category::numbered); | |
622 | ||
623 | callouts.insert(encoded_value(callout_id1)); | |
624 | callouts.insert(encoded_value(callout_id2)); | |
625 | callouts.insert(v); | |
626 | ||
627 | std::string code; | |
628 | code += "<co id=\"" + callout_id1 + "\" "; | |
629 | code += "linkends=\"" + callout_id2 + "\" />"; | |
630 | ||
631 | return code; | |
632 | } | |
633 | ||
634 | std::string state::end_callouts() | |
635 | { | |
636 | assert(callout_depth > 0); | |
637 | std::string block; | |
638 | ||
639 | --callout_depth; | |
640 | if (callout_depth > 0) return block; | |
641 | ||
642 | value_consumer c = callouts.release(); | |
643 | if (!c.check()) return block; | |
644 | ||
645 | block += "<calloutlist>"; | |
11fdf7f2 | 646 | while (c.check()) { |
7c673cae FG |
647 | std::string callout_id1 = c.consume().get_encoded(); |
648 | std::string callout_id2 = c.consume().get_encoded(); | |
649 | value callout_body = c.consume(); | |
650 | ||
651 | std::string callout_value; | |
652 | ||
653 | { | |
654 | state_save save(*this, state_save::scope_all); | |
655 | ++template_depth; | |
656 | ||
657 | bool r = parse_template(callout_body, *this); | |
658 | ||
11fdf7f2 TL |
659 | if (!r) { |
660 | detail::outerr( | |
661 | callout_body.get_file(), callout_body.get_position()) | |
7c673cae | 662 | << "Expanding callout." << std::endl |
11fdf7f2 | 663 | << "------------------begin------------------" |
7c673cae | 664 | << std::endl |
11fdf7f2 TL |
665 | << callout_body.get_quickbook() << std::endl |
666 | << "------------------end--------------------" | |
667 | << std::endl; | |
7c673cae FG |
668 | ++error_count; |
669 | } | |
670 | ||
671 | out.swap(callout_value); | |
672 | } | |
11fdf7f2 | 673 | |
7c673cae FG |
674 | block += "<callout arearefs=\"" + callout_id1 + "\" "; |
675 | block += "id=\"" + callout_id2 + "\">"; | |
676 | block += callout_value; | |
677 | block += "</callout>"; | |
678 | } | |
679 | block += "</calloutlist>"; | |
11fdf7f2 | 680 | |
7c673cae FG |
681 | return block; |
682 | } | |
683 | ||
b32b8144 | 684 | void list_action(quickbook::state& state, value list) |
7c673cae FG |
685 | { |
686 | write_anchors(state, state.out); | |
687 | ||
688 | detail::markup markup = detail::get_markup(list.get_tag()); | |
689 | ||
690 | state.out << markup.pre; | |
691 | ||
92f5a8d4 | 692 | QUICKBOOK_FOR (value item, list) { |
7c673cae FG |
693 | state.out << "<listitem>"; |
694 | state.out << item.get_encoded(); | |
695 | state.out << "</listitem>"; | |
696 | } | |
697 | ||
698 | state.out << markup.post; | |
699 | } | |
700 | ||
701 | void anchor_action(quickbook::state& state, value anchor) | |
702 | { | |
703 | value_consumer values = anchor; | |
704 | value anchor_id = values.consume(); | |
705 | // Note: anchor_id is never encoded as boostbook. If it | |
706 | // is encoded, it's just things like escapes. | |
707 | add_anchor(state, validate_id(state, anchor_id)); | |
708 | values.finish(); | |
709 | } | |
710 | ||
711 | void do_macro_action::operator()(std::string const& str) const | |
712 | { | |
713 | write_anchors(state, state.phrase); | |
714 | ||
11fdf7f2 | 715 | if (str == quickbook_get_date) { |
7c673cae FG |
716 | char strdate[64]; |
717 | strftime(strdate, sizeof(strdate), "%Y-%b-%d", current_time); | |
718 | state.phrase << strdate; | |
719 | } | |
11fdf7f2 | 720 | else if (str == quickbook_get_time) { |
7c673cae FG |
721 | char strdate[64]; |
722 | strftime(strdate, sizeof(strdate), "%I:%M:%S %p", current_time); | |
723 | state.phrase << strdate; | |
724 | } | |
11fdf7f2 | 725 | else { |
7c673cae FG |
726 | state.phrase << str; |
727 | } | |
728 | } | |
729 | ||
11fdf7f2 | 730 | void raw_char_action::operator()(char ch) const { state.phrase << ch; } |
7c673cae | 731 | |
11fdf7f2 TL |
732 | void raw_char_action::operator()( |
733 | parse_iterator first, parse_iterator last) const | |
7c673cae FG |
734 | { |
735 | while (first != last) | |
736 | state.phrase << *first++; | |
737 | } | |
738 | ||
739 | void source_mode_action(quickbook::state& state, value source_mode) | |
740 | { | |
741 | state.change_source_mode(source_mode.get_tag()); | |
742 | } | |
743 | ||
744 | void next_source_mode_action(quickbook::state& state, value source_mode) | |
745 | { | |
746 | value_consumer values = source_mode; | |
747 | state.source_mode_next_pos = values.consume(); | |
748 | state.source_mode_next = values.consume().get_int(); | |
749 | values.finish(); | |
750 | } | |
751 | ||
752 | void code_action(quickbook::state& state, value code_block) | |
753 | { | |
754 | int code_tag = code_block.get_tag(); | |
755 | ||
756 | value_consumer values = code_block; | |
b32b8144 | 757 | quickbook::string_view code_value = values.consume().get_quickbook(); |
7c673cae FG |
758 | values.finish(); |
759 | ||
11fdf7f2 TL |
760 | bool inline_code = |
761 | code_tag == code_tags::inline_code || | |
7c673cae FG |
762 | (code_tag == code_tags::inline_code_block && qbk_version_n < 106u); |
763 | bool block = code_tag != code_tags::inline_code; | |
764 | ||
11fdf7f2 TL |
765 | source_mode_type source_mode = |
766 | state.source_mode_next ? state.source_mode_next | |
767 | : state.current_source_mode().source_mode; | |
7c673cae FG |
768 | state.source_mode_next = 0; |
769 | ||
770 | if (inline_code) { | |
771 | write_anchors(state, state.phrase); | |
772 | } | |
773 | else { | |
774 | paragraph_action para(state); | |
775 | para(); | |
776 | write_anchors(state, state.out); | |
777 | } | |
778 | ||
779 | if (block) { | |
780 | // preprocess the code section to remove the initial indentation | |
781 | mapped_file_builder mapped; | |
782 | mapped.start(state.current_file); | |
783 | mapped.unindent_and_add(code_value); | |
784 | ||
785 | file_ptr f = mapped.release(); | |
786 | ||
787 | if (f->source().empty()) | |
788 | return; // Nothing left to do here. The program is empty. | |
789 | ||
790 | if (qbk_version_n >= 107u) state.start_callouts(); | |
791 | ||
792 | parse_iterator first_(f->source().begin()); | |
793 | parse_iterator last_(f->source().end()); | |
794 | ||
795 | file_ptr saved_file = f; | |
796 | boost::swap(state.current_file, saved_file); | |
797 | ||
798 | // print the code with syntax coloring | |
799 | // | |
800 | // We must not place a \n after the <programlisting> tag | |
801 | // otherwise PDF output starts code blocks with a blank line: | |
802 | state.phrase << "<programlisting>"; | |
803 | syntax_highlight(first_, last_, state, source_mode, block); | |
804 | state.phrase << "</programlisting>\n"; | |
805 | ||
806 | boost::swap(state.current_file, saved_file); | |
807 | ||
808 | if (qbk_version_n >= 107u) state.phrase << state.end_callouts(); | |
809 | ||
810 | if (!inline_code) { | |
811 | state.out << state.phrase.str(); | |
812 | state.phrase.clear(); | |
813 | } | |
814 | } | |
815 | else { | |
816 | parse_iterator first_(code_value.begin()); | |
817 | parse_iterator last_(code_value.end()); | |
818 | ||
819 | state.phrase << "<code>"; | |
820 | syntax_highlight(first_, last_, state, source_mode, block); | |
821 | state.phrase << "</code>"; | |
822 | } | |
823 | } | |
824 | ||
825 | void plain_char_action::operator()(char ch) const | |
826 | { | |
827 | write_anchors(state, state.phrase); | |
828 | ||
829 | detail::print_char(ch, state.phrase.get()); | |
830 | } | |
831 | ||
11fdf7f2 TL |
832 | void plain_char_action::operator()( |
833 | parse_iterator first, parse_iterator last) const | |
7c673cae FG |
834 | { |
835 | write_anchors(state, state.phrase); | |
836 | ||
837 | while (first != last) | |
838 | detail::print_char(*first++, state.phrase.get()); | |
839 | } | |
840 | ||
11fdf7f2 TL |
841 | void escape_unicode_action::operator()( |
842 | parse_iterator first, parse_iterator last) const | |
7c673cae FG |
843 | { |
844 | write_anchors(state, state.phrase); | |
845 | ||
11fdf7f2 TL |
846 | while (first != last && *first == '0') |
847 | ++first; | |
7c673cae FG |
848 | |
849 | // Just ignore \u0000 | |
850 | // Maybe I should issue a warning? | |
11fdf7f2 TL |
851 | if (first == last) return; |
852 | ||
7c673cae | 853 | std::string hex_digits(first, last); |
11fdf7f2 TL |
854 | |
855 | if (hex_digits.size() == 2 && *first > '0' && *first <= '7') { | |
7c673cae | 856 | using namespace std; |
b32b8144 | 857 | detail::print_char( |
11fdf7f2 | 858 | (char)strtol(hex_digits.c_str(), 0, 16), state.phrase.get()); |
7c673cae FG |
859 | } |
860 | else { | |
861 | state.phrase << "&#x" << hex_digits << ";"; | |
862 | } | |
863 | } | |
864 | ||
865 | void write_plain_text(std::ostream& out, value const& v) | |
866 | { | |
11fdf7f2 | 867 | if (v.is_encoded()) { |
7c673cae FG |
868 | detail::print_string(v.get_encoded(), out); |
869 | } | |
870 | else { | |
b32b8144 | 871 | quickbook::string_view value = v.get_quickbook(); |
11fdf7f2 TL |
872 | for (string_iterator first = value.begin(), last = value.end(); |
873 | first != last; ++first) { | |
7c673cae FG |
874 | if (*first == '\\' && ++first == last) break; |
875 | detail::print_char(*first, out); | |
876 | } | |
877 | } | |
878 | } | |
879 | ||
880 | void image_action(quickbook::state& state, value image) | |
881 | { | |
882 | write_anchors(state, state.phrase); | |
883 | ||
884 | // Note: attributes are never encoded as boostbook, if they're | |
885 | // encoded, it's just things like escapes. | |
886 | typedef std::map<std::string, value> attribute_map; | |
887 | attribute_map attributes; | |
888 | ||
889 | value_consumer values = image; | |
890 | attributes["fileref"] = values.consume(); | |
891 | ||
92f5a8d4 | 892 | QUICKBOOK_FOR (value pair_, values) { |
7c673cae FG |
893 | value_consumer pair = pair_; |
894 | value name = pair.consume(); | |
895 | value value = pair.consume(); | |
11fdf7f2 TL |
896 | std::string name_str( |
897 | name.get_quickbook().begin(), name.get_quickbook().end()); | |
7c673cae | 898 | pair.finish(); |
11fdf7f2 | 899 | if (!attributes.insert(std::make_pair(name_str, value)).second) { |
7c673cae | 900 | detail::outwarn(name.get_file(), name.get_position()) |
11fdf7f2 | 901 | << "Duplicate image attribute: " << name.get_quickbook() |
7c673cae FG |
902 | << std::endl; |
903 | } | |
904 | } | |
11fdf7f2 | 905 | |
7c673cae FG |
906 | values.finish(); |
907 | ||
908 | // Find the file basename and extension. | |
909 | // | |
910 | // Not using Boost.Filesystem because I want to stay in UTF-8. | |
911 | // Need to think about uri encoding. | |
11fdf7f2 TL |
912 | |
913 | std::string fileref = | |
914 | attributes["fileref"].is_encoded() | |
915 | ? attributes["fileref"].get_encoded() | |
916 | : attributes["fileref"].get_quickbook().to_s(); | |
7c673cae FG |
917 | |
918 | // Check for windows paths, then convert. | |
919 | // A bit crude, but there you go. | |
920 | ||
11fdf7f2 TL |
921 | if (fileref.find('\\') != std::string::npos) { |
922 | (qbk_version_n >= 106u ? detail::outerr( | |
923 | attributes["fileref"].get_file(), | |
924 | attributes["fileref"].get_position()) | |
925 | : detail::outwarn( | |
926 | attributes["fileref"].get_file(), | |
927 | attributes["fileref"].get_position())) | |
928 | << "Image path isn't portable: '" << fileref << "'" | |
7c673cae FG |
929 | << std::endl; |
930 | if (qbk_version_n >= 106u) ++state.error_count; | |
931 | } | |
932 | ||
933 | boost::replace(fileref, '\\', '/'); | |
934 | ||
935 | // Find the file basename and extension. | |
936 | // | |
937 | // Not using Boost.Filesystem because I want to stay in UTF-8. | |
938 | // Need to think about uri encoding. | |
939 | ||
940 | std::string::size_type pos; | |
941 | std::string stem, extension; | |
942 | ||
943 | pos = fileref.rfind('/'); | |
11fdf7f2 | 944 | stem = pos == std::string::npos ? fileref : fileref.substr(pos + 1); |
7c673cae FG |
945 | |
946 | pos = stem.rfind('.'); | |
11fdf7f2 | 947 | if (pos != std::string::npos) { |
7c673cae FG |
948 | extension = stem.substr(pos + 1); |
949 | stem = stem.substr(0, pos); | |
950 | } | |
951 | ||
952 | // Extract the alt tag, to use as a text description. | |
953 | // Or if there isn't one, use the stem of the file name. | |
954 | ||
955 | attribute_map::iterator alt_pos = attributes.find("alt"); | |
11fdf7f2 TL |
956 | quickbook::value alt_text = alt_pos != attributes.end() |
957 | ? alt_pos->second | |
958 | : qbk_version_n < 106u | |
959 | ? encoded_value(stem) | |
960 | : quickbook::value(); | |
7c673cae FG |
961 | attributes.erase("alt"); |
962 | ||
11fdf7f2 TL |
963 | if (extension == "svg") { |
964 | // | |
965 | // SVG's need special handling: | |
966 | // | |
967 | // 1) We must set the "format" attribute, otherwise | |
968 | // HTML generation produces code that will not display | |
969 | // the image at all. | |
970 | // 2) We need to set the "contentwidth" and "contentdepth" | |
971 | // attributes, otherwise the image will be displayed inside | |
972 | // a tiny box with scrollbars (Firefox), or else cropped to | |
973 | // fit in a tiny box (IE7). | |
974 | // | |
975 | ||
976 | attributes.insert( | |
977 | attribute_map::value_type("format", encoded_value("SVG"))); | |
978 | ||
979 | // | |
980 | // Image paths are relative to the html subdirectory: | |
981 | // | |
982 | fs::path img = detail::generic_to_path(fileref); | |
983 | if (!img.has_root_directory()) | |
984 | img = quickbook::image_location / img; // relative path | |
985 | ||
986 | // | |
987 | // Now load the SVG file: | |
988 | // | |
989 | std::string svg_text; | |
990 | if (state.dependencies.add_dependency(img)) { | |
991 | fs::ifstream fs(img); | |
992 | std::stringstream buffer; | |
993 | buffer << fs.rdbuf(); | |
994 | svg_text = buffer.str(); | |
995 | } | |
996 | ||
997 | // | |
998 | // Extract the svg header from the file: | |
999 | // | |
1000 | std::string::size_type a, b; | |
1001 | a = svg_text.find("<svg"); | |
1002 | b = svg_text.find('>', a); | |
1003 | svg_text = | |
1004 | (a == std::string::npos) ? "" : svg_text.substr(a, b - a); | |
1005 | // | |
1006 | // Now locate the "width" and "height" attributes | |
1007 | // and borrow their values: | |
1008 | // | |
1009 | a = svg_text.find("width"); | |
1010 | a = svg_text.find('=', a); | |
1011 | a = svg_text.find('\"', a); | |
1012 | b = svg_text.find('\"', a + 1); | |
1013 | if (a != std::string::npos) { | |
1014 | attributes.insert(std::make_pair( | |
1015 | "contentwidth", encoded_value(std::string( | |
1016 | boost::next(svg_text.begin(), a + 1), | |
1017 | boost::next(svg_text.begin(), b))))); | |
1018 | } | |
1019 | a = svg_text.find("height"); | |
1020 | a = svg_text.find('=', a); | |
1021 | a = svg_text.find('\"', a); | |
1022 | b = svg_text.find('\"', a + 1); | |
1023 | if (a != std::string::npos) { | |
1024 | attributes.insert(std::make_pair( | |
1025 | "contentdepth", encoded_value(std::string( | |
1026 | boost::next(svg_text.begin(), a + 1), | |
1027 | boost::next(svg_text.begin(), b))))); | |
1028 | } | |
7c673cae FG |
1029 | } |
1030 | ||
1031 | state.phrase << "<inlinemediaobject>"; | |
1032 | ||
1033 | state.phrase << "<imageobject><imagedata"; | |
11fdf7f2 | 1034 | |
92f5a8d4 | 1035 | QUICKBOOK_FOR (attribute_map::value_type const& attr, attributes) { |
7c673cae FG |
1036 | state.phrase << " " << attr.first << "=\""; |
1037 | write_plain_text(state.phrase.get(), attr.second); | |
1038 | state.phrase << "\""; | |
1039 | } | |
1040 | ||
1041 | state.phrase << "></imagedata></imageobject>"; | |
1042 | ||
1043 | // Add a textobject containing the alt tag from earlier. | |
1044 | // This will be used for the alt tag in html. | |
1045 | if (alt_text.check()) { | |
1046 | state.phrase << "<textobject><phrase>"; | |
1047 | write_plain_text(state.phrase.get(), alt_text); | |
1048 | state.phrase << "</phrase></textobject>"; | |
1049 | } | |
1050 | ||
1051 | state.phrase << "</inlinemediaobject>"; | |
1052 | } | |
1053 | ||
11fdf7f2 TL |
1054 | void macro_definition_action( |
1055 | quickbook::state& state, quickbook::value macro_definition) | |
7c673cae FG |
1056 | { |
1057 | value_consumer values = macro_definition; | |
b32b8144 | 1058 | std::string macro_id = values.consume().get_quickbook().to_s(); |
7c673cae FG |
1059 | value phrase_value = values.optional_consume(); |
1060 | std::string phrase; | |
1061 | if (phrase_value.check()) phrase = phrase_value.get_encoded(); | |
1062 | values.finish(); | |
1063 | ||
1064 | std::string* existing_macro = | |
1065 | boost::spirit::classic::find(state.macro, macro_id.c_str()); | |
1066 | quickbook::ignore_variable(&existing_macro); | |
1067 | ||
11fdf7f2 | 1068 | if (existing_macro) { |
7c673cae FG |
1069 | if (qbk_version_n < 106) return; |
1070 | ||
1071 | // Do this if you're using spirit's TST. | |
1072 | // | |
1073 | // *existing_macro = phrase; | |
1074 | // return; | |
1075 | } | |
1076 | ||
11fdf7f2 | 1077 | state.macro.add(macro_id.begin(), macro_id.end(), phrase); |
7c673cae FG |
1078 | } |
1079 | ||
11fdf7f2 TL |
1080 | void template_body_action( |
1081 | quickbook::state& state, quickbook::value template_definition) | |
7c673cae FG |
1082 | { |
1083 | value_consumer values = template_definition; | |
b32b8144 | 1084 | std::string identifier = values.consume().get_quickbook().to_s(); |
7c673cae FG |
1085 | |
1086 | std::vector<std::string> template_values; | |
92f5a8d4 | 1087 | QUICKBOOK_FOR (value const& p, values.consume()) { |
b32b8144 | 1088 | template_values.push_back(p.get_quickbook().to_s()); |
7c673cae FG |
1089 | } |
1090 | ||
11fdf7f2 TL |
1091 | BOOST_ASSERT( |
1092 | values.check(template_tags::block) || | |
1093 | values.check(template_tags::phrase)); | |
7c673cae FG |
1094 | value body = values.consume(); |
1095 | BOOST_ASSERT(!values.check()); | |
11fdf7f2 TL |
1096 | |
1097 | if (!state.templates.add(template_symbol( | |
1098 | identifier, template_values, body, | |
1099 | &state.templates.top_scope()))) { | |
7c673cae FG |
1100 | detail::outwarn(body.get_file(), body.get_position()) |
1101 | << "Template Redefinition: " << identifier << std::endl; | |
1102 | ++state.error_count; | |
1103 | } | |
1104 | } | |
1105 | ||
1106 | namespace | |
1107 | { | |
11fdf7f2 TL |
1108 | string_iterator find_first_seperator( |
1109 | string_iterator begin, string_iterator end) | |
7c673cae | 1110 | { |
11fdf7f2 TL |
1111 | if (qbk_version_n < 105) { |
1112 | for (; begin != end; ++begin) { | |
1113 | switch (*begin) { | |
7c673cae FG |
1114 | case ' ': |
1115 | case '\t': | |
1116 | case '\n': | |
1117 | case '\r': | |
1118 | return begin; | |
1119 | default: | |
1120 | break; | |
1121 | } | |
1122 | } | |
1123 | } | |
1124 | else { | |
1125 | unsigned int depth = 0; | |
1126 | ||
11fdf7f2 TL |
1127 | for (; begin != end; ++begin) { |
1128 | switch (*begin) { | |
7c673cae FG |
1129 | case '[': |
1130 | ++depth; | |
1131 | break; | |
1132 | case '\\': | |
11fdf7f2 | 1133 | if (++begin == end) return begin; |
7c673cae FG |
1134 | break; |
1135 | case ']': | |
1136 | if (depth > 0) --depth; | |
1137 | break; | |
1138 | case ' ': | |
1139 | case '\t': | |
1140 | case '\n': | |
1141 | case '\r': | |
1142 | if (depth == 0) return begin; | |
1143 | default: | |
1144 | break; | |
1145 | } | |
1146 | } | |
1147 | } | |
11fdf7f2 | 1148 | |
7c673cae FG |
1149 | return begin; |
1150 | } | |
11fdf7f2 TL |
1151 | |
1152 | std::pair<string_iterator, string_iterator> find_seperator( | |
1153 | string_iterator begin, string_iterator end) | |
7c673cae FG |
1154 | { |
1155 | string_iterator first = begin = find_first_seperator(begin, end); | |
1156 | ||
11fdf7f2 TL |
1157 | for (; begin != end; ++begin) { |
1158 | switch (*begin) { | |
7c673cae FG |
1159 | case ' ': |
1160 | case '\t': | |
1161 | case '\n': | |
1162 | case '\r': | |
1163 | break; | |
1164 | default: | |
1165 | return std::make_pair(first, begin); | |
1166 | } | |
1167 | } | |
11fdf7f2 | 1168 | |
7c673cae FG |
1169 | return std::make_pair(first, begin); |
1170 | } | |
11fdf7f2 | 1171 | |
7c673cae | 1172 | void break_arguments( |
11fdf7f2 TL |
1173 | std::vector<value>& args, |
1174 | std::vector<std::string> const& params, | |
1175 | fs::path const& /* filename */ | |
1176 | ) | |
7c673cae FG |
1177 | { |
1178 | // Quickbook 1.4-: If there aren't enough parameters seperated by | |
1179 | // '..' then seperate the last parameter using | |
1180 | // whitespace. | |
1181 | // Quickbook 1.5+: If '..' isn't used to seperate the parameters | |
1182 | // then use whitespace to separate them | |
1183 | // (2 = template name + argument). | |
1184 | ||
11fdf7f2 TL |
1185 | if (qbk_version_n < 105 ? args.size() : args.size() == 1) { |
1186 | ||
1187 | while (args.size() < params.size()) { | |
7c673cae FG |
1188 | // Try to break the last argument at the first space found |
1189 | // and push it into the back of args. Do this | |
1190 | // recursively until we have all the expected number of | |
1191 | // arguments, or if there are no more spaces left. | |
1192 | ||
1193 | value last_arg = args.back(); | |
1194 | string_iterator begin = last_arg.get_quickbook().begin(); | |
1195 | string_iterator end = last_arg.get_quickbook().end(); | |
11fdf7f2 | 1196 | |
7c673cae FG |
1197 | std::pair<string_iterator, string_iterator> pos = |
1198 | find_seperator(begin, end); | |
1199 | if (pos.second == end) break; | |
11fdf7f2 TL |
1200 | value new_arg(qbk_value( |
1201 | last_arg.get_file(), pos.second, end, | |
1202 | template_tags::phrase)); | |
7c673cae | 1203 | |
11fdf7f2 TL |
1204 | args.back() = qbk_value( |
1205 | last_arg.get_file(), begin, pos.first, | |
1206 | last_arg.get_tag()); | |
7c673cae FG |
1207 | args.push_back(new_arg); |
1208 | } | |
1209 | } | |
1210 | } | |
1211 | ||
11fdf7f2 TL |
1212 | std::pair<bool, std::vector<std::string>::const_iterator> get_arguments( |
1213 | std::vector<value> const& args, | |
1214 | std::vector<std::string> const& params, | |
1215 | template_scope const& scope, | |
1216 | string_iterator first, | |
1217 | quickbook::state& state) | |
7c673cae FG |
1218 | { |
1219 | std::vector<value>::const_iterator arg = args.begin(); | |
1220 | std::vector<std::string>::const_iterator tpl = params.begin(); | |
1221 | std::vector<std::string> empty_params; | |
1222 | ||
1223 | // Store each of the argument passed in as local templates: | |
11fdf7f2 | 1224 | while (arg != args.end()) { |
7c673cae | 1225 | if (!state.templates.add( |
11fdf7f2 | 1226 | template_symbol(*tpl, empty_params, *arg, &scope))) { |
7c673cae FG |
1227 | detail::outerr(state.current_file, first) |
1228 | << "Duplicate Symbol Found" << std::endl; | |
1229 | ++state.error_count; | |
1230 | return std::make_pair(false, tpl); | |
1231 | } | |
11fdf7f2 TL |
1232 | ++arg; |
1233 | ++tpl; | |
7c673cae FG |
1234 | } |
1235 | return std::make_pair(true, tpl); | |
1236 | } | |
11fdf7f2 | 1237 | |
7c673cae | 1238 | bool parse_template( |
11fdf7f2 TL |
1239 | value const& content, |
1240 | quickbook::state& state, | |
1241 | bool is_attribute_template) | |
7c673cae FG |
1242 | { |
1243 | file_ptr saved_current_file = state.current_file; | |
1244 | ||
1245 | state.current_file = content.get_file(); | |
b32b8144 | 1246 | quickbook::string_view source = content.get_quickbook(); |
7c673cae FG |
1247 | |
1248 | parse_iterator first(source.begin()); | |
1249 | parse_iterator last(source.end()); | |
1250 | ||
11fdf7f2 TL |
1251 | bool r = cl::parse( |
1252 | first, last, | |
1253 | is_attribute_template | |
1254 | ? state.grammar().attribute_template_body | |
1255 | : content.get_tag() == template_tags::phrase | |
1256 | ? state.grammar().inline_phrase | |
1257 | : state.grammar().block_start) | |
1258 | .full; | |
7c673cae FG |
1259 | |
1260 | boost::swap(state.current_file, saved_current_file); | |
1261 | ||
1262 | return r; | |
1263 | } | |
1264 | } | |
1265 | ||
11fdf7f2 TL |
1266 | void call_template( |
1267 | quickbook::state& state, | |
1268 | template_symbol const* symbol, | |
1269 | std::vector<value> const& args, | |
1270 | string_iterator first, | |
1271 | bool is_attribute_template = false) | |
7c673cae FG |
1272 | { |
1273 | bool is_block = symbol->content.get_tag() != template_tags::phrase; | |
1274 | assert(!(is_attribute_template && is_block)); | |
1275 | ||
1276 | quickbook::paragraph_action paragraph_action(state); | |
1277 | ||
1278 | // Finish off any existing paragraphs. | |
1279 | if (is_block) paragraph_action(); | |
1280 | ||
1281 | // If this template contains already encoded text, then just | |
1282 | // write it out, without going through any of the rigamarole. | |
1283 | ||
11fdf7f2 TL |
1284 | if (symbol->content.is_encoded()) { |
1285 | (is_block ? state.out : state.phrase) | |
1286 | << symbol->content.get_encoded(); | |
7c673cae FG |
1287 | return; |
1288 | } | |
1289 | ||
1290 | // The template arguments should have the scope that the template was | |
1291 | // called from, not the template's own scope. | |
1292 | // | |
1293 | // Note that for quickbook 1.4- this value is just ignored when the | |
1294 | // arguments are expanded. | |
1295 | template_scope const& call_scope = state.templates.top_scope(); | |
1296 | ||
1297 | { | |
1298 | state_save save(state, state_save::scope_callables); | |
1299 | std::string save_block; | |
1300 | std::string save_phrase; | |
1301 | ||
1302 | state.templates.start_template(symbol); | |
1303 | ||
1304 | qbk_version_n = symbol->content.get_file()->version(); | |
1305 | ||
1306 | ++state.template_depth; | |
11fdf7f2 | 1307 | if (state.template_depth > state.max_template_depth) { |
7c673cae FG |
1308 | detail::outerr(state.current_file, first) |
1309 | << "Infinite loop detected" << std::endl; | |
1310 | ++state.error_count; | |
1311 | return; | |
1312 | } | |
1313 | ||
1314 | // Store the current section level so that we can ensure that | |
1315 | // [section] and [endsect] tags in the template are balanced. | |
1316 | state.min_section_level = state.document.section_level(); | |
1317 | ||
1318 | /////////////////////////////////// | |
1319 | // Prepare the arguments as local templates | |
1320 | bool get_arg_result; | |
1321 | std::vector<std::string>::const_iterator tpl; | |
1322 | boost::tie(get_arg_result, tpl) = | |
1323 | get_arguments(args, symbol->params, call_scope, first, state); | |
1324 | ||
11fdf7f2 | 1325 | if (!get_arg_result) { |
7c673cae FG |
1326 | return; |
1327 | } | |
1328 | ||
1329 | /////////////////////////////////// | |
1330 | // parse the template body: | |
1331 | ||
1332 | if (symbol->content.get_file()->version() < 107u) { | |
1333 | state.out.swap(save_block); | |
1334 | state.phrase.swap(save_phrase); | |
1335 | } | |
1336 | ||
11fdf7f2 TL |
1337 | if (!parse_template( |
1338 | symbol->content, state, is_attribute_template)) { | |
7c673cae | 1339 | detail::outerr(state.current_file, first) |
11fdf7f2 | 1340 | << "Expanding " << (is_block ? "block" : "phrase") |
7c673cae FG |
1341 | << " template: " << symbol->identifier << "\n\n" |
1342 | << "------------------begin------------------\n" | |
1343 | << symbol->content.get_quickbook() | |
1344 | << "------------------end--------------------\n" | |
1345 | << std::endl; | |
1346 | ++state.error_count; | |
1347 | return; | |
1348 | } | |
1349 | ||
11fdf7f2 | 1350 | if (state.document.section_level() != state.min_section_level) { |
7c673cae | 1351 | detail::outerr(state.current_file, first) |
11fdf7f2 | 1352 | << "Mismatched sections in template " << symbol->identifier |
7c673cae FG |
1353 | << std::endl; |
1354 | ++state.error_count; | |
1355 | return; | |
1356 | } | |
1357 | ||
1358 | if (symbol->content.get_file()->version() < 107u) { | |
1359 | state.out.swap(save_block); | |
1360 | state.phrase.swap(save_phrase); | |
1361 | ||
11fdf7f2 | 1362 | if (is_block || !save_block.empty()) { |
7c673cae FG |
1363 | paragraph_action(); |
1364 | state.out << save_block; | |
1365 | state.phrase << save_phrase; | |
1366 | paragraph_action(); | |
1367 | } | |
1368 | else { | |
1369 | state.phrase << save_phrase; | |
1370 | } | |
1371 | } | |
11fdf7f2 | 1372 | else { |
7c673cae FG |
1373 | if (is_block) paragraph_action(); |
1374 | } | |
1375 | } | |
1376 | } | |
1377 | ||
11fdf7f2 TL |
1378 | void call_code_snippet( |
1379 | quickbook::state& state, | |
1380 | template_symbol const* symbol, | |
1381 | string_iterator first) | |
7c673cae FG |
1382 | { |
1383 | assert(symbol->params.size() == 0); | |
1384 | std::vector<value> args; | |
1385 | ||
1386 | // Create a fake symbol for call_template | |
1387 | template_symbol t( | |
11fdf7f2 | 1388 | symbol->identifier, symbol->params, symbol->content, |
7c673cae FG |
1389 | symbol->lexical_parent); |
1390 | ||
1391 | state.start_callouts(); | |
1392 | call_template(state, &t, args, first); | |
1393 | state.out << state.end_callouts(); | |
1394 | } | |
1395 | ||
11fdf7f2 TL |
1396 | void do_template_action( |
1397 | quickbook::state& state, value template_list, string_iterator first) | |
7c673cae FG |
1398 | { |
1399 | bool const is_attribute_template = | |
1400 | template_list.get_tag() == template_tags::attribute_template; | |
1401 | ||
1402 | // Get the arguments | |
1403 | value_consumer values = template_list; | |
1404 | ||
1405 | bool template_escape = values.check(template_tags::escape); | |
11fdf7f2 | 1406 | if (template_escape) values.consume(); |
7c673cae | 1407 | |
11fdf7f2 TL |
1408 | std::string identifier = |
1409 | values.consume(template_tags::identifier).get_quickbook().to_s(); | |
7c673cae FG |
1410 | |
1411 | std::vector<value> args; | |
1412 | ||
92f5a8d4 | 1413 | QUICKBOOK_FOR (value arg, values) { |
7c673cae FG |
1414 | args.push_back(arg); |
1415 | } | |
11fdf7f2 | 1416 | |
7c673cae FG |
1417 | values.finish(); |
1418 | ||
1419 | template_symbol const* symbol = state.templates.find(identifier); | |
1420 | BOOST_ASSERT(symbol); | |
1421 | ||
1422 | // Deal with escaped templates. | |
1423 | ||
11fdf7f2 TL |
1424 | if (template_escape) { |
1425 | if (!args.empty()) { | |
7c673cae | 1426 | detail::outerr(state.current_file, first) |
11fdf7f2 | 1427 | << "Arguments for escaped template." << std::endl; |
7c673cae FG |
1428 | ++state.error_count; |
1429 | } | |
1430 | ||
11fdf7f2 | 1431 | if (symbol->content.is_encoded()) { |
7c673cae FG |
1432 | state.phrase << symbol->content.get_encoded(); |
1433 | } | |
11fdf7f2 | 1434 | else { |
7c673cae FG |
1435 | state.phrase << symbol->content.get_quickbook(); |
1436 | ||
1437 | /* | |
1438 | ||
1439 | This would surround the escaped template in escape | |
1440 | comments to indicate to the post-processor that it | |
1441 | isn't quickbook generated markup. But I'm not sure if | |
1442 | it would work. | |
1443 | ||
1444 | quickbook::detail::markup escape_markup | |
1445 | = detail::get_markup(phrase_tags::escape); | |
1446 | ||
1447 | state.phrase | |
1448 | << escape_markup.pre | |
1449 | << symbol->content.get_quickbook() | |
1450 | << escape_markup.post | |
1451 | ; | |
1452 | */ | |
1453 | } | |
1454 | ||
1455 | return; | |
1456 | } | |
1457 | ||
1458 | /////////////////////////////////// | |
1459 | // Check that attribute templates are phrase templates | |
1460 | ||
1461 | if (is_attribute_template && | |
11fdf7f2 | 1462 | symbol->content.get_tag() != template_tags::phrase) { |
7c673cae FG |
1463 | detail::outerr(state.current_file, first) |
1464 | << "Only phrase templates can be used in attribute values." | |
1465 | << std::endl; | |
1466 | ||
1467 | ++state.error_count; | |
1468 | return; | |
1469 | } | |
1470 | ||
1471 | /////////////////////////////////// | |
1472 | // Initialise the arguments | |
1473 | ||
11fdf7f2 | 1474 | switch (symbol->content.get_tag()) { |
7c673cae FG |
1475 | case template_tags::block: |
1476 | case template_tags::phrase: | |
1477 | // Break the arguments for a template | |
1478 | ||
1479 | break_arguments(args, symbol->params, state.current_file->path); | |
1480 | ||
11fdf7f2 | 1481 | if (args.size() != symbol->params.size()) { |
7c673cae FG |
1482 | detail::outerr(state.current_file, first) |
1483 | << "Invalid number of arguments passed. Expecting: " | |
1484 | << symbol->params.size() | |
11fdf7f2 TL |
1485 | << " argument(s), got: " << args.size() |
1486 | << " argument(s) instead." << std::endl; | |
7c673cae FG |
1487 | |
1488 | ++state.error_count; | |
1489 | return; | |
1490 | } | |
1491 | ||
1492 | call_template(state, symbol, args, first, is_attribute_template); | |
1493 | break; | |
1494 | ||
1495 | case template_tags::snippet: | |
1496 | ||
11fdf7f2 | 1497 | if (!args.empty()) { |
7c673cae | 1498 | detail::outerr(state.current_file, first) |
11fdf7f2 | 1499 | << "Arguments for code snippet." << std::endl; |
7c673cae FG |
1500 | ++state.error_count; |
1501 | ||
1502 | args.clear(); | |
1503 | } | |
1504 | ||
1505 | call_code_snippet(state, symbol, first); | |
1506 | break; | |
1507 | ||
1508 | default: | |
1509 | assert(0); | |
1510 | } | |
1511 | } | |
1512 | ||
1513 | void link_action(quickbook::state& state, value link) | |
1514 | { | |
1515 | write_anchors(state, state.phrase); | |
1516 | ||
1517 | detail::markup markup = detail::get_markup(link.get_tag()); | |
1518 | ||
1519 | value_consumer values = link; | |
1520 | value dst_value = values.consume(); | |
1521 | value content = values.consume(); | |
1522 | values.finish(); | |
1523 | ||
1524 | std::string dst; | |
1525 | ||
1526 | if (link.get_tag() == phrase_tags::link) { | |
1527 | dst = validate_id(state, dst_value); | |
1528 | } | |
1529 | else { | |
1530 | dst = get_attribute_value(state, dst_value); | |
1531 | ||
7c673cae FG |
1532 | if (link.get_tag() == phrase_tags::url) { |
1533 | dst = detail::partially_escape_uri(dst); | |
1534 | } | |
11fdf7f2 TL |
1535 | } |
1536 | ||
7c673cae FG |
1537 | state.phrase << markup.pre; |
1538 | detail::print_string(dst, state.phrase.get()); | |
1539 | state.phrase << "\">"; | |
1540 | ||
1541 | if (content.empty()) | |
1542 | detail::print_string(dst, state.phrase.get()); | |
1543 | else | |
1544 | state.phrase << content.get_encoded(); | |
1545 | ||
1546 | state.phrase << markup.post; | |
1547 | } | |
1548 | ||
1549 | void variable_list_action(quickbook::state& state, value variable_list) | |
1550 | { | |
1551 | write_anchors(state, state.out); | |
1552 | ||
1553 | value_consumer values = variable_list; | |
11fdf7f2 TL |
1554 | std::string title = |
1555 | values.consume(table_tags::title).get_quickbook().to_s(); | |
7c673cae FG |
1556 | |
1557 | state.out << "<variablelist>\n"; | |
1558 | ||
1559 | state.out << "<title>"; | |
1560 | detail::print_string(title, state.out.get()); | |
1561 | state.out << "</title>\n"; | |
1562 | ||
92f5a8d4 | 1563 | QUICKBOOK_FOR (value_consumer entry, values) { |
7c673cae | 1564 | state.out << "<varlistentry>"; |
11fdf7f2 TL |
1565 | |
1566 | if (entry.check()) { | |
7c673cae FG |
1567 | state.out << "<term>"; |
1568 | state.out << entry.consume().get_encoded(); | |
1569 | state.out << "</term>"; | |
1570 | } | |
11fdf7f2 TL |
1571 | |
1572 | if (entry.check()) { | |
7c673cae | 1573 | state.out << "<listitem>"; |
92f5a8d4 | 1574 | QUICKBOOK_FOR (value phrase, entry) |
11fdf7f2 | 1575 | state.out << phrase.get_encoded(); |
7c673cae FG |
1576 | state.out << "</listitem>"; |
1577 | } | |
1578 | ||
1579 | state.out << "</varlistentry>\n"; | |
1580 | } | |
1581 | ||
1582 | state.out << "</variablelist>\n"; | |
11fdf7f2 | 1583 | |
7c673cae FG |
1584 | values.finish(); |
1585 | } | |
1586 | ||
1587 | void table_action(quickbook::state& state, value table) | |
1588 | { | |
1589 | write_anchors(state, state.out); | |
1590 | ||
1591 | value_consumer values = table; | |
1592 | ||
1593 | std::string element_id; | |
11fdf7f2 | 1594 | if (values.check(general_tags::element_id)) { |
7c673cae FG |
1595 | element_id = validate_id(state, values.consume()); |
1596 | } | |
1597 | ||
1598 | value title = values.consume(table_tags::title); | |
1599 | bool has_title = !title.empty(); | |
11fdf7f2 | 1600 | |
7c673cae FG |
1601 | std::string table_id; |
1602 | ||
1603 | if (!element_id.empty()) { | |
11fdf7f2 TL |
1604 | table_id = |
1605 | state.document.add_id(element_id, id_category::explicit_id); | |
7c673cae FG |
1606 | } |
1607 | else if (has_title) { | |
1608 | if (state.document.compatibility_version() >= 105) { | |
11fdf7f2 TL |
1609 | table_id = state.document.add_id( |
1610 | detail::make_identifier(title.get_quickbook()), | |
1611 | id_category::generated); | |
7c673cae FG |
1612 | } |
1613 | else { | |
1614 | table_id = state.document.add_id("t", id_category::numbered); | |
1615 | } | |
1616 | } | |
1617 | ||
1618 | // Emulating the old behaviour which used the width of the final | |
1619 | // row for span_count. | |
1620 | int row_count = 0; | |
1621 | int span_count = 0; | |
1622 | ||
1623 | value_consumer lookahead = values; | |
92f5a8d4 | 1624 | QUICKBOOK_FOR (value row, lookahead) { |
7c673cae FG |
1625 | ++row_count; |
1626 | span_count = boost::distance(row); | |
1627 | } | |
1628 | lookahead.finish(); | |
1629 | ||
11fdf7f2 | 1630 | if (has_title) { |
7c673cae | 1631 | state.out << "<table frame=\"all\""; |
11fdf7f2 | 1632 | if (!table_id.empty()) state.out << " id=\"" << table_id << "\""; |
7c673cae FG |
1633 | state.out << ">\n"; |
1634 | state.out << "<title>"; | |
1635 | if (qbk_version_n < 106u) { | |
1636 | detail::print_string(title.get_quickbook(), state.out.get()); | |
1637 | } | |
1638 | else { | |
1639 | state.out << title.get_encoded(); | |
1640 | } | |
1641 | state.out << "</title>"; | |
1642 | } | |
11fdf7f2 | 1643 | else { |
7c673cae | 1644 | state.out << "<informaltable frame=\"all\""; |
11fdf7f2 | 1645 | if (!table_id.empty()) state.out << " id=\"" << table_id << "\""; |
7c673cae FG |
1646 | state.out << ">\n"; |
1647 | } | |
1648 | ||
1649 | state.out << "<tgroup cols=\"" << span_count << "\">\n"; | |
1650 | ||
11fdf7f2 TL |
1651 | if (row_count > 1) { |
1652 | state.out << "<thead>" | |
1653 | << "<row>"; | |
92f5a8d4 | 1654 | QUICKBOOK_FOR (value cell, values.consume()) { |
7c673cae FG |
1655 | state.out << "<entry>" << cell.get_encoded() << "</entry>"; |
1656 | } | |
11fdf7f2 TL |
1657 | state.out << "</row>\n" |
1658 | << "</thead>\n"; | |
7c673cae FG |
1659 | } |
1660 | ||
1661 | state.out << "<tbody>\n"; | |
1662 | ||
92f5a8d4 | 1663 | QUICKBOOK_FOR (value row, values) { |
7c673cae | 1664 | state.out << "<row>"; |
92f5a8d4 | 1665 | QUICKBOOK_FOR (value cell, row) { |
7c673cae FG |
1666 | state.out << "<entry>" << cell.get_encoded() << "</entry>"; |
1667 | } | |
1668 | state.out << "</row>\n"; | |
1669 | } | |
11fdf7f2 | 1670 | |
7c673cae FG |
1671 | values.finish(); |
1672 | ||
1673 | state.out << "</tbody>\n" | |
11fdf7f2 | 1674 | << "</tgroup>\n"; |
7c673cae | 1675 | |
11fdf7f2 | 1676 | if (has_title) { |
7c673cae FG |
1677 | state.out << "</table>\n"; |
1678 | } | |
11fdf7f2 | 1679 | else { |
7c673cae FG |
1680 | state.out << "</informaltable>\n"; |
1681 | } | |
1682 | } | |
1683 | ||
1684 | void begin_section_action(quickbook::state& state, value begin_section_list) | |
11fdf7f2 | 1685 | { |
7c673cae FG |
1686 | value_consumer values = begin_section_list; |
1687 | ||
1688 | value element_id = values.optional_consume(general_tags::element_id); | |
1689 | value content = values.consume(); | |
1690 | values.finish(); | |
1691 | ||
1692 | std::string full_id = state.document.begin_section( | |
b32b8144 | 1693 | element_id, |
11fdf7f2 TL |
1694 | element_id.empty() |
1695 | ? detail::make_identifier(content.get_quickbook()) | |
1696 | : validate_id(state, element_id), | |
1697 | element_id.empty() ? id_category::generated_section | |
1698 | : id_category::explicit_section_id, | |
7c673cae FG |
1699 | state.current_source_mode()); |
1700 | ||
1701 | state.out << "\n<section id=\"" << full_id << "\">\n"; | |
7c673cae | 1702 | |
b32b8144 | 1703 | std::string title = content.get_encoded(); |
7c673cae | 1704 | |
b32b8144 FG |
1705 | if (!title.empty()) { |
1706 | state.out << "<title>"; | |
1707 | ||
1708 | write_anchors(state, state.out); | |
1709 | ||
11fdf7f2 TL |
1710 | if (self_linked_headers && |
1711 | state.document.compatibility_version() >= 103) { | |
b32b8144 FG |
1712 | state.out << quickbook::detail::linkify(title, full_id); |
1713 | } | |
11fdf7f2 | 1714 | else { |
b32b8144 FG |
1715 | state.out << title; |
1716 | } | |
1717 | ||
1718 | state.out << "</title>\n"; | |
7c673cae | 1719 | } |
7c673cae FG |
1720 | } |
1721 | ||
11fdf7f2 TL |
1722 | void end_section_action( |
1723 | quickbook::state& state, value end_section_list, string_iterator first) | |
7c673cae | 1724 | { |
b32b8144 FG |
1725 | value_consumer values = end_section_list; |
1726 | value element_id = values.optional_consume(general_tags::element_id); | |
1727 | values.finish(); | |
1728 | ||
7c673cae FG |
1729 | write_anchors(state, state.out); |
1730 | ||
11fdf7f2 | 1731 | if (state.document.section_level() <= state.min_section_level) { |
7c673cae FG |
1732 | file_position const pos = state.current_file->position_of(first); |
1733 | ||
1734 | detail::outerr(state.current_file->path, pos.line) | |
1735 | << "Mismatched [endsect] near column " << pos.column << ".\n"; | |
1736 | ++state.error_count; | |
11fdf7f2 | 1737 | |
7c673cae FG |
1738 | return; |
1739 | } | |
1740 | ||
11fdf7f2 TL |
1741 | if (!element_id.empty() && |
1742 | !(element_id == state.document.explicit_id())) { | |
b32b8144 FG |
1743 | file_position const pos = state.current_file->position_of(first); |
1744 | value section_element_id = state.document.explicit_id(); | |
1745 | ||
1746 | if (section_element_id.empty()) { | |
1747 | detail::outerr(state.current_file->path, pos.line) | |
1748 | << "Endsect has unexpected id '" | |
1749 | << element_id.get_quickbook() | |
1750 | << "' in section with no explicit id, near column " | |
1751 | << pos.column << ".\n"; | |
11fdf7f2 TL |
1752 | } |
1753 | else { | |
b32b8144 FG |
1754 | detail::outerr(state.current_file->path, pos.line) |
1755 | << "Endsect has incorrect id '" | |
11fdf7f2 | 1756 | << element_id.get_quickbook() << "', expected '" |
b32b8144 | 1757 | << state.document.explicit_id().get_quickbook() |
11fdf7f2 | 1758 | << "', near column " << pos.column << ".\n"; |
b32b8144 FG |
1759 | } |
1760 | ++state.error_count; | |
1761 | } | |
1762 | ||
7c673cae FG |
1763 | state.out << "</section>"; |
1764 | state.document.end_section(); | |
1765 | } | |
11fdf7f2 TL |
1766 | |
1767 | void element_id_warning_action::operator()( | |
1768 | parse_iterator first, parse_iterator) const | |
7c673cae FG |
1769 | { |
1770 | detail::outwarn(state.current_file, first.base()) << "Empty id.\n"; | |
1771 | } | |
1772 | ||
1773 | void xinclude_action(quickbook::state& state, value xinclude) | |
1774 | { | |
1775 | write_anchors(state, state.out); | |
1776 | ||
1777 | value_consumer values = xinclude; | |
1778 | path_parameter x = check_xinclude_path(values.consume(), state); | |
1779 | values.finish(); | |
1780 | ||
11fdf7f2 | 1781 | if (x.type == path_parameter::path) { |
b32b8144 | 1782 | quickbook_path path = resolve_xinclude_path(x.value, state, true); |
7c673cae FG |
1783 | |
1784 | state.out << "\n<xi:include href=\""; | |
11fdf7f2 TL |
1785 | detail::print_string( |
1786 | file_path_to_url(path.abstract_file_path), state.out.get()); | |
7c673cae FG |
1787 | state.out << "\" />\n"; |
1788 | } | |
1789 | } | |
1790 | ||
11fdf7f2 TL |
1791 | void load_quickbook( |
1792 | quickbook::state& state, | |
1793 | quickbook_path const& path, | |
1794 | value::tag_type load_type, | |
1795 | value const& include_doc_id = value()) | |
7c673cae | 1796 | { |
11fdf7f2 TL |
1797 | assert( |
1798 | load_type == block_tags::include || | |
7c673cae FG |
1799 | load_type == block_tags::import); |
1800 | ||
1801 | // Check this before qbk_version_n gets changed by the inner file. | |
1802 | bool keep_inner_source_mode = (qbk_version_n < 106); | |
11fdf7f2 | 1803 | |
7c673cae FG |
1804 | { |
1805 | // When importing, state doesn't scope templates and macros so that | |
1806 | // they're added to the existing scope. It might be better to add | |
1807 | // them to a new scope then explicitly import them into the | |
1808 | // existing scope. | |
1809 | // | |
1810 | // For old versions of quickbook, templates aren't scoped by the | |
1811 | // file. | |
11fdf7f2 TL |
1812 | state_save save( |
1813 | state, | |
1814 | load_type == block_tags::import | |
1815 | ? state_save::scope_output | |
1816 | : qbk_version_n >= 106u ? state_save::scope_callables | |
1817 | : state_save::scope_macros); | |
7c673cae FG |
1818 | |
1819 | state.current_file = load(path.file_path); // Throws load_error | |
1820 | state.current_path = path; | |
1821 | state.imported = (load_type == block_tags::import); | |
1822 | ||
1823 | // update the __FILENAME__ macro | |
1824 | state.update_filename_macro(); | |
11fdf7f2 | 1825 | |
7c673cae FG |
1826 | // parse the file |
1827 | quickbook::parse_file(state, include_doc_id, true); | |
1828 | ||
1829 | // Don't restore source_mode on older versions. | |
1830 | if (keep_inner_source_mode) save.source_mode = state.source_mode; | |
1831 | } | |
1832 | ||
1833 | // restore the __FILENAME__ macro | |
1834 | state.update_filename_macro(); | |
1835 | } | |
1836 | ||
11fdf7f2 TL |
1837 | void load_source_file( |
1838 | quickbook::state& state, | |
1839 | quickbook_path const& path, | |
1840 | value::tag_type load_type, | |
1841 | string_iterator first, | |
1842 | value const& /* include_doc_id */ = value()) | |
7c673cae | 1843 | { |
11fdf7f2 TL |
1844 | assert( |
1845 | load_type == block_tags::include || | |
7c673cae FG |
1846 | load_type == block_tags::import); |
1847 | ||
1848 | std::string ext = path.file_path.extension().generic_string(); | |
1849 | std::vector<template_symbol> storage; | |
1850 | // Throws load_error | |
1851 | state.error_count += | |
1852 | load_snippets(path.file_path, storage, ext, load_type); | |
1853 | ||
11fdf7f2 | 1854 | if (load_type == block_tags::include) { |
7c673cae FG |
1855 | state.templates.push(); |
1856 | } | |
1857 | ||
92f5a8d4 | 1858 | QUICKBOOK_FOR (template_symbol& ts, storage) { |
7c673cae | 1859 | std::string tname = ts.identifier; |
11fdf7f2 | 1860 | if (tname != "!") { |
7c673cae | 1861 | ts.lexical_parent = &state.templates.top_scope(); |
11fdf7f2 TL |
1862 | if (!state.templates.add(ts)) { |
1863 | detail::outerr( | |
1864 | ts.content.get_file(), ts.content.get_position()) | |
7c673cae FG |
1865 | << "Template Redefinition: " << tname << std::endl; |
1866 | ++state.error_count; | |
1867 | } | |
1868 | } | |
1869 | } | |
1870 | ||
11fdf7f2 | 1871 | if (load_type == block_tags::include) { |
92f5a8d4 | 1872 | QUICKBOOK_FOR (template_symbol& ts, storage) { |
7c673cae FG |
1873 | std::string tname = ts.identifier; |
1874 | ||
11fdf7f2 | 1875 | if (tname == "!") { |
7c673cae FG |
1876 | ts.lexical_parent = &state.templates.top_scope(); |
1877 | call_code_snippet(state, &ts, first); | |
1878 | } | |
1879 | } | |
1880 | ||
1881 | state.templates.pop(); | |
1882 | } | |
1883 | } | |
1884 | ||
11fdf7f2 TL |
1885 | void include_action( |
1886 | quickbook::state& state, value include, string_iterator first) | |
7c673cae FG |
1887 | { |
1888 | write_anchors(state, state.out); | |
1889 | ||
1890 | value_consumer values = include; | |
11fdf7f2 TL |
1891 | value include_doc_id = |
1892 | values.optional_consume(general_tags::include_id); | |
7c673cae FG |
1893 | path_parameter parameter = check_path(values.consume(), state); |
1894 | values.finish(); | |
1895 | ||
1896 | std::set<quickbook_path> search = | |
1897 | include_search(parameter, state, first); | |
92f5a8d4 | 1898 | QUICKBOOK_FOR (quickbook_path const& path, search) { |
7c673cae | 1899 | try { |
11fdf7f2 TL |
1900 | if (qbk_version_n >= 106) { |
1901 | if (state.imported && | |
1902 | include.get_tag() == block_tags::include) | |
7c673cae FG |
1903 | return; |
1904 | ||
11fdf7f2 TL |
1905 | std::string ext = |
1906 | path.file_path.extension().generic_string(); | |
7c673cae | 1907 | |
11fdf7f2 TL |
1908 | if (ext == ".qbk" || ext == ".quickbook") { |
1909 | load_quickbook( | |
1910 | state, path, include.get_tag(), include_doc_id); | |
7c673cae | 1911 | } |
11fdf7f2 TL |
1912 | else { |
1913 | load_source_file( | |
1914 | state, path, include.get_tag(), first, | |
1915 | include_doc_id); | |
7c673cae FG |
1916 | } |
1917 | } | |
11fdf7f2 TL |
1918 | else { |
1919 | if (include.get_tag() == block_tags::include) { | |
1920 | load_quickbook( | |
1921 | state, path, include.get_tag(), include_doc_id); | |
7c673cae | 1922 | } |
11fdf7f2 TL |
1923 | else { |
1924 | load_source_file( | |
1925 | state, path, include.get_tag(), first, | |
1926 | include_doc_id); | |
7c673cae FG |
1927 | } |
1928 | } | |
11fdf7f2 | 1929 | } catch (load_error& e) { |
7c673cae FG |
1930 | ++state.error_count; |
1931 | ||
1932 | detail::outerr(state.current_file, first) | |
11fdf7f2 | 1933 | << "Loading file " << path.file_path << ": " << e.what() |
7c673cae FG |
1934 | << std::endl; |
1935 | } | |
1936 | } | |
1937 | } | |
1938 | ||
1939 | bool to_value_scoped_action::start(value::tag_type t) | |
1940 | { | |
1941 | state.push_output(); | |
1942 | state.anchors.swap(saved_anchors); | |
1943 | tag = t; | |
1944 | ||
1945 | return true; | |
1946 | } | |
1947 | ||
11fdf7f2 TL |
1948 | void to_value_scoped_action::success( |
1949 | parse_iterator first, parse_iterator last) | |
7c673cae FG |
1950 | { |
1951 | std::string value; | |
1952 | ||
11fdf7f2 | 1953 | if (!state.out.str().empty()) { |
7c673cae FG |
1954 | paragraph_action para(state); |
1955 | para(); // For paragraphs before the template call. | |
1956 | write_anchors(state, state.out); | |
1957 | state.out.swap(value); | |
1958 | } | |
11fdf7f2 | 1959 | else { |
7c673cae FG |
1960 | write_anchors(state, state.phrase); |
1961 | state.phrase.swap(value); | |
1962 | } | |
1963 | ||
1964 | state.values.builder.insert(encoded_qbk_value( | |
1965 | state.current_file, first.base(), last.base(), value, tag)); | |
1966 | } | |
11fdf7f2 | 1967 | |
7c673cae FG |
1968 | void to_value_scoped_action::cleanup() |
1969 | { | |
1970 | state.pop_output(); | |
1971 | state.anchors.swap(saved_anchors); | |
1972 | } | |
1973 | } |