packages/engine/scram-node/src/xml_stream.h
Facilities to stream data in XML format.
Namespaces
| Name |
|---|
| scram |
| scram::xml |
| scram::xml::detail |
Classes
| Name | |
|---|---|
| struct | scram::xml::StreamError <br>Errors in using XML streaming facilities. |
| class | scram::xml::detail::Indenter <br>Manages XML element indentation upon output. |
| class | scram::xml::detail::Indenter::Indentation <br>RAII class to manage the current indentation string. |
| class | scram::xml::detail::FileStream <br>Adaptor for stdio FILE stream with write generic interface. |
| class | scram::xml::StreamElement <br>Writer of data formed as an XML element to a stream. |
| class | scram::xml::Stream <br>XML Stream document. |
Source code
cpp
/*
* Copyright (C) 2014-2018 Olzhas Rakhimov
* Copyright (C) 2023 OpenPRA ORG Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cassert>
#include <cstdio>
#include <algorithm>
#include <exception>
#include <string>
#include <boost/exception/errinfo_errno.hpp>
#include "error.h"
namespace scram::xml {
struct StreamError : public Error {
using Error::Error;
};
namespace detail { // XML streaming helpers.
const char kIndentChar = ' ';
const int kMaxIndent = 20;
class Indenter {
public:
class Indentation {
public:
Indentation(int num_chars, Indenter* indent)
: pos_(num_chars > kMaxIndent ? kMaxIndent : num_chars),
indent_(indent) {
indent_->spaces[pos_] = '\0'; // Indent string up to the position.
}
~Indentation() { indent_->spaces[pos_] = kIndentChar; }
operator const char*() { return indent_->spaces; }
private:
int pos_;
Indenter* indent_;
};
explicit Indenter(bool indent = true) : indent_(indent) {
std::fill_n(spaces, kMaxIndent, kIndentChar);
}
Indentation operator()(int num_chars) {
return indent_ ? Indentation(num_chars, this) : Indentation(0, this);
}
private:
bool indent_;
char spaces[kMaxIndent + 1];
};
class FileStream {
public:
explicit FileStream(std::FILE* file) : file_(file) {}
std::FILE* file() { return file_; }
void write(const std::string& value) { write(value.c_str()); }
void write(const char* value) { std::fputs(value, file_); }
void write(const char value) { std::fputc(value, file_); }
void write(int value) {
if (value < 0) {
std::fputc('-', file_);
value = -value;
}
write(static_cast<std::size_t>(value));
}
void write(std::size_t value) {
char temp[20];
char* p = temp;
do {
*p++ = value % 10 + '0';
value /= 10;
} while (value > 0);
do {
std::fputc(*--p, file_);
} while (p != temp);
}
void write(double value) { std::fprintf(file_, "%g", value); }
private:
std::FILE* file_;
};
template <typename T>
FileStream& operator<<(FileStream& file, T&& value) {
file.write(std::forward<T>(value));
return file;
}
} // namespace detail
class StreamElement {
public:
StreamElement(const char* name, detail::Indenter* indenter,
detail::FileStream* out)
: StreamElement(name, 0, nullptr, indenter, out) {}
~StreamElement() noexcept {
assert(active_ && "The child element may still be alive.");
assert(!(parent_ && parent_->active_) && "The parent must be inactive.");
if (parent_)
parent_->active_ = true;
if (accept_attributes_) {
out_ << "/>\n";
} else if (accept_elements_) {
out_ << indenter_(kIndent_);
closing_tag:
out_ << "</" << kName_ << ">\n";
} else {
assert(accept_text_ && "The element is in unspecified state.");
goto closing_tag;
}
}
template <typename T>
StreamElement& SetAttribute(const char* name, T&& value) {
if (!active_)
throw StreamError("The element is inactive.");
if (!accept_attributes_)
throw StreamError("Too late for attributes.");
if (*name == '\0')
throw StreamError("Attribute name can't be empty.");
out_ << " " << name << "=\"";
PutValue(std::forward<T>(value));
out_ << "\"";
return *this;
}
template <typename T>
StreamElement& AddText(T&& text) {
if (!active_)
throw StreamError("The element is inactive.");
if (!accept_text_)
throw StreamError("Too late to put text.");
if (accept_elements_)
accept_elements_ = false;
if (accept_attributes_) {
accept_attributes_ = false;
out_ << ">";
}
PutValue(std::forward<T>(text));
return *this;
}
StreamElement AddChild(const char* name) {
if (!active_)
throw StreamError("The element is inactive.");
if (!accept_elements_)
throw StreamError("Too late to add elements.");
if (*name == '\0')
throw StreamError("Element name can't be empty.");
if (accept_text_)
accept_text_ = false;
if (accept_attributes_) {
accept_attributes_ = false;
out_ << ">\n";
}
return StreamElement(name, kIndent_ + kIndentIncrement, this, &indenter_,
&out_);
}
private:
static const int kIndentIncrement = 2;
StreamElement(const char* name, int indent, StreamElement* parent,
detail::Indenter* indenter, detail::FileStream* out)
: kName_(name),
kIndent_(indent),
accept_attributes_(true),
accept_elements_(true),
accept_text_(true),
active_(true),
parent_(parent),
indenter_(*indenter),
out_(*out) {
if (*kName_ == '\0')
throw StreamError("The element name can't be empty.");
if (parent_) {
if (!parent_->active_)
throw StreamError("The parent is inactive.");
parent_->active_ = false;
}
assert(kIndent_ >= 0 && "Negative XML indentation.");
out_ << indenter_(kIndent_) << "<" << kName_;
}
void PutValue(int value) { out_ << value; }
void PutValue(double value) { out_ << value; }
void PutValue(std::size_t value) { out_ << value; }
void PutValue(bool value) { out_ << (value ? "true" : "false"); }
void PutValue(const std::string& value) { PutValue(value.c_str()); }
void PutValue(const char* value) {
bool has_special_chars = [value]() mutable { // 2x faster than strpbrk.
for (;; ++value) {
switch (*value) {
case '\0':
return false;
case '&':
case '<':
case '"':
return true;
}
}
}();
if (!has_special_chars) { // The most common case.
out_ << value;
} else {
for (;; ++value) {
switch (*value) {
case '\0':
return;
case '&':
out_ << "&";
break;
case '<':
out_ << "<";
break;
case '"':
out_ << """;
break;
default:
out_ << *value;
}
}
}
}
const char* kName_;
const int kIndent_;
bool accept_attributes_;
bool accept_elements_;
bool accept_text_;
bool active_;
StreamElement* parent_;
detail::Indenter& indenter_;
detail::FileStream& out_;
};
class Stream {
public:
explicit Stream(std::FILE* out, bool indent = true)
: indenter_(indent),
has_root_(false),
uncaught_exceptions_(std::uncaught_exceptions()),
out_(out) {
assert(!std::ferror(out) && "Unclean error state in output destination.");
out_ << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
}
~Stream() noexcept(false) {
int err = std::ferror(out_.file());
if (err && (std::uncaught_exceptions() == uncaught_exceptions_))
SCRAM_THROW(IOError("FILE error on write")) << boost::errinfo_errno(err);
}
StreamElement root(const char* name) {
if (has_root_)
throw StreamError("The XML stream document already has a root.");
StreamElement element(name, &indenter_, &out_);
has_root_ = true;
return element;
}
private:
detail::Indenter indenter_;
bool has_root_;
int uncaught_exceptions_;
detail::FileStream out_;
};
} // namespace scram::xmlUpdated on 2025-11-11 at 16:51:09 +0000
