21 explicit Parser(
const std::string&
text) : s_(
text) {}
23 bool parse(std::unique_ptr<Node>& out, std::string*
error) {
25 if (!parseElement(out,
nullptr, 0)) {
27 *
error =
"invalid XML near offset " + std::to_string(pos_);
31 if (pos_ != s_.size()) {
32 if (
error) *
error =
"trailing data at offset " + std::to_string(pos_);
39 static constexpr size_t kMaxDepth = 256;
40 const std::string& s_;
44 while (pos_ < s_.size() &&
45 (s_[pos_] ==
' ' || s_[pos_] ==
'\t' || s_[pos_] ==
'\n' || s_[pos_] ==
'\r'))
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;
55 if (!startsWith(
"<!--"))
return false;
57 while (pos_ + 2 < s_.size()) {
58 if (s_[pos_] ==
'-' && s_[pos_ + 1] ==
'-' && s_[pos_ + 2] ==
'>') {
67 bool skipProcessingInstruction() {
68 if (!startsWith(
"<?"))
return false;
70 while (pos_ + 1 < s_.size()) {
71 if (s_[pos_] ==
'?' && s_[pos_ + 1] ==
'>') {
81 if (!startsWith(
"<!DOCTYPE") && !startsWith(
"<!doctype"))
return false;
84 while (pos_ < s_.size() &&
depth > 0) {
87 else if (s_[pos_] ==
'>')
97 if (skipComment() || skipProcessingInstruction() || skipDoctype())
continue;
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 ==
':')
118 bool decodeEntity(std::string& out) {
119 if (pos_ >= s_.size() || s_[pos_] !=
'&')
return false;
121 if (startsWith(
"amp;")) {
126 if (startsWith(
"lt;")) {
131 if (startsWith(
"gt;")) {
136 if (startsWith(
"quot;")) {
141 if (startsWith(
"apos;")) {
146 if (pos_ < s_.size() && s_[pos_] ==
'#') {
148 unsigned long code = 0;
149 if (pos_ < s_.size() && (s_[pos_] ==
'x' || s_[pos_] ==
'X')) {
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);
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);
162 if (
code == 0 ||
code > 0x10FFFFul)
return false;
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));
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));
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;
189 while (pos_ < s_.size()) {
190 const char c = s_[pos_];
196 if (!decodeEntity(out))
return false;
199 if (
c ==
'<')
return false;
209 if (pos_ >= s_.size())
return false;
210 if (s_[pos_] ==
'/' || s_[pos_] ==
'>')
return true;
212 if (!parseName(
name))
return false;
214 if (pos_ >= s_.size() || s_[pos_] !=
'=')
return false;
218 if (!parseAttributeValue(
value))
return false;
223 bool skipTextUntilTag() {
224 while (pos_ < s_.size() && s_[pos_] !=
'<') {
225 if (s_[pos_] ==
'&') {
227 if (!decodeEntity(discard))
return false;
235 bool parseElement(std::unique_ptr<Node>& out,
Node*
parent,
size_t depth) {
236 if (
depth > kMaxDepth)
return false;
238 if (pos_ >= s_.size() || s_[pos_] !=
'<')
return false;
239 if (startsWith(
"</") || startsWith(
"<?") || startsWith(
"<!"))
return false;
241 auto node = std::make_unique<Node>();
243 if (!parseName(
node->tag))
return false;
244 if (!parseAttributes(*
node))
return false;
246 if (pos_ >= s_.size())
return false;
247 if (s_[pos_] ==
'/') {
249 if (pos_ >= s_.size() || s_[pos_] !=
'>')
return false;
251 out = std::move(
node);
254 if (s_[pos_] !=
'>')
return false;
258 if (!skipTextUntilTag())
return false;
259 if (pos_ >= s_.size() || s_[pos_] !=
'<')
return false;
260 if (startsWith(
"</")) {
263 if (!parseName(endTag))
return false;
265 if (pos_ >= s_.size() || s_[pos_] !=
'>')
return false;
267 if (endTag !=
node->tag)
return false;
268 out = std::move(
node);
271 if (startsWith(
"<!--")) {
272 if (!skipComment())
return false;
275 if (startsWith(
"<?")) {
276 if (!skipProcessingInstruction())
return false;
279 if (startsWith(
"<![CDATA[")) {
281 while (pos_ + 2 < s_.size()) {
282 if (s_[pos_] ==
']' && s_[pos_ + 1] ==
']' && s_[pos_ + 2] ==
'>') {
290 std::unique_ptr<Node> child;
291 if (!parseElement(child,
node.get(),
depth + 1))
return false;
302Document& Document::operator=(Document&&) noexcept = default;
308 if (!parser.parse(doc.root_,
error)) {
326 if (!node_ || !
name)
return fallback;
328 return it == node_->
attributes.end() ? fallback : it->second;
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()));
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()));