From c8ebc91d4799e59518ac887f69e584040cf29994 Mon Sep 17 00:00:00 2001 From: github actions Date: Sat, 1 Aug 2020 02:13:15 +0000 Subject: [PATCH 01/15] Squashed 'src/main/resources/csl-styles/' changes from bf698acec7..827b986621 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 827b986621 add DOI preprint to american-society-for-microbiology.csl (#4946) 7cab2f7d8a Create depro-ufs.csl (#4947) 2fcda1fe03 Create journal-of-sport-science-and-medicine.csl (#4949) cd457d4bce Update american-marketing-association.csl (#4945) cacc4eefbd Create nejm-catalyst.csl (#4943) bd769b91da Update and rename dependent/chinese-medical-journal.csl to chinese-me… (#4941) 1f706cda6c Update health-services-research.csl (#4939) 76bcd1d9d7 Update journal-of-the-royal-society-of-western-australia.csl (#4932) e79640e74d Create afro-asia.csl (#4934) c601aa4e0f Update lancaster-university-harvard.csl (#4938) 184fd90210 Update collection-du-centre-jean-berard.csl (#4936) 82f9aec90f fix et-al & add DOI thyroid.csl (#4937) 4af169fc11 Create universidade-estadual-de-alagoas-uneal-abnt (#4885) 47165b3d5a Update and rename medical-physics.csl to dependent/medical-physics.csl (AMA) (#4905) 3cab27dc1a Update thieme-german.csl (#4931) eb2c9776e0 Create zeitschrift-fur-zahnarztliche-implantologie.csl (#4925) 4adb1ea0be Create karstenia.csl (#4929) 530a136786 Fix author substitute in Universita Cattolica git-subtree-dir: src/main/resources/csl-styles git-subtree-split: 827b986621348627f8e894d1b7f11191f905a8e2 --- afro-asia.csl | 582 ++++++++++++++ american-marketing-association.csl | 11 +- american-society-for-microbiology.csl | 17 +- chinese-medical-journal.csl | 212 +++++ collection-du-centre-jean-berard.csl | 14 +- dependent/chinese-medical-journal.csl | 15 - dependent/health-services-research.csl | 18 + dependent/medical-physics.csl | 17 + health-services-research.csl | 233 ------ ...al-of-applied-clinical-medical-physics.csl | 2 +- journal-of-sports-science-and-medicine.csl | 188 +++++ journal-of-the-american-ceramic-society.csl | 2 +- ...the-royal-society-of-western-australia.csl | 1 + karstenia.csl | 163 ++++ lancaster-university-harvard.csl | 5 +- medical-physics.csl | 151 ---- ...-catalyst-innovations-in-care-delivery.csl | 176 +++++ thieme-german.csl | 5 + thyroid.csl | 11 +- universidade-estadual-de-alagoas-abnt.csl | 721 +++++++++++++++++ ...tamento-de-engenharia-de-producao-abnt.csl | 745 ++++++++++++++++++ universita-cattolica-del-sacro-cuore.csl | 2 +- ...chrift-fur-zahnarztliche-implantologie.csl | 161 ++++ 23 files changed, 3032 insertions(+), 420 deletions(-) create mode 100644 afro-asia.csl create mode 100644 chinese-medical-journal.csl delete mode 100644 dependent/chinese-medical-journal.csl create mode 100644 dependent/health-services-research.csl create mode 100644 dependent/medical-physics.csl delete mode 100644 health-services-research.csl create mode 100644 journal-of-sports-science-and-medicine.csl create mode 100644 karstenia.csl delete mode 100644 medical-physics.csl create mode 100644 nejm-catalyst-innovations-in-care-delivery.csl create mode 100644 universidade-estadual-de-alagoas-abnt.csl create mode 100644 universidade-federal-de-sergipe-departamento-de-engenharia-de-producao-abnt.csl create mode 100644 zeitschrift-fur-zahnarztliche-implantologie.csl diff --git a/afro-asia.csl b/afro-asia.csl new file mode 100644 index 00000000000..408dc49a275 --- /dev/null +++ b/afro-asia.csl @@ -0,0 +1,582 @@ + + diff --git a/american-marketing-association.csl b/american-marketing-association.csl index 0422cb7f0d8..eeb611cbc35 100644 --- a/american-marketing-association.csl +++ b/american-marketing-association.csl @@ -5,8 +5,8 @@ AMA http://www.zotero.org/styles/american-marketing-association - - + + Sebastian Karcher @@ -14,11 +14,14 @@ Rintze Zelle http://twitter.com/rintzezelle + + Patrick O'Brien + AMA Reference List Style published by the American Marketing Association - 2014-10-17T21:46:29+00:00 + 2020-07-27T14:58:51+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -189,7 +192,7 @@ - + diff --git a/american-society-for-microbiology.csl b/american-society-for-microbiology.csl index fa2d726ed57..c132f6c4c64 100644 --- a/american-society-for-microbiology.csl +++ b/american-society-for-microbiology.csl @@ -27,7 +27,7 @@ Style for all American Society for Microbiology journals. - 2015-03-13T23:14:05+00:00 + 2020-07-27T18:54:54+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -105,10 +105,17 @@ - - - - + + + + + + + + + + + diff --git a/chinese-medical-journal.csl b/chinese-medical-journal.csl new file mode 100644 index 00000000000..76685f5cab3 --- /dev/null +++ b/chinese-medical-journal.csl @@ -0,0 +1,212 @@ + + diff --git a/collection-du-centre-jean-berard.csl b/collection-du-centre-jean-berard.csl index 6b3c1a50394..94663e9accd 100644 --- a/collection-du-centre-jean-berard.csl +++ b/collection-du-centre-jean-berard.csl @@ -1,5 +1,5 @@ - diff --git a/dependent/health-services-research.csl b/dependent/health-services-research.csl new file mode 100644 index 00000000000..6098f69e5bc --- /dev/null +++ b/dependent/health-services-research.csl @@ -0,0 +1,18 @@ + + diff --git a/dependent/medical-physics.csl b/dependent/medical-physics.csl new file mode 100644 index 00000000000..6aabd4e5e81 --- /dev/null +++ b/dependent/medical-physics.csl @@ -0,0 +1,17 @@ + + diff --git a/health-services-research.csl b/health-services-research.csl deleted file mode 100644 index 10b4ff5cfc6..00000000000 --- a/health-services-research.csl +++ /dev/null @@ -1,233 +0,0 @@ - - diff --git a/journal-of-applied-clinical-medical-physics.csl b/journal-of-applied-clinical-medical-physics.csl index 857fc07b1ee..889e3cbef73 100644 --- a/journal-of-applied-clinical-medical-physics.csl +++ b/journal-of-applied-clinical-medical-physics.csl @@ -5,7 +5,7 @@ Journal of Applied Clinical Medical Physics http://www.zotero.org/styles/journal-of-applied-clinical-medical-physics - + Stefano Peca diff --git a/journal-of-sports-science-and-medicine.csl b/journal-of-sports-science-and-medicine.csl new file mode 100644 index 00000000000..a1178b9618b --- /dev/null +++ b/journal-of-sports-science-and-medicine.csl @@ -0,0 +1,188 @@ + + diff --git a/journal-of-the-american-ceramic-society.csl b/journal-of-the-american-ceramic-society.csl index 56f5053d00d..c14ca1571f9 100644 --- a/journal-of-the-american-ceramic-society.csl +++ b/journal-of-the-american-ceramic-society.csl @@ -4,7 +4,7 @@ Journal of the American Ceramic Society http://www.zotero.org/styles/journal-of-the-american-ceramic-society - + Sebastian Karcher diff --git a/journal-of-the-royal-society-of-western-australia.csl b/journal-of-the-royal-society-of-western-australia.csl index c4154ff82b5..4cf5e8fd74b 100644 --- a/journal-of-the-royal-society-of-western-australia.csl +++ b/journal-of-the-royal-society-of-western-australia.csl @@ -202,6 +202,7 @@ + diff --git a/karstenia.csl b/karstenia.csl new file mode 100644 index 00000000000..3b3998d5e1d --- /dev/null +++ b/karstenia.csl @@ -0,0 +1,163 @@ + + diff --git a/lancaster-university-harvard.csl b/lancaster-university-harvard.csl index 3c597b4306a..4d2dfd7cd93 100644 --- a/lancaster-university-harvard.csl +++ b/lancaster-university-harvard.csl @@ -6,13 +6,14 @@ + Andy Hartland The Harvard author-date style - adapted for Lancaster University - 2020-01-19T19:35:00+00:00 + 2020-07-22T08:41:45+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -436,7 +437,7 @@ - + diff --git a/medical-physics.csl b/medical-physics.csl deleted file mode 100644 index 0013d550771..00000000000 --- a/medical-physics.csl +++ /dev/null @@ -1,151 +0,0 @@ - - diff --git a/nejm-catalyst-innovations-in-care-delivery.csl b/nejm-catalyst-innovations-in-care-delivery.csl new file mode 100644 index 00000000000..e463ec17fe5 --- /dev/null +++ b/nejm-catalyst-innovations-in-care-delivery.csl @@ -0,0 +1,176 @@ + + diff --git a/thieme-german.csl b/thieme-german.csl index 44a43023588..c2b3dbdd07e 100644 --- a/thieme-german.csl +++ b/thieme-german.csl @@ -27,6 +27,11 @@ 2020-06-30T08:44:34+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License + + + et al. + + diff --git a/thyroid.csl b/thyroid.csl index a2ea1950665..ce99ea31cd2 100644 --- a/thyroid.csl +++ b/thyroid.csl @@ -14,7 +14,7 @@ 1050-7256 1557-9077 - 2017-09-26T14:41:06+00:00 + 2020-07-21T14:41:51+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -117,6 +117,13 @@ + + + + + + + @@ -127,7 +134,7 @@ - + diff --git a/universidade-estadual-de-alagoas-abnt.csl b/universidade-estadual-de-alagoas-abnt.csl new file mode 100644 index 00000000000..1789d030c17 --- /dev/null +++ b/universidade-estadual-de-alagoas-abnt.csl @@ -0,0 +1,721 @@ + + diff --git a/universidade-federal-de-sergipe-departamento-de-engenharia-de-producao-abnt.csl b/universidade-federal-de-sergipe-departamento-de-engenharia-de-producao-abnt.csl new file mode 100644 index 00000000000..69e9a2d1c40 --- /dev/null +++ b/universidade-federal-de-sergipe-departamento-de-engenharia-de-producao-abnt.csl @@ -0,0 +1,745 @@ + + diff --git a/universita-cattolica-del-sacro-cuore.csl b/universita-cattolica-del-sacro-cuore.csl index 312ca152259..14df6b9c24a 100644 --- a/universita-cattolica-del-sacro-cuore.csl +++ b/universita-cattolica-del-sacro-cuore.csl @@ -544,7 +544,7 @@ - + diff --git a/zeitschrift-fur-zahnarztliche-implantologie.csl b/zeitschrift-fur-zahnarztliche-implantologie.csl new file mode 100644 index 00000000000..1509ea92e6b --- /dev/null +++ b/zeitschrift-fur-zahnarztliche-implantologie.csl @@ -0,0 +1,161 @@ + + From 37c2d64e449ef617bb98c0ad3b39a903d8f3d886 Mon Sep 17 00:00:00 2001 From: github actions Date: Sat, 15 Aug 2020 02:17:10 +0000 Subject: [PATCH 02/15] Squashed 'src/main/resources/csl-styles/' changes from 827b986621..eb0d37e0ff eb0d37e0ff Create natbib-plainnat-author-dat.csl (#4967) fb1592a1cd Update journal-of-fish-biology.csl (#4969) f6876cbe0f Update .travis.yml (#4970) d6d400b207 Create london-review-of-international-law.csl (#4966) d2a5ae1b16 Update harvard-stellenbosch-university.csl (#4965) 9c62141a40 Create phytopathologia-mediterranea.csl (#4964) 8ca2ea1a9c Update historical-materialism.csl (#4960) 80456dc749 Update historical-materialism.csl be2d91090c Update historical-materialism.csl 1d1cf095a6 Create atlande.csl (#4930) 89f41d404d Create juristische-zitierweise-oeffentliches-recht.csl (#4944) 8c677a08c5 Create korean-journal-of-gastroenterology.csl (#4954) eadb9508dd Create historical-materialism.csl (#4955) 5553dcdc9c Create revista-materia.csl (#4957) d23a3abd79 Bug fix in APA 6 original publication macro (#4959) 66f9974980 Always print publisher in APA 6th edition (#4899) 868809c063 Create agora.csl (#4940) 99c19c397e Update anthropologie-et-societes.csl (#4952) ee17423a93 Create critical-reviews-in-solid-state-and-materials-science.csl (#4951) 7a13a7d59e Create korean-journal-of-internal-medicine.csl (#4953) git-subtree-dir: src/main/resources/csl-styles git-subtree-split: eb0d37e0ff253e7c6d256b619e4831af306a28cf --- .travis.yml | 4 +- Gemfile | 2 +- Gemfile.lock | 4 +- agora.csl | 266 +++++++++++ anthropologie-et-societes.csl | 26 +- apa-6th-edition-no-ampersand.csl | 115 +++-- apa-6th-edition.csl | 115 +++-- apa-no-doi-no-issue.csl | 131 +++--- apa-old-doi-prefix.csl | 115 +++-- atlande.csl | 338 ++++++++++++++ ...-in-solid-state-and-materials-sciences.csl | 187 ++++++++ harvard-stellenbosch-university.csl | 1 + historical-materialism.csl | 371 +++++++++++++++ journal-of-fish-biology.csl | 110 +++-- ...stische-zitierweise-offentliches-recht.csl | 429 ++++++++++++++++++ london-review-of-international-law.csl | 323 +++++++++++++ natbib-plainnat-author-date.csl | 276 +++++++++++ phytopathologia-mediterranea.csl | 201 ++++++++ revista-materia.csl | 343 ++++++++++++++ the-korean-journal-of-gastroenterology.csl | 204 +++++++++ the-korean-journal-of-internal-medicine.csl | 166 +++++++ 21 files changed, 3460 insertions(+), 267 deletions(-) create mode 100644 agora.csl create mode 100644 atlande.csl create mode 100644 critical-reviews-in-solid-state-and-materials-sciences.csl create mode 100644 historical-materialism.csl create mode 100644 juristische-zitierweise-offentliches-recht.csl create mode 100644 london-review-of-international-law.csl create mode 100644 natbib-plainnat-author-date.csl create mode 100644 phytopathologia-mediterranea.csl create mode 100644 revista-materia.csl create mode 100644 the-korean-journal-of-gastroenterology.csl create mode 100644 the-korean-journal-of-internal-medicine.csl diff --git a/.travis.yml b/.travis.yml index 44a11d7aac9..724298fd1ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ -dist: bionic +dist: focal language: ruby cache: bundler rvm: -- 2.6.5 +- 2.7.1 install: - bundle install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle} - bundle update sheldon diff --git a/Gemfile b/Gemfile index 65bb4267142..17b2c314a33 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ -ruby '2.6.5' +ruby '2.7.1' source 'https://rubygems.org' gem 'rake' diff --git a/Gemfile.lock b/Gemfile.lock index d41b71a1f32..b0baba6eaa8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -64,7 +64,7 @@ DEPENDENCIES sheldon! RUBY VERSION - ruby 2.6.5p114 + ruby 2.7.1 BUNDLED WITH - 1.17.3 + 2.1.4 diff --git a/agora.csl b/agora.csl new file mode 100644 index 00000000000..fd0a065ab82 --- /dev/null +++ b/agora.csl @@ -0,0 +1,266 @@ + + diff --git a/anthropologie-et-societes.csl b/anthropologie-et-societes.csl index 3a8ba466a67..f61e510a708 100644 --- a/anthropologie-et-societes.csl +++ b/anthropologie-et-societes.csl @@ -8,13 +8,12 @@ Patrick O'Brien, PhD - obrienpat86@gmail.com 0702-8997 1703-7921 - 2018-09-05T13:51:00+00:00 + 2020-07-31T08:06:37+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -115,14 +114,15 @@ - + + - + @@ -167,24 +167,24 @@ - + - - - - + + - - - - + + + + + + diff --git a/apa-6th-edition-no-ampersand.csl b/apa-6th-edition-no-ampersand.csl index 86ccc7e7845..c3b16aabf24 100644 --- a/apa-6th-edition-no-ampersand.csl +++ b/apa-6th-edition-no-ampersand.csl @@ -503,19 +503,10 @@ - - - - - - - - - @@ -796,6 +787,25 @@ + + + + + + + + + + + + + + + + + + + @@ -1386,6 +1396,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1492,57 +1542,28 @@ - - + + - + - + - - - - - - - - - - - - - - - - - + + + - + + - - - - - - - - - - - - - - - - diff --git a/apa-6th-edition.csl b/apa-6th-edition.csl index 83c3b70cce6..f49c6b13532 100644 --- a/apa-6th-edition.csl +++ b/apa-6th-edition.csl @@ -502,19 +502,10 @@ - - - - - - - - - @@ -795,6 +786,25 @@ + + + + + + + + + + + + + + + + + + + @@ -1385,6 +1395,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1491,57 +1541,28 @@ - - + + - + - + - - - - - - - - - - - - - - - - - + + + - + + - - - - - - - - - - - - - - - - diff --git a/apa-no-doi-no-issue.csl b/apa-no-doi-no-issue.csl index b71c07b4aa7..03ca7ff86be 100644 --- a/apa-no-doi-no-issue.csl +++ b/apa-no-doi-no-issue.csl @@ -460,23 +460,15 @@ - + - - - - - - + - - - - + - + @@ -517,19 +509,10 @@ - - - - - - - - - @@ -810,6 +793,25 @@ + + + + + + + + + + + + + + + + + + + @@ -1407,6 +1409,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1513,57 +1555,28 @@ - - + + - + - + - - - - - - - - - - - - - - - - - + + + - + + - - - - - - - - - - - - - - - - diff --git a/apa-old-doi-prefix.csl b/apa-old-doi-prefix.csl index 1bba65f0061..a7ef73be8aa 100644 --- a/apa-old-doi-prefix.csl +++ b/apa-old-doi-prefix.csl @@ -503,19 +503,10 @@ - - - - - - - - - @@ -796,6 +787,25 @@ + + + + + + + + + + + + + + + + + + + @@ -1386,6 +1396,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1492,57 +1542,28 @@ - - + + - + - + - - - - - - - - - - - - - - - - - + + + - + + - - - - - - - - - - - - - - - - diff --git a/atlande.csl b/atlande.csl new file mode 100644 index 00000000000..37051e8c417 --- /dev/null +++ b/atlande.csl @@ -0,0 +1,338 @@ + + diff --git a/critical-reviews-in-solid-state-and-materials-sciences.csl b/critical-reviews-in-solid-state-and-materials-sciences.csl new file mode 100644 index 00000000000..78d9b89214b --- /dev/null +++ b/critical-reviews-in-solid-state-and-materials-sciences.csl @@ -0,0 +1,187 @@ + + diff --git a/harvard-stellenbosch-university.csl b/harvard-stellenbosch-university.csl index 610543553b0..7ac7671b557 100644 --- a/harvard-stellenbosch-university.csl +++ b/harvard-stellenbosch-university.csl @@ -280,6 +280,7 @@ + diff --git a/historical-materialism.csl b/historical-materialism.csl new file mode 100644 index 00000000000..0d31db251cb --- /dev/null +++ b/historical-materialism.csl @@ -0,0 +1,371 @@ + + diff --git a/journal-of-fish-biology.csl b/journal-of-fish-biology.csl index 275532a225b..1db13f2a61b 100644 --- a/journal-of-fish-biology.csl +++ b/journal-of-fish-biology.csl @@ -16,7 +16,7 @@ 0022-1112 1095-8649 The style for the Journal of Fish Biology. - 2019-11-05T09:32:12+00:00 + 2020-08-13T10:48:28+00:00 This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License @@ -36,16 +36,10 @@ - - - - - - - - + + + @@ -73,18 +67,28 @@ - - - - + + + + + + + + + + + + + + - + - + @@ -98,23 +102,13 @@ - + + - - - - - - - - - - - - - - + @@ -141,10 +135,16 @@ + + + + + - + @@ -191,14 +191,16 @@ - - + + - + - - - + + + + + @@ -211,9 +213,12 @@ - - - + + + + + + @@ -236,15 +241,22 @@ - + + + + + + + + + + + + + + - - - diff --git a/juristische-zitierweise-offentliches-recht.csl b/juristische-zitierweise-offentliches-recht.csl new file mode 100644 index 00000000000..288261aa758 --- /dev/null +++ b/juristische-zitierweise-offentliches-recht.csl @@ -0,0 +1,429 @@ + + diff --git a/london-review-of-international-law.csl b/london-review-of-international-law.csl new file mode 100644 index 00000000000..f2a7c649526 --- /dev/null +++ b/london-review-of-international-law.csl @@ -0,0 +1,323 @@ + + diff --git a/natbib-plainnat-author-date.csl b/natbib-plainnat-author-date.csl new file mode 100644 index 00000000000..cde2857f70f --- /dev/null +++ b/natbib-plainnat-author-date.csl @@ -0,0 +1,276 @@ + + diff --git a/phytopathologia-mediterranea.csl b/phytopathologia-mediterranea.csl new file mode 100644 index 00000000000..964231e1334 --- /dev/null +++ b/phytopathologia-mediterranea.csl @@ -0,0 +1,201 @@ + + diff --git a/revista-materia.csl b/revista-materia.csl new file mode 100644 index 00000000000..8625641642f --- /dev/null +++ b/revista-materia.csl @@ -0,0 +1,343 @@ + + diff --git a/the-korean-journal-of-gastroenterology.csl b/the-korean-journal-of-gastroenterology.csl new file mode 100644 index 00000000000..72a7a71a938 --- /dev/null +++ b/the-korean-journal-of-gastroenterology.csl @@ -0,0 +1,204 @@ + + diff --git a/the-korean-journal-of-internal-medicine.csl b/the-korean-journal-of-internal-medicine.csl new file mode 100644 index 00000000000..62da01eef13 --- /dev/null +++ b/the-korean-journal-of-internal-medicine.csl @@ -0,0 +1,166 @@ + + From 172b11301d20b11d4ff3f22c1aa768b3fc6db9e4 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Wed, 26 Aug 2020 21:31:00 +0200 Subject: [PATCH 03/15] Enable Query String into ComplexQuery parsing using lucene. Fix some FetcherTests. Signed-off-by: Dominik Voigt --- build.gradle | 4 ++ src/main/java/module-info.java | 2 + .../jabref/logic/importer/QueryConverter.java | 48 +++++++++++++++ .../logic/importer/SearchBasedFetcher.java | 6 +- .../importer/SearchBasedParserFetcher.java | 6 +- .../jabref/logic/importer/fetcher/ArXiv.java | 2 +- .../importer/fetcher/ComplexSearchQuery.java | 42 +++++++++---- .../logic/importer/fetcher/GoogleScholar.java | 2 +- .../jabref/logic/importer/fetcher/IEEE.java | 2 +- .../importer/fetcher/SpringerFetcher.java | 2 +- .../logic/importer/QueryConverterTest.java | 59 +++++++++++++++++++ .../logic/importer/fetcher/ArXivTest.java | 2 + 12 files changed, 159 insertions(+), 18 deletions(-) create mode 100644 src/main/java/org/jabref/logic/importer/QueryConverter.java create mode 100644 src/test/java/org/jabref/logic/importer/QueryConverterTest.java diff --git a/build.gradle b/build.gradle index 654309487a3..d26f5296edd 100644 --- a/build.gradle +++ b/build.gradle @@ -137,6 +137,10 @@ dependencies { antlr4 'org.antlr:antlr4:4.8-1' implementation 'org.antlr:antlr4-runtime:4.8-1' + implementation (group: 'org.apache.lucene', name: 'lucene-queryparser', version: '8.6.1') { + exclude group: 'org.apache.lucene', module: 'lucene-sandbox' + } + implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.6.2' implementation 'org.postgresql:postgresql:42.2.16' diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index acb5dd6e5ef..eb7e0102e92 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -89,4 +89,6 @@ requires flexmark.util.ast; requires flexmark.util.data; requires com.h2database.mvstore; + requires lucene.queryparser; + requires lucene.core; } diff --git a/src/main/java/org/jabref/logic/importer/QueryConverter.java b/src/main/java/org/jabref/logic/importer/QueryConverter.java new file mode 100644 index 00000000000..21cf4c832b4 --- /dev/null +++ b/src/main/java/org/jabref/logic/importer/QueryConverter.java @@ -0,0 +1,48 @@ +package org.jabref.logic.importer; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jabref.logic.importer.fetcher.ComplexSearchQuery; + +import org.apache.lucene.index.Term; +import org.apache.lucene.queryparser.flexible.core.QueryNodeException; +import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryVisitor; + +/** + * This class converts a query string written in lucene syntax into a complex search query. + * + * For simplicity this is limited to fielded data and the boolean AND operator. + */ +public class QueryConverter { + + /** + * Converts the given query string into a complex query using lucene. + * Note: For unique fields, the alphabetically first instance in the query string is used in the complex query. + * + * @param queryString The given query string + * @return A complex query containing all fields of the query string + * @throws QueryNodeException Error during parsing + */ + public ComplexSearchQuery convertQueryStringIntoComplexQuery(String queryString) throws QueryNodeException { + ComplexSearchQuery.ComplexSearchQueryBuilder builder = ComplexSearchQuery.builder(); + + StandardQueryParser parser = new StandardQueryParser(); + Query luceneQuery = parser.parse(queryString, "default"); + Set terms = new HashSet<>(); + // This implementation collects all terms from the leaves of the query tree independent of the internal boolean structure + // If further capabilities are required in the future the visitor and ComplexSearchQuery has to be adapted accordingly. + QueryVisitor visitor = QueryVisitor.termCollector(terms); + luceneQuery.visit(visitor); + + List sortedTerms = new ArrayList<>(terms); + sortedTerms.sort(Comparator.comparing(Term::text)); + builder.terms(sortedTerms); + return builder.build(); + } +} diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java index d5d83f64ffb..1f47bd2faee 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java @@ -1,6 +1,7 @@ package org.jabref.logic.importer; import java.util.List; +import java.util.Optional; import org.jabref.logic.importer.fetcher.ComplexSearchQuery; import org.jabref.model.entry.BibEntry; @@ -26,7 +27,8 @@ public interface SearchBasedFetcher extends WebFetcher { * @return a list of {@link BibEntry}, which are matched by the query (may be empty) */ default List performComplexSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { - // Default Implementation behaves like perform search using the default field as query - return performSearch(complexSearchQuery.getDefaultField().orElse("")); + // Default Implementation behaves like perform search using the default field phrases as query + Optional> defaultPhrases = complexSearchQuery.getDefaultFieldPhrases(); + return performSearch(defaultPhrases.map(strings -> String.join(" ", strings)).orElse("")); } } diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java index 24b0e2c84d5..9fb3b60a579 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java @@ -7,6 +7,7 @@ import java.net.URL; import java.util.Collections; import java.util.List; +import java.util.Optional; import org.jabref.logic.importer.fetcher.ComplexSearchQuery; import org.jabref.model.cleanup.Formatter; @@ -80,8 +81,9 @@ default List performComplexSearch(ComplexSearchQuery complexSearchQuer } default URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URISyntaxException, MalformedURLException, FetcherException { - // Default Implementation behaves like getURLForQuery using the default field as query - return this.getURLForQuery(complexSearchQuery.getDefaultField().orElse("")); + // Default Implementation behaves like getURLForQuery using the default field phrases as query + Optional> defaultPhrases = complexSearchQuery.getDefaultFieldPhrases(); + return this.getURLForQuery(defaultPhrases.map(strings -> String.join(" ", strings)).orElse("")); } /** diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java b/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java index 7fc3fd866c9..5bbfe0a5357 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java @@ -269,7 +269,7 @@ public List performComplexSearch(ComplexSearchQuery complexSearchQuery complexSearchQuery.getJournal().ifPresent(journal -> searchTerms.add("jr:" + journal)); // Since ArXiv API does not support year search, we ignore the year related terms complexSearchQuery.getToYear().ifPresent(year -> searchTerms.add(year.toString())); - complexSearchQuery.getDefaultField().ifPresent(defaultField -> searchTerms.add(defaultField)); + complexSearchQuery.getDefaultFieldPhrases().ifPresent(searchTerms::addAll); String complexQueryString = String.join(" AND ", searchTerms); return performSearch(complexQueryString); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java index f08a1a59418..e16c5a8cf56 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java @@ -1,15 +1,18 @@ package org.jabref.logic.importer.fetcher; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Optional; import org.jabref.model.strings.StringUtil; +import org.apache.lucene.index.Term; + public class ComplexSearchQuery { // Field for non-fielded search - private final String defaultField; + private final List defaultField; private final List authors; private final List titlePhrases; private final Integer fromYear; @@ -17,7 +20,7 @@ public class ComplexSearchQuery { private final Integer singleYear; private final String journal; - private ComplexSearchQuery(String defaultField, List authors, List titlePhrases, Integer fromYear, Integer toYear, Integer singleYear, String journal) { + private ComplexSearchQuery(List defaultField, List authors, List titlePhrases, Integer fromYear, Integer toYear, Integer singleYear, String journal) { this.defaultField = defaultField; this.authors = authors; this.titlePhrases = titlePhrases; @@ -28,7 +31,7 @@ private ComplexSearchQuery(String defaultField, List authors, List getDefaultField() { + public Optional> getDefaultFieldPhrases() { return Optional.ofNullable(defaultField); } @@ -61,7 +64,7 @@ public static ComplexSearchQueryBuilder builder() { } public static class ComplexSearchQueryBuilder { - private String defaultField; + private List defaultFieldPhrases; private List authors; private List titlePhrases; private String journal; @@ -69,14 +72,18 @@ public static class ComplexSearchQueryBuilder { private Integer toYear; private Integer singleYear; - public ComplexSearchQueryBuilder() { + private ComplexSearchQueryBuilder() { } - public ComplexSearchQueryBuilder defaultField(String defaultField) { - if (Objects.requireNonNull(defaultField).isBlank()) { + public ComplexSearchQueryBuilder defaultFieldPhrase(String defaultFieldPhrase) { + if (Objects.requireNonNull(defaultFieldPhrase).isBlank()) { throw new IllegalArgumentException("Parameter must not be blank"); } - this.defaultField = defaultField; + if (Objects.isNull(defaultFieldPhrases)) { + this.defaultFieldPhrases = new ArrayList<>(); + } + // Strip all quotes before wrapping + this.defaultFieldPhrases.add(String.format("\"%s\"", defaultFieldPhrase.replace("\"", ""))); return this; } @@ -135,6 +142,21 @@ public ComplexSearchQueryBuilder journal(String journal) { return this; } + public ComplexSearchQueryBuilder terms(Collection terms) { + terms.forEach(term -> { + String termText = term.text(); + switch (term.field().toLowerCase()) { + case "author" -> this.author(termText); + case "title" -> this.titlePhrase(termText); + case "journal" -> this.journal(termText); + case "year" -> this.singleYear(Integer.valueOf(termText)); + case "year-range" -> this.fromYearAndToYear(Integer.valueOf(termText.split("-")[0]), Integer.valueOf(termText.split("-")[1])); + case "default" -> this.defaultFieldPhrase(termText); + } + }); + return this; + } + /** * Instantiates the AdvancesSearchConfig from the provided Builder parameters * If all text fields are empty an empty optional is returned @@ -147,11 +169,11 @@ public ComplexSearchQuery build() throws IllegalStateException { if (textSearchFieldsAndYearFieldsAreEmpty()) { throw new IllegalStateException("At least one text field has to be set"); } - return new ComplexSearchQuery(defaultField, authors, titlePhrases, fromYear, toYear, singleYear, journal); + return new ComplexSearchQuery(defaultFieldPhrases, authors, titlePhrases, fromYear, toYear, singleYear, journal); } private boolean textSearchFieldsAndYearFieldsAreEmpty() { - return StringUtil.isBlank(defaultField) && this.stringListIsBlank(titlePhrases) && + return this.stringListIsBlank(defaultFieldPhrases) && this.stringListIsBlank(titlePhrases) && this.stringListIsBlank(authors) && StringUtil.isBlank(journal) && yearFieldsAreEmpty(); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java index ebfb5f7e3a7..4a5cc6dd598 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java @@ -203,7 +203,7 @@ public List performComplexSearch(ComplexSearchQuery complexSearchQuery private String constructComplexQueryString(ComplexSearchQuery complexSearchQuery) { List searchTerms = new ArrayList<>(); - complexSearchQuery.getDefaultField().ifPresent(defaultField -> searchTerms.add(defaultField)); + complexSearchQuery.getDefaultFieldPhrases().ifPresent(searchTerms::addAll); complexSearchQuery.getAuthors().ifPresent(authors -> authors.forEach(author -> searchTerms.add("author:" + author))); complexSearchQuery.getTitlePhrases().ifPresent(phrases -> searchTerms.add("allintitle:" + String.join(" ", phrases))); complexSearchQuery.getJournal().ifPresent(journal -> searchTerms.add("source:" + journal)); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java index 6c87d9c7b35..306f7796ac6 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java @@ -236,7 +236,7 @@ public Optional getHelpPage() { public URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URISyntaxException, MalformedURLException { URIBuilder uriBuilder = new URIBuilder("https://ieeexploreapi.ieee.org/api/v1/search/articles"); uriBuilder.addParameter("apikey", API_KEY); - complexSearchQuery.getDefaultField().ifPresent(defaultField -> uriBuilder.addParameter("querytext", defaultField)); + complexSearchQuery.getDefaultFieldPhrases().ifPresent(defaultFieldPhrases -> uriBuilder.addParameter("querytext", String.join(" AND ", defaultFieldPhrases))); complexSearchQuery.getAuthors().ifPresent(authors -> uriBuilder.addParameter("author", String.join(" AND ", authors))); complexSearchQuery.getTitlePhrases().ifPresent(articleTitlePhrases -> diff --git a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java index 9cdddcfb0fc..cdd50b1d2d6 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java @@ -176,7 +176,7 @@ private String constructComplexQueryString(ComplexSearchQuery complexSearchQuery complexSearchQuery.getJournal().ifPresent(journal -> searchTerms.add("journal:" + journal)); // Since Springer API does not support year range search we ignore formYear and toYear. complexSearchQuery.getSingleYear().ifPresent(year -> searchTerms.add("year:" + year.toString())); - complexSearchQuery.getDefaultField().ifPresent(defaultField -> searchTerms.add(defaultField)); + complexSearchQuery.getDefaultFieldPhrases().ifPresent(searchTerms::addAll); return String.join(" AND ", searchTerms); } diff --git a/src/test/java/org/jabref/logic/importer/QueryConverterTest.java b/src/test/java/org/jabref/logic/importer/QueryConverterTest.java new file mode 100644 index 00000000000..b6c5a8683d7 --- /dev/null +++ b/src/test/java/org/jabref/logic/importer/QueryConverterTest.java @@ -0,0 +1,59 @@ +package org.jabref.logic.importer; + +import java.util.Collections; +import java.util.List; + +import org.jabref.logic.importer.fetcher.ComplexSearchQuery; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class QueryConverterTest { + QueryConverter converter = new QueryConverter(); + + @Test + public void convertAuthorField() throws Exception { + ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("author:\"Igor Steinmacher\""); + assertEquals(List.of("\"Igor Steinmacher\""), searchQuery.getAuthors().get()); + } + + @Test + public void convertDefaultField() throws Exception { + ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("\"default value\""); + assertEquals(List.of("\"default value\""), searchQuery.getDefaultFieldPhrases().get()); + } + + @Test + public void convertExplicitDefaultField() throws Exception { + ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("default:\"default value\""); + assertEquals(List.of("\"default value\""), searchQuery.getDefaultFieldPhrases().get()); + } + + @Test + public void convertJournalField() throws Exception { + ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("journal:\"Nature\""); + assertEquals("\"Nature\"", searchQuery.getJournal().get()); + } + + @Test + public void convertYearField() throws Exception { + ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("year:2015"); + assertEquals(2015, searchQuery.getSingleYear().get()); + } + + @Test + public void convertYearRangeField() throws Exception { + ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("year-range:2012-2015"); + assertEquals(2012, searchQuery.getFromYear().get()); + assertEquals(2015, searchQuery.getToYear().get()); + } + + @Test + public void convertMultipleValuesWithTheSameField() throws Exception { + ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("author:\"Igor Steinmacher\" author:\"Christoph Treude\""); + List sortedAuthors = searchQuery.getAuthors().get(); + Collections.sort(sortedAuthors); + assertEquals(List.of("\"Christoph Treude\"", "\"Igor Steinmacher\""), sortedAuthors); + } +} diff --git a/src/test/java/org/jabref/logic/importer/fetcher/ArXivTest.java b/src/test/java/org/jabref/logic/importer/fetcher/ArXivTest.java index 6c3458475b2..398603a2e02 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/ArXivTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/ArXivTest.java @@ -256,6 +256,7 @@ public void supportsPhraseSearch() throws Exception { .withField(StandardField.TITLE, "Instability and fingering of interfaces in growing tissue") .withField(StandardField.DATE, "2020-03-10") .withField(StandardField.ABSTRACT, "Interfaces in tissues are ubiquitous, both between tissue and environment as well as between populations of different cell types. The propagation of an interface can be driven mechanically. % e.g. by a difference in the respective homeostatic stress of the different cell types. Computer simulations of growing tissues are employed to study the stability of the interface between two tissues on a substrate. From a mechanical perspective, the dynamics and stability of this system is controlled mainly by four parameters of the respective tissues: (i) the homeostatic stress (ii) cell motility (iii) tissue viscosity and (iv) substrate friction. For propagation driven by a difference in homeostatic stress, the interface is stable for tissue-specific substrate friction even for very large differences of homeostatic stress; however, it becomes unstable above a critical stress difference when the tissue with the larger homeostatic stress has a higher viscosity. A small difference in directed bulk motility between the two tissues suffices to result in propagation with a stable interface, even for otherwise identical tissues. Larger differences in motility force, however, result in a finite-wavelength instability of the interface. Interestingly, the instability is apparently bound by nonlinear effects and the amplitude of the interface undulations only grows to a finite value in time.") + .withField(StandardField.DOI, "10.1088/1367-2630/ab9e88") .withField(StandardField.EPRINT, "2003.04601") .withField(StandardField.FILE, ":http\\://arxiv.org/pdf/2003.04601v1:PDF") .withField(StandardField.EPRINTTYPE, "arXiv") @@ -279,6 +280,7 @@ public void supportsBooleanANDSearch() throws Exception { .withField(StandardField.TITLE, "Instability and fingering of interfaces in growing tissue") .withField(StandardField.DATE, "2020-03-10") .withField(StandardField.ABSTRACT, "Interfaces in tissues are ubiquitous, both between tissue and environment as well as between populations of different cell types. The propagation of an interface can be driven mechanically. % e.g. by a difference in the respective homeostatic stress of the different cell types. Computer simulations of growing tissues are employed to study the stability of the interface between two tissues on a substrate. From a mechanical perspective, the dynamics and stability of this system is controlled mainly by four parameters of the respective tissues: (i) the homeostatic stress (ii) cell motility (iii) tissue viscosity and (iv) substrate friction. For propagation driven by a difference in homeostatic stress, the interface is stable for tissue-specific substrate friction even for very large differences of homeostatic stress; however, it becomes unstable above a critical stress difference when the tissue with the larger homeostatic stress has a higher viscosity. A small difference in directed bulk motility between the two tissues suffices to result in propagation with a stable interface, even for otherwise identical tissues. Larger differences in motility force, however, result in a finite-wavelength instability of the interface. Interestingly, the instability is apparently bound by nonlinear effects and the amplitude of the interface undulations only grows to a finite value in time.") + .withField(StandardField.DOI, "10.1088/1367-2630/ab9e88") .withField(StandardField.EPRINT, "2003.04601") .withField(StandardField.FILE, ":http\\://arxiv.org/pdf/2003.04601v1:PDF") .withField(StandardField.EPRINTTYPE, "arXiv") From 90a629e1db45de25abb05d2fb849a85528c8de35 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Thu, 27 Aug 2020 10:44:31 +0200 Subject: [PATCH 04/15] Add ADR Signed-off-by: Dominik Voigt --- ...tract-query-syntax-for-query-conversion.md | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 docs/adr/0014-support-an-abstract-query-syntax-for-query-conversion.md diff --git a/docs/adr/0014-support-an-abstract-query-syntax-for-query-conversion.md b/docs/adr/0014-support-an-abstract-query-syntax-for-query-conversion.md new file mode 100644 index 00000000000..e6aab48b267 --- /dev/null +++ b/docs/adr/0014-support-an-abstract-query-syntax-for-query-conversion.md @@ -0,0 +1,56 @@ +# Query syntax design + +## Context and Problem Statement + +All libraries use their own query syntax for advanced search options. To increase usability, users should be able to formulate their (abstract) search queries in a query syntax that can be mapped to the library specific search queries. To achieve this, the query has to be parsed into an AST. + +Which query syntax should be used for the abstract queries? +Which features should the syntax support? + +## Considered Options + +* Use a simplified syntax that is derived of the [lucene](https://lucene.apache.org/core/8_6_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html) query syntax +* Formulate a own query syntax + +## Decision Outcome + +Chosen option: "Use a syntax that is derived of the lucene query syntax", because only option that is already known, and easy to implemenent. +Furthermore parsers for lucene already exist and are tested. +For simplicitly, and lack of universal capabilities across fetchers, only basic query features and therefor syntax is supported: + +* All terms in the query are whitespace separated and will be ANDed +* Default and certain fielded terms are supported +* Fielded Terms: + * author + * title + * journal + * year (for single year) + * year-range (for range e.g. year-range:2012-2015) +* The journal, year, and year-range fields should only be populated once in each query +* Example: + * \[author:"Igor Steinmacher" author:"Christoph Treude" year:2017\] will be converted to + * \[author:"Igor Steinmacher" AND author:"Christoph Treude" AND year:2017\] + +### Positive Consequences + +* Already tested +* Well Known +* Easy to implement +* Can use an existing parser + +## Pros and Cons of the Options + +### Use a syntax that is derived of the lucene query syntax + +* Good, because already exists +* Good, because already well known +* Good, because there already exists a [parser for lucene syntax](https://lucene.apache.org/core/8_0_0/queryparser/org/apache/lucene/queryparser/flexible/standard/StandardQueryParser.html) +* Good, because capabilities of query conversion can easily be extended using the [flexible lucene framework](https://lucene.apache.org/core/8_0_0/queryparser/org/apache/lucene/queryparser/flexible/core/package-summary.html) + +### Formulate a own query syntax + +* Good, because allows for flexibility +* Bad, because needs a new parser (has to be decided whether to use [ANTLR](https://www.antlr.org/), [JavaCC](https://javacc.github.io/javacc/), or [LogicNG](https://github.com/logic-ng/LogicNG)) +* Bad, because has to be tested +* Bad, because syntax is not well known +* Bad, because the design should be easily extensible, requires an appropriate design (high effort) From 3af3781a00296847eb92188334250825189b9631 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Thu, 27 Aug 2020 11:38:29 +0200 Subject: [PATCH 05/15] Add Changelog entry Signed-off-by: Dominik Voigt --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 814d5209d60..a4af5ef7000 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We added native support for biblatex-software [#6574](https://github.com/JabRef/jabref/issues/6574) - We added a missing restart warning for AutoComplete in the preferences dialog. [#6351](https://github.com/JabRef/jabref/issues/6351) - We added a note to the citation key pattern preferences dialog as a temporary workaround for a JavaFX bug, about committing changes in a table cell, if the focus is lost. [#5825](https://github.com/JabRef/jabref/issues/5825) +- We added a query parser and mapping layer to enable conversion of queries formulated in simplified lucene syntax by the user into api queries. [#6799](https://github.com/JabRef/jabref/pull/6799) ### Changed From 8468ad440b989cc67a3745ee3a690b0a2e6b3ef2 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Thu, 27 Aug 2020 11:40:19 +0200 Subject: [PATCH 06/15] Move Changelog entry Signed-off-by: Dominik Voigt --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 794339626c9..3d5338299f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve ### Added +- We added a query parser and mapping layer to enable conversion of queries formulated in simplified lucene syntax by the user into api queries. [#6799](https://github.com/JabRef/jabref/pull/6799) + ### Changed ### Fixed @@ -49,7 +51,6 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve - We added native support for biblatex-software [#6574](https://github.com/JabRef/jabref/issues/6574) - We added a missing restart warning for AutoComplete in the preferences dialog. [#6351](https://github.com/JabRef/jabref/issues/6351) - We added a note to the citation key pattern preferences dialog as a temporary workaround for a JavaFX bug, about committing changes in a table cell, if the focus is lost. [#5825](https://github.com/JabRef/jabref/issues/5825) -- We added a query parser and mapping layer to enable conversion of queries formulated in simplified lucene syntax by the user into api queries. [#6799](https://github.com/JabRef/jabref/pull/6799) ### Changed From d592a47b853ae6978696d635776e32e8e105e478 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Thu, 27 Aug 2020 11:44:53 +0200 Subject: [PATCH 07/15] Increment ADR number Signed-off-by: Dominik Voigt --- ...0015-support-an-abstract-query-syntax-for-query-conversion.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/adr/{0014-support-an-abstract-query-syntax-for-query-conversion.md => 0015-support-an-abstract-query-syntax-for-query-conversion.md} (100%) diff --git a/docs/adr/0014-support-an-abstract-query-syntax-for-query-conversion.md b/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md similarity index 100% rename from docs/adr/0014-support-an-abstract-query-syntax-for-query-conversion.md rename to docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md From 4610451ff908accf977328100f4a5f578fed9dd7 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Thu, 27 Aug 2020 14:46:08 +0200 Subject: [PATCH 08/15] Remove Optional from Lists Rename QueryConverter to QueryParser Modify parser tests to compare complex queries Make complex query construction from terms independent from normal building Signed-off-by: Dominik Voigt --- .../jabref/logic/importer/QueryConverter.java | 48 ------------- .../jabref/logic/importer/QueryParser.java | 53 +++++++++++++++ .../logic/importer/SearchBasedFetcher.java | 5 +- .../importer/SearchBasedParserFetcher.java | 5 +- .../jabref/logic/importer/fetcher/ArXiv.java | 6 +- .../importer/fetcher/ComplexSearchQuery.java | 68 ++++++++++++++----- .../logic/importer/fetcher/GoogleScholar.java | 6 +- .../jabref/logic/importer/fetcher/IEEE.java | 14 ++-- .../importer/fetcher/SpringerFetcher.java | 6 +- .../logic/importer/QueryConverterTest.java | 59 ---------------- .../logic/importer/QueryParserTest.java | 60 ++++++++++++++++ 11 files changed, 185 insertions(+), 145 deletions(-) delete mode 100644 src/main/java/org/jabref/logic/importer/QueryConverter.java create mode 100644 src/main/java/org/jabref/logic/importer/QueryParser.java delete mode 100644 src/test/java/org/jabref/logic/importer/QueryConverterTest.java create mode 100644 src/test/java/org/jabref/logic/importer/QueryParserTest.java diff --git a/src/main/java/org/jabref/logic/importer/QueryConverter.java b/src/main/java/org/jabref/logic/importer/QueryConverter.java deleted file mode 100644 index 21cf4c832b4..00000000000 --- a/src/main/java/org/jabref/logic/importer/QueryConverter.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.jabref.logic.importer; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.jabref.logic.importer.fetcher.ComplexSearchQuery; - -import org.apache.lucene.index.Term; -import org.apache.lucene.queryparser.flexible.core.QueryNodeException; -import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.QueryVisitor; - -/** - * This class converts a query string written in lucene syntax into a complex search query. - * - * For simplicity this is limited to fielded data and the boolean AND operator. - */ -public class QueryConverter { - - /** - * Converts the given query string into a complex query using lucene. - * Note: For unique fields, the alphabetically first instance in the query string is used in the complex query. - * - * @param queryString The given query string - * @return A complex query containing all fields of the query string - * @throws QueryNodeException Error during parsing - */ - public ComplexSearchQuery convertQueryStringIntoComplexQuery(String queryString) throws QueryNodeException { - ComplexSearchQuery.ComplexSearchQueryBuilder builder = ComplexSearchQuery.builder(); - - StandardQueryParser parser = new StandardQueryParser(); - Query luceneQuery = parser.parse(queryString, "default"); - Set terms = new HashSet<>(); - // This implementation collects all terms from the leaves of the query tree independent of the internal boolean structure - // If further capabilities are required in the future the visitor and ComplexSearchQuery has to be adapted accordingly. - QueryVisitor visitor = QueryVisitor.termCollector(terms); - luceneQuery.visit(visitor); - - List sortedTerms = new ArrayList<>(terms); - sortedTerms.sort(Comparator.comparing(Term::text)); - builder.terms(sortedTerms); - return builder.build(); - } -} diff --git a/src/main/java/org/jabref/logic/importer/QueryParser.java b/src/main/java/org/jabref/logic/importer/QueryParser.java new file mode 100644 index 00000000000..d3ca60c0135 --- /dev/null +++ b/src/main/java/org/jabref/logic/importer/QueryParser.java @@ -0,0 +1,53 @@ +package org.jabref.logic.importer; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.jabref.logic.importer.fetcher.ComplexSearchQuery; + +import org.apache.lucene.index.Term; +import org.apache.lucene.queryparser.flexible.core.QueryNodeException; +import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryVisitor; + +/** + * This class converts a query string written in lucene syntax into a complex search query. + * + * For simplicity this is limited to fielded data and the boolean AND operator. + */ +public class QueryParser { + + /** + * Converts the given query string into a complex query using lucene. + * Note: For unique fields, the alphabetically first instance in the query string is used in the complex query. + * + * @param queryString The given query string + * @return A complex query containing all fields of the query string + * @throws QueryNodeException Error during parsing + */ + public Optional convertQueryStringIntoComplexQuery(String queryString) { + try { + ComplexSearchQuery.ComplexSearchQueryBuilder builder = ComplexSearchQuery.builder(); + + StandardQueryParser parser = new StandardQueryParser(); + Query luceneQuery = parser.parse(queryString, "default"); + Set terms = new HashSet<>(); + // This implementation collects all terms from the leaves of the query tree independent of the internal boolean structure + // If further capabilities are required in the future the visitor and ComplexSearchQuery has to be adapted accordingly. + QueryVisitor visitor = QueryVisitor.termCollector(terms); + luceneQuery.visit(visitor); + + List sortedTerms = new ArrayList<>(terms); + sortedTerms.sort(Comparator.comparing(Term::text)); + builder.terms(sortedTerms); + return Optional.of(builder.build()); + } catch (QueryNodeException ex) { + return Optional.empty(); + } + } +} diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java index 1f47bd2faee..bd27d1523f3 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java @@ -1,7 +1,6 @@ package org.jabref.logic.importer; import java.util.List; -import java.util.Optional; import org.jabref.logic.importer.fetcher.ComplexSearchQuery; import org.jabref.model.entry.BibEntry; @@ -28,7 +27,7 @@ public interface SearchBasedFetcher extends WebFetcher { */ default List performComplexSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { // Default Implementation behaves like perform search using the default field phrases as query - Optional> defaultPhrases = complexSearchQuery.getDefaultFieldPhrases(); - return performSearch(defaultPhrases.map(strings -> String.join(" ", strings)).orElse("")); + List defaultPhrases = complexSearchQuery.getDefaultFieldPhrases(); + return performSearch(String.join(" ", defaultPhrases)); } } diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java index a1c8990c5ca..44f26510498 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java @@ -7,7 +7,6 @@ import java.net.URL; import java.util.Collections; import java.util.List; -import java.util.Optional; import org.jabref.logic.importer.fetcher.ComplexSearchQuery; import org.jabref.model.cleanup.Formatter; @@ -85,8 +84,8 @@ private List getBibEntries(URL urlForQuery) throws FetcherException { default URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URISyntaxException, MalformedURLException, FetcherException { // Default Implementation behaves like getURLForQuery using the default field phrases as query - Optional> defaultPhrases = complexSearchQuery.getDefaultFieldPhrases(); - return this.getURLForQuery(defaultPhrases.map(strings -> String.join(" ", strings)).orElse("")); + List defaultPhrases = complexSearchQuery.getDefaultFieldPhrases(); + return this.getURLForQuery(String.join(" ", defaultPhrases)); } /** diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java b/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java index 5bbfe0a5357..58c6bbc7498 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ArXiv.java @@ -264,12 +264,12 @@ public List performSearch(String query) throws FetcherException { @Override public List performComplexSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { List searchTerms = new ArrayList<>(); - complexSearchQuery.getAuthors().ifPresent(authors -> authors.forEach(author -> searchTerms.add("au:" + author))); - complexSearchQuery.getTitlePhrases().ifPresent(title -> searchTerms.add("ti:" + title)); + complexSearchQuery.getAuthors().forEach(author -> searchTerms.add("au:" + author)); + complexSearchQuery.getTitlePhrases().forEach(title -> searchTerms.add("ti:" + title)); complexSearchQuery.getJournal().ifPresent(journal -> searchTerms.add("jr:" + journal)); // Since ArXiv API does not support year search, we ignore the year related terms complexSearchQuery.getToYear().ifPresent(year -> searchTerms.add(year.toString())); - complexSearchQuery.getDefaultFieldPhrases().ifPresent(searchTerms::addAll); + searchTerms.addAll(complexSearchQuery.getDefaultFieldPhrases()); String complexQueryString = String.join(" AND ", searchTerms); return performSearch(complexQueryString); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java index e16c5a8cf56..802675fafcf 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java @@ -31,16 +31,32 @@ private ComplexSearchQuery(List defaultField, List authors, List this.singleYear = singleYear; } - public Optional> getDefaultFieldPhrases() { - return Optional.ofNullable(defaultField); + public static ComplexSearchQuery fromTerms(Collection terms) { + ComplexSearchQueryBuilder builder = ComplexSearchQuery.builder(); + terms.forEach(term -> { + String termText = term.text(); + switch (term.field().toLowerCase()) { + case "author" -> builder.author(termText); + case "title" -> builder.titlePhrase(termText); + case "journal" -> builder.journal(termText); + case "year" -> builder.singleYear(Integer.valueOf(termText)); + case "year-range" -> builder.fromYearAndToYear(Integer.valueOf(termText.split("-")[0]), Integer.valueOf(termText.split("-")[1])); + case "default" -> builder.defaultFieldPhrase(termText); + } + }); + return builder.build(); + } + + public List getDefaultFieldPhrases() { + return defaultField; } - public Optional> getAuthors() { - return Optional.ofNullable(authors); + public List getAuthors() { + return authors; } - public Optional> getTitlePhrases() { - return Optional.ofNullable(titlePhrases); + public List getTitlePhrases() { + return titlePhrases; } public Optional getFromYear() { @@ -63,10 +79,35 @@ public static ComplexSearchQueryBuilder builder() { return new ComplexSearchQueryBuilder(); } + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + ComplexSearchQuery that = (ComplexSearchQuery) o; + + // Just check for set equality, order does not matter + if (!(getDefaultFieldPhrases().containsAll(that.getDefaultFieldPhrases()) && that.getDefaultFieldPhrases().containsAll(getDefaultFieldPhrases()))) + return false; + if (!(getAuthors().containsAll(that.getAuthors()) && that.getAuthors().containsAll(getAuthors()))) + return false; + if (!(getTitlePhrases().containsAll(that.getTitlePhrases()) && that.getTitlePhrases().containsAll(getTitlePhrases()))) + return false; + if (getFromYear().isPresent() ? !getFromYear().equals(that.getFromYear()) : that.getFromYear().isPresent()) + return false; + if (getToYear().isPresent() ? !getToYear().equals(that.getToYear()) : that.getToYear().isPresent()) + return false; + if (getSingleYear().isPresent() ? !getSingleYear().equals(that.getSingleYear()) : that.getSingleYear().isPresent()) + return false; + return getJournal().isPresent() ? getJournal().equals(that.getJournal()) : !that.getJournal().isPresent(); + } + public static class ComplexSearchQueryBuilder { - private List defaultFieldPhrases; - private List authors; - private List titlePhrases; + private List defaultFieldPhrases = new ArrayList<>(); + private List authors = new ArrayList<>(); + private List titlePhrases = new ArrayList<>(); private String journal; private Integer fromYear; private Integer toYear; @@ -79,9 +120,6 @@ public ComplexSearchQueryBuilder defaultFieldPhrase(String defaultFieldPhrase) { if (Objects.requireNonNull(defaultFieldPhrase).isBlank()) { throw new IllegalArgumentException("Parameter must not be blank"); } - if (Objects.isNull(defaultFieldPhrases)) { - this.defaultFieldPhrases = new ArrayList<>(); - } // Strip all quotes before wrapping this.defaultFieldPhrases.add(String.format("\"%s\"", defaultFieldPhrase.replace("\"", ""))); return this; @@ -94,9 +132,6 @@ public ComplexSearchQueryBuilder author(String author) { if (Objects.requireNonNull(author).isBlank()) { throw new IllegalArgumentException("Parameter must not be blank"); } - if (Objects.isNull(authors)) { - this.authors = new ArrayList<>(); - } // Strip all quotes before wrapping this.authors.add(String.format("\"%s\"", author.replace("\"", ""))); return this; @@ -109,9 +144,6 @@ public ComplexSearchQueryBuilder titlePhrase(String titlePhrase) { if (Objects.requireNonNull(titlePhrase).isBlank()) { throw new IllegalArgumentException("Parameter must not be blank"); } - if (Objects.isNull(titlePhrases)) { - this.titlePhrases = new ArrayList<>(); - } // Strip all quotes before wrapping this.titlePhrases.add(String.format("\"%s\"", titlePhrase.replace("\"", ""))); return this; diff --git a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java index 90792ecde62..a35e1353373 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/GoogleScholar.java @@ -205,9 +205,9 @@ public List performComplexSearch(ComplexSearchQuery complexSearchQuery private String constructComplexQueryString(ComplexSearchQuery complexSearchQuery) { List searchTerms = new ArrayList<>(); - complexSearchQuery.getDefaultFieldPhrases().ifPresent(searchTerms::addAll); - complexSearchQuery.getAuthors().ifPresent(authors -> authors.forEach(author -> searchTerms.add("author:" + author))); - complexSearchQuery.getTitlePhrases().ifPresent(phrases -> searchTerms.add("allintitle:" + String.join(" ", phrases))); + searchTerms.addAll(complexSearchQuery.getDefaultFieldPhrases()); + complexSearchQuery.getAuthors().forEach(author -> searchTerms.add("author:" + author)); + searchTerms.add("allintitle:" + String.join(" ", complexSearchQuery.getTitlePhrases())); complexSearchQuery.getJournal().ifPresent(journal -> searchTerms.add("source:" + journal)); // API automatically ANDs the terms return String.join(" ", searchTerms); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java index 306f7796ac6..2b1b0a0f33e 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/IEEE.java @@ -236,11 +236,15 @@ public Optional getHelpPage() { public URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URISyntaxException, MalformedURLException { URIBuilder uriBuilder = new URIBuilder("https://ieeexploreapi.ieee.org/api/v1/search/articles"); uriBuilder.addParameter("apikey", API_KEY); - complexSearchQuery.getDefaultFieldPhrases().ifPresent(defaultFieldPhrases -> uriBuilder.addParameter("querytext", String.join(" AND ", defaultFieldPhrases))); - complexSearchQuery.getAuthors().ifPresent(authors -> - uriBuilder.addParameter("author", String.join(" AND ", authors))); - complexSearchQuery.getTitlePhrases().ifPresent(articleTitlePhrases -> - uriBuilder.addParameter("article_title", String.join(" AND ", articleTitlePhrases))); + if (!complexSearchQuery.getDefaultFieldPhrases().isEmpty()) { + uriBuilder.addParameter("querytext", String.join(" AND ", complexSearchQuery.getDefaultFieldPhrases())); + } + if (!complexSearchQuery.getAuthors().isEmpty()) { + uriBuilder.addParameter("author", String.join(" AND ", complexSearchQuery.getAuthors())); + } + if (!complexSearchQuery.getAuthors().isEmpty()) { + uriBuilder.addParameter("article_title", String.join(" AND ", complexSearchQuery.getTitlePhrases())); + } complexSearchQuery.getJournal().ifPresent(journalTitle -> uriBuilder.addParameter("publication_title", journalTitle)); complexSearchQuery.getFromYear().map(String::valueOf).ifPresent(year -> uriBuilder.addParameter("start_year", year)); complexSearchQuery.getToYear().map(String::valueOf).ifPresent(year -> uriBuilder.addParameter("end_year", year)); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java index c633452617f..30b63e5004a 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/SpringerFetcher.java @@ -171,12 +171,12 @@ public URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URIS private String constructComplexQueryString(ComplexSearchQuery complexSearchQuery) { List searchTerms = new ArrayList<>(); - complexSearchQuery.getAuthors().ifPresent(authors -> authors.forEach(author -> searchTerms.add("name:" + author))); - complexSearchQuery.getTitlePhrases().ifPresent(titlePhrases -> titlePhrases.forEach(title -> searchTerms.add("title:" + title))); + complexSearchQuery.getAuthors().forEach(author -> searchTerms.add("name:" + author)); + complexSearchQuery.getTitlePhrases().forEach(title -> searchTerms.add("title:" + title)); complexSearchQuery.getJournal().ifPresent(journal -> searchTerms.add("journal:" + journal)); // Since Springer API does not support year range search, we ignore formYear and toYear and use "singleYear" only complexSearchQuery.getSingleYear().ifPresent(year -> searchTerms.add("year:" + year.toString())); - complexSearchQuery.getDefaultFieldPhrases().ifPresent(searchTerms::addAll); + searchTerms.addAll(complexSearchQuery.getDefaultFieldPhrases()); return String.join(" AND ", searchTerms); } diff --git a/src/test/java/org/jabref/logic/importer/QueryConverterTest.java b/src/test/java/org/jabref/logic/importer/QueryConverterTest.java deleted file mode 100644 index b6c5a8683d7..00000000000 --- a/src/test/java/org/jabref/logic/importer/QueryConverterTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.jabref.logic.importer; - -import java.util.Collections; -import java.util.List; - -import org.jabref.logic.importer.fetcher.ComplexSearchQuery; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class QueryConverterTest { - QueryConverter converter = new QueryConverter(); - - @Test - public void convertAuthorField() throws Exception { - ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("author:\"Igor Steinmacher\""); - assertEquals(List.of("\"Igor Steinmacher\""), searchQuery.getAuthors().get()); - } - - @Test - public void convertDefaultField() throws Exception { - ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("\"default value\""); - assertEquals(List.of("\"default value\""), searchQuery.getDefaultFieldPhrases().get()); - } - - @Test - public void convertExplicitDefaultField() throws Exception { - ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("default:\"default value\""); - assertEquals(List.of("\"default value\""), searchQuery.getDefaultFieldPhrases().get()); - } - - @Test - public void convertJournalField() throws Exception { - ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("journal:\"Nature\""); - assertEquals("\"Nature\"", searchQuery.getJournal().get()); - } - - @Test - public void convertYearField() throws Exception { - ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("year:2015"); - assertEquals(2015, searchQuery.getSingleYear().get()); - } - - @Test - public void convertYearRangeField() throws Exception { - ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("year-range:2012-2015"); - assertEquals(2012, searchQuery.getFromYear().get()); - assertEquals(2015, searchQuery.getToYear().get()); - } - - @Test - public void convertMultipleValuesWithTheSameField() throws Exception { - ComplexSearchQuery searchQuery = new QueryConverter().convertQueryStringIntoComplexQuery("author:\"Igor Steinmacher\" author:\"Christoph Treude\""); - List sortedAuthors = searchQuery.getAuthors().get(); - Collections.sort(sortedAuthors); - assertEquals(List.of("\"Christoph Treude\"", "\"Igor Steinmacher\""), sortedAuthors); - } -} diff --git a/src/test/java/org/jabref/logic/importer/QueryParserTest.java b/src/test/java/org/jabref/logic/importer/QueryParserTest.java new file mode 100644 index 00000000000..c0a82a75db7 --- /dev/null +++ b/src/test/java/org/jabref/logic/importer/QueryParserTest.java @@ -0,0 +1,60 @@ +package org.jabref.logic.importer; + +import org.jabref.logic.importer.fetcher.ComplexSearchQuery; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class QueryParserTest { + QueryParser converter = new QueryParser(); + + @Test + public void convertAuthorField() throws Exception { + ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("author:\"Igor Steinmacher\"").get(); + ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().author("\"Igor Steinmacher\"").build(); + assertEquals(expectedQuery, searchQuery); + } + + @Test + public void convertDefaultField() throws Exception { + ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("\"default value\"").get(); + ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().defaultFieldPhrase("\"default value\"").build(); + assertEquals(expectedQuery, searchQuery); + } + + @Test + public void convertExplicitDefaultField() throws Exception { + ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("default:\"default value\"").get(); + ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().defaultFieldPhrase("\"default value\"").build(); + assertEquals(expectedQuery, searchQuery); + } + + @Test + public void convertJournalField() throws Exception { + ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("journal:\"Nature\"").get(); + ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().journal("\"Nature\"").build(); + assertEquals(expectedQuery, searchQuery); + } + + @Test + public void convertYearField() throws Exception { + ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("year:2015").get(); + ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().singleYear(2015).build(); + assertEquals(expectedQuery, searchQuery); + } + + @Test + public void convertYearRangeField() throws Exception { + ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("year-range:2012-2015").get(); + ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().fromYearAndToYear(2012, 2015).build(); + assertEquals(expectedQuery, searchQuery); + } + + @Test + public void convertMultipleValuesWithTheSameField() throws Exception { + ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("author:\"Igor Steinmacher\" author:\"Christoph Treude\"").get(); + ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().author("\"Igor Steinmacher\"").author("\"Christoph Treude\"").build(); + assertEquals(expectedQuery, searchQuery); + } +} From 86c65767484f8c3379c822fbd2eb97e1bf206e48 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Thu, 27 Aug 2020 16:59:10 +0200 Subject: [PATCH 09/15] Rename slipped occurences of converter Signed-off-by: Dominik Voigt --- .../org/jabref/logic/importer/QueryParser.java | 4 ++-- .../jabref/logic/importer/QueryParserTest.java | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/QueryParser.java b/src/main/java/org/jabref/logic/importer/QueryParser.java index d3ca60c0135..b48c45cf468 100644 --- a/src/main/java/org/jabref/logic/importer/QueryParser.java +++ b/src/main/java/org/jabref/logic/importer/QueryParser.java @@ -23,14 +23,14 @@ public class QueryParser { /** - * Converts the given query string into a complex query using lucene. + * Parses the given query string into a complex query using lucene. * Note: For unique fields, the alphabetically first instance in the query string is used in the complex query. * * @param queryString The given query string * @return A complex query containing all fields of the query string * @throws QueryNodeException Error during parsing */ - public Optional convertQueryStringIntoComplexQuery(String queryString) { + public Optional parseQueryStringIntoComplexQuery(String queryString) { try { ComplexSearchQuery.ComplexSearchQueryBuilder builder = ComplexSearchQuery.builder(); diff --git a/src/test/java/org/jabref/logic/importer/QueryParserTest.java b/src/test/java/org/jabref/logic/importer/QueryParserTest.java index c0a82a75db7..624117d5289 100644 --- a/src/test/java/org/jabref/logic/importer/QueryParserTest.java +++ b/src/test/java/org/jabref/logic/importer/QueryParserTest.java @@ -7,53 +7,53 @@ import static org.junit.jupiter.api.Assertions.assertEquals; class QueryParserTest { - QueryParser converter = new QueryParser(); + QueryParser parser = new QueryParser(); @Test public void convertAuthorField() throws Exception { - ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("author:\"Igor Steinmacher\"").get(); + ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("author:\"Igor Steinmacher\"").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().author("\"Igor Steinmacher\"").build(); assertEquals(expectedQuery, searchQuery); } @Test public void convertDefaultField() throws Exception { - ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("\"default value\"").get(); + ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("\"default value\"").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().defaultFieldPhrase("\"default value\"").build(); assertEquals(expectedQuery, searchQuery); } @Test public void convertExplicitDefaultField() throws Exception { - ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("default:\"default value\"").get(); + ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("default:\"default value\"").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().defaultFieldPhrase("\"default value\"").build(); assertEquals(expectedQuery, searchQuery); } @Test public void convertJournalField() throws Exception { - ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("journal:\"Nature\"").get(); + ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("journal:\"Nature\"").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().journal("\"Nature\"").build(); assertEquals(expectedQuery, searchQuery); } @Test public void convertYearField() throws Exception { - ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("year:2015").get(); + ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("year:2015").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().singleYear(2015).build(); assertEquals(expectedQuery, searchQuery); } @Test public void convertYearRangeField() throws Exception { - ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("year-range:2012-2015").get(); + ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("year-range:2012-2015").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().fromYearAndToYear(2012, 2015).build(); assertEquals(expectedQuery, searchQuery); } @Test public void convertMultipleValuesWithTheSameField() throws Exception { - ComplexSearchQuery searchQuery = converter.convertQueryStringIntoComplexQuery("author:\"Igor Steinmacher\" author:\"Christoph Treude\"").get(); + ComplexSearchQuery searchQuery = parser.parseQueryStringIntoComplexQuery("author:\"Igor Steinmacher\" author:\"Christoph Treude\"").get(); ComplexSearchQuery expectedQuery = ComplexSearchQuery.builder().author("\"Igor Steinmacher\"").author("\"Christoph Treude\"").build(); assertEquals(expectedQuery, searchQuery); } From 0642d49e208776d9bff095782b110061aacbf7d3 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Sun, 30 Aug 2020 14:05:50 +0200 Subject: [PATCH 10/15] Mapping of unknown fields onto default field Signed-off-by: Dominik Voigt --- .../org/jabref/logic/importer/fetcher/ComplexSearchQuery.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java index 802675fafcf..9ff2376a9bf 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java @@ -41,7 +41,8 @@ public static ComplexSearchQuery fromTerms(Collection terms) { case "journal" -> builder.journal(termText); case "year" -> builder.singleYear(Integer.valueOf(termText)); case "year-range" -> builder.fromYearAndToYear(Integer.valueOf(termText.split("-")[0]), Integer.valueOf(termText.split("-")[1])); - case "default" -> builder.defaultFieldPhrase(termText); + // Unknown fields and the default field will be mapped to default field + default -> builder.defaultFieldPhrase(termText); } }); return builder.build(); From 11afbe9f1e94e6058b0078bc2da1e62e98e65ca5 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Sun, 30 Aug 2020 14:20:39 +0200 Subject: [PATCH 11/15] Ignores unknown fields Signed-off-by: Dominik Voigt --- src/main/java/org/jabref/logic/importer/QueryParser.java | 2 +- .../org/jabref/logic/importer/fetcher/ComplexSearchQuery.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/QueryParser.java b/src/main/java/org/jabref/logic/importer/QueryParser.java index b48c45cf468..6ec1be9d9f7 100644 --- a/src/main/java/org/jabref/logic/importer/QueryParser.java +++ b/src/main/java/org/jabref/logic/importer/QueryParser.java @@ -46,7 +46,7 @@ public Optional parseQueryStringIntoComplexQuery(String quer sortedTerms.sort(Comparator.comparing(Term::text)); builder.terms(sortedTerms); return Optional.of(builder.build()); - } catch (QueryNodeException ex) { + } catch (QueryNodeException | IllegalStateException ex) { return Optional.empty(); } } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java index 9ff2376a9bf..802675fafcf 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java @@ -41,8 +41,7 @@ public static ComplexSearchQuery fromTerms(Collection terms) { case "journal" -> builder.journal(termText); case "year" -> builder.singleYear(Integer.valueOf(termText)); case "year-range" -> builder.fromYearAndToYear(Integer.valueOf(termText.split("-")[0]), Integer.valueOf(termText.split("-")[1])); - // Unknown fields and the default field will be mapped to default field - default -> builder.defaultFieldPhrase(termText); + case "default" -> builder.defaultFieldPhrase(termText); } }); return builder.build(); From 239095e9e5b6d854435383caa5edc929719ecbc1 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Mon, 31 Aug 2020 12:36:52 +0200 Subject: [PATCH 12/15] Fix checkstyle issues Signed-off-by: Dominik Voigt --- .../importer/fetcher/ComplexSearchQuery.java | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java index 802675fafcf..d6b93f646d1 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java @@ -81,29 +81,49 @@ public static ComplexSearchQueryBuilder builder() { @Override public boolean equals(Object o) { - if (this == o) + if (this == o) { return true; - if (o == null || getClass() != o.getClass()) + } + if (o == null || getClass() != o.getClass()) { return false; + } ComplexSearchQuery that = (ComplexSearchQuery) o; // Just check for set equality, order does not matter - if (!(getDefaultFieldPhrases().containsAll(that.getDefaultFieldPhrases()) && that.getDefaultFieldPhrases().containsAll(getDefaultFieldPhrases()))) + if (!(getDefaultFieldPhrases().containsAll(that.getDefaultFieldPhrases()) && that.getDefaultFieldPhrases().containsAll(getDefaultFieldPhrases()))) { return false; - if (!(getAuthors().containsAll(that.getAuthors()) && that.getAuthors().containsAll(getAuthors()))) + } + if (!(getAuthors().containsAll(that.getAuthors()) && that.getAuthors().containsAll(getAuthors()))) { return false; - if (!(getTitlePhrases().containsAll(that.getTitlePhrases()) && that.getTitlePhrases().containsAll(getTitlePhrases()))) + } + if (!(getTitlePhrases().containsAll(that.getTitlePhrases()) && that.getTitlePhrases().containsAll(getTitlePhrases()))) { return false; - if (getFromYear().isPresent() ? !getFromYear().equals(that.getFromYear()) : that.getFromYear().isPresent()) + } + if (getFromYear().isPresent() ? !getFromYear().equals(that.getFromYear()) : that.getFromYear().isPresent()) { return false; - if (getToYear().isPresent() ? !getToYear().equals(that.getToYear()) : that.getToYear().isPresent()) + } + if (getToYear().isPresent() ? !getToYear().equals(that.getToYear()) : that.getToYear().isPresent()) { return false; - if (getSingleYear().isPresent() ? !getSingleYear().equals(that.getSingleYear()) : that.getSingleYear().isPresent()) + } + if (getSingleYear().isPresent() ? !getSingleYear().equals(that.getSingleYear()) : that.getSingleYear().isPresent()) { return false; + } return getJournal().isPresent() ? getJournal().equals(that.getJournal()) : !that.getJournal().isPresent(); } + @Override + public int hashCode() { + int result = defaultField != null ? defaultField.hashCode() : 0; + result = 31 * result + (getAuthors() != null ? getAuthors().hashCode() : 0); + result = 31 * result + (getTitlePhrases() != null ? getTitlePhrases().hashCode() : 0); + result = 31 * result + (getFromYear().isPresent() ? getFromYear().hashCode() : 0); + result = 31 * result + (getToYear().isPresent() ? getToYear().hashCode() : 0); + result = 31 * result + (getSingleYear().isPresent() ? getSingleYear().hashCode() : 0); + result = 31 * result + (getJournal().isPresent() ? getJournal().hashCode() : 0); + return result; + } + public static class ComplexSearchQueryBuilder { private List defaultFieldPhrases = new ArrayList<>(); private List authors = new ArrayList<>(); From 542d3a7c521f81c99fc156bb9d23166154a4e9f9 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Mon, 31 Aug 2020 13:11:26 +0200 Subject: [PATCH 13/15] Apply suggestions from code review Co-authored-by: Oliver Kopp --- ...stract-query-syntax-for-query-conversion.md | 18 +++++++++--------- .../logic/importer/SearchBasedFetcher.java | 2 +- .../importer/SearchBasedParserFetcher.java | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md b/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md index e6aab48b267..cb6a8f214fa 100644 --- a/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md +++ b/docs/adr/0015-support-an-abstract-query-syntax-for-query-conversion.md @@ -21,20 +21,20 @@ For simplicitly, and lack of universal capabilities across fetchers, only basic * All terms in the query are whitespace separated and will be ANDed * Default and certain fielded terms are supported * Fielded Terms: - * author - * title - * journal - * year (for single year) - * year-range (for range e.g. year-range:2012-2015) -* The journal, year, and year-range fields should only be populated once in each query + * `author` + * `title` + * `journal` + * `year` (for single year) + * `year-range` (for range e.g. `year-range:2012-2015`) +* The `journal`, `year`, and `year-range` fields should only be populated once in each query * Example: - * \[author:"Igor Steinmacher" author:"Christoph Treude" year:2017\] will be converted to - * \[author:"Igor Steinmacher" AND author:"Christoph Treude" AND year:2017\] + * `author:"Igor Steinmacher" author:"Christoph Treude" year:2017` will be converted to + * `author:"Igor Steinmacher" AND author:"Christoph Treude" AND year:2017` ### Positive Consequences * Already tested -* Well Known +* Well known * Easy to implement * Can use an existing parser diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java index bd27d1523f3..c16c9001a4b 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedFetcher.java @@ -26,7 +26,7 @@ public interface SearchBasedFetcher extends WebFetcher { * @return a list of {@link BibEntry}, which are matched by the query (may be empty) */ default List performComplexSearch(ComplexSearchQuery complexSearchQuery) throws FetcherException { - // Default Implementation behaves like perform search using the default field phrases as query + // Default implementation behaves as performSearch using the default field phrases as query List defaultPhrases = complexSearchQuery.getDefaultFieldPhrases(); return performSearch(String.join(" ", defaultPhrases)); } diff --git a/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java b/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java index 44f26510498..1e403a4829d 100644 --- a/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java +++ b/src/main/java/org/jabref/logic/importer/SearchBasedParserFetcher.java @@ -83,7 +83,7 @@ private List getBibEntries(URL urlForQuery) throws FetcherException { } default URL getComplexQueryURL(ComplexSearchQuery complexSearchQuery) throws URISyntaxException, MalformedURLException, FetcherException { - // Default Implementation behaves like getURLForQuery using the default field phrases as query + // Default implementation behaves as getURLForQuery using the default field phrases as query List defaultPhrases = complexSearchQuery.getDefaultFieldPhrases(); return this.getURLForQuery(String.join(" ", defaultPhrases)); } From 4b2ab98fce9cdbbbd04ffeb06cff909273fe54f3 Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Mon, 31 Aug 2020 13:27:14 +0200 Subject: [PATCH 14/15] Handle year-range parsing in a more robust way Signed-off-by: Dominik Voigt --- .../importer/fetcher/ComplexSearchQuery.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java index d6b93f646d1..726b6cff437 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java @@ -40,13 +40,32 @@ public static ComplexSearchQuery fromTerms(Collection terms) { case "title" -> builder.titlePhrase(termText); case "journal" -> builder.journal(termText); case "year" -> builder.singleYear(Integer.valueOf(termText)); - case "year-range" -> builder.fromYearAndToYear(Integer.valueOf(termText.split("-")[0]), Integer.valueOf(termText.split("-")[1])); + case "year-range" -> parseYearRange(builder, termText); case "default" -> builder.defaultFieldPhrase(termText); } }); return builder.build(); } + private static void parseYearRange(ComplexSearchQueryBuilder builder, String termText) { + String[] split = termText.split("-"); + int fromYear = 0; + int toYear = 9999; + try { + fromYear = Integer.parseInt(split[0]); + } catch (NumberFormatException e) { + // default value already set + } + if (split.length > 1) { + try { + toYear = Integer.parseInt(split[1]); + } catch (NumberFormatException e) { + // default value already set + } + } + builder.fromYearAndToYear(fromYear, toYear); + } + public List getDefaultFieldPhrases() { return defaultField; } @@ -202,7 +221,7 @@ public ComplexSearchQueryBuilder terms(Collection terms) { case "title" -> this.titlePhrase(termText); case "journal" -> this.journal(termText); case "year" -> this.singleYear(Integer.valueOf(termText)); - case "year-range" -> this.fromYearAndToYear(Integer.valueOf(termText.split("-")[0]), Integer.valueOf(termText.split("-")[1])); + case "year-range" -> parseYearRange(this, termText); case "default" -> this.defaultFieldPhrase(termText); } }); From 8b3b7cdd08f7926e7d758dccef5c71897132287d Mon Sep 17 00:00:00 2001 From: Dominik Voigt Date: Mon, 31 Aug 2020 13:29:43 +0200 Subject: [PATCH 15/15] Move extracted method into builder Signed-off-by: Dominik Voigt --- .../importer/fetcher/ComplexSearchQuery.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java index 726b6cff437..ed6a1c0a776 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ComplexSearchQuery.java @@ -40,32 +40,13 @@ public static ComplexSearchQuery fromTerms(Collection terms) { case "title" -> builder.titlePhrase(termText); case "journal" -> builder.journal(termText); case "year" -> builder.singleYear(Integer.valueOf(termText)); - case "year-range" -> parseYearRange(builder, termText); + case "year-range" -> builder.parseYearRange(termText); case "default" -> builder.defaultFieldPhrase(termText); } }); return builder.build(); } - private static void parseYearRange(ComplexSearchQueryBuilder builder, String termText) { - String[] split = termText.split("-"); - int fromYear = 0; - int toYear = 9999; - try { - fromYear = Integer.parseInt(split[0]); - } catch (NumberFormatException e) { - // default value already set - } - if (split.length > 1) { - try { - toYear = Integer.parseInt(split[1]); - } catch (NumberFormatException e) { - // default value already set - } - } - builder.fromYearAndToYear(fromYear, toYear); - } - public List getDefaultFieldPhrases() { return defaultField; } @@ -221,7 +202,7 @@ public ComplexSearchQueryBuilder terms(Collection terms) { case "title" -> this.titlePhrase(termText); case "journal" -> this.journal(termText); case "year" -> this.singleYear(Integer.valueOf(termText)); - case "year-range" -> parseYearRange(this, termText); + case "year-range" -> this.parseYearRange(termText); case "default" -> this.defaultFieldPhrase(termText); } }); @@ -243,6 +224,25 @@ public ComplexSearchQuery build() throws IllegalStateException { return new ComplexSearchQuery(defaultFieldPhrases, authors, titlePhrases, fromYear, toYear, singleYear, journal); } + void parseYearRange(String termText) { + String[] split = termText.split("-"); + int fromYear = 0; + int toYear = 9999; + try { + fromYear = Integer.parseInt(split[0]); + } catch (NumberFormatException e) { + // default value already set + } + if (split.length > 1) { + try { + toYear = Integer.parseInt(split[1]); + } catch (NumberFormatException e) { + // default value already set + } + } + this.fromYearAndToYear(fromYear, toYear); + } + private boolean textSearchFieldsAndYearFieldsAreEmpty() { return this.stringListIsBlank(defaultFieldPhrases) && this.stringListIsBlank(titlePhrases) && this.stringListIsBlank(authors) && StringUtil.isBlank(journal) && yearFieldsAreEmpty();