载入中...
搜索中...
未找到
Xml.cpp
浏览该文件的文档.
1#include "common/Xml.h"
2
3#include <cctype>
4#include <cstdlib>
5#include <functional>
6#include <utility>
7
8namespace eve::xml {
9
10struct Node {
11 std::string tag;
12 std::unordered_map<std::string, std::string> attributes;
13 std::vector<std::unique_ptr<Node>> children;
14 Node* parent = nullptr;
15};
16
17namespace {
18
19class Parser {
20public:
21 explicit Parser(const std::string& text) : s_(text) {}
22
23 bool parse(std::unique_ptr<Node>& out, std::string* error) {
24 skipMisc();
25 if (!parseElement(out, nullptr, 0)) {
26 if (error && error->empty())
27 *error = "invalid XML near offset " + std::to_string(pos_);
28 return false;
29 }
30 skipMisc();
31 if (pos_ != s_.size()) {
32 if (error) *error = "trailing data at offset " + std::to_string(pos_);
33 return false;
34 }
35 return true;
36 }
37
38private:
39 static constexpr size_t kMaxDepth = 256;
40 const std::string& s_;
41 size_t pos_ = 0;
42
43 void skipWs() {
44 while (pos_ < s_.size() &&
45 (s_[pos_] == ' ' || s_[pos_] == '\t' || s_[pos_] == '\n' || s_[pos_] == '\r'))
46 ++pos_;
47 }
48
49 bool startsWith(const char* lit) const {
50 const size_t n = std::char_traits<char>::length(lit);
51 return pos_ + n <= s_.size() && s_.compare(pos_, n, lit) == 0;
52 }
53
54 bool skipComment() {
55 if (!startsWith("<!--")) return false;
56 pos_ += 4;
57 while (pos_ + 2 < s_.size()) {
58 if (s_[pos_] == '-' && s_[pos_ + 1] == '-' && s_[pos_ + 2] == '>') {
59 pos_ += 3;
60 return true;
61 }
62 ++pos_;
63 }
64 return false;
65 }
66
67 bool skipProcessingInstruction() {
68 if (!startsWith("<?")) return false;
69 pos_ += 2;
70 while (pos_ + 1 < s_.size()) {
71 if (s_[pos_] == '?' && s_[pos_ + 1] == '>') {
72 pos_ += 2;
73 return true;
74 }
75 ++pos_;
76 }
77 return false;
78 }
79
80 bool skipDoctype() {
81 if (!startsWith("<!DOCTYPE") && !startsWith("<!doctype")) return false;
82 pos_ += 9;
83 int depth = 1;
84 while (pos_ < s_.size() && depth > 0) {
85 if (s_[pos_] == '<')
86 ++depth;
87 else if (s_[pos_] == '>')
88 --depth;
89 ++pos_;
90 }
91 return depth == 0;
92 }
93
94 void skipMisc() {
95 for (;;) {
96 skipWs();
97 if (skipComment() || skipProcessingInstruction() || skipDoctype()) continue;
98 break;
99 }
100 }
101
102 bool parseName(std::string& out) {
103 if (pos_ >= s_.size()) return false;
104 const unsigned char c0 = static_cast<unsigned char>(s_[pos_]);
105 if (!(std::isalpha(c0) || c0 == '_' || c0 == ':')) return false;
106 const size_t start = pos_++;
107 while (pos_ < s_.size()) {
108 const unsigned char c = static_cast<unsigned char>(s_[pos_]);
109 if (std::isalnum(c) || c == '_' || c == '-' || c == '.' || c == ':')
110 ++pos_;
111 else
112 break;
113 }
114 out = s_.substr(start, pos_ - start);
115 return !out.empty();
116 }
117
118 bool decodeEntity(std::string& out) {
119 if (pos_ >= s_.size() || s_[pos_] != '&') return false;
120 ++pos_;
121 if (startsWith("amp;")) {
122 pos_ += 4;
123 out += '&';
124 return true;
125 }
126 if (startsWith("lt;")) {
127 pos_ += 3;
128 out += '<';
129 return true;
130 }
131 if (startsWith("gt;")) {
132 pos_ += 3;
133 out += '>';
134 return true;
135 }
136 if (startsWith("quot;")) {
137 pos_ += 5;
138 out += '"';
139 return true;
140 }
141 if (startsWith("apos;")) {
142 pos_ += 5;
143 out += '\'';
144 return true;
145 }
146 if (pos_ < s_.size() && s_[pos_] == '#') {
147 ++pos_;
148 unsigned long code = 0;
149 if (pos_ < s_.size() && (s_[pos_] == 'x' || s_[pos_] == 'X')) {
150 ++pos_;
151 const size_t start = pos_;
152 while (pos_ < s_.size() && std::isxdigit(static_cast<unsigned char>(s_[pos_]))) ++pos_;
153 if (pos_ == start || pos_ >= s_.size() || s_[pos_] != ';') return false;
154 code = std::strtoul(s_.substr(start, pos_ - start).c_str(), nullptr, 16);
155 } else {
156 const size_t start = pos_;
157 while (pos_ < s_.size() && std::isdigit(static_cast<unsigned char>(s_[pos_]))) ++pos_;
158 if (pos_ == start || pos_ >= s_.size() || s_[pos_] != ';') return false;
159 code = std::strtoul(s_.substr(start, pos_ - start).c_str(), nullptr, 10);
160 }
161 ++pos_; // ';'
162 if (code == 0 || code > 0x10FFFFul) return false;
163 if (code < 0x80) {
164 out += static_cast<char>(code);
165 } else if (code < 0x800) {
166 out += static_cast<char>(0xC0 | (code >> 6));
167 out += static_cast<char>(0x80 | (code & 0x3F));
168 } else if (code < 0x10000) {
169 out += static_cast<char>(0xE0 | (code >> 12));
170 out += static_cast<char>(0x80 | ((code >> 6) & 0x3F));
171 out += static_cast<char>(0x80 | (code & 0x3F));
172 } else {
173 out += static_cast<char>(0xF0 | (code >> 18));
174 out += static_cast<char>(0x80 | ((code >> 12) & 0x3F));
175 out += static_cast<char>(0x80 | ((code >> 6) & 0x3F));
176 out += static_cast<char>(0x80 | (code & 0x3F));
177 }
178 return true;
179 }
180 return false;
181 }
182
183 bool parseAttributeValue(std::string& out) {
184 if (pos_ >= s_.size()) return false;
185 const char quote = s_[pos_];
186 if (quote != '"' && quote != '\'') return false;
187 ++pos_;
188 out.clear();
189 while (pos_ < s_.size()) {
190 const char c = s_[pos_];
191 if (c == quote) {
192 ++pos_;
193 return true;
194 }
195 if (c == '&') {
196 if (!decodeEntity(out)) return false;
197 continue;
198 }
199 if (c == '<') return false;
200 out += c;
201 ++pos_;
202 }
203 return false;
204 }
205
206 bool parseAttributes(Node& node) {
207 for (;;) {
208 skipWs();
209 if (pos_ >= s_.size()) return false;
210 if (s_[pos_] == '/' || s_[pos_] == '>') return true;
211 std::string name;
212 if (!parseName(name)) return false;
213 skipWs();
214 if (pos_ >= s_.size() || s_[pos_] != '=') return false;
215 ++pos_;
216 skipWs();
217 std::string value;
218 if (!parseAttributeValue(value)) return false;
219 node.attributes[std::move(name)] = std::move(value);
220 }
221 }
222
223 bool skipTextUntilTag() {
224 while (pos_ < s_.size() && s_[pos_] != '<') {
225 if (s_[pos_] == '&') {
226 std::string discard;
227 if (!decodeEntity(discard)) return false;
228 } else {
229 ++pos_;
230 }
231 }
232 return true;
233 }
234
235 bool parseElement(std::unique_ptr<Node>& out, Node* parent, size_t depth) {
236 if (depth > kMaxDepth) return false;
237 skipMisc();
238 if (pos_ >= s_.size() || s_[pos_] != '<') return false;
239 if (startsWith("</") || startsWith("<?") || startsWith("<!")) return false;
240 ++pos_;
241 auto node = std::make_unique<Node>();
242 node->parent = parent;
243 if (!parseName(node->tag)) return false;
244 if (!parseAttributes(*node)) return false;
245 skipWs();
246 if (pos_ >= s_.size()) return false;
247 if (s_[pos_] == '/') {
248 ++pos_;
249 if (pos_ >= s_.size() || s_[pos_] != '>') return false;
250 ++pos_;
251 out = std::move(node);
252 return true;
253 }
254 if (s_[pos_] != '>') return false;
255 ++pos_;
256
257 for (;;) {
258 if (!skipTextUntilTag()) return false;
259 if (pos_ >= s_.size() || s_[pos_] != '<') return false;
260 if (startsWith("</")) {
261 pos_ += 2;
262 std::string endTag;
263 if (!parseName(endTag)) return false;
264 skipWs();
265 if (pos_ >= s_.size() || s_[pos_] != '>') return false;
266 ++pos_;
267 if (endTag != node->tag) return false;
268 out = std::move(node);
269 return true;
270 }
271 if (startsWith("<!--")) {
272 if (!skipComment()) return false;
273 continue;
274 }
275 if (startsWith("<?")) {
276 if (!skipProcessingInstruction()) return false;
277 continue;
278 }
279 if (startsWith("<![CDATA[")) {
280 pos_ += 9;
281 while (pos_ + 2 < s_.size()) {
282 if (s_[pos_] == ']' && s_[pos_ + 1] == ']' && s_[pos_ + 2] == '>') {
283 pos_ += 3;
284 break;
285 }
286 ++pos_;
287 }
288 continue;
289 }
290 std::unique_ptr<Node> child;
291 if (!parseElement(child, node.get(), depth + 1)) return false;
292 node->children.push_back(std::move(child));
293 }
294 }
295};
296
297} // namespace
298
299Document::Document() = default;
300Document::~Document() = default;
301Document::Document(Document&&) noexcept = default;
302Document& Document::operator=(Document&&) noexcept = default;
303
304Document Document::parse(const std::string& text, std::string* error) {
305 Document doc;
306 if (error) error->clear();
307 Parser parser(text);
308 if (!parser.parse(doc.root_, error)) {
309 doc.root_.reset();
310 return doc;
311 }
312 return doc;
313}
314
315std::string Element::tagName() const { return node_ ? node_->tag : std::string{}; }
316
318 return node_ && node_->parent ? Element(node_->parent) : Element();
319}
320
321bool Element::hasAttribute(const char* name) const {
322 return node_ && name && node_->attributes.find(name) != node_->attributes.end();
323}
324
325std::string Element::getAttribute(const char* name, const std::string& fallback) const {
326 if (!node_ || !name) return fallback;
327 const auto it = node_->attributes.find(name);
328 return it == node_->attributes.end() ? fallback : it->second;
329}
330
331int Element::getIntAttribute(const char* name, int fallback) const {
332 if (!hasAttribute(name)) return fallback;
333 try {
334 return std::stoi(getAttribute(name));
335 } catch (...) {
336 return fallback;
337 }
338}
339
340std::vector<Element> Element::children(const char* tag) const {
341 std::vector<Element> out;
342 if (!node_) return out;
343 for (const auto& child : node_->children) {
344 if (!tag || child->tag == tag) out.emplace_back(Element(child.get()));
345 }
346 return out;
347}
348
349std::vector<Element> Element::elementsByTag(const char* tag) const {
350 std::vector<Element> out;
351 if (!node_ || !tag) return out;
352 std::function<void(const Node*)> walk = [&](const Node* node) {
353 for (const auto& child : node->children) {
354 if (child->tag == tag) out.emplace_back(Element(child.get()));
355 walk(child.get());
356 }
357 };
358 walk(node_);
359 return out;
360}
361
362} // namespace eve::xml
double value
Duration start
DiagnosticCode code
glm::vec3 n
Definition Grass.cpp:63
std::int32_t c
std::string text
char quote
std::int32_t parent
std::string name
const std::string * tag
std::string error
Definition Package.cpp:60
const RoadNode * node
std::uint32_t depth
bool hasAttribute(const char *name) const
Definition Xml.cpp:321
std::vector< Element > children(const char *tag=nullptr) const
Definition Xml.cpp:340
Element parent() const
Definition Xml.cpp:317
std::string tagName() const
Definition Xml.cpp:315
std::string getAttribute(const char *name, const std::string &fallback={}) const
Definition Xml.cpp:325
int getIntAttribute(const char *name, int fallback=0) const
Definition Xml.cpp:331
std::vector< Element > elementsByTag(const char *tag) const
Definition Xml.cpp:349
std::vector< WidgetDesc > children
Definition Widget.h:111
std::string tag
Definition Xml.cpp:11
std::unordered_map< std::string, std::string > attributes
Definition Xml.cpp:12
std::vector< std::unique_ptr< Node > > children
Definition Xml.cpp:13
Node * parent
Definition Xml.cpp:14