184inline std::unique_ptr<Element>
parse(
const std::string& text) {
186 std::unique_ptr<Element> root;
187 std::vector<Element*> stack;
189 while (i < text.size()) {
190 if (text[i] !=
'<') {
197 const std::size_t start = i;
198 while (i < text.size() && text[i] !=
'<') ++i;
199 const std::string raw = text.substr(start, i - start);
200 std::size_t b = 0, e = raw.size();
201 while (b < e && detail::is_space(raw[b])) ++b;
202 while (e > b && detail::is_space(raw[e - 1])) --e;
203 if (e > b && !stack.empty()) stack.back()->text += detail::unescape(raw.substr(b, e - b));
206 if (text.compare(i, 4,
"<!--") == 0) {
207 const std::size_t end = text.find(
"-->", i + 4);
208 if (end == std::string::npos)
throw InputError(
"xml: unterminated comment");
212 if (text.compare(i, 9,
"<![CDATA[") == 0) {
213 const std::size_t end = text.find(
"]]>", i + 9);
214 if (end == std::string::npos)
throw InputError(
"xml: unterminated CDATA section");
218 if (text.compare(i, 2,
"<?") == 0) {
219 const std::size_t end = text.find(
"?>", i + 2);
220 if (end == std::string::npos)
throw InputError(
"xml: unterminated processing instruction");
224 if (text.compare(i, 2,
"<!") == 0) {
225 const std::size_t end = text.find(
'>', i + 2);
226 if (end == std::string::npos)
throw InputError(
"xml: unterminated declaration");
230 if (text.compare(i, 2,
"</") == 0) {
232 detail::skip_space(text, i);
233 const std::string name = detail::read_name(text, i);
234 detail::skip_space(text, i);
235 if (i >= text.size() || text[i] !=
'>')
throw InputError(
"xml: malformed close tag");
237 if (stack.empty() || stack.back()->name != name)
238 throw InputError(
"xml: close tag </" + name +
"> does not match the open element");
245 detail::skip_space(text, i);
246 std::unique_ptr<Element> owned(
new Element());
248 el->
name = detail::read_name(text, i);
251 detail::skip_space(text, i);
252 if (i >= text.size())
throw InputError(
"xml: unterminated element <" + el->
name +
">");
253 if (text[i] ==
'>' || (text[i] ==
'/' && i + 1 < text.size() && text[i + 1] ==
'>'))
break;
254 const std::string key = detail::read_name(text, i);
255 detail::skip_space(text, i);
256 if (i >= text.size() || text[i] !=
'=')
257 throw InputError(
"xml: attribute '" + key +
"' has no value");
259 detail::skip_space(text, i);
260 if (i >= text.size() || (text[i] !=
'"' && text[i] !=
'\''))
261 throw InputError(
"xml: attribute '" + key +
"' value is not quoted");
262 const char quote = text[i++];
263 const std::size_t vstart = i;
264 while (i < text.size() && text[i] != quote) ++i;
265 if (i >= text.size())
throw InputError(
"xml: unterminated attribute value");
266 el->
attrs.emplace_back(key, detail::unescape(text.substr(vstart, i - vstart)));
270 const bool self_closing = text[i] ==
'/';
271 i += self_closing ? 2 : 1;
273 el->
parent = stack.empty() ? nullptr : stack.back();
276 if (root)
throw InputError(
"xml: more than one root element");
277 root = std::move(owned);
279 stack.back()->children.push_back(std::move(owned));
281 if (!self_closing) stack.push_back(raw);
284 if (!stack.empty())
throw InputError(
"xml: unterminated element <" + stack.back()->name +
">");
285 if (!root)
throw InputError(
"xml: the document has no root element");