From 964e7620eba1906b2ef75ab9c84d925481f6d5d9 Mon Sep 17 00:00:00 2001 From: "Maarten L. Hekkelman" Date: Sat, 27 Dec 2025 16:46:04 +0100 Subject: [PATCH] format output --- include/cif++/category.hpp | 37 +++++++- src/category.cpp | 187 ++++++++++++++++++++++++++++++++++++- 2 files changed, 215 insertions(+), 9 deletions(-) diff --git a/include/cif++/category.hpp b/include/cif++/category.hpp index 112e3fb..8ae6721 100644 --- a/include/cif++/category.hpp +++ b/include/cif++/category.hpp @@ -26,9 +26,8 @@ #pragma once -#include "cif++/forward_decl.hpp" - #include "cif++/condition.hpp" +#include "cif++/forward_decl.hpp" #include "cif++/iterator.hpp" #include "cif++/row.hpp" #include "cif++/text.hpp" @@ -1133,16 +1132,46 @@ class category /// Write the contents of the category to the std::ostream @a os void write(std::ostream &os) const; + /// \brief Various supported output formats + enum class output_format + { + cif, // Output in mmCIF format + csv, // comma separated values + tsv, // tab separated values + list, // values delimited by a '|' character + column, // output in columns + markdown, // + table, // ascii art table + }; + + /// @brief + /// @brief Write the contents of the category to the std::ostream @a os and + /// use @a order as the order of the items. If @a addMissingItems is + /// false, items that do not contain any value will be suppressed. Use this version + /// to write out + /// @param os The std::ostream to write to + /// @param fmt The format to use + /// @param order The order in which the items should appear + /// @param addMissingItems When false, empty items are suppressed from the output + void write(std::ostream &os, output_format fmt, + const std::vector &order, bool addMissingItems = true); + /// @brief Write the contents of the category to the std::ostream @a os and /// use @a order as the order of the items. If @a addMissingItems is /// false, items that do not contain any value will be suppressed /// @param os The std::ostream to write to /// @param order The order in which the items should appear /// @param addMissingItems When false, empty items are suppressed from the output - void write(std::ostream &os, const std::vector &order, bool addMissingItems = true); + void write(std::ostream &os, const std::vector &order, bool addMissingItems = true) + { + write(os, output_format::cif, order, addMissingItems); + } private: - void write(std::ostream &os, const std::vector &order, bool includeEmptyItems) const; + void write_cif(std::ostream &os, const std::vector &order, bool includeEmptyItems) const; + void write_delimited(std::ostream &os, const std::vector &order, bool includeEmptyItems, std::string_view delimiter, bool aligned) const; + void write_markdown(std::ostream &os, const std::vector &order, bool includeEmptyItems) const; + void write_table(std::ostream &os, const std::vector &order, bool includeEmptyItems) const; public: /// friend function to make it possible to do: diff --git a/src/category.cpp b/src/category.cpp index ba59907..998e3ac 100644 --- a/src/category.cpp +++ b/src/category.cpp @@ -25,6 +25,7 @@ */ #include "cif++/category.hpp" + #include "cif++/datablock.hpp" #include "cif++/parser.hpp" #include "cif++/utilities.hpp" @@ -32,6 +33,7 @@ #include #include +#include #include // TODO: Find out what the rules are exactly for linked items, the current implementation @@ -109,7 +111,7 @@ class row_comparator std::string_view ka = ai->value; std::string_view kb = rhb[k].text(); - if (not (ai->may_be_null and rhb[k].empty())) + if (not(ai->may_be_null and rhb[k].empty())) d = f(ka, kb); if (d != 0) @@ -1913,10 +1915,10 @@ void category::write(std::ostream &os) const { std::vector order(m_items.size()); iota(order.begin(), order.end(), static_cast(0)); - write(os, order, false); + write_cif(os, order, false); } -void category::write(std::ostream &os, const std::vector &items, bool addMissingItems) +void category::write(std::ostream &os, output_format fmt, const std::vector &items, bool addMissingItems) { // make sure all items are present for (auto &c : items) @@ -1937,10 +1939,39 @@ void category::write(std::ostream &os, const std::vector &items, bo } } - write(os, order, true); + switch (fmt) + { + case output_format::cif: + write_cif(os, order, addMissingItems); + break; + + case output_format::csv: + write_delimited(os, order, addMissingItems, ",", false); + break; + + case output_format::tsv: + write_delimited(os, order, addMissingItems, "\t", false); + break; + + case output_format::list: + write_delimited(os, order, addMissingItems, "|", false); + break; + + case output_format::column: + write_delimited(os, order, addMissingItems, " ", true); + break; + + case output_format::markdown: + write_markdown(os, order, addMissingItems); + break; + + case output_format::table: + write_table(os, order, addMissingItems); + break; + } } -void category::write(std::ostream &os, const std::vector &order, bool includeEmptyItems) const +void category::write_cif(std::ostream &os, const std::vector &order, bool includeEmptyItems) const { if (empty()) return; @@ -2113,6 +2144,152 @@ void category::write(std::ostream &os, const std::vector &order, bool os << "# \n"; } +void category::write_delimited(std::ostream &os, const std::vector &order, bool includeEmptyItems, std::string_view delimiter, bool aligned) const +{ + if (empty()) + return; + + std::vector right_aligned(m_items.size(), false); + + if (m_cat_validator != nullptr) + { + for (auto cix : order) + { + auto &col = m_items[cix]; + right_aligned[cix] = col.m_validator != nullptr and + col.m_validator->m_type != nullptr and + col.m_validator->m_type->m_primitive_type == cif::DDL_PrimitiveType::Numb; + } + } + + std::vector itemWidths(m_items.size()); + auto get_line = [delimiter](std::string_view s) -> std::string + { + if (delimiter == ",") + { + if (s.find_first_of("\",") == std::string::npos) + return std::string{ s }; + + std::string r{ '"' }; + r.reserve(s.length() + 2); + for (auto ch : s) + { + if (ch == '"') + r.append("\"\""); + else + r.push_back(ch); + } + r.push_back('"'); + return r; + } + else if (delimiter == "\t") + { + std::string r; + r.reserve(s.length()); + for (auto ch : s) + { + if (ch == '\r' or ch == '\n' or ch == '\t' or ch == '\\') + r.push_back('\\'); + r.push_back(ch); + } + return r; + } + else if (delimiter == "|" or delimiter == " ") + return std::string{ s }; + else + assert(false); + }; + + if (aligned) + { + for (auto cix : order) + { + auto &col = m_items[cix]; + itemWidths[cix] = col.m_name.length(); + } + + for (auto r = m_head; r != nullptr; r = r->m_next) + { + for (uint16_t ix = 0; ix < r->size(); ++ix) + { + auto v = r->get(ix); + if (v == nullptr) + continue; + + size_t l = get_line(v->text()).length(); + if (itemWidths[ix] < l) + itemWidths[ix] = l; + } + } + } + + if (true /* header */) + { + for (bool first = true; uint16_t cix : order) + { + if (not std::exchange(first, false)) + os << delimiter; + + std::size_t w = itemWidths[cix]; + std::string_view s = m_items[cix].m_name; + + if (s.length() < w) + { + if (right_aligned[cix]) + os << std::string(w - s.length(), ' '); + os << s; + if (not right_aligned[cix]) + os << std::string(w - s.length(), ' '); + } + else + os << s; + } + + os << '\n'; + } + + for (auto r = m_head; r != nullptr; r = r->m_next) // loop over rows + { + for (bool first = true; uint16_t cix : order) + { + if (not std::exchange(first, false)) + os << delimiter; + + std::size_t w = itemWidths[cix]; + + std::string_view s; + auto iv = r->get(cix); + + if (iv != nullptr) + s = iv->text(); + + if (s == "?" or s == ".") + s = ""; + + if (s.length() < w) + { + if (right_aligned[cix]) + os << std::string(w - s.length(), ' '); + os << s; + if (not right_aligned[cix]) + os << std::string(w - s.length(), ' '); + } + else + os << s; + } + + os << '\n'; + } +} + +void category::write_markdown(std::ostream &os, const std::vector &order, bool includeEmptyItems) const +{ +} + +void category::write_table(std::ostream &os, const std::vector &order, bool includeEmptyItems) const +{ +} + bool category::operator==(const category &rhs) const { // shortcut