pgLab/pglablib/util.cpp

411 lines
10 KiB
C++
Raw Normal View History

#include "util.h"
#include "CsvWriter.h"
#include "SqlLexer.h"
#include <QApplication>
#include <QTextStream>
#include <QClipboard>
2017-02-05 08:23:06 +01:00
#include <sstream>
#include <stdexcept>
// Supported range from microseconds to seconds
// min:sec to hours::min::sec
QString msfloatToHumanReadableString(float ms)
{
QString unit;
float val;
int deci = 2;
if (ms < 1.0f) {
val = ms * 1000.f;
//result = QString::asprintf("%0.3f", ms * 1000.0f);
unit = u8"μs";
}
else if (ms >= 1000.0) {
val = ms / 1000.0f;
unit = "s";
if (val >= 60.0) {
int secs = val;
int min = secs / 60.0;
secs -= min * 60;
if (min >= 60) {
int hour = min / 60;
min -= hour * 60;
return QString::asprintf("%d:%02d:%02d", hour, min, secs);
}
else {
return QString::asprintf("%02d:%02d", min, secs);
}
}
}
else {
val = ms;
unit = "ms";
}
// if (val >= 1000.f) {
// deci = 0;
// }
// else
if (val >= 100.f) {
deci = 0;
}
else if (val >= 10.f) {
deci = 1;
}
QString result = QString::asprintf("%0.*f", deci, val);
return result + unit;
}
2017-02-05 13:35:41 +01:00
void exportTable(const QTableView *view, QTextStream &out)
{
auto model = view->model();
if (model) {
CsvWriter csv(&out);
csv.setSeperator('\t');
csv.setQuote('"');
auto cols = model->columnCount();
auto rows = model->rowCount();
2021-04-10 09:48:48 +02:00
// header
for (int col = 0; col < cols; ++col) {
auto display_text = model->headerData(col, Qt::Horizontal, Qt::DisplayRole).toString();
csv.writeField(display_text);
}
csv.nextRow();
for (auto row = 0; row < rows; ++row) {
2017-02-05 13:35:41 +01:00
for (int col = 0; col < cols; ++col) {
auto idx = model->index(row, col);
auto display_text = idx.data(Qt::DisplayRole).toString();
csv.writeField(display_text);
}
csv.nextRow();
}
out.flush();
}
}
void copySelectionToClipboard(const QTableView *view)
{
//QAbstractItemModel * model = resultModel; //view->model();
QItemSelectionModel * selection = view->selectionModel();
QModelIndexList selectedIndexes = selection->selectedIndexes();
if (selectedIndexes.count() > 0) {
QString clipboard_string;
2021-03-06 13:13:31 +01:00
QTextStream out(&clipboard_string, QTextStream::WriteOnly);
CsvWriter csv(&out);
csv.setSeperator('\t');
csv.setQuote('"');
int previous_row = selectedIndexes[0].row();
// for (int i = 0; i < selectedIndexes.count(); ++i) {
// QModelIndex current = selectedIndexes[i];
for (auto current : selectedIndexes) {
if (current.row() > previous_row) {
csv.nextRow();
previous_row = current.row();
}
QString display_text = current.data(Qt::DisplayRole).toString();
csv.writeField(display_text);
}
out.flush();
QApplication::clipboard()->setText(clipboard_string);
}
}
2017-02-05 08:23:06 +01:00
QString EscapeForCString(QString qi)
{
int escape_count = 0;
for (QChar c : qi)
if (c == '"' || c == '\\') ++escape_count;
QString out;
out.reserve(qi.size() + escape_count);
for (QChar c : qi)
if (c == '"' || c == '\\') {
out += "\\";
out += c;
}
else
out += c;
return out;
}
QString ConvertToMultiLineCString(const QString &in_)
2017-02-05 08:23:06 +01:00
{
// We need to atleast escape " and \ and also any multi byte utf8 char
2017-02-05 08:23:06 +01:00
// remove empty lines at start
int last_nl_idx = 0;
for (int idx = 0; idx < in_.length(); ++idx) {
QChar c = in_[idx];
if (c == '\n') last_nl_idx = idx+1;
if (!c.isSpace()) {
break;
2017-02-05 08:23:06 +01:00
}
}
QString in = in_.right(in_.length() - last_nl_idx);
int idx;
for (idx = in.length() - 1; idx >= 0 && in[idx].isSpace(); --idx) ;
++idx;
in.truncate(idx);
SqlLexer lexer(in, LexerState::Null, true);
QString out;
QString line = "\"";
QString comment;
while (true) {
SqlToken token = lexer.nextBasicToken();
if (token.ok) {
if (token.tokenType == BasicTokenType::Comment) {
// save comment is seperate variable
2021-03-06 13:13:31 +01:00
comment = "//" + token.out.right(token.out.length()-2);
// Trim whitespace on right
int idx;
for (idx = comment.length() - 1; idx >= 0 && comment[idx].isSpace(); --idx) ;
++idx;
comment.truncate(idx);
}
else if (token.tokenType == BasicTokenType::End || token.tokenType == BasicTokenType::NewLine) {
// trim right
{
int idx;
for (idx = line.length() - 1; idx >= 0 && line[idx].isSpace(); --idx) ;
++idx;
if (!comment.isEmpty()) {
// put the whitespace in front of the comment so it will be outside the contents of the string literal but alignment of comments is preserved
2021-03-06 13:13:31 +01:00
comment = line.right(line.length() - (idx)) + comment;
}
line.truncate(idx);
}
out += line;
if (token.tokenType == BasicTokenType::End) {
out += "\"";
out += comment;
break;
}
else {
out += "\\n\"";
out += comment;
out += "\n";
line = "\"";
}
comment.clear();
}
else {
line += EscapeForCString(token.out);
}
2017-02-05 08:23:06 +01:00
}
else {
// error during lexical analysis, need to recover
throw std::runtime_error("Unrecognized input");
2017-02-05 08:23:06 +01:00
}
}
return out;
}
QString ConvertToMultiLineRawCppString(const QString &in)
{
const QString delim = "__SQL__";
QString out;
out.append("R\"" + delim + "(\n");
out.append(in.trimmed());
out.append("\n)" + delim + "\"");
return out;
}
namespace {
// index points at the first char of the token. On success index is placed after the opening quote and quote is set to '"' or '\''.
bool readOpeningQuote(const QString &in, int &index, QChar &quote)
{
QChar c = in[index];
if (c == '\'' || c == '\"') {
quote = c;
++index;
return true;
}
if (c == 'L' || c == 'U') {
if (in.length() > index + 1 && in[index + 1] == '\"') {
quote = '\"';
index += 2;
return true;
}
return false;
}
if (c == 'u') {
if (in.length() > index + 2 && in[index + 1] == '8' && in[index + 2] == '\"') {
quote = '\"';
index += 3;
return true;
}
if (in.length() > index + 1 && in[index + 1] == '\"') {
quote = '\"';
index += 2;
return true;
}
}
return false;
}
int hexDigitValue(QChar c)
{
if (c >= '0' && c <= '9') return c.unicode() - '0';
if (c >= 'a' && c <= 'f') return c.unicode() - 'a' + 10;
if (c >= 'A' && c <= 'F') return c.unicode() - 'A' + 10;
return -1;
}
// Reads exactly `count` hex digits starting at `first`. Fails on out-of-range or non-hex input.
bool parseHexDigits(const QString &in, int first, int count, char32_t &value)
{
if (in.length() < first + count) return false;
value = 0;
for (int i = first; i < first + count; ++i) {
int digit = hexDigitValue(in[i]);
if (digit < 0) return false;
value = value * 16 + static_cast<char32_t>(digit);
}
return true;
}
// Appends a code point as UTF-16. Rejects > 0x10FFFF and surrogates D800-DFFF.
bool appendCodePoint(QString &out, char32_t cp)
{
if (cp > 0x10FFFF || (cp >= 0xD800 && cp <= 0xDFFF)) return false;
if (QChar::requiresSurrogates(cp)) {
out += QChar(QChar::highSurrogate(cp));
out += QChar(QChar::lowSurrogate(cp));
}
else {
out += QChar(static_cast<char16_t>(cp));
}
return true;
}
// index points at the first octal digit. On success index points at the last consumed digit.
bool decodeOctalEscape(const QString &in, int &index, QString &out)
{
if (in.length() <= index + 2) return false;
int value = 0;
for (int i = index; i <= index + 2; ++i) {
QChar digit = in[i];
if (digit < '0' || digit > '7') return false;
value = value * 8 + (digit.unicode() - '0');
}
if (value >= 0x80) return false;
out += static_cast<QChar>(static_cast<char>(value));
index += 2;
return true;
}
// index points at the char after the backslash. On success index points at the last consumed char.
bool decodeEscape(const QString &in, int &index, QString &out)
{
switch (in[index].unicode()) {
case 'a':
out += '\x07';
break;
case 'b':
out += '\x08';
break;
case 'f':
out += '\x0c';
break;
case 'n':
out += '\n';
break;
case 'r':
case 'v':
break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
return decodeOctalEscape(in, index, out);
case 'x': {
char32_t value = 0;
if (!parseHexDigits(in, index + 1, 2, value)) return false;
out += static_cast<QChar>(static_cast<char>(value));
index += 2;
break;
}
case 'u': {
char32_t value = 0;
if (!parseHexDigits(in, index + 1, 4, value)) return false;
out += QChar(static_cast<ushort>(value));
index += 4;
break;
}
case 'U': {
char32_t value = 0;
if (!parseHexDigits(in, index + 1, 8, value)) return false;
if (!appendCodePoint(out, value)) return false;
index += 8;
break;
}
default:
out += in[index];
break;
}
return true;
}
// index points at the first char after the opening quote. Stops at the closing quote (consumed), at newline (consumed) or at end of input.
bool readStringBody(const QString &in, int &index, QChar quote, QString &out)
{
while (index < in.length()) {
QChar c = in[index];
if (c == quote || c == '\n') {
++index;
return true;
}
if (c == '\\') {
++index;
if (index == in.length()) return true;
if (!decodeEscape(in, index, out)) return false;
}
else {
out += c;
}
++index;
}
return true;
}
}
QString ConvertLangToSqlString(const QString &in)
{
// Assume mostly C++ for now but allow some other things like
// - single quotes (php)
// - concattenation operators . (php) and + (java)
// Allow cpp prefixes L u8 u U
// Parser flow, we start in whitespace state and search for prefix|opening quote
// parse string and process escapes
// if escape is \r strip it if \n go to new line
// until we reach matching end quote
// skip whitespace and + or .
QString output;
int index = 0;
QChar quote = '\0';
while (true) {
// skip all whitespace untill we encounter something else
// we also skip concatenation operators. Note this code is not trying to validate
// for correct syntax so it will quite happily accept many incorrect constructs
// that doesn't matter however as we are just trying to strip everything which is not SQL.
while (index < in.length() && (in[index].isSpace() || in[index] == '+' || in[index] == '.')) ++index;
if (index == in.length()) break;
if (!readOpeningQuote(in, index, quote)) break;
if (!readStringBody(in, index, quote, output)) break;
}
return output;
}