diff --git a/src/actions.cpp b/src/actions.cpp index eb78dd8..3693cd6 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -1754,10 +1754,7 @@ namespace quickbook if (self_linked_headers && state.document.compatibility_version() >= 103) { - state.out << "" - << content.get_encoded() - << "" - ; + state.out << quickbook::detail::linkify(content.get_encoded(), full_id); } else { diff --git a/src/id_xml.cpp b/src/id_xml.cpp index 40f07f6..1b65417 100644 --- a/src/id_xml.cpp +++ b/src/id_xml.cpp @@ -150,4 +150,127 @@ namespace quickbook c.finish(source); } + + namespace detail { + std::string linkify(quickbook::string_view source, quickbook::string_view linkend) + { + typedef quickbook::string_view::const_iterator iterator; + + std::string result; + + iterator it = source.begin(), end = source.end(); + + int link_depth = 0; + iterator start = it; + read_some_of(it, end, " \t\n\r"); + result.append(start, it); + start = it; + // If the source is empty, we'll still create an empty link tag + // to avoid gratuitous changes in generated output, need to track + // that special case. + bool empty = true; + + for(;;) + { + read_to_one_of(it, end, "<"); + if (it == end) break; + iterator tag_start = it; + ++it; + if (it == end) break; + + if (read(it, end, "!--quickbook-escape-prefix-->")) + { + read_past(it, end, ""); + continue; + } + + switch(*it) + { + case '?': + ++it; + read_past(it, end, "?>"); + break; + + case '!': + if (read(it, end, "!--")) { + read_past(it, end, "-->"); + } else { + read_past(it, end, ">"); + } + break; + + case '/': + { + ++it; + read_some_of(it, end, " \t\n\r"); + if (it == end) { break; } + iterator tag_name_start = it; + read_to_one_of(it, end, " \t\n\r>"); + quickbook::string_view tag_name(tag_name_start, it - tag_name_start); + read_past(it, end, ">"); + + if (tag_name == "link") { + if (link_depth > 0) { --link_depth; } + if (link_depth == 0) { + read_some_of(it, end, " \t\n\r"); + result.append(start, it); + start = it; + empty = false; + } + } + } + break; + + default: + if ((*it >= 'a' && *it <= 'z') || + (*it >= 'A' && *it <= 'Z') || + *it == '_' || *it == ':') + { + iterator tag_name_start = it; + read_to_one_of(it, end, " \t\n\r>"); + quickbook::string_view tag_name(tag_name_start, it - tag_name_start); + + if (tag_name == "link") { + if (link_depth == 0 && start != tag_start) { + result += ""; + result.append(start, tag_start); + result += ""; + start = tag_start; + empty = false; + } + ++link_depth; + } + + for (;;) { + read_to_one_of(it, end, "\"'\n\r>"); + if (it == end || *it == '>') break; + if (*it == '"' || *it == '\'') { + char delim = *it; + ++it; + it = std::find(it, end, delim); + if (it == end) break; + ++it; + } + } + } + else + { + read_past(it, end, ">"); + } + } + } + + if (start != it || empty) { + result += ""; + result.append(start, it); + result += ""; + } + + return result; + } + } } diff --git a/src/utils.hpp b/src/utils.hpp index 93e7ed1..86163cf 100644 --- a/src/utils.hpp +++ b/src/utils.hpp @@ -49,6 +49,9 @@ namespace quickbook { namespace detail { inline std::string to_s(quickbook::string_view x) { return std::string(x.begin(), x.end()); } + + // Defined in id_xml.cpp. Just because. + std::string linkify(quickbook::string_view source, quickbook::string_view linkend); }} #endif // BOOST_SPIRIT_QUICKBOOK_UTILS_HPP diff --git a/test/section-1_7.gold b/test/section-1_7.gold index 33c6767..4071386 100644 --- a/test/section-1_7.gold +++ b/test/section-1_7.gold @@ -23,4 +23,11 @@
<link linkend="section_id_1_7.sect-abc">Section with template in id</link>
+ + diff --git a/test/section-1_7.quickbook b/test/section-1_7.quickbook index ada95a2..9c8baeb 100644 --- a/test/section-1_7.quickbook +++ b/test/section-1_7.quickbook @@ -16,3 +16,8 @@ [template thing[] abc] [section:sect-[thing] Section with template in id] [endsect] + +[section [link section_id_1_7.id_test1 Link in title]] +[endsect] +[section [link section_id_1_7.id_test1 Link] in title] +[endsect] diff --git a/test/unit/Jamfile.v2 b/test/unit/Jamfile.v2 index a7a3de0..f828b53 100644 --- a/test/unit/Jamfile.v2 +++ b/test/unit/Jamfile.v2 @@ -22,6 +22,7 @@ run values_test.cpp ../../src/values.cpp ../../src/files.cpp ; run post_process_test.cpp ../../src/post_process.cpp ; run source_map_test.cpp ../../src/files.cpp ; run glob_test.cpp ../../src/glob.cpp ; +run linkify_test.cpp ../../src/id_xml.cpp ; # TODO: Reinstate this test? Too painful to run at the moment. # run file_path_to_url_test.cpp ../../src/native_text.cpp ../../src/utils.cpp ; diff --git a/test/unit/linkify_test.cpp b/test/unit/linkify_test.cpp new file mode 100644 index 0000000..e597e7f --- /dev/null +++ b/test/unit/linkify_test.cpp @@ -0,0 +1,30 @@ + +/*============================================================================= + Copyright (c) 2017 Daniel James + + Use, modification and distribution is subject to the Boost Software + License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +=============================================================================*/ + +#include "utils.hpp" +#include + +#include + +void linkify_test() { + using quickbook::detail::linkify; + + BOOST_TEST(linkify("abc", "link") == "abc"); + BOOST_TEST(linkify("abc", "link") == + "abc"); + BOOST_TEST(linkify("abc def", "link") == + "abc def"); + BOOST_TEST(linkify("abc def", "link") == + "abc def"); +} + +int main() { + linkify_test(); + return boost::report_errors(); +}