larra/library/include/larra/raw_xml_stream.hpp

331 lines
11 KiB
C++
Raw Normal View History

2024-09-28 19:15:31 +00:00
#pragma once
#include <libxml++/libxml++.h>
#include <spdlog/spdlog.h>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/read.hpp>
#include <boost/asio/streambuf.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/write.hpp>
#include <boost/system/result.hpp>
#include <larra/utils.hpp>
#include <stack>
#include <utempl/utils.hpp>
struct _xmlError;
namespace larra::xmpp {
template <typename T>
concept AsXml = requires(xmlpp::Element* element, const T& obj) {
element << obj;
{ T::kDefaultName } -> std::convertible_to<const std::string&>;
};
template <typename T>
concept HasDefaultNamespace = requires {
{ T::kDefaultNamespace } -> std::convertible_to<const std::string&>;
};
template <typename T>
concept HasDefaultPrefix = requires {
{ T::kPrefix } -> std::convertible_to<const std::string&>;
};
template <typename T>
concept HasAddXmlDecl = requires {
{ T::kAddXmlDecl } -> std::convertible_to<bool>;
};
template <typename T>
concept HasRemoveEnd = requires {
{ T::kRemoveEnd } -> std::convertible_to<bool>;
};
struct XmlGroup : xmlpp::Element {
using Element::Element;
};
struct XmlPath : public xmlpp::Element {
public:
using Element::Element;
[[nodiscard]] auto GetData() const -> std::string {
return get_attribute("d")->get_value();
}
};
namespace impl {
2024-10-06 12:25:42 +00:00
constexpr std::size_t kXmlStreamReadChunkSize = 4096;
2024-09-28 19:15:31 +00:00
constexpr auto BufferToStringView(const boost::asio::const_buffer& buffer, size_t size) -> std::string_view {
assert(size <= buffer.size());
return {boost::asio::buffer_cast<const char*>(buffer), size};
};
constexpr auto BufferToStringView(const boost::asio::const_buffer& buffer) -> std::string_view {
return BufferToStringView(buffer, buffer.size());
};
class Parser : private xmlpp::SaxParser {
public:
inline explicit Parser(xmlpp::Document& document) : doc(document) {};
~Parser() override = default;
auto ParseChunk(std::string_view str) -> const _xmlError*;
std::stack<xmlpp::Element*> context;
xmlpp::Document& doc;
private:
inline auto on_start_document() -> void override {
}
inline auto on_end_document() -> void override {
}
auto on_start_element(const std::string& name, const AttributeList& properties) -> void override;
auto on_end_element(const std::string& name) -> void override;
auto on_characters(const std::string& characters) -> void override;
auto on_cdata_block(const std::string& characters) -> void override;
};
constexpr auto GetCharsRangeFromBuf(auto&& buf) {
return buf.data() //
| std::views::transform([](const auto& buf) -> std::string_view { //
return ::larra::xmpp::impl::BufferToStringView(buf); //
}) //
| std::views::join;
};
constexpr auto SplitStreamBuf(auto&& buf, char delim) {
return GetCharsRangeFromBuf(buf) //
| std::views::lazy_split(delim); //
};
auto GetIndex(const boost::asio::streambuf&, const _xmlError* error, std::size_t alreadyCountedLines = 1) -> std::size_t;
auto CountLines(const boost::asio::streambuf&) -> std::size_t;
auto CountLines(std::string_view) -> std::size_t;
auto IsExtraContentAtTheDocument(const _xmlError* error) -> bool;
} // namespace impl
template <typename Stream, typename BufferType = boost::asio::streambuf>
struct RawXmlStream : Stream {
constexpr RawXmlStream(Stream stream, std::unique_ptr<BufferType> buff = std::make_unique<BufferType>()) :
Stream(std::forward<Stream>(stream)), streambuf(std::move(buff)) {};
using Stream::Stream;
auto next_layer() -> Stream& {
return *this;
}
auto next_layer() const -> const Stream& {
return *this;
}
2024-10-06 12:25:42 +00:00
auto ReadOne(auto& socket) -> boost::asio::awaitable<std::unique_ptr<xmlpp::Document>> {
auto doc = std::make_unique<xmlpp::Document>();
impl::Parser parser(*doc);
for(;;) {
auto enumerated = std::views::zip(std::views::iota(std::size_t{}, this->streambuf->size()),
::larra::xmpp::impl::GetCharsRangeFromBuf(*this->streambuf));
auto it = std::ranges::find(enumerated, '>', [](auto v) {
auto [_, c] = v;
return c;
});
if(it == std::ranges::end(enumerated)) {
for(const auto& buf : this->streambuf->data()) {
auto error = parser.ParseChunk(impl::BufferToStringView(buf));
if(error) {
throw std::runtime_error(std::format("Bad xml object: {}", xmlpp::format_xml_error(error)));
}
}
this->streambuf->consume(this->streambuf->size());
auto buff = this->streambuf->prepare(impl::kXmlStreamReadChunkSize);
auto n = co_await socket.async_read_some(buff, boost::asio::use_awaitable);
this->streambuf->commit(n);
continue;
}
auto [i, _] = *it;
auto toRead = i + 1;
for(const auto& buf : this->streambuf->data()) {
if(toRead == 0) {
break;
}
auto toReadCurrent = std::min(buf.size(), toRead);
auto error = parser.ParseChunk(impl::BufferToStringView(buf, toReadCurrent));
if(error) {
throw std::runtime_error(std::format("Bad xml object: {}", xmlpp::format_xml_error(error)));
}
toRead -= toReadCurrent;
}
this->streambuf->consume(i + 1);
co_return doc;
}
}
auto ReadOne() -> boost::asio::awaitable<std::unique_ptr<xmlpp::Document>> {
co_return co_await this->ReadOne(this->next_layer());
}
2024-09-28 19:15:31 +00:00
inline auto Read(auto& socket) -> boost::asio::awaitable<std::unique_ptr<xmlpp::Document>> {
auto doc = std::make_unique<xmlpp::Document>(); // Not movable :(
impl::Parser parser(*doc);
std::size_t lines = 1;
std::size_t size{};
for(auto elem : this->streambuf->data()) {
auto error = parser.ParseChunk(impl::BufferToStringView(elem));
if(!error) {
auto linesAdd = impl::CountLines(impl::BufferToStringView(elem));
lines += linesAdd;
if(linesAdd == 0) {
size += elem.size();
}
if(parser.context.empty() && parser.doc.get_root_node() != nullptr) {
SPDLOG_DEBUG("Object already transferred");
co_return doc;
}
continue;
}
if(!impl::IsExtraContentAtTheDocument(error)) {
throw std::runtime_error(std::format("Bad xml object: {}", xmlpp::format_xml_error(error)));
}
std::size_t size = impl::GetIndex(*this->streambuf, error, lines) - size;
this->streambuf->consume(size);
SPDLOG_DEBUG("Object already transferred");
co_return doc;
}
this->streambuf->consume(this->streambuf->size());
for(;;) {
2024-10-06 12:25:42 +00:00
auto buff = this->streambuf->prepare(impl::kXmlStreamReadChunkSize);
2024-09-28 19:15:31 +00:00
auto [e, n] = co_await socket.async_read_some(buff, boost::asio::as_tuple(boost::asio::use_awaitable));
if(e) {
boost::system::throw_exception_from_error(e, boost::source_location());
}
this->streambuf->commit(n);
auto error = parser.ParseChunk(impl::BufferToStringView(buff, n));
if(!error) {
auto linesAdd = impl::CountLines(impl::BufferToStringView(buff, n));
SPDLOG_DEBUG("Readed {} bytes for RawXmlStream with {} lines", n, linesAdd);
lines += linesAdd;
if(linesAdd == 0) {
size += n;
}
this->streambuf->consume(this->streambuf->size());
if(parser.context.empty() && parser.doc.get_root_node() != nullptr) {
co_return doc;
}
SPDLOG_DEBUG(
"Object not transferred. context size: {}, isValidRootNode: {}", parser.context.size(), parser.doc.get_root_node() != nullptr);
continue;
}
if(!impl::IsExtraContentAtTheDocument(error)) {
throw std::runtime_error(std::format("Bad xml object: {}", xmlpp::format_xml_error(error)));
}
auto toConsume = impl::GetIndex(*this->streambuf, error, lines) - size;
this->streambuf->consume(toConsume);
co_return doc;
}
}
auto Read() -> boost::asio::awaitable<std::unique_ptr<xmlpp::Document>> {
co_return co_await this->Read(this->next_layer());
}
template <typename T>
auto Read(auto& stream) -> boost::asio::awaitable<T> {
auto doc = co_await this->Read(stream);
co_return T::Parse(doc->get_root_node());
}
template <typename T>
auto Read(auto& stream) -> boost::asio::awaitable<T>
requires requires(std::unique_ptr<xmlpp::Document> ptr) {
{ T::Parse(std::move(ptr)) } -> std::same_as<T>;
}
{
co_return T::Parse(co_await this->Read(stream));
}
template <typename T>
auto Read() -> boost::asio::awaitable<T> {
co_return co_await this->template Read<T>(this->next_layer());
}
auto Send(xmlpp::Document& doc, auto& stream, bool bAddXmlDecl, bool removeEnd) const -> boost::asio::awaitable<void> {
constexpr auto beginSize = sizeof("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") - 1;
auto str = doc.write_to_string();
auto view = std::string_view{str}.substr(beginSize, str.size() - beginSize - 1);
if(bAddXmlDecl) {
if(removeEnd) {
std::string data = "<?xml version=\"1.0\"?>" + static_cast<std::string>(view.substr(0, view.size() - 2)) + ">";
co_await boost::asio::async_write(stream, boost::asio::buffer(data), boost::asio::use_awaitable);
co_return;
}
std::string data = "<?xml version=\"1.0\"?>" + static_cast<std::string>(view);
co_await boost::asio::async_write(stream, boost::asio::buffer(data), boost::asio::use_awaitable);
co_return;
}
if(removeEnd) {
std::string data = static_cast<std::string>(view.substr(0, view.size() - 2)) + ">";
co_await boost::asio::async_write(stream, boost::asio::buffer(data), boost::asio::use_awaitable);
} else {
co_await boost::asio::async_write(stream, boost::asio::buffer(view), boost::asio::use_awaitable);
}
}
auto Send(xmlpp::Document& doc, bool bAddXmlDecl = false) -> boost::asio::awaitable<void> {
co_await this->Send(doc, this->next_layer(), bAddXmlDecl);
}
template <AsXml T>
auto Send(const T& xso, auto& stream) const -> boost::asio::awaitable<void> {
xmlpp::Document doc;
const std::string empty;
const std::string namespaceStr = [&] -> std::string {
if constexpr(HasDefaultNamespace<T>) {
return T::kDefaultNamespace;
} else {
return empty;
}
}();
const std::string prefixStr = [&] -> decltype(auto) {
if constexpr(HasDefaultPrefix<T>) {
return T::kPrefix;
} else {
return empty;
}
}();
const bool bAddXmlDecl = [&] -> bool {
if constexpr(HasAddXmlDecl<T>) {
return T::kAddXmlDecl;
}
return false;
}();
const bool removeEnd = [&] -> bool {
if constexpr(HasRemoveEnd<T>) {
return T::kRemoveEnd;
}
return false;
}();
doc.create_root_node(T::kDefaultName, namespaceStr, prefixStr) << xso;
co_await this->Send(doc, stream, bAddXmlDecl, removeEnd);
}
auto Send(const AsXml auto& xso) -> boost::asio::awaitable<void> {
co_await this->Send(xso, this->next_layer());
}
RawXmlStream(RawXmlStream&& other) = default;
std::unique_ptr<BufferType> streambuf; // Not movable :(
};
} // namespace larra::xmpp