diff --git a/docs/test_cases/t00028.md b/docs/test_cases/t00028.md new file mode 100644 index 00000000..b1cdf941 --- /dev/null +++ b/docs/test_cases/t00028.md @@ -0,0 +1,80 @@ +# t00028 - PlantUML note decorator test case +## Config +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t00028_class: + type: class + glob: + - ../../tests/t00028/t00028.cc + using_namespace: + - clanguml::t00028 + include: + namespaces: + - clanguml::t00028 + +``` +## Source code +File t00028.cc +```cpp +#include +#include + +namespace clanguml { +namespace t00028 { + +/// \clanguml{note[top] A class note.} +class A { +}; + +/// \clanguml{note[] B class note.} +class B { +}; + +/// +/// @clanguml{note:t00028_class[bottom] C class note.} +/// This is class C. +class C { +}; + +/// \clanguml{note +/// D +/// class +/// note.} +class D { +}; + +/// \clanguml{note E template class note.} +template class E { + T param; +}; + +/// \clanguml{note:other_diagram[left] G class note.} +class G { +}; + +/// @clanguml{note[ bottom ] F enum note.} +enum class F { one, two, three }; + +/// \clanguml{note[right] R class note.} +class R { + A aaa; + + B *bbb; + + C &ccc; + + std::vector> ddd; + + E eee; + + G **ggg; +}; + +} // namespace t00028 +} // namespace clanguml + +``` +## Generated UML diagrams +![t00028_class](./t00028_class.png "PlantUML note decorator test case") diff --git a/docs/test_cases/t00028_class.png b/docs/test_cases/t00028_class.png new file mode 100644 index 00000000..9f8f8195 Binary files /dev/null and b/docs/test_cases/t00028_class.png differ diff --git a/src/puml/class_diagram_generator.h b/src/puml/class_diagram_generator.h index f6b66644..b25f8dbc 100644 --- a/src/puml/class_diagram_generator.h +++ b/src/puml/class_diagram_generator.h @@ -288,7 +288,7 @@ public: // for (auto decorator : c.decorators) { auto note = std::dynamic_pointer_cast(decorator); - if (note) { + if (note && note->applies_to_diagram(m_config.name)) { ostr << "note " << note->position << " of " << c.alias() << '\n' << note->text << '\n' << "end note\n"; @@ -355,7 +355,7 @@ public: // for (auto decorator : e.decorators) { auto note = std::dynamic_pointer_cast(decorator); - if (note) { + if (note && note->applies_to_diagram(m_config.name)) { ostr << "note " << note->position << " of " << e.alias() << '\n' << note->text << '\n' << "end note\n"; diff --git a/src/uml/decorators.cc b/src/uml/decorators.cc index ea358d1d..404fe623 100644 --- a/src/uml/decorators.cc +++ b/src/uml/decorators.cc @@ -52,66 +52,90 @@ std::shared_ptr decorator::from_string(std::string_view c) return {}; } -std::shared_ptr note::from_string(std::string_view c) +bool decorator::applies_to_diagram(std::string name) { - auto res = std::make_shared(); + return diagrams.empty() || + (std::find(diagrams.begin(), diagrams.end(), name) != diagrams.end()); +} + +decorator_toks decorator::tokenize(const std::string &label, std::string_view c) +{ + decorator_toks res; + res.label = label; + size_t pos{}; auto it = c.begin(); - std::advance(it, note::label.size()); + std::advance(it, label.size()); + + if (*it == ':') { + std::advance(it, 1); + + pos = std::distance(c.begin(), it); + // If the diagram list is provided after ':', [] is mandatory + // even if empty + auto d = c.substr(pos, c.find("[", pos) - pos); + if (!d.empty()) { + std::string d_str{d}; + d_str.erase(std::remove_if(d_str.begin(), d_str.end(), + (int (*)(int))std::isspace), + d_str.end()); + res.diagrams = util::split(d_str, ","); + } + + std::advance(it, d.size()); + } if (*it == '[') { std::advance(it, 1); - auto pos = std::distance(c.begin(), it); - auto note_position = c.substr(pos, c.find("]", pos) - pos); - if (!note_position.empty()) - res->position = note_position; + pos = std::distance(c.begin(), it); + res.param = c.substr(pos, c.find("]", pos) - pos); - std::advance(it, note_position.size() + 1); + std::advance(it, res.param.size() + 1); } else if (std::isspace(*it)) { std::advance(it, 1); } - else { - LOG_WARN("Invalid note decorator: {}", c); - return {}; - } - auto pos = std::distance(c.begin(), it); - res->text = c.substr(pos, c.find("}", pos) - pos); + pos = std::distance(c.begin(), it); + res.text = c.substr(pos, c.find("}", pos) - pos); + res.text = util::trim(res.text); + res.param = util::trim(res.param); - res->position = util::trim(res->position); - res->text = util::trim(res->text); + return res; +} + +std::shared_ptr note::from_string(std::string_view c) +{ + auto res = std::make_shared(); + auto toks = res->tokenize(note::label, c); + + res->diagrams = toks.diagrams; + + if (!toks.param.empty()) + res->position = toks.param; + + res->text = toks.text; return res; } std::shared_ptr skip::from_string(std::string_view c) { - auto res = std::make_shared(); - return res; + return std::make_shared(); } std::shared_ptr skip_relationship::from_string(std::string_view c) { - auto res = std::make_shared(); - return res; + return std::make_shared(); } std::shared_ptr style::from_string(std::string_view c) { auto res = std::make_shared