mirror of
https://github.com/YACReader/yacreader
synced 2025-07-27 09:24:57 -04:00
YACReader
YACReaderLibrary
comic_vine
db
comic_item.cpp
comic_item.h
comic_model.cpp
comic_model.h
comic_query_result_processor.cpp
comic_query_result_processor.h
data_base_management.cpp
data_base_management.h
folder_item.cpp
folder_item.h
folder_model.cpp
folder_model.h
folder_query_result_processor.cpp
folder_query_result_processor.h
query_lexer.cpp
query_lexer.h
query_parser.cpp
query_parser.h
reading_list.cpp
reading_list.h
reading_list_item.cpp
reading_list_item.h
reading_list_model.cpp
reading_list_model.h
qml
server
Info.plist
YACReaderLibrary.icns
YACReaderLibrary.pro
add_label_dialog.cpp
add_label_dialog.h
add_library_dialog.cpp
add_library_dialog.h
bundle_creator.cpp
bundle_creator.h
classic_comics_view.cpp
classic_comics_view.h
comic_files_manager.cpp
comic_files_manager.h
comic_flow.cpp
comic_flow.h
comic_flow_widget.cpp
comic_flow_widget.h
comics_remover.cpp
comics_remover.h
comics_view.cpp
comics_view.h
comics_view_transition.cpp
comics_view_transition.h
create_library_dialog.cpp
create_library_dialog.h
current_comic_view_helper.cpp
current_comic_view_helper.h
db_helper.cpp
db_helper.h
empty_container_info.cpp
empty_container_info.h
empty_folder_widget.cpp
empty_folder_widget.h
empty_label_widget.cpp
empty_label_widget.h
empty_reading_list_widget.cpp
empty_reading_list_widget.h
empty_special_list.cpp
empty_special_list.h
export_comics_info_dialog.cpp
export_comics_info_dialog.h
export_library_dialog.cpp
export_library_dialog.h
files.qrc
grid_comics_view.cpp
grid_comics_view.h
icon.ico
icon.rc
icon2.ico
icon3.ico
images.qrc
images_osx.qrc
images_win.qrc
import_comics_info_dialog.cpp
import_comics_info_dialog.h
import_library_dialog.cpp
import_library_dialog.h
import_widget.cpp
import_widget.h
info_comics_view.cpp
info_comics_view.h
initial_comic_info_extractor.cpp
initial_comic_info_extractor.h
library_comic_opener.cpp
library_comic_opener.h
library_creator.cpp
library_creator.h
library_window.cpp
library_window.h
macostrayicon.svg
main.cpp
no_libraries_widget.cpp
no_libraries_widget.h
no_search_results_widget.cpp
no_search_results_widget.h
options_dialog.cpp
options_dialog.h
package_manager.cpp
package_manager.h
properties_dialog.cpp
properties_dialog.h
qml.qrc
qml_osx.qrc
qml_win.qrc
rename_library_dialog.cpp
rename_library_dialog.h
server_config_dialog.cpp
server_config_dialog.h
trayhandler.h
trayhandler.mm
trayicon_controller.cpp
trayicon_controller.h
xml_info_library_scanner.cpp
xml_info_library_scanner.h
xml_info_parser.cpp
xml_info_parser.h
yacreader_comic_info_helper.cpp
yacreader_comic_info_helper.h
yacreader_comics_selection_helper.cpp
yacreader_comics_selection_helper.h
yacreader_comics_views_manager.cpp
yacreader_comics_views_manager.h
yacreader_folders_view.cpp
yacreader_folders_view.h
yacreader_history_controller.cpp
yacreader_history_controller.h
yacreader_libraries.cpp
yacreader_libraries.h
yacreader_local_server.cpp
yacreader_local_server.h
yacreader_main_toolbar.cpp
yacreader_main_toolbar.h
yacreader_navigation_controller.cpp
yacreader_navigation_controller.h
yacreader_reading_lists_view.cpp
yacreader_reading_lists_view.h
yacreaderlibrary_de.ts
yacreaderlibrary_es.ts
yacreaderlibrary_fr.ts
yacreaderlibrary_it.ts
yacreaderlibrary_nl.ts
yacreaderlibrary_pt.ts
yacreaderlibrary_ru.ts
yacreaderlibrary_source.ts
yacreaderlibrary_tr.ts
yacreaderlibrary_zh.ts
YACReaderLibraryServer
ci
common
compressed_archive
custom_widgets
dependencies
files
images
release
shortcuts_management
tests
third_party
.clang-format
.editorconfig
.gitattributes
.gitignore
CHANGELOG.md
COPYING.txt
INSTALL.md
README.md
YACReader.1
YACReader.desktop
YACReader.pro
YACReader.svg
YACReaderLibrary.1
YACReaderLibrary.desktop
YACReaderLibrary.svg
azure-pipelines-build-number.yml
azure-pipelines-windows-template.yml
azure-pipelines.yml
background.png
background@2x.png
cleanOSX.sh
compileOSX.sh
config.pri
dmg.json
icon.icns
mktarball.sh
signapps.sh
248 lines
8.3 KiB
C++
248 lines
8.3 KiB
C++
#include "query_parser.h"
|
|
|
|
#include <QVariant>
|
|
#include <type_traits>
|
|
#include <numeric>
|
|
#include <stdexcept>
|
|
|
|
const std::map<QueryParser::FieldType, std::vector<std::string>> QueryParser::fieldNames {
|
|
{ FieldType::numeric, { "numpages", "number", "count", "arcnumber", "arccount" } },
|
|
{ FieldType::text, { "title", "volume", "storyarc", "genere", "writer", "penciller", "inker", "colorist", "letterer", "coverartist", "publisher", "format", "agerating", "synopsis", "characters", "notes" } },
|
|
{ FieldType::boolean, { "isbis", "color", "read", "manga" } },
|
|
{ FieldType::date, { "date" } },
|
|
{ FieldType::filename, { "filename" } },
|
|
{ FieldType::folder, { "folder" } },
|
|
{ FieldType::booleanFolder, { "completed", "finished" } },
|
|
};
|
|
|
|
int QueryParser::TreeNode::buildSqlString(std::string &sqlString, int bindPosition) const
|
|
{
|
|
if (t == "token") {
|
|
++bindPosition;
|
|
if (toLower(children[0].t) == "all") {
|
|
sqlString += "(";
|
|
for (const auto &field : fieldNames.at(FieldType::text)) {
|
|
sqlString += "UPPER(ci." + field + ") LIKE UPPER(:bindPosition" + std::to_string(bindPosition) + ") OR ";
|
|
}
|
|
sqlString += "UPPER(c.filename) LIKE UPPER(:bindPosition" + std::to_string(bindPosition) + ") OR ";
|
|
sqlString += "UPPER(f.name) LIKE UPPER(:bindPosition" + std::to_string(bindPosition) + ")) ";
|
|
} else if (isIn(fieldType(children[0].t), { FieldType::numeric, FieldType::boolean })) {
|
|
sqlString += "ci." + children[0].t + " = :bindPosition" + std::to_string(bindPosition) + " ";
|
|
} else if (fieldType(children[0].t) == FieldType::filename) {
|
|
sqlString += "(UPPER(c." + children[0].t + ") LIKE UPPER(:bindPosition" + std::to_string(bindPosition) + ")) ";
|
|
} else if (fieldType(children[0].t) == FieldType::folder) {
|
|
sqlString += "(UPPER(f.name) LIKE UPPER(:bindPosition" + std::to_string(bindPosition) + ")) ";
|
|
} else if (fieldType(children[0].t) == FieldType::booleanFolder) {
|
|
sqlString += "f." + children[0].t + " = :bindPosition" + std::to_string(bindPosition) + " ";
|
|
} else {
|
|
sqlString += "(UPPER(ci." + children[0].t + ") LIKE UPPER(:bindPosition" + std::to_string(bindPosition) + ")) ";
|
|
}
|
|
} else if (t == "not") {
|
|
sqlString += "(NOT ";
|
|
bindPosition = children[0].buildSqlString(sqlString, bindPosition);
|
|
sqlString += ")";
|
|
} else {
|
|
sqlString += "(";
|
|
bindPosition = children[0].buildSqlString(sqlString, bindPosition);
|
|
sqlString += " " + t + " ";
|
|
bindPosition = children[1].buildSqlString(sqlString, bindPosition);
|
|
sqlString += ")";
|
|
}
|
|
|
|
return bindPosition;
|
|
}
|
|
|
|
int QueryParser::TreeNode::bindValues(QSqlQuery &selectQuery, int bindPosition) const
|
|
{
|
|
if (t == "token") {
|
|
std::string bind_string(":bindPosition" + std::to_string(++bindPosition));
|
|
if (isIn(fieldType(children[0].t), { FieldType::numeric })) {
|
|
selectQuery.bindValue(QString::fromStdString(bind_string), std::stoi(children[1].t));
|
|
} else if (isIn(fieldType(children[0].t), { FieldType::boolean, FieldType::booleanFolder })) {
|
|
auto value = toLower(children[1].t);
|
|
if (value == "true") {
|
|
selectQuery.bindValue(QString::fromStdString(bind_string), 1);
|
|
} else if (value == "false") {
|
|
selectQuery.bindValue(QString::fromStdString(bind_string), 0);
|
|
} else {
|
|
selectQuery.bindValue(QString::fromStdString(bind_string), std::stoi(value));
|
|
}
|
|
} else {
|
|
selectQuery.bindValue(QString::fromStdString(bind_string), QString::fromStdString("%%" + children[1].t + "%%"));
|
|
}
|
|
} else if (t == "not") {
|
|
bindPosition = children[0].bindValues(selectQuery, bindPosition);
|
|
} else {
|
|
bindPosition = children[0].bindValues(selectQuery, bindPosition);
|
|
bindPosition = children[1].bindValues(selectQuery, bindPosition);
|
|
}
|
|
|
|
return bindPosition;
|
|
}
|
|
|
|
QueryParser::QueryParser()
|
|
{
|
|
}
|
|
|
|
QueryParser::TreeNode QueryParser::parse(const std::string &expr)
|
|
{
|
|
lexer = QueryLexer(expr);
|
|
advance();
|
|
|
|
auto prog = orExpression();
|
|
|
|
if (!isEof()) {
|
|
throw std::invalid_argument("Extra characters at end of search");
|
|
}
|
|
|
|
return prog;
|
|
}
|
|
|
|
std::string QueryParser::toLower(const std::string &string)
|
|
{
|
|
std::string res(string);
|
|
std::transform(res.begin(), res.end(), res.begin(), ::tolower);
|
|
return res;
|
|
}
|
|
|
|
std::string QueryParser::token(bool advance)
|
|
{
|
|
if (isEof()) {
|
|
return "";
|
|
}
|
|
|
|
auto lexeme = currentToken.lexeme();
|
|
|
|
auto res = (tokenType() == Token::Type::quotedWord) ? currentToken.lexeme().substr(1, lexeme.size() - 2) : lexeme; // TODO process quotedWordDiferently?
|
|
if (advance) {
|
|
this->advance();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::string QueryParser::lcaseToken(bool advance)
|
|
{
|
|
if (isEof()) {
|
|
return "";
|
|
}
|
|
|
|
auto lexeme = currentToken.lexeme();
|
|
|
|
auto res = (tokenType() == Token::Type::quotedWord) ? currentToken.lexeme().substr(1, lexeme.size() - 2) : lexeme;
|
|
|
|
if (advance) {
|
|
this->advance();
|
|
}
|
|
return toLower(res);
|
|
}
|
|
|
|
Token::Type QueryParser::tokenType()
|
|
{
|
|
return currentToken.type();
|
|
}
|
|
|
|
bool QueryParser::isEof() const
|
|
{
|
|
return currentToken.type() == Token::Type::eof;
|
|
}
|
|
|
|
void QueryParser::advance()
|
|
{
|
|
currentToken = lexer.next();
|
|
}
|
|
|
|
QueryParser::FieldType QueryParser::fieldType(const std::string &str)
|
|
{
|
|
for (const auto &names : fieldNames) {
|
|
if (std::find(names.second.begin(), names.second.end(), toLower(str)) != names.second.end()) {
|
|
return names.first;
|
|
}
|
|
}
|
|
|
|
return FieldType::unknown;
|
|
}
|
|
|
|
std::string QueryParser::join(const QStringList &strings, const std::string &delim)
|
|
{
|
|
return std::accumulate(strings.begin(), strings.end(), std::string(),
|
|
[&delim](const std::string &a, const QString &b) -> std::string {
|
|
return a + (a.length() > 0 && b.length() > 0 ? delim : "") + b.toStdString();
|
|
});
|
|
}
|
|
|
|
QStringList QueryParser::split(const std::string &string, char delim)
|
|
{
|
|
auto words = QString::fromStdString(string).split(delim);
|
|
return words;
|
|
}
|
|
|
|
QueryParser::TreeNode QueryParser::orExpression()
|
|
{
|
|
auto lhs = andExpression();
|
|
if (lcaseToken() == "or") {
|
|
advance();
|
|
return TreeNode("or", { lhs, orExpression() });
|
|
}
|
|
return lhs;
|
|
}
|
|
|
|
QueryParser::TreeNode QueryParser::andExpression()
|
|
{
|
|
auto lhs = notExpression();
|
|
if (lcaseToken() == "and") {
|
|
advance();
|
|
return TreeNode("and", { lhs, andExpression() });
|
|
}
|
|
|
|
if ((isIn(tokenType(), { Token::Type::word, Token::Type::quotedWord }) || token() == "(") && lcaseToken() != "or") {
|
|
return TreeNode("and", { lhs, andExpression() });
|
|
}
|
|
|
|
return lhs;
|
|
}
|
|
|
|
QueryParser::TreeNode QueryParser::notExpression()
|
|
{
|
|
if (lcaseToken() == "not") {
|
|
advance();
|
|
return TreeNode("not", { notExpression() });
|
|
}
|
|
return locationExpression();
|
|
}
|
|
|
|
QueryParser::TreeNode QueryParser::locationExpression()
|
|
{
|
|
if (tokenType() == Token::Type::opcode && token() == "(") {
|
|
advance();
|
|
auto res = orExpression();
|
|
if (tokenType() != Token::Type::opcode || token(true) != ")") {
|
|
throw std::invalid_argument("missing )'");
|
|
}
|
|
return res;
|
|
}
|
|
if (!isIn(tokenType(), { Token::Type::word, Token::Type::quotedWord })) {
|
|
throw std::invalid_argument("Invalid syntax. Expected a lookup name or a word");
|
|
}
|
|
return baseToken();
|
|
}
|
|
|
|
QueryParser::TreeNode QueryParser::baseToken()
|
|
{
|
|
if (tokenType() == Token::Type::quotedWord) {
|
|
return TreeNode("token", { TreeNode("all", {}), TreeNode(token(true), {}) });
|
|
}
|
|
|
|
auto words(split(token(true), ':'));
|
|
|
|
if (words.size() > 1 && fieldType(words[0].toStdString()) != FieldType::unknown) {
|
|
auto loc(toLower(words[0].toStdString()));
|
|
words.erase(words.begin());
|
|
if (words.size() == 1 && tokenType() == Token::Type::quotedWord) {
|
|
return TreeNode("token", { TreeNode(loc, {}), TreeNode(token(true), {}) });
|
|
}
|
|
return TreeNode("token", { TreeNode(loc, {}), TreeNode(join(words, ":"), {}) });
|
|
}
|
|
|
|
return TreeNode("token", { TreeNode("all", {}), TreeNode(join(words, ":"), {}) });
|
|
}
|