diff --git a/lib/rst2rfcxml.cpp b/lib/rst2rfcxml.cpp index 49dbb4d..8c3667b 100644 --- a/lib/rst2rfcxml.cpp +++ b/lib/rst2rfcxml.cpp @@ -303,6 +303,33 @@ _replace_all(string line, string from, string to) return line; } +string +rst2rfcxml::define_anchor(string value) +{ + string anchor; + if (_anchors.find(value) == _anchors.end()) { + // Create a new anchor. + anchor = _anchor(value); + } else { + // This is a duplicate anchor definition so create a new one and + // map all future lookups to this latest one. + anchor = _anchors[value] + "-"; + } + _anchors[value] = anchor; + return anchor; + } + +string +rst2rfcxml::lookup_anchor(string value) +{ + if (_anchors.find(value) != _anchors.end()) { + return _anchors[value]; + } + + // Undefined anchor. + return _anchor(value); +} + string rst2rfcxml::replace_term_links(string line) { @@ -325,7 +352,7 @@ rst2rfcxml::replace_term_links(string line) term = middle.substr(label_end + 4, term_end - label_end - 4); } - line = fmt::format("{}{}{}", before, _anchor(term), label, after); + line = fmt::format("{}{}{}", before, lookup_anchor("term-" + term), label, after); } return line; } @@ -425,7 +452,7 @@ rst2rfcxml::replace_reference_links(string line) } } - line = fmt::format("{}{}{}", before, _anchor(middle), middle, after); + line = fmt::format("{}{}{}", before, lookup_anchor(middle), middle, after); } return line; } @@ -802,11 +829,14 @@ rst2rfcxml::handle_section_title(int level, string marker, string current, strin push_context(output_stream, xml_context::MIDDLE); } string title = handle_escapes_and_links(current); - push_context( - output_stream, - xml_context::SECTION, - current_indentation, - fmt::format("anchor=\"{}\" title=\"{}\"", _anchor(title), title)); + string anchor = define_anchor(title); + string attributes; + if (anchor.empty()) { + attributes = fmt::format("title=\"{}\"", title); + } else { + attributes = fmt::format("anchor=\"{}\" title=\"{}\"", anchor, title); + } + push_context(output_stream, xml_context::SECTION, current_indentation, attributes); return true; } if (current.starts_with(marker) && current.find_first_not_of(marker, 0) == string::npos && @@ -972,8 +1002,13 @@ rst2rfcxml::process_line(string current, string next, ostream& output_stream) if (!in_context(xml_context::DEFINITION_LIST)) { push_context(output_stream, xml_context::DEFINITION_LIST, current_indentation); } - string attributes = fmt::format("anchor=\"term-{}\"", _anchor(_trim(current))); - push_context(output_stream, xml_context::DEFINITION_TERM, current_indentation, attributes); + string anchor = define_anchor("term-" + _trim(current)); + if (anchor.empty()) { + push_context(output_stream, xml_context::DEFINITION_TERM, current_indentation); + } else { + string attributes = fmt::format("anchor=\"{}\"", anchor); + push_context(output_stream, xml_context::DEFINITION_TERM, current_indentation, attributes); + } } // Handle artwork. diff --git a/lib/rst2rfcxml.h b/lib/rst2rfcxml.h index 721ee96..d3964b5 100644 --- a/lib/rst2rfcxml.h +++ b/lib/rst2rfcxml.h @@ -131,6 +131,10 @@ class rst2rfcxml std::string replace_term_links(std::string line); std::string + define_anchor(std::string term); + std::string + lookup_anchor(std::string term); + std::string handle_escapes_and_links(std::string line); void output_table_row(std::ostream& output_stream); @@ -140,6 +144,7 @@ class rst2rfcxml std::string _ipr; std::string _category; std::vector _column_indices; + std::map _anchors; std::map _authors; std::string _submission_type; std::string _abbreviated_title; diff --git a/test/basic_tests.cpp b/test/basic_tests.cpp index 332cf84..dd973cd 100644 --- a/test/basic_tests.cpp +++ b/test/basic_tests.cpp @@ -9,6 +9,25 @@ using namespace std; +constexpr const char* BASIC_PREAMBLE = R"( + + + + + + + + + + + + + + + +)"; + void test_rst2rfcxml(const char* input, const char* expected_output) { @@ -99,6 +118,40 @@ TEST_CASE("titles", "[basic]") test_rst2rfcxml("Foo\n~~~\n", "
\n
\n"); } +TEST_CASE("duplicate titles", "[basic]") +{ + test_rst2rfcxml( + R"(.. header:: + +foo +=== + +bar + +foo +=== + +baz +)", (std::string(BASIC_PREAMBLE) + R"( + + + + +
+ + bar + +
+
+ + baz + +
+
+
+)").c_str()); +} + TEST_CASE("line block", "[basic]") { test_rst2rfcxml( @@ -315,6 +368,101 @@ baz :term:`foo-ish` baz )"); } +TEST_CASE("definition list duplication", "[basic]") +{ + test_rst2rfcxml( + R"( +foo + description + +bar + description + +baz :term:`foo-ish` baz + +foo + description 2 + +bar + description 2 + +baz :term:`foo-ish` baz + +foo + description 3 + +bar + description 3 + +baz :term:`foo-ish` baz + +)", + R"(
+
+ foo +
+
+ + description + +
+
+ bar +
+
+ + description + +
+
+ + baz foo-ish baz + +
+
+ foo +
+
+ + description 2 + +
+
+ bar +
+
+ + description 2 + +
+
+ + baz foo-ish baz + +
+
+ foo +
+
+ + description 3 + +
+
+ bar +
+
+ + description 3 + +
+
+ + baz foo-ish baz + +)"); +} + TEST_CASE("definition list with glossary label", "[basic]") { test_rst2rfcxml( @@ -775,25 +923,6 @@ Paragraph two. )"); } -constexpr const char* BASIC_PREAMBLE = R"( - - - - - - - - - - - - - - - -)"; - TEST_CASE("empty header", "[basic]") { string expected_output = BASIC_PREAMBLE;