Skip to content

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
structscram::xml::StreamError <br>Errors in using XML streaming facilities.
classscram::xml::detail::Indenter <br>Manages XML element indentation upon output.
classscram::xml::detail::Indenter::Indentation <br>RAII class to manage the current indentation string.
classscram::xml::detail::FileStream <br>Adaptor for stdio FILE stream with write generic interface.
classscram::xml::StreamElement <br>Writer of data formed as an XML element to a stream.
classscram::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_ << "&amp;";
            break;
          case '<':
            out_ << "&lt;";
            break;
          case '"':
            out_ << "&quot;";
            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::xml

Updated on 2025-11-11 at 16:51:09 +0000