1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 20:47:45 +00:00

Spreadsheet: Move to a non-owning model for Stream in Writer::XSV

This commit is contained in:
Lucas CHOLLET 2023-01-14 12:11:29 -05:00 committed by Ali Mohammad Pur
parent fc413711ee
commit 4952cdfe2b
5 changed files with 75 additions and 59 deletions

View file

@ -88,7 +88,7 @@ CSVExportDialogPage::CSVExportDialogPage(Sheet const& sheet)
update_preview(); update_preview();
} }
auto CSVExportDialogPage::make_writer(Core::Stream::Handle<Core::Stream::Stream> stream) -> ErrorOr<NonnullOwnPtr<XSV>> auto CSVExportDialogPage::generate(Core::Stream::Stream& stream, GenerationType type) -> ErrorOr<void>
{ {
auto delimiter = TRY([this]() -> ErrorOr<DeprecatedString> { auto delimiter = TRY([this]() -> ErrorOr<DeprecatedString> {
if (m_delimiter_other_radio->is_checked()) { if (m_delimiter_other_radio->is_checked()) {
@ -150,15 +150,25 @@ auto CSVExportDialogPage::make_writer(Core::Stream::Handle<Core::Stream::Stream>
if (should_quote_all_fields) if (should_quote_all_fields)
behaviors = behaviors | Writer::WriterBehavior::QuoteAll; behaviors = behaviors | Writer::WriterBehavior::QuoteAll;
return try_make<XSV>(move(stream), m_data, move(traits), *headers, behaviors); switch (type) {
case GenerationType::Normal:
TRY((Writer::XSV<decltype(m_data), Vector<DeprecatedString>>::generate(stream, m_data, move(traits), *headers, behaviors)));
break;
case GenerationType::Preview:
TRY((Writer::XSV<decltype(m_data), decltype(*headers)>::generate_preview(stream, m_data, move(traits), *headers, behaviors)));
break;
default:
VERIFY_NOT_REACHED();
}
return {};
} }
void CSVExportDialogPage::update_preview() void CSVExportDialogPage::update_preview()
{ {
auto maybe_error = [this]() -> ErrorOr<void> { auto maybe_error = [this]() -> ErrorOr<void> {
Core::Stream::AllocatingMemoryStream memory_stream; Core::Stream::AllocatingMemoryStream memory_stream;
auto writer = TRY(make_writer(Core::Stream::Handle<Core::Stream::Stream>(memory_stream))); TRY(generate(memory_stream, GenerationType::Preview));
TRY(writer->generate_preview());
auto buffer = TRY(memory_stream.read_until_eof()); auto buffer = TRY(memory_stream.read_until_eof());
m_data_preview_text_editor->set_text(StringView(buffer)); m_data_preview_text_editor->set_text(StringView(buffer));
m_data_preview_text_editor->update(); m_data_preview_text_editor->update();
@ -185,8 +195,8 @@ ErrorOr<void> ExportDialog::make_and_run_for(StringView mime, NonnullOwnPtr<Core
if (wizard->exec() != GUI::Dialog::ExecResult::OK) if (wizard->exec() != GUI::Dialog::ExecResult::OK)
return Error::from_string_literal("CSV Export was cancelled"); return Error::from_string_literal("CSV Export was cancelled");
auto writer = TRY(page.make_writer(move(file))); TRY(page.generate(*file, CSVExportDialogPage::GenerationType::Normal));
return writer->generate(); return {};
}; };
auto export_worksheet = [&]() -> ErrorOr<void> { auto export_worksheet = [&]() -> ErrorOr<void> {

View file

@ -18,12 +18,16 @@ class Sheet;
class Workbook; class Workbook;
struct CSVExportDialogPage { struct CSVExportDialogPage {
using XSV = Writer::XSV<Vector<Vector<DeprecatedString>>, Vector<DeprecatedString>>;
explicit CSVExportDialogPage(Sheet const&); explicit CSVExportDialogPage(Sheet const&);
NonnullRefPtr<GUI::WizardPage> page() { return *m_page; } NonnullRefPtr<GUI::WizardPage> page() { return *m_page; }
ErrorOr<NonnullOwnPtr<XSV>> make_writer(Core::Stream::Handle<Core::Stream::Stream>);
enum class GenerationType {
Normal,
Preview
};
ErrorOr<void> generate(Core::Stream::Stream&, GenerationType);
protected: protected:
void update_preview(); void update_preview();

View file

@ -12,12 +12,18 @@
namespace Writer { namespace Writer {
template<typename ContainerType> class CSV {
class CSV : public XSV<ContainerType> {
public: public:
CSV(Core::Stream::Handle<Core::Stream::Stream> output, ContainerType const& data, Vector<StringView> headers = {}, WriterBehavior behaviors = default_behaviors()) template<typename ContainerType>
: XSV<ContainerType>(move(output), data, { ",", "\"", WriterTraits::Repeat }, move(headers), behaviors) static ErrorOr<void> generate(Core::Stream::Stream& output, ContainerType const& data, Vector<StringView> headers = {}, WriterBehavior behaviors = default_behaviors())
{ {
return XSV<ContainerType>::generate(output, data, { ",", "\"", WriterTraits::Repeat }, move(headers), behaviors);
}
template<typename ContainerType>
static ErrorOr<void> generate_preview(Core::Stream::Stream& output, ContainerType const& data, Vector<StringView> headers = {}, WriterBehavior behaviors = default_behaviors())
{
return XSV<ContainerType>::generate_preview(output, data, { ",", "\"", WriterTraits::Repeat }, move(headers), behaviors);
} }
}; };

View file

@ -7,7 +7,6 @@
#include <LibTest/TestCase.h> #include <LibTest/TestCase.h>
#include "../CSV.h" #include "../CSV.h"
#include "../XSV.h"
#include <LibCore/MemoryStream.h> #include <LibCore/MemoryStream.h>
TEST_CASE(can_write) TEST_CASE(can_write)
@ -19,8 +18,7 @@ TEST_CASE(can_write)
}; };
Core::Stream::AllocatingMemoryStream stream; Core::Stream::AllocatingMemoryStream stream;
auto csv = Writer::CSV(Core::Stream::Handle<Core::Stream::Stream>(stream), data); MUST(Writer::CSV::generate(stream, data));
MUST(csv.generate());
auto expected_output = R"~(1,2,3 auto expected_output = R"~(1,2,3
4,5,6 4,5,6
@ -40,8 +38,7 @@ TEST_CASE(can_write_with_header)
}; };
Core::Stream::AllocatingMemoryStream stream; Core::Stream::AllocatingMemoryStream stream;
auto csv = Writer::CSV(Core::Stream::Handle<Core::Stream::Stream>(stream), data, { "A"sv, "B\""sv, "C"sv }); MUST(Writer::CSV::generate(stream, data, { "A"sv, "B\""sv, "C"sv }));
MUST(csv.generate());
auto expected_output = R"~(A,"B""",C auto expected_output = R"~(A,"B""",C
1,2,3 1,2,3
@ -61,8 +58,7 @@ TEST_CASE(can_write_with_different_behaviors)
}; };
Core::Stream::AllocatingMemoryStream stream; Core::Stream::AllocatingMemoryStream stream;
auto csv = Writer::CSV(Core::Stream::Handle<Core::Stream::Stream>(stream), data, { "A"sv, "B\""sv, "C"sv }, Writer::WriterBehavior::QuoteOnlyInFieldStart | Writer::WriterBehavior::WriteHeaders); MUST(Writer::CSV::generate(stream, data, { "A"sv, "B\""sv, "C"sv }, Writer::WriterBehavior::QuoteOnlyInFieldStart | Writer::WriterBehavior::WriteHeaders));
MUST(csv.generate());
auto expected_output = R"~(A,B",C auto expected_output = R"~(A,B",C
Well,Hello",Friends Well,Hello",Friends

View file

@ -42,59 +42,48 @@ constexpr WriterBehavior default_behaviors()
template<typename ContainerType, typename HeaderType = Vector<StringView>> template<typename ContainerType, typename HeaderType = Vector<StringView>>
class XSV { class XSV {
public: public:
XSV(Core::Stream::Handle<Core::Stream::Stream> output, ContainerType const& data, WriterTraits traits, HeaderType headers = {}, WriterBehavior behaviors = default_behaviors()) static ErrorOr<void> generate(Core::Stream::Stream& output, ContainerType const& data, WriterTraits traits, HeaderType headers = {}, WriterBehavior behaviors = default_behaviors())
: m_data(data)
, m_traits(move(traits))
, m_behaviors(behaviors)
, m_names(headers)
, m_output(move(output))
{ {
if (!headers.is_empty()) auto writer = XSV(output, data, traits, headers, behaviors);
m_behaviors = m_behaviors | WriterBehavior::WriteHeaders; auto with_headers = has_flag(writer.m_behaviors, WriterBehavior::WriteHeaders);
}
virtual ~XSV() = default;
ErrorOr<void> generate()
{
auto with_headers = has_flag(m_behaviors, WriterBehavior::WriteHeaders);
if (with_headers) { if (with_headers) {
TRY(write_row(m_names)); TRY(writer.write_row(writer.m_names));
TRY(m_output->write_entire_buffer({ "\n", 1 })); TRY(writer.m_output.write_entire_buffer({ "\n", 1 }));
} }
for (auto&& row : m_data) { for (auto&& row : writer.m_data) {
if (with_headers) { if (with_headers) {
if (row.size() != m_names.size()) if (row.size() != writer.m_names.size())
return Error::from_string_literal("Header count does not match given column count"); return Error::from_string_literal("Header count does not match given column count");
} }
TRY(write_row(row)); TRY(writer.write_row(row));
TRY(m_output->write_entire_buffer({ "\n", 1 })); TRY(writer.m_output.write_entire_buffer({ "\n", 1 }));
} }
return {}; return {};
} }
ErrorOr<void> generate_preview() static ErrorOr<void> generate_preview(Core::Stream::Stream& output, ContainerType const& data, WriterTraits traits, HeaderType headers = {}, WriterBehavior behaviors = default_behaviors())
{ {
auto writer = XSV(output, data, traits, headers, behaviors);
auto lines_written = 0; auto lines_written = 0;
constexpr auto max_preview_lines = 8; constexpr auto max_preview_lines = 8;
auto with_headers = has_flag(m_behaviors, WriterBehavior::WriteHeaders); auto with_headers = has_flag(writer.m_behaviors, WriterBehavior::WriteHeaders);
if (with_headers) { if (with_headers) {
TRY(write_row(m_names)); TRY(writer.write_row(writer.m_names));
TRY(m_output->write_entire_buffer({ "\n", 1 })); TRY(writer.m_output.write_entire_buffer({ "\n", 1 }));
++lines_written; ++lines_written;
} }
for (auto&& row : m_data) { for (auto&& row : writer.m_data) {
if (with_headers) { if (with_headers) {
if (row.size() != m_names.size()) if (row.size() != writer.m_names.size())
return Error::from_string_literal("Header count does not match given column count"); return Error::from_string_literal("Header count does not match given column count");
} }
TRY(write_row(row)); TRY(writer.write_row(row));
TRY(m_output->write_entire_buffer({ "\n", 1 })); TRY(writer.m_output.write_entire_buffer({ "\n", 1 }));
++lines_written; ++lines_written;
if (lines_written >= max_preview_lines) if (lines_written >= max_preview_lines)
@ -104,13 +93,24 @@ public:
} }
private: private:
XSV(Core::Stream::Stream& output, ContainerType const& data, WriterTraits traits, HeaderType headers = {}, WriterBehavior behaviors = default_behaviors())
: m_data(data)
, m_traits(move(traits))
, m_behaviors(behaviors)
, m_names(headers)
, m_output(output)
{
if (!headers.is_empty())
m_behaviors = m_behaviors | WriterBehavior::WriteHeaders;
}
template<typename T> template<typename T>
ErrorOr<void> write_row(T&& row) ErrorOr<void> write_row(T&& row)
{ {
bool first = true; bool first = true;
for (auto&& entry : row) { for (auto&& entry : row) {
if (!first) { if (!first) {
TRY(m_output->write_entire_buffer(m_traits.separator.bytes())); TRY(m_output.write_entire_buffer(m_traits.separator.bytes()));
} }
first = false; first = false;
TRY(write_entry(entry)); TRY(write_entry(entry));
@ -136,33 +136,33 @@ private:
if (safe_to_write_normally) { if (safe_to_write_normally) {
if (!string.is_empty()) if (!string.is_empty())
TRY(m_output->write_entire_buffer(string.bytes())); TRY(m_output.write_entire_buffer(string.bytes()));
return {}; return {};
} }
TRY(m_output->write_entire_buffer(m_traits.quote.bytes())); TRY(m_output.write_entire_buffer(m_traits.quote.bytes()));
GenericLexer lexer(string); GenericLexer lexer(string);
while (!lexer.is_eof()) { while (!lexer.is_eof()) {
if (lexer.consume_specific(m_traits.quote)) { if (lexer.consume_specific(m_traits.quote)) {
switch (m_traits.quote_escape) { switch (m_traits.quote_escape) {
case WriterTraits::Repeat: case WriterTraits::Repeat:
TRY(m_output->write_entire_buffer(m_traits.quote.bytes())); TRY(m_output.write_entire_buffer(m_traits.quote.bytes()));
TRY(m_output->write_entire_buffer(m_traits.quote.bytes())); TRY(m_output.write_entire_buffer(m_traits.quote.bytes()));
break; break;
case WriterTraits::Backslash: case WriterTraits::Backslash:
TRY(m_output->write_entire_buffer({ "\\", 1 })); TRY(m_output.write_entire_buffer({ "\\", 1 }));
TRY(m_output->write_entire_buffer(m_traits.quote.bytes())); TRY(m_output.write_entire_buffer(m_traits.quote.bytes()));
break; break;
} }
continue; continue;
} }
auto ch = lexer.consume(); auto ch = lexer.consume();
TRY(m_output->write_entire_buffer({ &ch, 1 })); TRY(m_output.write_entire_buffer({ &ch, 1 }));
} }
TRY(m_output->write_entire_buffer(m_traits.quote.bytes())); TRY(m_output.write_entire_buffer(m_traits.quote.bytes()));
return {}; return {};
} }
@ -170,7 +170,7 @@ private:
WriterTraits m_traits; WriterTraits m_traits;
WriterBehavior m_behaviors; WriterBehavior m_behaviors;
HeaderType m_names; HeaderType m_names;
Core::Stream::Handle<Core::Stream::Stream> m_output; Core::Stream::Stream& m_output;
}; };
} }