• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyberZHG / SVGDiagram / 20417213331

21 Dec 2025 11:10PM UTC coverage: 98.755% (+0.05%) from 98.705%
20417213331

push

github

web-flow
Add docs for background color (#33)

1745 of 1767 relevant lines covered (98.75%)

1242.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.59
/src/xml_element.cpp
1
#include "xml_element.h"
2
#include "attribute_utils.h"
3

4
#include <format>
5
#include <cmath>
6
using namespace std;
7
using namespace svg_diagram;
8

9
XMLElement::XMLElement(const string& tag) {
3,625✔
10
    _tag = tag;
3,625✔
11
}
3,625✔
12

13
XMLElement::XMLElement(const string& tag, const string& content) {
3✔
14
    _tag = tag;
3✔
15
    _content = content;
3✔
16
}
3✔
17

18
XMLElement::XMLElement(const string& tag, const AttributesType& attributes) {
11✔
19
    _tag = tag;
11✔
20
    addAttributes(attributes);
11✔
21
}
11✔
22

23
XMLElement::XMLElement(const string& tag, const AttributesType& attributes, const ChildType &child) {
1✔
24
    _tag = tag;
1✔
25
    addAttributes(attributes);
1✔
26
    addChild(child);
1✔
27
}
1✔
28

29
XMLElement::XMLElement(const string& tag, const AttributesType& attributes, const ChildrenType& children) {
3✔
30
    _tag = tag;
3✔
31
    addAttributes(attributes);
3✔
32
    addChildren(children);
3✔
33
}
3✔
34

35
XMLElement::XMLElement(const string& tag, const AttributesType& attributes, const string& content) {
1✔
36
    _tag = tag;
1✔
37
    addAttributes(attributes);
1✔
38
    _content = content;
1✔
39
}
1✔
40

41
void XMLElement::setTag(const string& tag) {
1✔
42
    _tag = tag;
1✔
43
}
1✔
44

45
void XMLElement::addAttribute(const string& name, const string& value) {
9,044✔
46
    if (!_attributes.contains(name)) {
9,044✔
47
        _attributeKeys.push_back(name);
9,044✔
48
    }
49
    _attributes.emplace(name, escapeAttributeValue(value));
9,044✔
50
}
9,044✔
51

52
void XMLElement::addAttribute(const string& name, const double value) {
3,481✔
53
    if (!_attributes.contains(name)) {
3,481✔
54
        _attributeKeys.push_back(name);
3,481✔
55
    }
56
    _attributes.emplace(name, format("{}", value));
3,481✔
57
}
3,481✔
58

59
void XMLElement::addAttributes(const AttributesType& attributes) {
16✔
60
    for (const auto& [key, value] : attributes) {
31✔
61
        addAttribute(key, value);
15✔
62
    }
63
}
16✔
64

65
void XMLElement::addChild(const ChildType& child) {
4,474✔
66
    _children.emplace_back(child);
4,474✔
67
}
4,474✔
68

69
void XMLElement::addChildren(const ChildrenType& children) {
3,085✔
70
    for (auto& child : children) {
7,261✔
71
        addChild(child);
4,176✔
72
    }
73
}
3,085✔
74

75
const XMLElement::ChildrenType & XMLElement::children() const {
76✔
76
    return _children;
76✔
77
}
78

79
void XMLElement::setContent(const string& content) {
1,178✔
80
    _content = content;
1,178✔
81
}
1,178✔
82

83
string XMLElement::toString(const int indent) const {
2,540✔
84
    if (_tag.empty()) {
2,540✔
85
        string s;
2✔
86
        for (const auto& child : _children) {
6✔
87
            s += child->toString(indent);
4✔
88
        }
89
        return s;
2✔
90
    }
2✔
91
    const auto indentStr = string(indent, ' ');
2,538✔
92
    string s = indentStr + "<" + _tag;
2,538✔
93
    for (const auto& key : _attributeKeys) {
11,230✔
94
        s += format(R"( {}="{}")", key, _attributes.at(key));
8,692✔
95
    }
96
    if (_content.empty() && _children.empty()) {
2,538✔
97
        s += "/>\n";
912✔
98
    } else {
99
        s += ">";
1,626✔
100
        if (!_children.empty()) {
1,626✔
101
            s += "\n";
767✔
102
            if (_tag == "text") {
767✔
103
                // Since whitespace characters also affect text rendering,
104
                // special handling is required here to merge the `tspan` elements into a single line.
105
                s += string(indent + 2, ' ');
36✔
106
                for (const auto& child : _children) {
117✔
107
                    const auto childStr = child->toString(0);
81✔
108
                    s += childStr.substr(0, childStr.size() - 1);
81✔
109
                }
81✔
110
                s += '\n';
36✔
111
            } else {
112
                for (const auto& child : _children) {
3,634✔
113
                    s += child->toString(indent + 2);
2,903✔
114
                }
115
            }
116
            s += indentStr;
767✔
117
        }
118
        s += _content;
1,626✔
119
        s += "</" + _tag + ">\n";
1,626✔
120
    }
121
    return s;
2,538✔
122
}
2,538✔
123

124
std::string XMLElement::toString() const {
154✔
125
    return toString(0);
154✔
126
}
127

128
bool XMLElement::operator==(const XMLElement& other) const {
687✔
129
    static constexpr double EPSILON = 1e-6;
130
    if (_tag != other._tag) {
687✔
131
        return false;
1✔
132
    }
133
    if (_content != other._content) {
686✔
134
        return false;
2✔
135
    }
136
    if (_attributes.size() != other._attributes.size()) {
684✔
137
        return false;
1✔
138
    }
139
    const auto mayBeNumber = [](const string& s, const size_t index) {
7,711✔
140
        if (isdigit(s[index])) {
7,711✔
141
            return true;
2,548✔
142
        }
143
        if (index + 1 < s.size() && isdigit(s[index + 1])) {
5,163✔
144
            if (s[index] == '.' || s[index] == '+' || s[index] == '-') {
695✔
145
                return true;
272✔
146
            }
147
        }
148
        return false;
4,891✔
149
    };
150
    for (const auto& [key, value1] : _attributes) {
2,445✔
151
        if (!other._attributes.contains(key)) {
1,770✔
152
            return false;
8✔
153
        }
154
        const auto& value2 = other._attributes.at(key);
1,769✔
155
        const size_t n = value1.size(), m = value2.size();
1,769✔
156
        size_t i = 0, j = 0;
1,769✔
157
        while (i < n && j < m) {
8,064✔
158
            if (mayBeNumber(value1, i) && mayBeNumber(value2, j)) {
6,301✔
159
                size_t pos1, pos2;
160
                const double doubleValue1 = stod(value1.substr(i), &pos1);
1,410✔
161
                const double doubleValue2 = stod(value2.substr(j), &pos2);
1,410✔
162
                if (fabs(doubleValue1 - doubleValue2) > EPSILON) {
1,410✔
163
                    return false;
3✔
164
                }
165
                i += pos1;
1,407✔
166
                j += pos2;
1,407✔
167
            } else {
168
                if (value1[i++] != value2[j++]) {
4,891✔
169
                    return false;
3✔
170
                }
171
            }
172
        }
173
        if (i != n || j != m) {
1,763✔
174
            return false;
1✔
175
        }
176
    }
177
    if (_children.size() != other._children.size()) {
675✔
178
        return false;
1✔
179
    }
180
    for (int i = 0; i < static_cast<int>(_children.size()); ++i) {
1,279✔
181
        if (*_children[i].get() != *other._children[i].get()) {
606✔
182
            return false;
1✔
183
        }
184
    }
185
    return true;
673✔
186
}
187

188
XMLElement::ChildrenType XMLElement::parse(const string& source) {
139✔
189
    const auto [children, stop] = parse(source, 0);
139✔
190
    return children;
278✔
191
}
139✔
192

193
std::pair<XMLElement::ChildrenType, int> XMLElement::parse(const std::string &source, const int start) {
464✔
194
    constexpr int STATE_START = 0;
464✔
195
    int state = STATE_START;
464✔
196
    ChildrenType children;
464✔
197
    const int n = static_cast<int>(source.size());
464✔
198
    int i = start;
464✔
199
    while (i < n) {
14,139✔
200
        constexpr int STATE_TAG = 1;
13,536✔
201
        constexpr int STATE_FIND_CLOSE = 2;
13,536✔
202
        constexpr int STATE_ATTRIBUTE = 3;
13,536✔
203
        constexpr int STATE_CONTENT = 4;
13,536✔
204
        constexpr int STATE_CLOSE = 5;
13,536✔
205
        switch (state) {
13,536✔
206
            case STATE_START:
1,762✔
207
                while (i < n && source[i] != '<') {
6,312✔
208
                    ++i;
4,550✔
209
                }
210
                state = STATE_TAG;
1,762✔
211
                i += 1;
1,762✔
212
                break;
1,762✔
213
            case STATE_TAG: {
1,675✔
214
                if (source[i] == '/') {
1,675✔
215
                    return {children, i - 1};
650✔
216
                }
217
                if (source[i] == '!') {
1,350✔
218
                    i += 4;
253✔
219
                    const int commentStart = i;
253✔
220
                    for (; i + 2 < n; ++i) {
5,922✔
221
                        if (source[i] == '-' && source[i + 1] == '-' && source[i + 2] == '>') {
5,922✔
222
                            const auto comment = source.substr(commentStart, i - 1 - commentStart);
253✔
223
                            children.emplace_back(make_shared<XMLElementComment>(comment));
253✔
224
                            i += 3;
253✔
225
                            break;
253✔
226
                        }
253✔
227
                    }
228
                    state = STATE_START;
253✔
229
                } else {
230
                    const int tagStart = i;
1,097✔
231
                    while (i < n && isalpha(source[i])) {
5,295✔
232
                        ++i;
4,198✔
233
                    }
234
                    children.emplace_back(make_shared<XMLElement>(source.substr(tagStart, i - tagStart)));
1,097✔
235
                    state = STATE_FIND_CLOSE;
1,097✔
236
                }
237
                break;
1,350✔
238
            }
239
            case STATE_FIND_CLOSE: {
4,952✔
240
                while (i < n && isspace(source[i])) {
8,807✔
241
                    ++i;
3,855✔
242
                }
243
                if (i < n) {
4,952✔
244
                    if (source[i] == '>') {
4,952✔
245
                        state = STATE_CONTENT;
646✔
246
                        i += 1;
646✔
247
                    } else if (source[i] == '/') {
4,306✔
248
                        state = STATE_START;
451✔
249
                        i += 2;
451✔
250
                    } else {
251
                        state = STATE_ATTRIBUTE;
3,855✔
252
                    }
253
                }
254
                break;
4,952✔
255
            }
256
            case STATE_ATTRIBUTE: {
3,855✔
257
                const int keyStart = i;
3,855✔
258
                while (i < n && source[i] != '=') {
22,112✔
259
                    ++i;
18,257✔
260
                }
261
                const auto key = source.substr(keyStart, i - keyStart);
3,855✔
262
                i += 2;
3,855✔
263
                if (i < n) {
3,855✔
264
                    const int valueStart = i;
3,855✔
265
                    while (i < n && source[i] != '"') {
40,824✔
266
                        ++i;
36,969✔
267
                    }
268
                    const auto value = source.substr(valueStart, i - valueStart);
3,855✔
269
                    children[children.size() - 1]->addAttribute(key, value);
3,855✔
270
                    i += 1;
3,855✔
271
                }
3,855✔
272
                state = STATE_FIND_CLOSE;
3,855✔
273
                break;
3,855✔
274
            }
3,855✔
275
            case STATE_CONTENT: {
646✔
276
                const int contentStart = i;
646✔
277
                while (i < n && source[i] != '<') {
3,084✔
278
                    ++i;
2,438✔
279
                }
280
                if (i + 1 < n && source[i + 1] != '/') {
646✔
281
                    const auto [subChildren, nextIndex] = parse(source, i);
325✔
282
                    children[children.size() - 1]->addChildren(subChildren);
325✔
283
                    i = nextIndex;
325✔
284
                } else {
325✔
285
                    const auto content = source.substr(contentStart, i - contentStart);
321✔
286
                    children[children.size() - 1]->setContent(content);
321✔
287
                }
321✔
288
                state = STATE_CLOSE;
646✔
289
                break;
646✔
290
            }
291
            case STATE_CLOSE: {
646✔
292
                while (i < n && source[i] != '>') {
3,914✔
293
                    ++i;
3,268✔
294
                }
295
                i += 1;
646✔
296
                state = STATE_START;
646✔
297
                break;
646✔
298
            }
299
        }
300
    }
301
    return {children, n};
139✔
302
}
464✔
303

304
string XMLElement::escapeAttributeValue(const string& value) {
9,044✔
305
    string escapedValue;
9,044✔
306
    for (const auto ch : value) {
139,608✔
307
        if (ch == '"') {
130,564✔
308
            escapedValue += "&quot;";
1✔
309
        } else {
310
            escapedValue += ch;
130,563✔
311
        }
312
    }
313
    return escapedValue;
9,044✔
314
}
×
315

316
XMLElementComment::XMLElementComment(const string& content) {
854✔
317
    _content = content;
854✔
318
}
854✔
319

320
string XMLElementComment::toString(const int indent) const {
604✔
321
    string escapedComment;
604✔
322
    for (int i = 0; i < static_cast<int>(_content.length()); ++i) {
14,496✔
323
        if (i + 1 < static_cast<int>(_content.length()) && _content[i] == '-' && _content[i + 1] == '-') {
13,892✔
324
            escapedComment += "‑‑";
4✔
325
            ++i;
4✔
326
        } else {
327
            escapedComment += _content[i];
13,888✔
328
        }
329
    }
330
    const auto indentStr = string(indent, ' ');
604✔
331
    return indentStr + format("<!-- {} -->\n", escapedComment);
1,208✔
332
}
604✔
333

334
std::string XMLElementComment::toString() const {
2✔
335
    return toString(0);
2✔
336
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc