12#ifndef GMP_SERIALIZATION_XML_HPP_
13#define GMP_SERIALIZATION_XML_HPP_
24#include <gmp/serialization/detail/text.hpp>
25#include <gmp/serialization/detail/write_value.hpp>
28namespace gmp::detail {
30inline bool is_xml_code_point(std::uint32_t value)
noexcept {
31 return value == 0x9 || value == 0xa || value == 0xd || (value >= 0x20 && value <= 0xd7ff) ||
32 (value >= 0xe000 && value <= 0xfffd) || (value >= 0x10000 && value <= 0x10ffff);
35inline bool has_only_xml_characters(std::string_view
text)
noexcept {
42 for (std::size_t
i = 0;
i <
text.size();) {
43 const auto first =
static_cast<unsigned char>(
text[
i]);
45 std::size_t
width = 1;
56 for (std::size_t
j = 1;
j <
width; ++
j) {
67inline serialization_result<void> xml_escape(std::string &
out, std::string_view
text,
68 std::size_t
limit,
bool attribute =
false) {
72 if (!has_only_xml_characters(
text)) {
74 "string contains a character forbidden by XML 1.0");
76 for (
const char c :
text) {
117 "XML output exceeds configured limit");
123 "XML output exceeds configured limit");
133 explicit xml_writer(xml_write_options options = {}) : options_(options) {}
135 serialization_result<void> write_null() {
136 auto status = before_value();
140 if (!(
status = append(
"<value kind=\"null\"/>"))) {
143 return complete_value();
146 serialization_result<void> write_bool(
bool value) {
147 return scalar(value ?
"true" :
"false",
"boolean");
150 serialization_result<void> write_signed(std::int64_t value) {
151 return number(value,
"signed");
154 serialization_result<void> write_unsigned(std::uint64_t value) {
155 return number(value,
"unsigned");
158 serialization_result<void> write_floating(
double value) {
159 if (!std::isfinite(value)) {
161 "non-finite number cannot be represented");
166 std::numeric_limits<double>::max_digits10);
167 if (
result.ec != std::errc{}) {
169 "failed to format floating point value");
174 serialization_result<void> write_string(std::string_view value) {
175 return scalar(value,
"string",
true);
178 serialization_result<void> begin_array(std::size_t) {
179 return begin(
"array");
182 serialization_result<void> end_array() {
186 serialization_result<void> begin_object(std::size_t) {
187 return begin(
"object");
190 serialization_result<void> write_key(std::string_view key) {
191 if (stack_.empty() || stack_.back().kind !=
"object" || stack_.back().expecting_value) {
193 "object key in invalid archive state");
195 serialization_result<void>
status;
196 if (options_.pretty && !(
status = newline_and_indent(stack_.back().value_depth + 1))) {
199 status = append(
"<member name=\"");
203 if (!(
status = xml_escape(output_, key, options_.max_output_size,
true))) {
206 if (!(
status = append(
"\">"))) {
209 stack_.back().expecting_value =
true;
213 serialization_result<void> end_object() {
214 return end(
"object");
220 "XML archive is already finished");
222 if (!stack_.empty() || !has_root_) {
225 serialization_result<void>
status;
226 if (options_.pretty) {
227 status = newline_and_indent(0);
231 status = append(
"</gmp>");
233 status = append(
"</gmp>");
239 return std::move(output_);
244 std::string_view kind;
245 bool expecting_value =
false;
246 std::size_t count = 0;
247 std::size_t value_depth = 0;
250 serialization_result<void> append(std::string_view
text) {
251 if (output_.size() > options_.max_output_size ||
252 text.size() > options_.max_output_size - output_.size()) {
254 "XML output exceeds configured limit");
260 serialization_result<void> before_value() {
263 "XML archive is already finished");
265 if (stack_.empty()) {
269 if (!document_open_) {
270 auto status = append(
"<gmp version=\"1\">");
274 document_open_ =
true;
276 if (options_.pretty) {
277 auto status = newline_and_indent(1);
284 auto &
parent = stack_.back();
285 if (
parent.kind ==
"object" && !
parent.expecting_value) {
288 if (options_.pretty &&
parent.kind ==
"array") {
289 return newline_and_indent(
parent.value_depth + 1);
294 serialization_result<void> complete_value() {
295 if (stack_.empty()) {
299 auto &
parent = stack_.back();
300 if (
parent.kind ==
"object") {
301 auto status = append(
"</member>");
305 parent.expecting_value =
false;
311 serialization_result<void> newline_and_indent(std::size_t
depth) {
312 auto status = append(
"\n");
316 if (output_.size() > options_.max_output_size) {
318 "XML output exceeds configured limit");
320 const auto remaining = options_.max_output_size - output_.size();
323 "XML output exceeds configured limit");
325 output_.append(
depth * options_.indent_width,
' ');
330 if (stack_.empty()) {
333 const auto &
parent = stack_.back();
334 return parent.value_depth + (
parent.kind ==
"object" ? 2 : 1);
337 serialization_result<void> scalar(std::string_view value, std::string_view kind,
339 auto status = before_value();
343 status = append(
"<value kind=\"");
356 status = xml_escape(output_, value, options_.max_output_size);
363 status = append(
"</value>");
367 return complete_value();
370 template <
typename I> serialization_result<void> number(
I value, std::string_view kind) {
373 if (
result.ec != std::errc{}) {
375 "failed to format integer");
380 serialization_result<void> begin(std::string_view kind) {
381 if (stack_.size() >= options_.max_depth) {
383 "maximum XML output depth exceeded");
385 const auto value_depth = next_value_depth();
386 auto status = before_value();
390 if (!(
status = append(
"<value kind=\"")) || !(
status = append(kind)) ||
391 !(
status = append(
"\">"))) {
394 stack_.push_back({kind,
false, 0, value_depth});
398 serialization_result<void> end(std::string_view kind) {
399 if (stack_.empty() || stack_.back().kind != kind || stack_.back().expecting_value) {
401 "mismatched XML archive end");
403 const auto frame = stack_.back();
405 serialization_result<void>
status;
406 if (options_.pretty && frame.count && !(
status = newline_and_indent(frame.value_depth))) {
409 status = append(
"</value>");
413 return complete_value();
416 xml_write_options options_;
418 std::vector<frame> stack_;
419 bool has_root_ =
false;
420 bool document_open_ =
false;
421 bool finished_ =
false;
426 xml_parser(std::string_view
input, xml_read_options options) : input_(
input), options_(options) {}
429 if (input_.size() > options_.max_input_size) {
431 "XML input exceeds the configured size limit");
436 if (starts(
"\xef\xbb\xbf")) {
439 if (starts(
"<?xml")) {
446 if (starts(
"<!DOCTYPE")) {
448 "XML DTDs and external entities are not supported");
450 if (!consume(
"<gmp")) {
453 if (position_ >= input_.size() || (input_[position_] !=
' ' && input_[position_] !=
'\t' &&
454 input_[position_] !=
'\r' && input_[position_] !=
'\n')) {
456 "expected whitespace after GMP XML document name");
458 auto version = attribute(
"version");
470 auto value = parse_node(0);
472 return value.error();
475 if (!consume(
"</gmp>")) {
479 if (position_ != input_.size()) {
490 bool starts(std::string_view
text)
const {
491 return input_.substr(position_,
text.size()) ==
text;
494 static bool is_space(
char value)
noexcept {
495 return value ==
' ' || value ==
'\t' || value ==
'\r' || value ==
'\n';
499 while (position_ < input_.size() && is_space(input_[position_])) {
504 bool consume(std::string_view
text) {
508 position_ +=
text.size();
521 if (position_ >= input_.size() || (input_[position_] !=
'"' && input_[position_] !=
'\'')) {
524 const char quote = input_[position_++];
525 const auto start = position_;
526 while (position_ < input_.size() && input_[position_] !=
quote) {
527 const auto c =
static_cast<unsigned char>(input_[position_]);
528 if (
c >= 0x80 || !is_xml_code_point(
c) || input_[position_] ==
'<' ||
529 input_[position_] ==
'&') {
534 if (position_ >= input_.size()) {
537 std::string value(input_.substr(
start, position_ -
start));
542 static bool ascii_iequal(std::string_view
left, std::string_view
right)
noexcept {
546 for (std::size_t
i = 0;
i <
left.size(); ++
i) {
549 if (
a >=
'A' &&
a <=
'Z') {
550 a =
static_cast<char>(
a -
'A' +
'a');
552 if (
b >=
'A' &&
b <=
'Z') {
553 b =
static_cast<char>(
b -
'A' +
'a');
562 serialization_result<void> parse_declaration() {
564 if (position_ >= input_.size() || !is_space(input_[position_])) {
568 auto version = declaration_attribute(
"version");
586 "XML declaration attributes must be separated");
588 if (starts(
"encoding")) {
591 "invalid XML declaration attribute order");
593 auto encoding = declaration_attribute(
"encoding");
597 if (!ascii_iequal(*
encoding,
"UTF-8")) {
601 }
else if (starts(
"standalone")) {
605 auto standalone = declaration_attribute(
"standalone");
620 const auto start = position_;
621 const auto end = input_.find(
';', position_);
622 if (end == std::string_view::npos) {
628 return std::string(
"&");
631 return std::string(
"<");
634 return std::string(
">");
636 if (
name ==
"quot") {
637 return std::string(
"\"");
639 if (
name ==
"apos") {
640 return std::string(
"'");
642 if (!
name.empty() &&
name.front() ==
'#') {
665 while (position_ < input_.size() && input_[position_] !=
'<') {
668 "']]>' is not allowed in XML character data");
670 if (input_[position_] ==
'&') {
672 auto part = entity();
678 if (input_[position_] ==
'\r') {
680 if (position_ < input_.size() && input_[position_] ==
'\n') {
683 text.push_back(
'\n');
685 text.push_back(input_[position_++]);
688 if (
text.size() > options_.max_string_size) {
690 "XML text exceeds the configured size limit");
696 if (!has_only_xml_characters(
text)) {
712 if (position_ >= input_.size() || (input_[position_] !=
'\"' && input_[position_] !=
'\'')) {
715 const char quote = input_[position_++];
717 while (position_ < input_.size() && input_[position_] !=
quote) {
718 if (input_[position_] ==
'&') {
720 auto part = entity();
726 if (input_[position_] ==
'<') {
728 "'<' is not allowed in an XML attribute");
730 if (input_[position_] ==
'\r') {
732 if (position_ < input_.size() && input_[position_] ==
'\n') {
735 value.push_back(
' ');
736 }
else if (input_[position_] ==
'\n' || input_[position_] ==
'\t') {
738 value.push_back(
' ');
740 value.push_back(input_[position_++]);
743 if (value.size() > options_.max_string_size) {
745 "XML attribute exceeds the configured size limit");
748 if (position_ >= input_.size() || input_[position_] !=
quote) {
755 if (!has_only_xml_characters(value)) {
757 "XML attribute contains a forbidden character");
763 const auto source_offset = position_;
764 if (!consume(
"<value")) {
767 if (position_ >= input_.size() || (input_[position_] !=
' ' && input_[position_] !=
'\t' &&
768 input_[position_] !=
'\r' && input_[position_] !=
'\n')) {
770 "expected whitespace after XML element name");
772 auto kind = attribute(
"kind");
778 if (*kind !=
"null") {
780 "only null XML values may be self-closing");
782 serialization_value
result(
nullptr);
783 result.source_offset(source_offset);
789 serialization_value
result;
790 if (*kind ==
"array") {
791 if (
depth >= options_.max_depth) {
793 "maximum XML nesting depth exceeded");
797 while (!starts(
"</value>")) {
798 if (values.size() >= options_.max_container_size) {
800 "XML array exceeds the configured size limit");
806 values.push_back(std::move(*
item));
810 result = serialization_value(std::move(values));
811 }
else if (*kind ==
"object") {
812 if (
depth >= options_.max_depth) {
814 "maximum XML nesting depth exceeded");
818 while (!starts(
"</value>")) {
819 if (values.size() >= options_.max_container_size) {
821 "XML object exceeds the configured size limit");
823 if (!consume(
"<member")) {
826 if (position_ >= input_.size() ||
827 (input_[position_] !=
' ' && input_[position_] !=
'\t' && input_[position_] !=
'\r' &&
828 input_[position_] !=
'\n')) {
830 "expected whitespace after XML member name");
832 auto name = attribute(
"name");
846 if (!consume(
"</member>")) {
849 values.emplace_back(std::move(*
name), std::move(*
item));
853 result = serialization_value(std::move(values));
855 auto text = text_until_tag();
859 if (!consume(
"</value>")) {
862 if (*kind ==
"string") {
863 result = serialization_value(std::move(*
text));
864 }
else if (*kind ==
"boolean") {
865 if (*
text !=
"true" && *
text !=
"false") {
868 result = serialization_value(*
text ==
"true");
869 }
else if (*kind ==
"signed") {
870 std::int64_t number = 0;
871 const auto parsed = std::from_chars(
text->data(),
text->data() +
text->size(), number);
875 result = serialization_value(number);
876 }
else if (*kind ==
"unsigned") {
877 std::uint64_t number = 0;
878 const auto parsed = std::from_chars(
text->data(),
text->data() +
text->size(), number);
882 result = serialization_value(number);
883 }
else if (*kind ==
"floating") {
885 const auto parsed = std::from_chars(
text->data(),
text->data() +
text->size(), number,
886 std::chars_format::general);
888 !std::isfinite(number)) {
891 result = serialization_value(number);
896 result.source_offset(source_offset);
900 std::string_view input_;
901 xml_read_options options_;
902 std::size_t position_ = 0;
910 xml_read_options options = {}) {
911 return detail::xml_parser(
input, options).parse();
915 xml_write_options options = {}) {
916 detail::xml_writer
writer(options);
917 auto status = detail::write_serialization_value(
writer, value);
926 xml_write_options
xml = {}) {
939 xml_read_options
xml = {}) {
942 return value.error();
948std::string to_xml_or_throw(
const T &value, serialization_options serialization = {},
949 xml_write_options
xml = {}) {
950 auto result = to_xml(value, serialization,
xml);
952 throw serialization_exception(
result.error());
954 return std::move(*
result);
959 xml_read_options
xml = {}) {
962 throw serialization_exception(
result.error());
964 return std::move(*
result);
std::vector< std::pair< std::string, serialization_value > > object
std::vector< serialization_value > array
serialization_error make_serialization_error(serialization_errc code, std::string message, std::string path={}, std::size_t offset=serialization_error::unknown_offset)