0.6.0
C++ to UML diagram generator based on Clang
Loading...
Searching...
No Matches
generator.h
Go to the documentation of this file.
1/**
2 * @file src/common/generators/graphml/generator.h
3 *
4 * Copyright (c) 2021-2025 Bartek Kryza <bkryza@gmail.com>
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18#pragma once
19
23#include "config/config.h"
24#include "util/error.h"
25#include "util/util.h"
26#include "version/version.h"
27
28#include <clang/Basic/Version.h>
29#include <clang/Frontend/CompilerInstance.h>
30#include <clang/Tooling/CompilationDatabase.h>
31#include <clang/Tooling/Tooling.h>
32#include <glob/glob.hpp>
33#include <pugixml/pugixml.hpp>
34
35#include <ostream>
36
38
45
46using graphml_t = pugi::xml_document;
47using graphml_node_t = pugi::xml_node;
48
49/**
50 * The types of graph nodes in the GraphML XML document
51 */
52enum class xml_node_t { kGraph, kNode, kEdge };
53
54/**
55 * Types of data node attributes that can be added to GraphML nodes
56 * (xs:NMTOKEN)
57 */
59
60std::string to_string(property_type t);
61
62using key_property_map_t = std::map</*property name*/ std::string,
63 std::pair</*property id*/ std::string, /*property type*/ property_type>>;
64
65/**
66 * Mapping of GraphML node properties and their keys. Necessary to generate
67 * the <key> property definition elements at the beginning of GraphML document.
68 */
70public:
71 property_keymap_t(std::string prefix);
72
73 [[maybe_unused]] std::pair<std::string, property_type> add(
74 const std::string &name, property_type pt = property_type::kString);
75
76 std::optional<std::pair<std::string, property_type>> get(
77 const std::string &name) const;
78
79private:
80 uint64_t next_data_key_id_{0};
81
82 std::string prefix_;
84};
85
86/**
87 * Mapping of diagram elements to their GraphML node ids. Each type of node
88 * has a different prefix. With parser attribute set to `canonical`, the nodes
89 * must have a `n` prefix and edges must have a `e` prefix.
90 */
92public:
93 graphml_node_map_t(std::string prefix);
94
95 [[maybe_unused]] std::string add(const std::string &name);
96
97 std::optional<std::string> get(const std::string &name) const;
98
99private:
100 uint64_t next_node_id_{0};
101
102 std::string prefix_;
103 std::map<std::string, std::string> map_;
104};
105
106/**
107 * @brief Base class for diagram generators
108 *
109 * @tparam ConfigType Configuration type
110 * @tparam DiagramType Diagram model type
111 */
112template <typename ConfigType, typename DiagramType>
114 : public clanguml::common::generators::generator<ConfigType, DiagramType> {
115public:
117 DiagramType>::generator;
118 ~generator() override = default;
119
120 virtual std::vector<std::pair<std::string, property_type>>
122 {
123 using namespace std::string_literals;
124 return {
125 {"id"s, property_type::kString},
126 {"diagram_type"s, property_type::kString},
127 {"name"s, property_type::kString},
128 {"using_namespace"s, property_type::kString},
129 };
130 }
131
132 virtual std::vector<std::pair<std::string, property_type>>
134 {
135 using namespace std::string_literals;
136 return {{"id"s, property_type::kString},
137 {"type"s, property_type::kString},
138 {"name"s, property_type::kString},
139 {"stereotype"s, property_type::kString},
140 {"url"s, property_type::kString},
141 {"tooltip"s, property_type::kString}};
142 }
143
144 virtual std::vector<std::pair<std::string, property_type>>
146 {
147 using namespace std::string_literals;
148 return {{"type"s, property_type::kString},
149 {"access"s, property_type::kString},
150 {"label"s, property_type::kString},
151 {"url"s, property_type::kString}};
152 }
153
154 /**
155 * @brief Generate diagram
156 *
157 * This is the main diagram generation entrypoint. It is responsible for
158 * calling other methods in appropriate order to generate the diagram
159 * into the output stream. It generates diagram elements, that are
160 * common to all types of diagrams in a given generator.
161 *
162 * @param ostr Output stream
163 */
164 void generate(std::ostream &ostr) const override;
165
166 /**
167 * @brief Generate diagram model
168 *
169 * This method must be implemented in subclasses for specific diagram
170 * types.
171 *
172 * @param ostr Output stream
173 */
175 {
177
179 DiagramType>::model(),
180 parent);
181
183 }
184
185 /**
186 * @brief In a nested diagram, generate the top level elements.
187 *
188 * This method iterates over the top level elements. In case the diagram
189 * is nested (i.e. includes packages), for each package it recursively
190 * call generation of elements contained in each package.
191 *
192 * @param parent GraphML node
193 */
194 virtual void generate_top_level_elements(graphml_node_t &parent) const = 0;
195
196 /**
197 * @brief Generate any notes to be attached to diagram elements
198 *
199 * @param e Diagram element
200 * @param parent Parent GraphML package node to attach the note to
201 */
202 template <typename T>
203 void generate_notes(const T &e, graphml_node_t &parent) const;
204
205 /**
206 * @brief Generate metadata element with diagram metadata
207 *
208 * @param parent Root JSON object
209 */
210 void generate_metadata(graphml_t &parent) const;
211
212 /**
213 * @brief Generate diagram package
214 *
215 * @param p Diagram package element
216 * @param parent Parent JSON node
217 */
218 virtual void generate(const model::package &p, graphml_node_t &parent) const
219 {
220 }
221
222 /**
223 * @brief Generate all relationships in the diagram.
224 *
225 * @param parent GraphML node
226 */
227 virtual void generate_relationships(graphml_node_t &parent) const;
228
229 /**
230 * @brief Generate all relationships originating at a diagram element.
231 *
232 * @tparam T Type of diagram element
233 * @param c Diagram diagram element
234 * @param parent JSON node
235 */
236 virtual void generate_relationships(
237 const model::diagram_element &c, graphml_node_t &parent) const;
238
239 template <typename T>
240 void generate_link(pugi::xml_node &node, const T &c) const;
241
243 {
244 return graph_properties_;
245 }
246
248 {
249 return node_properties_;
250 }
251
253 {
254 return edge_properties_;
255 }
256
257 pugi::xml_node make_node(
258 graphml_node_t &parent, const std::string &id) const;
259
260 pugi::xml_node make_graph(
261 graphml_node_t &parent, const std::string &id) const;
262
263 pugi::xml_node make_subgraph(graphml_node_t &parent, const std::string &id,
264 const std::string &name = "", const std::string &type = "") const;
265
266 void add_data(pugi::xml_node &node, const std::string &key,
267 const std::string &value, bool cdata = false) const;
268
269 void add_cdata(pugi::xml_node &node, const std::string &key,
270 const std::string &value) const;
271
272protected:
273 void generate_key(pugi::xml_node &parent, const std::string &attr_name,
274 const std::string &for_value, const std::string &id_value,
275 const std::string &attr_type = "string") const;
276
280
284
285private:
286 void generate_keys(graphml_node_t &parent) const;
287
291
292 mutable uint64_t edge_id_{0};
293 mutable uint64_t note_id_{0};
294};
295
296template <typename DiagramModel, typename DiagramConfig>
297std::ostream &operator<<(
298 std::ostream &os, const generator<DiagramModel, DiagramConfig> &g)
299{
300 g.generate(os);
301 return os;
302}
303
304template <typename C, typename D>
305void generator<C, D>::generate(std::ostream &ostr) const
306{
307 const auto &config = generators::generator<C, D>::config();
308
309 graphml_t graph;
310
311 auto node_decl = graph.append_child(pugi::node_declaration);
312 node_decl.append_attribute("version") = "1.0";
313 node_decl.append_attribute("encoding") = "UTF-8";
314
315 generate_metadata(graph);
316
317 auto graphml = graph.append_child("graphml");
318 graphml.append_attribute("xmlns") = "http://graphml.graphdrawing.org/xmlns";
319 graphml.append_attribute("xmlns:xsi") =
320 "http://www.w3.org/2001/XMLSchema-instance";
321 graphml.append_attribute("xsi:schemaLocation") =
322 "http://graphml.graphdrawing.org/xmlns "
323 "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd";
324
325 if (config.title) {
326 graphml.append_child("desc")
327 .append_child(pugi::node_cdata)
328 .set_value(config.title());
329 }
330
331 generate_keys(graphml);
332
333 auto graph_node = make_graph(graphml, "G");
334
335 if (config.using_namespace) {
336 add_data(graph_node, "using_namespace",
337 config.using_namespace().to_string());
338 }
339
340 generate_diagram(graph_node);
341
342 graph.save(ostr, " ");
343}
344
345template <typename C, typename D>
347 graphml_node_t &parent) const
348{
349 generator<C, D>::model().for_all_elements([&](auto &&view) {
350 for (const auto &el : view)
351 generate_relationships(el, parent);
352 });
353}
354
355template <typename C, typename D>
357 const model::diagram_element &c, graphml_node_t &parent) const
358{
359 const auto &model = generator<C, D>::model();
360
361 for (const auto &r : c.relationships()) {
362 auto target_element = model.get(r.destination());
363 if (!target_element.has_value()) {
364 LOG_DBG("Skipping {} relation from '{}' to '{}' due "
365 "to unresolved destination id",
366 to_string(r.type()), c.full_name(true),
367 r.destination().value());
368 continue;
369 }
370
371 const auto maybe_target_id =
372 node_ids_.get(target_element.value().alias());
373 const auto maybe_src_id = node_ids_.get(c.alias());
374
375 if (!maybe_src_id || !maybe_target_id)
376 continue;
377
378 auto edge_node = parent.append_child("edge");
379
380 edge_node.append_attribute("id") = fmt::format("e{}", edge_id_++);
381 edge_node.append_attribute("source") = *maybe_src_id;
382 edge_node.append_attribute("target") = *maybe_target_id;
383
384 const auto maybe_link = generator<C, D>::render_link(r);
385
386 if (maybe_link) {
387 add_data(edge_node, "url", *maybe_link);
388 }
389
390 add_data(edge_node, "type", to_string(r.type()));
391 if (!r.label().empty())
392 add_data(edge_node, "label", r.label());
393
394 if (r.access() != access_t::kNone)
395 add_data(edge_node, "access", to_string(r.access()));
396 }
397}
398
399template <typename C, typename D>
400template <typename T>
401void generator<C, D>::generate_notes(const T &p, graphml_node_t &parent) const
402{
403 const auto &config = generators::generator<C, D>::config();
404
405 std::vector<std::pair</* element node id */ std::string,
406 /* note node id */ std::string>>
407 note_id_map;
408
409 // First generate the note XML nodes
410 auto note_index{0U};
411 for (const auto &e : p) {
412 // First try to extract notes from comment decorators (i.e. \uml
413 // directives in code comments)
414 for (const auto &decorator : e->decorators()) {
415 auto note = std::dynamic_pointer_cast<decorators::note>(decorator);
416 if (note && note->applies_to_diagram(config.name)) {
417 auto note_id = node_ids_.add(
418 fmt::format("{}-N_{}", e->alias(), note_index));
419 auto maybe_element_node_id = node_ids_.get(e->alias());
420 if (maybe_element_node_id) {
421 auto note_node = make_node(parent, note_id);
422 add_data(note_node, "type", "note");
423 add_data(note_node, "name", note->text, true);
424 note_id_map.emplace_back(*maybe_element_node_id, note_id);
425 note_index++;
426 }
427 }
428 }
429
430 // Now try to extract notes from `graphml` configuration file section
431 if (config.graphml &&
432 config.graphml().notes.count(e->full_name(false))) {
433 for (const auto &note :
434 config.graphml().notes.at(e->full_name(false))) {
435 auto note_id = node_ids_.add(
436 fmt::format("{}-N_{}", e->alias(), note_index));
437 auto maybe_element_node_id = node_ids_.get(e->alias());
438 if (maybe_element_node_id) {
439 auto rendered_note = common::jinja::render_template(
440 generator<C, D>::env(), note);
441 if (rendered_note) {
442 auto note_node = make_node(parent, note_id);
443 add_data(note_node, "type", "note");
444 add_data(note_node, "name", *rendered_note, true);
445 note_id_map.emplace_back(
446 *maybe_element_node_id, note_id);
447 note_index++;
448 }
449 }
450 }
451 }
452 }
453
454 // Now generate the edges connecting note nodes to element notes
455 for (const auto &[element_node_id, note_node_id] : note_id_map) {
456 auto edge_node = parent.append_child("edge");
457
458 edge_node.append_attribute("id") = fmt::format("e{}", edge_id_++);
459 edge_node.append_attribute("source") = note_node_id;
460 edge_node.append_attribute("target") = element_node_id;
461
462 add_data(edge_node, "type", "none");
463 }
464}
465
466template <typename C, typename D>
468{
469 if (generators::generator<C, D>::config().generate_metadata()) {
470 auto comment = parent.append_child(pugi::node_comment);
471 comment.set_value(fmt::format(
472 " Generated with clang-uml {} ", clanguml::version::version()));
473
474 comment = parent.append_child(pugi::node_comment);
475 comment.set_value(
476 fmt::format(" LLVM version {} ", clang::getClangFullVersion()));
477 }
478}
479
480template <typename C, typename D>
482 graphml_node_t &parent, const std::string &id) const
483{
484 auto result = parent.append_child("node");
485 result.append_attribute("id") = id;
486 return result;
487}
488
489template <typename C, typename D>
491 graphml_node_t &parent, const std::string &id) const
492{
493 auto result = parent.append_child("graph");
494 result.append_attribute("id") = graph_ids_.add(id);
495 result.append_attribute("edgedefault").set_value("directed");
496 result.append_attribute("parse.nodeids").set_value("canonical");
497 result.append_attribute("parse.edgeids").set_value("canonical");
498 result.append_attribute("parse.order").set_value("nodesfirst");
499 return result;
500}
501
502template <typename C, typename D>
504 const std::string &id, const std::string &name,
505 const std::string &type) const
506{
507 auto graph_node = parent.append_child("node");
508 graph_node.append_attribute("id") = node_ids_.add(id);
509
510 if (!name.empty())
511 add_data(graph_node, "name", name);
512
513 if (!type.empty())
514 add_data(graph_node, "type", type);
515
516 return graph_node;
517}
518
519template <typename C, typename D>
520void generator<C, D>::add_data(pugi::xml_node &node,
521 const std::string &key_name, const std::string &value, bool cdata) const
522{
523 using namespace std::string_literals;
524
525 std::optional<std::pair<std::string, property_type>> key_id;
526 if (node.name() == "node"s)
527 key_id = node_properties().get(key_name);
528 else if (node.name() == "graph"s)
529 key_id = graph_properties().get(key_name);
530 else if (node.name() == "edge"s)
531 key_id = edge_properties().get(key_name);
532
533 if (key_id.has_value()) {
534 auto data = node.append_child("data");
535 data.append_attribute("key") = key_id->first;
536 if (cdata)
537 data.append_child(pugi::node_cdata).set_value(value);
538 else
539 data.text().set(value);
540 }
541}
542
543template <typename C, typename D>
544void generator<C, D>::add_cdata(pugi::xml_node &node,
545 const std::string &key_name, const std::string &value) const
546{
547 return add_data(node, key_name, value, true);
548}
549
550template <typename C, typename D>
551template <typename T>
552void generator<C, D>::generate_link(pugi::xml_node &node, const T &c) const
553{
554 const auto maybe_link = generator<C, D>::render_link(c);
555 const auto maybe_tooltip = generator<C, D>::render_tooltip(c);
556
557 if (maybe_link) {
558 add_data(node, "url", *maybe_link);
559 }
560
561 if (maybe_tooltip) {
562 add_data(node, "tooltip", *maybe_tooltip);
563 }
564}
565
566template <typename C, typename D>
567void generator<C, D>::generator::generate_key(pugi::xml_node &parent,
568 const std::string &attr_name, const std::string &for_value,
569 const std::string &id_value, const std::string &attr_type) const
570{
571 auto key = parent.append_child("key");
572 key.append_attribute("attr.name") = attr_name.c_str();
573 key.append_attribute("attr.type") = attr_type.c_str();
574 key.append_attribute("for") = for_value.c_str();
575 key.append_attribute("id") = id_value.c_str();
576}
577
578template <typename C, typename D>
580{
581 for (const auto &[name, type] : graph_property_names()) {
582 const auto [id, t] = graph_properties_.add(name);
583 generate_key(parent, name, "graph", id, to_string(type));
584 }
585
586 for (const auto &[name, type] : node_property_names()) {
587 const auto [id, t] = node_properties_.add(name);
588 generate_key(parent, name, "node", id, to_string(type));
589 }
590
591 for (const auto &[name, type] : edge_property_names()) {
592 const auto [id, t] = edge_properties_.add(name);
593 generate_key(parent, name, "edge", id, to_string(type));
594 }
595}
596} // namespace clanguml::common::generators::graphml