#include "util.h" #include "CsvWriter.h" #include "SqlLexer.h" #include #include #include #include #include // 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 = QString::fromUtf8("μ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; } 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(); // 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) { 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; 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); } } 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_) { // We need to atleast escape " and \ and also any multi byte utf8 char // 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; } } 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 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 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); } } else { // error during lexical analysis, need to recover throw std::runtime_error("Unrecognized input"); } } 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 { class Token { public: enum Type { Code, StringLiteral, SingleLineComment, MultiLineComment }; const Type type; /// Depends on type /// Code: literal copy of the matched code /// StringLiteral: the contents of the string literal, escapes have been unescaped /// *Comment, the text in the comment const QString data; Token(Type type, QString data) : type(type) , data(data) {} }; /// Tokenizer to get SQL out of a piece of programming language code /// /// It works by ignoring most input and only get's triggered by string literals /// and comments. It does return tokens for the code in between just so class ProgLangTokenizer { public: ProgLangTokenizer(const QString &in) : input(in) {} private: const QString input; int position = 0; }; } 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; enum { WHITESPACE, PREFIX, IN_STRING, SingleLineComment, END, ERROR } state = WHITESPACE; int index = 0; QChar quote = '\0'; while (state != ERROR && state != END && index < in.length()) { if (state == WHITESPACE) { // 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()) { state = END; break; } // Assume quotes can vary if (in[index] == '\'' || in[index] == '\"') { quote = in[index]; ++index; state = IN_STRING; } else { state = PREFIX; } } else if (state == PREFIX) { auto c = in[index]; if (c == 'L' || c == 'U') { // C++ prefix expect C++ double quote if (in.length() > index+ 1 && in[index+1] == '"') { index += 2; state = IN_STRING; } else { state = ERROR; break; } } if (c == 'u') { // C++ prefix expect C++ double quote if (in.length() > index+ 2 && in[index+1] == '8' && in[index+2] == '"') { index += 3; state = IN_STRING; } else if (in.length() > index+ 1 && in[index+1] == '"') { index += 2; state = IN_STRING; } else { state = ERROR; break; } } else { state = ERROR; break; } } else if (state == IN_STRING) { // scan contents of string and process any escapes encountered bool escape = false; while (state != ERROR && index < in.length()) { QChar c = in[index]; if (escape) { if (c == 'a') output += '\x07'; else if (c == 'a') output += '\x07'; else if (c == 'b') output += '\x08'; else if (c == 'f') output += '\x0c'; else if (c == 'n') output += '\n'; else if (c == 'r') ; else if (c == 'v') ; else if (c >= '0' && c <= '7') { // process octal escape if (in.length() > index + 2) { char buf[4]; buf[0] = c.toLatin1(); buf[1] = in[++index].toLatin1(); buf[2] = in[++index].toLatin1(); buf[3] = 0; long int v = strtol(buf, nullptr, 8); if (v < 0x80) { output += static_cast(static_cast(v)); } else { state = ERROR; break; } } else { state = ERROR; break; } } else if (c == 'x') { // hex byte if (in.length() > index + 2) { char buf[3]; buf[0] = in[++index].toLatin1(); buf[1] = in[++index].toLatin1(); buf[2] = 0; long int v = strtol(buf, nullptr, 16); output += static_cast(static_cast(v)); } else { state = ERROR; break; } } else if (c == 'u') { // 4 digit hax unicode codepoint // hex byte if (in.length() > index + 4) { char buf[5]; buf[0] = in[++index].toLatin1(); buf[1] = in[++index].toLatin1(); buf[2] = in[++index].toLatin1(); buf[3] = in[++index].toLatin1(); buf[4] = 0; long int v = strtol(buf, nullptr, 16); output += static_cast(static_cast(v)); } else { state = ERROR; break; } } else if (c == 'U') { // 8 digit hax unicode codepoint if (in.length() > index + 8) { char buf[9]; buf[0] = in[++index].toLatin1(); buf[1] = in[++index].toLatin1(); buf[2] = in[++index].toLatin1(); buf[3] = in[++index].toLatin1(); buf[4] = in[++index].toLatin1(); buf[5] = in[++index].toLatin1(); buf[6] = in[++index].toLatin1(); buf[7] = in[++index].toLatin1(); buf[8] = 0; uint v = static_cast(strtol(buf, nullptr, 16)); if (QChar::requiresSurrogates(v)) { output += QChar(QChar::highSurrogate(v)); output += QChar(QChar::lowSurrogate(v)); } } else { state = ERROR; break; } } else { output += c; } escape = false; } else { if (c == quote) { state = WHITESPACE; ++index; break; } // Is there any language where string literals do not have to be terminated before the end of the line // Not considering string literals that explicitly allow for multiline strings as these are often raw strings // and can be copy pasted normally. else if (c == '\n') { state = WHITESPACE; ++index; break; } else if (c == '\\') { escape = true; } else { output += c; } } ++index; } } } return output; }