← Documentation
ADS-IDAC Engine 5.0.0
xml_lite.cpp
Go to the documentation of this file.
2
3#include <cctype>
4#include <fstream>
5#include <sstream>
6
7namespace faulttree::xml {
8
9const std::string *Element::attribute(const std::string &name) const {
10 for (const auto &attr : attributes) {
11 if (attr.name == name) { return &attr.value; }
12 }
13 return nullptr;
14}
15
16std::string Element::attributeOr(const std::string &name,
17 const std::string &fallback) const {
18 const std::string *value = attribute(name);
19 return value ? *value : fallback;
20}
21
22const Element *Element::child(const std::string &name) const {
23 for (const auto &element : children) {
24 if (element.name == name) { return &element; }
25 }
26 return nullptr;
27}
28
29std::vector<const Element *>
30Element::childrenNamed(const std::string &name) const {
31 std::vector<const Element *> found;
32 for (const auto &element : children) {
33 if (element.name == name) { found.push_back(&element); }
34 }
35 return found;
36}
37
38namespace {
39
40bool isNameStart(char c) {
41 return std::isalpha(static_cast<unsigned char>(c)) != 0 || c == '_' || c == ':';
42}
43
44bool isNameChar(char c) {
45 return std::isalnum(static_cast<unsigned char>(c)) != 0 || c == '_' ||
46 c == '-' || c == '.' || c == ':';
47}
48
49class Parser {
50public:
51 explicit Parser(const std::string &text) : text_(text) {}
52
53 Element parseDocument() {
54 skipProlog();
55 if (!looking_at("<")) { fail("expected a root element"); }
56 Element root = parseElement();
57 skipTrivia();
58 if (pos_ != text_.size()) { fail("trailing content after the root element"); }
59 return root;
60 }
61
62private:
63 [[noreturn]] void fail(const std::string &message) const {
64 std::size_t line = 1;
65 std::size_t column = 1;
66 for (std::size_t i = 0; i < pos_ && i < text_.size(); ++i) {
67 if (text_[i] == '\n') {
68 ++line;
69 column = 1;
70 } else {
71 ++column;
72 }
73 }
74 std::ostringstream out;
75 out << "XML parse error at line " << line << ", column " << column << ": "
76 << message;
77 throw XmlError(out.str());
78 }
79
80 [[nodiscard]] bool eof() const { return pos_ >= text_.size(); }
81
82 [[nodiscard]] bool looking_at(const char *literal) const {
83 return text_.compare(pos_, std::char_traits<char>::length(literal),
84 literal) == 0;
85 }
86
87 void expect(const char *literal, const std::string &context) {
88 if (!looking_at(literal)) { fail("expected '" + std::string(literal) + "' " + context); }
89 pos_ += std::char_traits<char>::length(literal);
90 }
91
92 void skipSpace() {
93 while (!eof() && std::isspace(static_cast<unsigned char>(text_[pos_])) != 0) {
94 ++pos_;
95 }
96 }
97
99 void skipTrivia() {
100 for (;;) {
101 skipSpace();
102 if (looking_at("<!--")) {
103 std::size_t end = text_.find("-->", pos_ + 4);
104 if (end == std::string::npos) { fail("unterminated comment"); }
105 pos_ = end + 3;
106 continue;
107 }
108 return;
109 }
110 }
111
113 void skipProlog() {
114 skipTrivia();
115 if (looking_at("<?xml")) {
116 std::size_t end = text_.find("?>", pos_);
117 if (end == std::string::npos) { fail("unterminated XML declaration"); }
118 pos_ = end + 2;
119 skipTrivia();
120 }
121 }
122
123 std::string parseName(const std::string &context) {
124 if (eof() || !isNameStart(text_[pos_])) { fail("expected a name " + context); }
125 std::size_t start = pos_;
126 while (!eof() && isNameChar(text_[pos_])) { ++pos_; }
127 return text_.substr(start, pos_ - start);
128 }
129
130 std::string decodeEntities(const std::string &raw) {
131 if (raw.find('&') == std::string::npos) { return raw; }
132 std::string out;
133 out.reserve(raw.size());
134 for (std::size_t i = 0; i < raw.size(); ++i) {
135 if (raw[i] != '&') {
136 out.push_back(raw[i]);
137 continue;
138 }
139 std::size_t semi = raw.find(';', i);
140 if (semi == std::string::npos) { fail("unterminated entity reference"); }
141 std::string entity = raw.substr(i + 1, semi - i - 1);
142 if (entity == "amp") {
143 out.push_back('&');
144 } else if (entity == "lt") {
145 out.push_back('<');
146 } else if (entity == "gt") {
147 out.push_back('>');
148 } else if (entity == "quot") {
149 out.push_back('"');
150 } else if (entity == "apos") {
151 out.push_back('\'');
152 } else if (entity.size() > 1 && entity[0] == '#') {
153 int base = 10;
154 std::size_t offset = 1;
155 if (entity[1] == 'x' || entity[1] == 'X') {
156 base = 16;
157 offset = 2;
158 }
159 try {
160 unsigned long code = std::stoul(entity.substr(offset), nullptr, base);
161 if (code > 0x7F) {
162 fail("only ASCII character references are supported");
163 }
164 out.push_back(static_cast<char>(code));
165 } catch (const std::invalid_argument &) {
166 fail("malformed character reference '&" + entity + ";'");
167 } catch (const std::out_of_range &) {
168 fail("character reference out of range '&" + entity + ";'");
169 }
170 } else {
171 fail("unknown entity '&" + entity + ";'");
172 }
173 i = semi;
174 }
175 return out;
176 }
177
178 Attribute parseAttribute() {
179 Attribute attr;
180 attr.name = parseName("for an attribute");
181 skipSpace();
182 expect("=", "after attribute '" + attr.name + "'");
183 skipSpace();
184 if (eof() || (text_[pos_] != '"' && text_[pos_] != '\'')) {
185 fail("attribute '" + attr.name + "' must have a quoted value");
186 }
187 const char quote = text_[pos_++];
188 std::size_t start = pos_;
189 while (!eof() && text_[pos_] != quote) {
190 if (text_[pos_] == '<') { fail("'<' is not allowed inside an attribute value"); }
191 ++pos_;
192 }
193 if (eof()) { fail("unterminated attribute value for '" + attr.name + "'"); }
194 attr.value = decodeEntities(text_.substr(start, pos_ - start));
195 ++pos_; // closing quote
196 return attr;
197 }
198
199 Element parseElement() {
200 expect("<", "to open an element");
201 if (looking_at("!") || looking_at("?")) {
202 fail("DOCTYPE, CDATA and processing instructions are not supported");
203 }
204 Element element;
205 element.name = parseName("for an element");
206
207 for (;;) {
208 skipSpace();
209 if (looking_at("/>")) {
210 pos_ += 2;
211 return element; // self-closing
212 }
213 if (looking_at(">")) {
214 ++pos_;
215 break;
216 }
217 if (eof()) { fail("unterminated start tag '" + element.name + "'"); }
218 element.attributes.push_back(parseAttribute());
219 }
220
221 // Content: child elements and comments; text is not meaningful in the
222 // subset we accept, so it is skipped.
223 for (;;) {
224 skipTextAndComments();
225 if (eof()) { fail("unterminated element '" + element.name + "'"); }
226 if (looking_at("</")) {
227 pos_ += 2;
228 std::string closing = parseName("for a closing tag");
229 if (closing != element.name) {
230 fail("closing tag '" + closing + "' does not match '" + element.name + "'");
231 }
232 skipSpace();
233 expect(">", "to close '" + closing + "'");
234 return element;
235 }
236 element.children.push_back(parseElement());
237 }
238 }
239
240 void skipTextAndComments() {
241 for (;;) {
242 if (looking_at("<!--")) {
243 std::size_t end = text_.find("-->", pos_ + 4);
244 if (end == std::string::npos) { fail("unterminated comment"); }
245 pos_ = end + 3;
246 continue;
247 }
248 if (eof() || text_[pos_] == '<') { return; }
249 ++pos_;
250 }
251 }
252
253 const std::string &text_;
254 std::size_t pos_ = 0;
255};
256
257} // namespace
258
259Element parse(const std::string &text) { return Parser(text).parseDocument(); }
260
261Element parseFile(const std::string &path) {
262 std::ifstream in(path, std::ios::binary);
263 if (!in) { throw XmlError("cannot open '" + path + "'"); }
264 std::ostringstream buffer;
265 buffer << in.rdbuf();
266 return parse(buffer.str());
267}
268
269} // namespace faulttree::xml
Exception thrown by parse() and parseFile().
Definition: xml_lite.hpp:36
Element parseFile(const std::string &path)
Definition: xml_lite.cpp:261
Element parse(const std::string &text)
Definition: xml_lite.cpp:259
One XML element with its attributes and child elements.
Definition: xml_lite.hpp:53
std::vector< Attribute > attributes
in document order
Definition: xml_lite.hpp:55
std::string name
tag name as written
Definition: xml_lite.hpp:54
const Element * child(const std::string &name) const
Definition: xml_lite.cpp:22
const std::string * attribute(const std::string &name) const
Definition: xml_lite.cpp:9
std::vector< Element > children
child elements in document order
Definition: xml_lite.hpp:56
std::string attributeOr(const std::string &name, const std::string &fallback) const
Definition: xml_lite.cpp:16
std::vector< const Element * > childrenNamed(const std::string &name) const
Definition: xml_lite.cpp:30
std::size_t pos_
Definition: xml_lite.cpp:254
const std::string & text_
Definition: xml_lite.cpp:253
Minimal XML reader used by the .fta parser: the Element tree, its lookup helpers, and parse() / parse...