format output

This commit is contained in:
Maarten L. Hekkelman
2025-12-27 16:46:04 +01:00
parent 8485747377
commit 964e7620eb
2 changed files with 215 additions and 9 deletions

View File

@@ -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<std::string> &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<std::string> &order, bool addMissingItems = true);
void write(std::ostream &os, const std::vector<std::string> &order, bool addMissingItems = true)
{
write(os, output_format::cif, order, addMissingItems);
}
private:
void write(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems) const;
void write_cif(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems) const;
void write_delimited(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems, std::string_view delimiter, bool aligned) const;
void write_markdown(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems) const;
void write_table(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems) const;
public:
/// friend function to make it possible to do:

View File

@@ -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 <numeric>
#include <stack>
#include <utility>
#include <vector>
// 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<uint16_t> order(m_items.size());
iota(order.begin(), order.end(), static_cast<uint16_t>(0));
write(os, order, false);
write_cif(os, order, false);
}
void category::write(std::ostream &os, const std::vector<std::string> &items, bool addMissingItems)
void category::write(std::ostream &os, output_format fmt, const std::vector<std::string> &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<std::string> &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<uint16_t> &order, bool includeEmptyItems) const
void category::write_cif(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems) const
{
if (empty())
return;
@@ -2113,6 +2144,152 @@ void category::write(std::ostream &os, const std::vector<uint16_t> &order, bool
os << "# \n";
}
void category::write_delimited(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems, std::string_view delimiter, bool aligned) const
{
if (empty())
return;
std::vector<bool> 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<std::size_t> 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<uint16_t> &order, bool includeEmptyItems) const
{
}
void category::write_table(std::ostream &os, const std::vector<uint16_t> &order, bool includeEmptyItems) const
{
}
bool category::operator==(const category &rhs) const
{
// shortcut