Made CSV expoer RFC 4180 compliant.

This commit is contained in:
tastytea 2019-05-16 03:56:17 +02:00
parent dfc382b92c
commit bb9cff2992
Signed by: tastytea
GPG Key ID: CFC39497F1B26E07
6 changed files with 116 additions and 19 deletions

View File

@ -29,7 +29,7 @@ remwharead - Remember what you read, and when
Add tags to _URL_, delimited by commas.
*-e* _format_, *--export* _format_::
Export to _format_. Possible values are _csv_ and _asciidoc_.
Export to _format_. Possible values are _csv_ and _asciidoc_. See _FORMATS_.
*-f* _file_, *--file* _file_::
Save output to _file_. Default is stdout.
@ -56,6 +56,15 @@ Print version, copyright and license.
`remwharead -e asciidoc | asciidoctor --backend=html5 --out-file=out.html -`
== FORMATS
=== CSV
CSV is short for comma separated values. All fields are quoted and delimited by
commas. Line breaks in the full text are converted to "\n". Our CSV
implementation follows RFC 4180 and the full MIME media type is
`text/csv;charset=utf-8;header=present`.
== FILES
* *Database*: `${XDG_DATA_HOME}/remwharead/database.sqlite`

62
src/csv.cpp Normal file
View File

@ -0,0 +1,62 @@
/* This file is part of remwharead.
* Copyright © 2019 tastytea <tastytea@tastytea.de>
*
* 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, version 3.
*
* 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/>.
*/
#include <iostream>
#include <regex>
#include "time.hpp"
#include "csv.hpp"
using std::cerr;
using std::endl;
using std::regex;
using std::regex_replace;
void export_csv(const vector<Database::entry> &entries, ostream &out)
{
try
{
out << "\"URI\",\"Archived URI\",\"Date & time\",\"Tags\","
<< "\"Title\",\"Description\",\"Full text\"\r\n";
for (const Database::entry &entry : entries)
{
string strtags;
for (const string &tag : entry.tags)
{
strtags += tag;
if (tag != *(entry.tags.rbegin()))
{
strtags += ",";
}
}
out << '"' << quote_csv(entry.uri) << "\",\""
<< quote_csv(entry.archive_uri) << "\",\""
<< timepoint_to_string(entry.datetime) << "\",\""
<< quote_csv(strtags) << "\",\""
<< quote_csv(entry.title) << "\",\""
<< quote_csv(entry.description) << "\",\""
<< quote_csv(entry.fulltext_oneline()) << '"'<< "\r\n";
}
}
catch (std::exception &e)
{
cerr << "Error in " << __func__ << ": " << e.what() << endl;
}
}
const string quote_csv(const string &field)
{
return regex_replace(field, regex("\""), "\"\"");
}

31
src/csv.hpp Normal file
View File

@ -0,0 +1,31 @@
/* This file is part of remwharead.
* Copyright © 2019 tastytea <tastytea@tastytea.de>
*
* 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, version 3.
*
* 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/>.
*/
#ifndef REMWHAREAD_CSV_HPP
#define REMWHAREAD_CSV_HPP
#include <vector>
#include <iostream>
#include "sqlite.hpp"
using std::vector;
using std::ostream;
using std::cout;
void export_csv(const vector<Database::entry> &entries, ostream &out = cout);
const string quote_csv(const string &field);
#endif // REMWHAREAD_CSV_HPP

View File

@ -20,6 +20,7 @@
#include "sqlite.hpp"
#include "parse_options.hpp"
#include "url.hpp"
#include "csv.hpp"
using std::cout;
using std::cerr;
@ -55,24 +56,7 @@ int main(const int argc, const char *argv[])
{
case export_format::csv:
{
cout << "#URI;Archived URI;Date & time;Tags;Title;Description\n";
for (const Database::entry &entry
: db.retrieve(opts.span[0], opts.span[1]))
{
string strtags;
for (const string &tag : entry.tags)
{
strtags += tag;
if (tag != *(entry.tags.rbegin()))
{
strtags += ",";
}
}
cout << '"' << entry.uri << "\";\"" << entry.archive_uri << "\";\""
<< timepoint_to_string(entry.datetime) << "\";\""
<< strtags << "\";\"" << entry.title << "\";\""
<< entry.description << '"'<< endl;
}
export_csv(db.retrieve(opts.span[0], opts.span[1]));
break;
}
case export_format::asciidoc:

View File

@ -17,6 +17,7 @@
#include <exception>
#include <iostream>
#include <algorithm>
#include <regex>
#include <basedir.h>
#include <sqlite/execute.hpp>
#include <sqlite/query.hpp>
@ -25,6 +26,8 @@
using std::cerr;
using std::endl;
using std::regex;
using std::regex_replace;
Database::Database()
: _connected(false)
@ -60,6 +63,11 @@ Database::operator bool() const
return _connected;
}
const string Database::entry::fulltext_oneline() const
{
return regex_replace(fulltext, regex("\n"), "\\n");
}
void Database::store(const Database::entry &data) const
{
try

View File

@ -43,6 +43,9 @@ public:
string title;
string description;
string fulltext;
//! The full text in one line.
const string fulltext_oneline() const;
} entry;
Database();