From b8051e3b6bad514a80f7b0d505eafe84155b6c10 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sun, 10 Mar 2024 18:43:18 +0100 Subject: [PATCH 01/26] Update Texworks icon (#10998) * Update Texworks icon * checkstyle --- .../java/org/jabref/gui/icon/IconTheme.java | 1 + .../gui/icon/JabRefMaterialDesignIcon.java | 5 +++-- .../org/jabref/gui/push/PushToTeXworks.java | 3 +-- .../resources/fonts/JabRefMaterialDesign.ttf | Bin 8460 -> 8604 bytes 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/jabref/gui/icon/IconTheme.java b/src/main/java/org/jabref/gui/icon/IconTheme.java index 10be9e16090..470357981af 100644 --- a/src/main/java/org/jabref/gui/icon/IconTheme.java +++ b/src/main/java/org/jabref/gui/icon/IconTheme.java @@ -278,6 +278,7 @@ public enum JabRefIcons implements JabRefIcon { APPLICATION_WINEDT(JabRefMaterialDesignIcon.WINEDT), APPLICATION_SUBLIMETEXT(JabRefMaterialDesignIcon.SUBLIME_TEXT), APPLICATION_TEXSHOP(JabRefMaterialDesignIcon.TEXSHOP), + APPLICATION_TEXWORS(JabRefMaterialDesignIcon.TEXWORKS), KEY_BINDINGS(MaterialDesignK.KEYBOARD), FIND_DUPLICATES(MaterialDesignC.CODE_EQUAL), CONNECT_DB(MaterialDesignC.CLOUD_UPLOAD), diff --git a/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java b/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java index 6f697cc1e4a..5d8deaa5d91 100644 --- a/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java +++ b/src/main/java/org/jabref/gui/icon/JabRefMaterialDesignIcon.java @@ -5,7 +5,7 @@ /** * Provides the same true-type font interface as MaterialDesignIcon itself, but uses a font we created ourselves that * contains icons that are not available in MaterialDesignIcons. - * + *

* The glyphs of the ttf (speak: the icons) were created with Illustrator and a template from the material design icons * web-page. The art boards for each icon was exported as SVG and then converted with * IcoMoon. The final TTF font is located in the resource folder. @@ -31,7 +31,8 @@ public enum JabRefMaterialDesignIcon implements Ikon { VSCODE("jab-vsvode", '\ue90d'), CANCEL("jab-cancel", '\ue90e'), SUBLIME_TEXT("jab-sublime-text", '\ue90f'), - TEXSHOP("jab-texshop", '\ue910'); + TEXSHOP("jab-texshop", '\ue910'), + TEXWORKS("jab-texworks", '\ue911'); private String description; private int code; diff --git a/src/main/java/org/jabref/gui/push/PushToTeXworks.java b/src/main/java/org/jabref/gui/push/PushToTeXworks.java index fbc052a64c7..af1be1241be 100644 --- a/src/main/java/org/jabref/gui/push/PushToTeXworks.java +++ b/src/main/java/org/jabref/gui/push/PushToTeXworks.java @@ -26,8 +26,7 @@ public String getDisplayName() { @Override public JabRefIcon getApplicationIcon() { - // TODO: replace the placeholder icon with the real one. - return IconTheme.JabRefIcons.APPLICATION_GENERIC; + return IconTheme.JabRefIcons.APPLICATION_TEXWORS; } @Override diff --git a/src/main/resources/fonts/JabRefMaterialDesign.ttf b/src/main/resources/fonts/JabRefMaterialDesign.ttf index 1dad4686d10f5684a9fe64081c824557f19ea9da..21525c7a697f89e507065bfd4625306f2cfe1fb5 100644 GIT binary patch delta 1764 zcmbtVO>7%Q7@coscW2k@tbgL&b(+LU?Zm65C0_qSlR9ZQKm`qghN`DfQ_>cx`BBqD zs6a+STu`BEEfA{U$N^eaRFx11ge)YkNJTGbj|-&X-l53GvH? z-W;Va(i4>GK1t!iy&hwt$2eonM^tO7zyHMPN+*67z3ZRT4Ive{4e*X$)*^5j;4A9` zioizzXYJSf;EL#Vje#3c;sO%i52X5uZ;<%IX-A0NUjAyZu;Axa_%o5kU8>{}c7P%h{(RsbW6KUPF9Eyn%Ra=u+oikN+upw1TdyZo$aQ zydfRx-{p+w=g6;kRp0dsQ?#4v%}S$MDV9iPluQXil|`~p%)6l?3!zcS7YpTC+DFa3 zR1RzPSt=Lvg`i#yYQ_3A1^ru59k=RLU@$^5%ww3mKTER?)BDx9s3~K(&`ePfHkvIe zH!9V=!cW6mO!<)kCQVtkL)p}@gNz*q9ZL);7i$0n5r|Ygt~!^RO}lebIdD20xItK+ z=(>QBnsSDi&Z~L_(t^h@DUs(SJffs+r-*mRbsSe!FEuU3Rb{HgsMi?`=EM!dK1^5{ ztPj!th*Ocyxx|!)ZiDKxnSn75zidolRh^+}hDvd~7iq;L-Un)l9HKk zvmy)GESqO+&~Fyi-fJ`mJwy7Ffl&-ZquKC$&u<3$picoI5Y0g1#}+nxZ0vdsyV`Rz z2KhDoruJFcp#zh})HBJYmPKz`b4L=3sfo$O{LXl*uzmU4(=@!Wy|B>T*ld*^?>OW0 zfoGT|cA81QwpwEJyjjd+_@5WNp%v^K?+?hMr1JndkT1{c@FB^V^5!+o!gF zJGiHQV0c3FOL-)kpp3>aepw#ks?&ObauR+A7I5!czL? z7oWTI^`|4}39mEOIkhlXeX{TRq{dkM8?sjxNTGd+KQPuM2=^{5U0y%S#EXRQB0ROY za`8mvP;$VRThU&2)|Fbd%3zacVVahHsP-rgOPLQW|M+#)j{at$F=P`F}cvbufm1V59jt+(OMJ@EdM7uPJ$STrXgwekro2ZP9rPZ`7Z#_b6f$8+^Ap>)0}f2kIN{Ptd5( zMkh$))l>gCdQ-cyzk{z5#d2)B#@-Q(H(`#tF4~$2ho{{It~V24yQzLiY31QD7%rrT zT5fj4AqTQ%`XQ4{+c}lO^CqN{=}dkEM#;C5&y~s}kQ#(kraYV(giI+dcW=PjFehZJ;*W+p;S`R9iBE>iMmvgWt1w#g5&071+HB@1_#x~W%6?FPL4y#>|#Mi z7Acr!dDzaBYX_weX*xLoHq|Rl|IKkpmMW#o<4~xy!p2~{RXs72IiD_#0;P9{?Shkc zB6cTe5mok~dZl$iF(*mOUf*;H_`r2TH5DbGC_b!V)NBhwkDImyn!zYcr7A)*R2{&G zq!8MTV3-5fRl|p!p^&QzMTP3Qh5Mm15CRAVI{E-O2+kFbnh@bow}P&j1OPK)9Z&ZZ zxt6qCE)t=TB78`0TmYa9GW4Kc14L4sU7W50a*j|tbqrJ#iW3ZwlK@h2V%Jdkg>t0l zXv7y5LYEndVQ`Q(GJ`HK?6Z&e#Z+>WX;iNu9aSnM9G_v4Bdps zY6BUClMkqx?3w7=biWCf-)|uYzY^rlA!(-KP!*VrxSasS)X_M30z{*bmqsU15)co! zA8B=VpzV#t=yGUSx(o2g*rQ`~&VgS96%i0Zr6!<8JpX(zxm?|nzH)e+00X3^3s4mi zR25s6B}X=q$a;q!*XiX7xy9pIDJ@-ZSA?JLiX-(zx6EOKJUJ*_1WX4$0>=mVTvFZ(-5vKTJR8s3q~T!7251vAFuvOS7yRK@RL5?m&XWq(31LT zS~D#aEkKN%?&azju$+-|qhVU!UOBm;e9( From 7bf2b0e54a878dd60312b41c10b7d740deac609b Mon Sep 17 00:00:00 2001 From: Christoph Date: Sun, 10 Mar 2024 20:08:04 +0100 Subject: [PATCH 02/26] Update custom-svg-icons.md (#10999) * Update custom-svg-icons.md * Update custom-svg-icons.md * lint --- docs/code-howtos/custom-svg-icons.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/code-howtos/custom-svg-icons.md b/docs/code-howtos/custom-svg-icons.md index cd20685a80a..5f6070c5700 100644 --- a/docs/code-howtos/custom-svg-icons.md +++ b/docs/code-howtos/custom-svg-icons.md @@ -23,7 +23,14 @@ Good icon design requires years of experience and cannot be covered here. Adapti ## Step 2. Packing the icons into a font -Use the [IcoMoon](https://icomoon.io) tool for packing the icons. Create a new set and import _all_ icons. Rearrange them so that they have the same order as in `org.jabref.gui.JabRefMaterialDesignIcon`. This will avoid that you have to change the code points for the existing glyphs. In the settings for your icon set, set the _Grid_ to 24. This is important to get the correct spacing. The name of the font is `JabRefMaterialDesign`. When your icon-set is ready, select all of them and download the font-package. +Use the [IcoMoon](https://icomoon.io) tool for packing the icons. + +1. Create a new set by importing the json file +2. Next to the icons, click on the hamburger menu, chose "Import to Set" to add a new icon (it will be added to the front) +Rearrange them so that they have the same order as in `org.jabref.gui.JabRefMaterialDesignIcon`. This will avoid that you have to change the code points for the existing glyphs. In the settings for your icon set, set the _Grid_ to 24. This is important to get the correct spacing. The name of the font is `JabRefMaterialDesign`. +3. Next to the icons, click on the hamburger menu and click "Select all". +4. Proceed with the font creating, set the font property name to `JabRefMaterialDesign` +When your icon-set is ready, select all of them and download the font-package. ## Step 3. Replace the existing `JabRefMaterialDesign.ttf` From aa3b1b91e65d02b2cf6ae0d16d101bc68394a759 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:15:26 +0000 Subject: [PATCH 03/26] Bump gittools/actions from 0.13.2 to 0.13.4 (#11001) Bumps [gittools/actions](https://github.com/gittools/actions) from 0.13.2 to 0.13.4. - [Release notes](https://github.com/gittools/actions/releases) - [Commits](https://github.com/gittools/actions/compare/v0.13.2...v0.13.4) --- updated-dependencies: - dependency-name: gittools/actions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deployment-arm64.yml | 4 ++-- .github/workflows/deployment-jdk-ea.yml | 4 ++-- .github/workflows/deployment.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deployment-arm64.yml b/.github/workflows/deployment-arm64.yml index 9f8f4c99917..a505062a065 100644 --- a/.github/workflows/deployment-arm64.yml +++ b/.github/workflows/deployment-arm64.yml @@ -66,12 +66,12 @@ jobs: submodules: 'true' show-progress: 'false' - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.13.2 + uses: gittools/actions/gitversion/setup@v0.13.4 with: versionSpec: "5.x" - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.13.2 + uses: gittools/actions/gitversion/execute@v0.13.4 - name: Setup JDK uses: actions/setup-java@v4 with: diff --git a/.github/workflows/deployment-jdk-ea.yml b/.github/workflows/deployment-jdk-ea.yml index 5978e568342..20c018e7968 100644 --- a/.github/workflows/deployment-jdk-ea.yml +++ b/.github/workflows/deployment-jdk-ea.yml @@ -87,12 +87,12 @@ jobs: packages: pigz version: 1.0 - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.13.2 + uses: gittools/actions/gitversion/setup@v0.13.4 with: versionSpec: "5.x" - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.13.2 + uses: gittools/actions/gitversion/execute@v0.13.4 - name: 'Set up JDK ${{ matrix.jdk }}' uses: oracle-actions/setup-java@v1 with: diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 02f62504013..a18391321ed 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -79,12 +79,12 @@ jobs: packages: pigz version: 1.0 - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v0.13.2 + uses: gittools/actions/gitversion/setup@v0.13.4 with: versionSpec: "5.x" - name: Run GitVersion id: gitversion - uses: gittools/actions/gitversion/execute@v0.13.2 + uses: gittools/actions/gitversion/execute@v0.13.4 - name: Setup JDK uses: actions/setup-java@v4 with: From 015b60f60b19ecd7b67c6e8e9baa50d715bd7a43 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:16:57 +0000 Subject: [PATCH 04/26] Bump softprops/action-gh-release from 1 to 2 (#11000) Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2. - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2) --- updated-dependencies: - dependency-name: softprops/action-gh-release dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/upload-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upload-release.yml b/.github/workflows/upload-release.yml index 41a73d8d6f8..d7020264bc5 100644 --- a/.github/workflows/upload-release.yml +++ b/.github/workflows/upload-release.yml @@ -18,7 +18,7 @@ jobs: cd build wget -q -m -r -nH --cut-dirs 2 --no-parent --accept=tar.gz,dmg,pkg,deb,rpm,zip,msi https://builds.jabref.org/tags/ - name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: draft: true files: build/** From 8d8d7befae8736359a5c2f8157ee9d27e13a7315 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:40:36 +0000 Subject: [PATCH 05/26] Bump jakarta.xml.bind:jakarta.xml.bind-api from 4.0.1 to 4.0.2 (#11004) Bumps [jakarta.xml.bind:jakarta.xml.bind-api](https://github.com/jakartaee/jaxb-api) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/jakartaee/jaxb-api/releases) - [Commits](https://github.com/jakartaee/jaxb-api/compare/4.0.1...4.0.2) --- updated-dependencies: - dependency-name: jakarta.xml.bind:jakarta.xml.bind-api dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 146f6bfc54f..398e74f9394 100644 --- a/build.gradle +++ b/build.gradle @@ -203,7 +203,7 @@ dependencies { } // jakarta.activation is already dependency of glassfish - implementation group: 'jakarta.xml.bind', name: 'jakarta.xml.bind-api', version: '4.0.1' + implementation group: 'jakarta.xml.bind', name: 'jakarta.xml.bind-api', version: '4.0.2' implementation group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '4.0.3' implementation ('com.github.tomtung:latex2unicode_2.13:0.3.2') { From b7aea11b29d7c44403d1b380855930fd8e224e94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:41:22 +0000 Subject: [PATCH 06/26] Bump org.javamodularity.moduleplugin from 1.8.14 to 1.8.15 (#11002) Bumps org.javamodularity.moduleplugin from 1.8.14 to 1.8.15. --- updated-dependencies: - dependency-name: org.javamodularity.moduleplugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 398e74f9394..dd278438ee5 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ plugins { id 'me.champeau.jmh' version '0.7.2' - id 'org.javamodularity.moduleplugin' version '1.8.14' + id 'org.javamodularity.moduleplugin' version '1.8.15' id 'org.openjfx.javafxplugin' version '0.1.0' From dbc7093be0da914eb70996ca3e904863506abf64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:42:01 +0000 Subject: [PATCH 07/26] Bump org.apache.logging.log4j:log4j-to-slf4j from 2.23.0 to 2.23.1 (#11003) Bumps org.apache.logging.log4j:log4j-to-slf4j from 2.23.0 to 2.23.1. --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-to-slf4j dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dd278438ee5..52e84a8a15a 100644 --- a/build.gradle +++ b/build.gradle @@ -196,7 +196,7 @@ dependencies { // route all requests to java.util.logging to SLF4J (which in turn routes to tinylog) implementation 'org.slf4j:jul-to-slf4j:2.0.9' // route all requests to log4j to SLF4J - implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.23.0' + implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.23.1' implementation('de.undercouch:citeproc-java:3.0.0-beta.2') { exclude group: 'org.antlr' From 1b62c94cc67e2cfe915c23444850641a9d4e3893 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:47:23 +0000 Subject: [PATCH 08/26] Bump org.glassfish.hk2:hk2-api from 3.0.6 to 3.1.0 (#11006) Bumps [org.glassfish.hk2:hk2-api](https://github.com/eclipse-ee4j/glassfish-hk2) from 3.0.6 to 3.1.0. - [Release notes](https://github.com/eclipse-ee4j/glassfish-hk2/releases) - [Changelog](https://github.com/eclipse-ee4j/glassfish-hk2/blob/master/CHANGELOG) - [Commits](https://github.com/eclipse-ee4j/glassfish-hk2/commits) --- updated-dependencies: - dependency-name: org.glassfish.hk2:hk2-api dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 52e84a8a15a..92005c0f9f5 100644 --- a/build.gradle +++ b/build.gradle @@ -228,7 +228,7 @@ dependencies { implementation 'org.glassfish.jersey.core:jersey-server:3.1.5' // injection framework implementation 'org.glassfish.jersey.inject:jersey-hk2:3.1.5' - implementation 'org.glassfish.hk2:hk2-api:3.0.6' + implementation 'org.glassfish.hk2:hk2-api:3.1.0' // testImplementation 'org.glassfish.hk2:hk2-testing:3.0.4' // implementation 'org.glassfish.hk2:hk2-testing-jersey:3.0.4' // testImplementation 'org.glassfish.hk2:hk2-junitrunner:3.0.4' From be34c97b46e5b9bd56f8a02f9998d0c3644fb42b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:49:21 +0000 Subject: [PATCH 09/26] Bump io.github.classgraph:classgraph from 4.8.165 to 4.8.168 (#11005) Bumps [io.github.classgraph:classgraph](https://github.com/classgraph/classgraph) from 4.8.165 to 4.8.168. - [Release notes](https://github.com/classgraph/classgraph/releases) - [Commits](https://github.com/classgraph/classgraph/compare/classgraph-4.8.165...classgraph-4.8.168) --- updated-dependencies: - dependency-name: io.github.classgraph:classgraph dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 92005c0f9f5..a2e998897ae 100644 --- a/build.gradle +++ b/build.gradle @@ -242,7 +242,7 @@ dependencies { // Because of GraalVM quirks, we need to ship that. See https://github.com/jspecify/jspecify/issues/389#issuecomment-1661130973 for details implementation 'org.jspecify:jspecify:0.3.0' - testImplementation 'io.github.classgraph:classgraph:4.8.165' + testImplementation 'io.github.classgraph:classgraph:4.8.168' testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' testImplementation 'org.junit.platform:junit-platform-launcher:1.10.2' From 67748209b2b90dc96f8df4df9135b515a671c381 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Mon, 11 Mar 2024 22:15:17 +0100 Subject: [PATCH 10/26] Use SequencedSet for required and optional fields (#11007) --- .../gui/entryeditor/RequiredFieldsTab.java | 2 +- .../CustomEntryTypePreferenceMigration.java | 9 ++--- .../org/jabref/model/entry/BibEntryType.java | 21 ++++++------ .../model/entry/BibEntryTypeBuilder.java | 34 +++++++++++-------- .../jabref/model/entry/EntryConverter.java | 2 +- .../model/entry/field/FieldFactory.java | 5 +-- .../jabref/model/entry/field/OrFields.java | 11 ++++-- .../types/BibtexEntryTypeDefinitions.java | 4 ++- 8 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java index 9e7d03eae64..a4939e66d79 100644 --- a/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/RequiredFieldsTab.java @@ -58,7 +58,7 @@ protected SequencedSet determineFieldsToShow(BibEntry entry) { for (OrFields orFields : entryType.get().getRequiredFields()) { fields.addAll(orFields.getFields()); } - // Add the edit field for Bibtex-key. + // Add the edit field for BibTeX key (AKA citation key) fields.add(InternalField.KEY_FIELD); } else { // Entry type unknown -> treat all fields as required diff --git a/src/main/java/org/jabref/migrations/CustomEntryTypePreferenceMigration.java b/src/main/java/org/jabref/migrations/CustomEntryTypePreferenceMigration.java index e2973ea207a..59db3eb61dd 100644 --- a/src/main/java/org/jabref/migrations/CustomEntryTypePreferenceMigration.java +++ b/src/main/java/org/jabref/migrations/CustomEntryTypePreferenceMigration.java @@ -1,6 +1,7 @@ package org.jabref.migrations; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -57,18 +58,18 @@ private static Optional getBibEntryType(int number, JabRefPreferen BibEntryTypeBuilder entryTypeBuilder = new BibEntryTypeBuilder() .withType(EntryTypeFactory.parse(name)) - .withRequiredFields(req.stream().map(FieldFactory::parseOrFields).collect(Collectors.toList())); + .withRequiredFields(req.stream().map(FieldFactory::parseOrFields).collect(Collectors.toCollection(LinkedHashSet::new))); if (priOpt.isEmpty()) { entryTypeBuilder = entryTypeBuilder - .withImportantFields(opt.stream().map(FieldFactory::parseField).collect(Collectors.toSet())); + .withImportantFields(opt.stream().map(FieldFactory::parseField).collect(Collectors.toCollection(LinkedHashSet::new))); return Optional.of(entryTypeBuilder.build()); } else { List secondary = new ArrayList<>(opt); secondary.removeAll(priOpt); entryTypeBuilder = entryTypeBuilder - .withImportantFields(priOpt.stream().map(FieldFactory::parseField).collect(Collectors.toSet())) - .withDetailFields(secondary.stream().map(FieldFactory::parseField).collect(Collectors.toSet())); + .withImportantFields(priOpt.stream().map(FieldFactory::parseField).collect(Collectors.toCollection(LinkedHashSet::new))) + .withDetailFields(secondary.stream().map(FieldFactory::parseField).collect(Collectors.toCollection(LinkedHashSet::new))); return Optional.of(entryTypeBuilder.build()); } } diff --git a/src/main/java/org/jabref/model/entry/BibEntryType.java b/src/main/java/org/jabref/model/entry/BibEntryType.java index b58a9e89098..b804d8255e7 100644 --- a/src/main/java/org/jabref/model/entry/BibEntryType.java +++ b/src/main/java/org/jabref/model/entry/BibEntryType.java @@ -2,6 +2,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Objects; import java.util.SequencedSet; @@ -21,8 +22,8 @@ public class BibEntryType implements Comparable { private final EntryType type; - private final LinkedHashSet requiredFields; - private final LinkedHashSet fields; + private final SequencedSet requiredFields; + private final SequencedSet fields; /** * Provides an enriched EntryType with information about defined standards as mandatory fields etc. @@ -43,7 +44,7 @@ public EntryType getType() { return type; } - public Set getOptionalFields() { + public SequencedSet getOptionalFields() { return getAllBibFields().stream() .filter(field -> !isRequired(field.field())) .collect(Collectors.toCollection(LinkedHashSet::new)); @@ -61,15 +62,15 @@ public boolean isRequired(Field field) { * * @return a Set of required field name Strings */ - public Set getRequiredFields() { - return Collections.unmodifiableSet(requiredFields); + public SequencedSet getRequiredFields() { + return Collections.unmodifiableSequencedSet(requiredFields); } /** * Returns all defined fields. */ - public Set getAllBibFields() { - return Collections.unmodifiableSet(fields); + public SequencedSet getAllBibFields() { + return Collections.unmodifiableSequencedSet(fields); } public Set getAllFields() { @@ -90,11 +91,11 @@ public SequencedSet getSecondaryOptionalFields() { .collect(Collectors.toCollection(LinkedHashSet::new)); } - public SequencedSet getDeprecatedFields(BibDatabaseMode mode) { + public Set getDeprecatedFields(BibDatabaseMode mode) { if (mode == BibDatabaseMode.BIBTEX) { - return new LinkedHashSet<>(); + return Set.of(); } - SequencedSet deprecatedFields = new LinkedHashSet<>(EntryConverter.FIELD_ALIASES_BIBTEX_TO_BIBLATEX.keySet()); + Set deprecatedFields = new HashSet<>(EntryConverter.FIELD_ALIASES_BIBTEX_TO_BIBLATEX.keySet()); // Only the optional fields which are mapped to another BibLaTeX name should be shown as "deprecated" deprecatedFields.retainAll(getOptionalFieldsAndAliases()); diff --git a/src/main/java/org/jabref/model/entry/BibEntryTypeBuilder.java b/src/main/java/org/jabref/model/entry/BibEntryTypeBuilder.java index b8613157ac6..91656a6c50d 100644 --- a/src/main/java/org/jabref/model/entry/BibEntryTypeBuilder.java +++ b/src/main/java/org/jabref/model/entry/BibEntryTypeBuilder.java @@ -1,9 +1,9 @@ package org.jabref.model.entry; import java.util.Arrays; -import java.util.Collection; import java.util.LinkedHashSet; -import java.util.List; +import java.util.SequencedCollection; +import java.util.SequencedSet; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -20,29 +20,25 @@ public class BibEntryTypeBuilder { private EntryType type = StandardEntryType.Misc; - private Set fields = new LinkedHashSet<>(); - private Set requiredFields = new LinkedHashSet<>(); + private SequencedSet fields = new LinkedHashSet<>(); + private SequencedSet requiredFields = new LinkedHashSet<>(); public BibEntryTypeBuilder withType(EntryType type) { this.type = type; return this; } - public BibEntryTypeBuilder withImportantFields(Set newFields) { - return withImportantFields(newFields.stream().map(BibField::field).collect(Collectors.toCollection(LinkedHashSet::new))); - } - - public BibEntryTypeBuilder withImportantFields(Collection newFields) { + public BibEntryTypeBuilder withImportantFields(SequencedSet newFields) { this.fields = Streams.concat(fields.stream(), newFields.stream().map(field -> new BibField(field, FieldPriority.IMPORTANT))) .collect(Collectors.toCollection(LinkedHashSet::new)); return this; } public BibEntryTypeBuilder withImportantFields(Field... newFields) { - return withImportantFields(Arrays.asList(newFields)); + return withImportantFields(Arrays.stream(newFields).collect(Collectors.toCollection(LinkedHashSet::new))); } - public BibEntryTypeBuilder withDetailFields(Collection newFields) { + public BibEntryTypeBuilder withDetailFields(SequencedCollection newFields) { this.fields = Streams.concat(fields.stream(), newFields.stream().map(field -> new BibField(field, FieldPriority.DETAIL))) .collect(Collectors.toCollection(LinkedHashSet::new)); return this; @@ -52,11 +48,21 @@ public BibEntryTypeBuilder withDetailFields(Field... fields) { return withDetailFields(Arrays.asList(fields)); } - public BibEntryTypeBuilder withRequiredFields(Set requiredFields) { + public BibEntryTypeBuilder withRequiredFields(SequencedSet requiredFields) { this.requiredFields = requiredFields; return this; } + public BibEntryTypeBuilder addRequiredFields(OrFields... requiredFields) { + this.requiredFields.addAll(Arrays.asList(requiredFields)); + return this; + } + + public BibEntryTypeBuilder addRequiredFields(Field... requiredFields) { + this.requiredFields.addAll(Arrays.stream(requiredFields).map(OrFields::new).toList()); + return this; + } + public BibEntryTypeBuilder withRequiredFields(Field... requiredFields) { this.requiredFields = Arrays.stream(requiredFields).map(OrFields::new).collect(Collectors.toCollection(LinkedHashSet::new)); return this; @@ -67,7 +73,7 @@ public BibEntryTypeBuilder withRequiredFields(OrFields first, Field... requiredF return this; } - public BibEntryTypeBuilder withRequiredFields(List first, Field... requiredFields) { + public BibEntryTypeBuilder withRequiredFields(SequencedSet first, Field... requiredFields) { this.requiredFields = Stream.concat(first.stream(), Arrays.stream(requiredFields).map(OrFields::new)).collect(Collectors.toCollection(LinkedHashSet::new)); return this; } @@ -78,7 +84,7 @@ public BibEntryType build() { .map(OrFields::getFields) .flatMap(Set::stream) .map(field -> new BibField(field, FieldPriority.IMPORTANT)); - Set allFields = Stream.concat(fields.stream(), requiredAsImportant).collect(Collectors.toCollection(LinkedHashSet::new)); + SequencedSet allFields = Stream.concat(fields.stream(), requiredAsImportant).collect(Collectors.toCollection(LinkedHashSet::new)); return new BibEntryType(type, allFields, requiredFields); } } diff --git a/src/main/java/org/jabref/model/entry/EntryConverter.java b/src/main/java/org/jabref/model/entry/EntryConverter.java index 0a6f6621dd9..162afb219d1 100644 --- a/src/main/java/org/jabref/model/entry/EntryConverter.java +++ b/src/main/java/org/jabref/model/entry/EntryConverter.java @@ -8,7 +8,7 @@ import org.jabref.model.entry.field.StandardField; /** - * Converts Entry models from BibTex to biblatex and back. + * Converts Entry models from BibTeX to biblatex and back. */ public class EntryConverter { diff --git a/src/main/java/org/jabref/model/entry/field/FieldFactory.java b/src/main/java/org/jabref/model/entry/field/FieldFactory.java index 645a1295221..5c0a9036d2b 100644 --- a/src/main/java/org/jabref/model/entry/field/FieldFactory.java +++ b/src/main/java/org/jabref/model/entry/field/FieldFactory.java @@ -8,6 +8,7 @@ import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.SequencedSet; import java.util.Set; import java.util.TreeSet; import java.util.function.Predicate; @@ -64,14 +65,14 @@ public static OrFields parseOrFields(String fieldNames) { return new OrFields(fields); } - public static Set parseOrFieldsList(String fieldNames) { + public static SequencedSet parseOrFieldsList(String fieldNames) { return Arrays.stream(fieldNames.split(FieldFactory.DELIMITER)) .filter(StringUtil::isNotBlank) .map(FieldFactory::parseOrFields) .collect(Collectors.toCollection(LinkedHashSet::new)); } - public static Set parseFieldList(String fieldNames) { + public static SequencedSet parseFieldList(String fieldNames) { return Arrays.stream(fieldNames.split(FieldFactory.DELIMITER)) .filter(StringUtil::isNotBlank) .map(FieldFactory::parseField) diff --git a/src/main/java/org/jabref/model/entry/field/OrFields.java b/src/main/java/org/jabref/model/entry/field/OrFields.java index 253a7257cfb..5a49202d718 100644 --- a/src/main/java/org/jabref/model/entry/field/OrFields.java +++ b/src/main/java/org/jabref/model/entry/field/OrFields.java @@ -4,12 +4,17 @@ import java.util.Collection; import java.util.LinkedHashSet; import java.util.Objects; -import java.util.Set; +import java.util.SequencedSet; import java.util.StringJoiner; +/** + * Represents a choice between two (or more) fields or any combination of them. + *

+ * Example is that a BibEntry requires either an author or an editor, but both can be be present. + */ public class OrFields implements Comparable { - private LinkedHashSet fields = new LinkedHashSet<>(); + private SequencedSet fields = new LinkedHashSet<>(); public OrFields(Field field) { fields.add(field); @@ -35,7 +40,7 @@ public Field getPrimary() { return fields.getFirst(); } - public Set getFields() { + public SequencedSet getFields() { return this.fields; } diff --git a/src/main/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitions.java b/src/main/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitions.java index 594301642b7..d2109ebc8d4 100644 --- a/src/main/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitions.java +++ b/src/main/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitions.java @@ -69,7 +69,9 @@ public class BibtexEntryTypeDefinitions { */ private static final BibEntryType INBOOK = new BibEntryTypeBuilder() .withType(StandardEntryType.InBook) - .withRequiredFields(Arrays.asList(new OrFields(StandardField.CHAPTER, StandardField.PAGES), new OrFields(StandardField.AUTHOR, StandardField.EDITOR)), StandardField.TITLE, StandardField.PUBLISHER, StandardField.YEAR) + .addRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR)) + .addRequiredFields(StandardField.TITLE, StandardField.PUBLISHER, StandardField.YEAR) + .addRequiredFields(new OrFields(StandardField.CHAPTER, StandardField.PAGES)) .withImportantFields(StandardField.VOLUME, StandardField.NUMBER, StandardField.SERIES, StandardField.TYPE, StandardField.ADDRESS, StandardField.EDITION, StandardField.MONTH, StandardField.ISBN, StandardField.NOTE) .build(); From d7f7b67dee86be1a8b30d4681910883d4f4481ab Mon Sep 17 00:00:00 2001 From: Loay Ghreeb <52158423+LoayGhreeb@users.noreply.github.com> Date: Tue, 12 Mar 2024 00:19:20 +0200 Subject: [PATCH 11/26] fix suggestion provider for crossref field (#10962) * fix suggestion provider for crossref * Remove duplicates from the suggestions list * delete space * Simplify casts Co-authored-by: Oliver Kopp Co-authored-by: Christoph --------- Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Co-authored-by: Oliver Kopp Co-authored-by: Christoph --- src/main/java/org/jabref/gui/LibraryTab.java | 4 +-- .../ContentSelectorSuggestionProvider.java | 3 +-- .../gui/autocompleter/SuggestionProvider.java | 3 ++- .../autocompleter/SuggestionProviders.java | 9 ------- .../jabref/gui/fieldeditors/FieldEditors.java | 5 ++-- .../gui/fieldeditors/LinkedEntriesEditor.java | 12 +++------ .../LinkedEntriesEditorViewModel.java | 26 +++++++++++++++++++ 7 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 8b9488b1559..c8a5b809d99 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -597,8 +597,8 @@ private void setupAutoCompletion() { if (autoCompletePreferences.shouldAutoComplete()) { suggestionProviders = new SuggestionProviders(getDatabase(), Globals.journalAbbreviationRepository, autoCompletePreferences); } else { - // Create suggestion providers with database for crossref if auto-completion is deactivated - suggestionProviders = new SuggestionProviders(getDatabase()); + // Create empty suggestion providers if auto-completion is deactivated + suggestionProviders = new SuggestionProviders(); } searchAutoCompleter = new PersonNameSuggestionProvider(FieldFactory.getPersonNameFields(), getDatabase()); } diff --git a/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java index 01e56ff5350..ebb6ec04837 100644 --- a/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/ContentSelectorSuggestionProvider.java @@ -1,7 +1,6 @@ package org.jabref.gui.autocompleter; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.stream.Stream; @@ -26,7 +25,7 @@ public Stream getSource() { } @Override - public Collection getPossibleSuggestions() { + public List getPossibleSuggestions() { List suggestions = new ArrayList<>(); if (suggestionProvider != null) { suggestions.addAll(suggestionProvider.getPossibleSuggestions()); diff --git a/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java b/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java index 341c9ef8f5e..fb64bb372b7 100644 --- a/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java +++ b/src/main/java/org/jabref/gui/autocompleter/SuggestionProvider.java @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -60,7 +61,7 @@ public final Collection provideSuggestions(ISuggestionRequest request) { protected abstract Equivalence getEquivalence(); - public Collection getPossibleSuggestions() { + public List getPossibleSuggestions() { Comparator comparator = getComparator().reversed(); Equivalence equivalence = getEquivalence(); return getSource().map(equivalence::wrap) // Need to do a bit of acrobatic as there is no distinctBy method diff --git a/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java b/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java index 7d023f4d815..06d2abef8a8 100644 --- a/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java +++ b/src/main/java/org/jabref/gui/autocompleter/SuggestionProviders.java @@ -22,21 +22,12 @@ public SuggestionProviders(BibDatabase database, JournalAbbreviationRepository a this.isEmpty = false; } - public SuggestionProviders(BibDatabase database) { - this.database = database; - this.isEmpty = true; - } - public SuggestionProviders() { this.isEmpty = true; } public SuggestionProvider getForField(Field field) { if (isEmpty || !autoCompletePreferences.getCompleteFields().contains(field)) { - Set fieldProperties = field.getProperties(); - if (fieldProperties.contains(FieldProperty.SINGLE_ENTRY_LINK)) { - return new BibEntrySuggestionProvider(database); - } return new EmptySuggestionProvider(); } diff --git a/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java b/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java index ec921b2f523..e680a7dc4d1 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java +++ b/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java @@ -15,7 +15,6 @@ import org.jabref.logic.integrity.FieldCheckers; import org.jabref.logic.journals.JournalAbbreviationRepository; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.FieldProperty; @@ -90,9 +89,9 @@ public static FieldEditorFX getForField(final Field field, return new OptionEditor<>(new TypeEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); } } else if (fieldProperties.contains(FieldProperty.SINGLE_ENTRY_LINK)) { - return new LinkedEntriesEditor(field, databaseContext, (SuggestionProvider) suggestionProvider, fieldCheckers); + return new LinkedEntriesEditor(field, databaseContext, suggestionProvider, fieldCheckers); } else if (fieldProperties.contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { - return new LinkedEntriesEditor(field, databaseContext, (SuggestionProvider) suggestionProvider, fieldCheckers); + return new LinkedEntriesEditor(field, databaseContext, suggestionProvider, fieldCheckers); } else if (fieldProperties.contains(FieldProperty.PERSON_NAMES)) { return new PersonsEditor(field, suggestionProvider, preferences, fieldCheckers, isMultiLine, undoManager); } else if (StandardField.KEYWORDS == field) { diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java index 3c530084dd3..c13125727cc 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java @@ -1,7 +1,6 @@ package org.jabref.gui.fieldeditors; import java.util.Comparator; -import java.util.stream.Collectors; import javax.swing.undo.UndoManager; @@ -51,7 +50,7 @@ public class LinkedEntriesEditor extends HBox implements FieldEditorFX { private final LinkedEntriesEditorViewModel viewModel; - public LinkedEntriesEditor(Field field, BibDatabaseContext databaseContext, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { + public LinkedEntriesEditor(Field field, BibDatabaseContext databaseContext, SuggestionProvider suggestionProvider, FieldCheckers fieldCheckers) { ViewLoader.view(this) .root(this) .load(); @@ -59,13 +58,8 @@ public LinkedEntriesEditor(Field field, BibDatabaseContext databaseContext, Sugg this.viewModel = new LinkedEntriesEditorViewModel(field, suggestionProvider, databaseContext, fieldCheckers, undoManager); entryLinkField.setCellFactory(new ViewModelListCellFactory().withText(ParsedEntryLink::getKey)); - // Mind the .collect(Collectors.toList()) as the list needs to be mutable - entryLinkField.setSuggestionProvider(request -> - suggestionProvider.getPossibleSuggestions().stream() - .filter(suggestion -> suggestion.getCitationKey().orElse("").toLowerCase() - .contains(request.getUserText().toLowerCase())) - .map(ParsedEntryLink::new) - .collect(Collectors.toList())); + entryLinkField.setSuggestionProvider(request -> viewModel.getSuggestions(request.getUserText())); + entryLinkField.setTagViewFactory(this::createTag); entryLinkField.setConverter(viewModel.getStringConverter()); entryLinkField.setNewItemProducer(searchText -> viewModel.getStringConverter().fromString(searchText)); diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java index 28048defa97..2f0b051e78a 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java @@ -1,5 +1,9 @@ package org.jabref.gui.fieldeditors; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + import javax.swing.undo.UndoManager; import javafx.beans.property.ListProperty; @@ -11,6 +15,7 @@ import org.jabref.gui.util.BindingsHelper; import org.jabref.logic.integrity.FieldCheckers; import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.EntryLinkList; import org.jabref.model.entry.ParsedEntryLink; import org.jabref.model.entry.field.Field; @@ -18,12 +23,15 @@ public class LinkedEntriesEditorViewModel extends AbstractEditorViewModel { private final BibDatabaseContext databaseContext; + private final SuggestionProvider suggestionProvider; private final ListProperty linkedEntries; public LinkedEntriesEditorViewModel(Field field, SuggestionProvider suggestionProvider, BibDatabaseContext databaseContext, FieldCheckers fieldCheckers, UndoManager undoManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.databaseContext = databaseContext; + this.suggestionProvider = suggestionProvider; + linkedEntries = new SimpleListProperty<>(FXCollections.observableArrayList()); BindingsHelper.bindContentBidirectional( linkedEntries, @@ -53,6 +61,24 @@ public ParsedEntryLink fromString(String key) { }; } + public List getSuggestions(String request) { + List suggestions = suggestionProvider + .getPossibleSuggestions() + .stream() + .map(suggestion -> suggestion instanceof BibEntry bibEntry ? bibEntry.getCitationKey().orElse("") : (String) suggestion) + .filter(suggestion -> suggestion.toLowerCase(Locale.ROOT).contains(request.toLowerCase(Locale.ROOT))) + .map(suggestion -> new ParsedEntryLink(suggestion, databaseContext.getDatabase())) + .distinct() + .collect(Collectors.toList()); + + ParsedEntryLink requestedLink = new ParsedEntryLink(request, databaseContext.getDatabase()); + if (!suggestions.contains(requestedLink)) { + suggestions.addFirst(requestedLink); + } + + return suggestions; + } + public void jumpToEntry(ParsedEntryLink parsedEntryLink) { // TODO: Implement jump to entry - The implementation can be based on JabRefFrame.jumpToEntry // TODO: Add toolitp for tag: Localization.lang("Jump to entry") From a7a69db459c06d394ca6c3735a8e1ecd642b182d Mon Sep 17 00:00:00 2001 From: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> Date: Tue, 12 Mar 2024 00:23:45 +0100 Subject: [PATCH 12/26] Fixed jump to entry from crossref (#11009) * Simplify casts Co-authored-by: Oliver Kopp * CHANGELOG.md Co-authored-by: Oliver Kopp * Add control-click Co-authored-by: Oliver Kopp * Fix l10n Co-authored-by: Oliver Kopp --------- Co-authored-by: Oliver Kopp --- CHANGELOG.md | 1 + src/main/java/org/jabref/gui/JabRefFrame.java | 2 ++ src/main/java/org/jabref/gui/LibraryTab.java | 3 ++- .../java/org/jabref/gui/StateManager.java | 5 +++++ .../jabref/gui/fieldeditors/FieldEditors.java | 4 +--- .../gui/fieldeditors/LinkedEntriesEditor.java | 6 +++-- .../LinkedEntriesEditorViewModel.java | 22 ++++++++++--------- src/main/resources/l10n/JabRef_en.properties | 1 - 8 files changed, 27 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41a596a3abb..12b8f3c24a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed an issue where the `CommentsTab` was not properly formatted when the `defaultOwner` contained capital or special letters. [#10870](https://github.com/JabRef/jabref/issues/10870) - We fixed an issue where the `File -> Close library` menu item was not disabled when no library was open. [#10948](https://github.com/JabRef/jabref/issues/10948) - We fixed an issue where the Document Viewer would show the PDF in only half the window when maximized. [#10934](https://github.com/JabRef/jabref/issues/10934) +- Clicking on the crossref and related tags in the entry editor jumps to the linked entry. [#5484](https://github.com/JabRef/jabref/issues/5484) [#9369](https://github.com/JabRef/jabref/issues/9369) ### Removed diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 1265cac4d31..6f66180350b 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -574,9 +574,11 @@ public void init() { EasyBind.subscribe(tabbedPane.getSelectionModel().selectedItemProperty(), selectedTab -> { if (selectedTab instanceof LibraryTab libraryTab) { stateManager.setActiveDatabase(libraryTab.getBibDatabaseContext()); + stateManager.activeTabProperty().set(Optional.of(libraryTab)); } else if (selectedTab == null) { // All databases are closed stateManager.setActiveDatabase(null); + stateManager.activeTabProperty().set(Optional.empty()); } }); diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index c8a5b809d99..cec2adb1644 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -282,8 +282,9 @@ private void setDatabaseContext(BibDatabaseContext bibDatabaseContext) { return; } if (tabPane.getSelectionModel().selectedItemProperty().get().equals(this)) { - LOGGER.debug("This case should not happen."); + LOGGER.warn("This case should not happen."); stateManager.setActiveDatabase(bibDatabaseContext); + stateManager.activeTabProperty().set(Optional.of(this)); } // Remove existing dummy BibDatabaseContext and add correct BibDatabaseContext from ParserResult to trigger changes in the openDatabases list in the stateManager diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index 743ad0ef473..bdc1659ac75 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -54,6 +54,7 @@ public class StateManager { private final CustomLocalDragboard localDragboard = new CustomLocalDragboard(); private final ObservableList openDatabases = FXCollections.observableArrayList(); private final OptionalObjectProperty activeDatabase = OptionalObjectProperty.empty(); + private final OptionalObjectProperty activeTab = OptionalObjectProperty.empty(); private final ReadOnlyListWrapper activeGroups = new ReadOnlyListWrapper<>(FXCollections.observableArrayList()); private final ObservableList selectedEntries = FXCollections.observableArrayList(); private final ObservableMap> selectedGroups = FXCollections.observableHashMap(); @@ -91,6 +92,10 @@ public OptionalObjectProperty activeDatabaseProperty() { return activeDatabase; } + public OptionalObjectProperty activeTabProperty() { + return activeTab; + } + public OptionalObjectProperty activeSearchQueryProperty() { return activeSearchQuery; } diff --git a/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java b/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java index e680a7dc4d1..bbdefc05f1c 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java +++ b/src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java @@ -88,9 +88,7 @@ public static FieldEditorFX getForField(final Field field, } else { return new OptionEditor<>(new TypeEditorViewModel(field, suggestionProvider, fieldCheckers, undoManager)); } - } else if (fieldProperties.contains(FieldProperty.SINGLE_ENTRY_LINK)) { - return new LinkedEntriesEditor(field, databaseContext, suggestionProvider, fieldCheckers); - } else if (fieldProperties.contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { + } else if (fieldProperties.contains(FieldProperty.SINGLE_ENTRY_LINK) || fieldProperties.contains(FieldProperty.MULTIPLE_ENTRY_LINK)) { return new LinkedEntriesEditor(field, databaseContext, suggestionProvider, fieldCheckers); } else if (fieldProperties.contains(FieldProperty.PERSON_NAMES)) { return new PersonsEditor(field, suggestionProvider, preferences, fieldCheckers, isMultiLine, undoManager); diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java index c13125727cc..0ac0a2e064d 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditor.java @@ -17,6 +17,7 @@ import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.JabRefDialogService; +import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; @@ -47,6 +48,7 @@ public class LinkedEntriesEditor extends HBox implements FieldEditorFX { @Inject private ClipBoardManager clipBoardManager; @Inject private KeyBindingRepository keyBindingRepository; @Inject private UndoManager undoManager; + @Inject private StateManager stateManager; private final LinkedEntriesEditorViewModel viewModel; @@ -55,7 +57,7 @@ public LinkedEntriesEditor(Field field, BibDatabaseContext databaseContext, Sugg .root(this) .load(); - this.viewModel = new LinkedEntriesEditorViewModel(field, suggestionProvider, databaseContext, fieldCheckers, undoManager); + this.viewModel = new LinkedEntriesEditorViewModel(field, suggestionProvider, databaseContext, fieldCheckers, undoManager, stateManager); entryLinkField.setCellFactory(new ViewModelListCellFactory().withText(ParsedEntryLink::getKey)); entryLinkField.setSuggestionProvider(request -> viewModel.getSuggestions(request.getUserText())); @@ -79,7 +81,7 @@ private Node createTag(ParsedEntryLink entryLink) { tagLabel.getGraphic().setOnMouseClicked(event -> entryLinkField.removeTags(entryLink)); tagLabel.setContentDisplay(ContentDisplay.RIGHT); tagLabel.setOnMouseClicked(event -> { - if (event.getClickCount() == 2 && event.getButton().equals(MouseButton.PRIMARY)) { + if ((event.getClickCount() == 2 || event.isControlDown()) && event.getButton().equals(MouseButton.PRIMARY)) { viewModel.jumpToEntry(entryLink); } }); diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java index 2f0b051e78a..a61599e9a30 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedEntriesEditorViewModel.java @@ -11,6 +11,7 @@ import javafx.collections.FXCollections; import javafx.util.StringConverter; +import org.jabref.gui.StateManager; import org.jabref.gui.autocompleter.SuggestionProvider; import org.jabref.gui.util.BindingsHelper; import org.jabref.logic.integrity.FieldCheckers; @@ -25,12 +26,19 @@ public class LinkedEntriesEditorViewModel extends AbstractEditorViewModel { private final BibDatabaseContext databaseContext; private final SuggestionProvider suggestionProvider; private final ListProperty linkedEntries; - - public LinkedEntriesEditorViewModel(Field field, SuggestionProvider suggestionProvider, BibDatabaseContext databaseContext, FieldCheckers fieldCheckers, UndoManager undoManager) { + private final StateManager stateManager; + + public LinkedEntriesEditorViewModel(Field field, + SuggestionProvider suggestionProvider, + BibDatabaseContext databaseContext, + FieldCheckers fieldCheckers, + UndoManager undoManager, + StateManager stateManager) { super(field, suggestionProvider, fieldCheckers, undoManager); this.databaseContext = databaseContext; this.suggestionProvider = suggestionProvider; + this.stateManager = stateManager; linkedEntries = new SimpleListProperty<>(FXCollections.observableArrayList()); BindingsHelper.bindContentBidirectional( @@ -80,13 +88,7 @@ public List getSuggestions(String request) { } public void jumpToEntry(ParsedEntryLink parsedEntryLink) { - // TODO: Implement jump to entry - The implementation can be based on JabRefFrame.jumpToEntry - // TODO: Add toolitp for tag: Localization.lang("Jump to entry") - // This feature was removed while converting the linked entries editor to JavaFX - // Right now there is no nice way to re-implement it as we have no good interface to control the focus of the main table - // (except directly using the JabRefFrame class as below) - // parsedEntryLink.getLinkedEntry().ifPresent( - // e -> frame.getCurrentBasePanel().highlightEntry(e) - // ); + parsedEntryLink.getLinkedEntry().ifPresent(entry -> + stateManager.activeTabProperty().get().ifPresent(tab -> tab.clearAndSelect(entry))); } } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 26eb731b57d..2b05bedc01a 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1926,7 +1926,6 @@ Group\ view\ mode\ set\ to\ union=Group view mode set to union Open\ file\ %0=Open file %0 Toggle\ intersection=Toggle intersection Toggle\ union=Toggle union -Jump\ to\ entry=Jump to entry The\ group\ name\ contains\ the\ keyword\ separator\ "%0"\ and\ thus\ probably\ does\ not\ work\ as\ expected.=The group name contains the keyword separator "%0" and thus probably does not work as expected. Blog=Blog From 4c64706b6ab0404b518cd1488f15cf03239c115a Mon Sep 17 00:00:00 2001 From: Nitin Suresh Date: Mon, 11 Mar 2024 16:55:28 -0700 Subject: [PATCH 13/26] [WIP] Extract PDF References (#10437) * add grobid reference processing * add references processing action * add grobid enabled check * Add test document for reference parsing * shorten PDFs * Add test library * Add CHANGELOG.md entry --------- Co-authored-by: Oliver Kopp --- CHANGELOG.md | 1 + .../jabref/gui/actions/StandardActions.java | 1 + .../java/org/jabref/gui/icon/IconTheme.java | 1 + .../maintable/ExtractReferencesAction.java | 101 ++++++ .../jabref/gui/maintable/RightClickMenu.java | 1 + .../logic/importer/util/GrobidService.java | 29 ++ src/main/resources/l10n/JabRef_en.properties | 6 + .../importer/util/GrobidServiceTest.java | 19 ++ src/test/resources/pdfs/IEEE/.gitignore | 314 ++++++++++++++++++ .../pdfs/IEEE/extract-references-test.bib | 8 + src/test/resources/pdfs/IEEE/ieee-paper.bib | 51 +++ src/test/resources/pdfs/IEEE/ieee-paper.pdf | Bin 0 -> 73470 bytes src/test/resources/pdfs/IEEE/ieee-paper.tex | 39 +++ 13 files changed, 571 insertions(+) create mode 100644 src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java create mode 100644 src/test/resources/pdfs/IEEE/.gitignore create mode 100644 src/test/resources/pdfs/IEEE/extract-references-test.bib create mode 100644 src/test/resources/pdfs/IEEE/ieee-paper.bib create mode 100644 src/test/resources/pdfs/IEEE/ieee-paper.pdf create mode 100644 src/test/resources/pdfs/IEEE/ieee-paper.tex diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b8f3c24a9..ea79b7a3dc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added ability to push entries to TeXworks. [#3197](https://github.com/JabRef/jabref/issues/3197) - We added the ability to zoom in and out in the document viewer using Ctrl + Scroll. [#10964](https://github.com/JabRef/jabref/pull/10964) - We added a Cleanup for removing non-existent files and grouped the related options [#10929](https://github.com/JabRef/jabref/issues/10929) +- We added the functionality to parse the bibliography of PDFs using the GROBID online service. [#10200](https://github.com/JabRef/jabref/issues/10200) ### Changed diff --git a/src/main/java/org/jabref/gui/actions/StandardActions.java b/src/main/java/org/jabref/gui/actions/StandardActions.java index 6f74e2caa1f..949dac28b29 100644 --- a/src/main/java/org/jabref/gui/actions/StandardActions.java +++ b/src/main/java/org/jabref/gui/actions/StandardActions.java @@ -32,6 +32,7 @@ public enum StandardActions implements Action { REBUILD_FULLTEXT_SEARCH_INDEX(Localization.lang("Rebuild fulltext search index"), IconTheme.JabRefIcons.FILE), REDOWNLOAD_MISSING_FILES(Localization.lang("Redownload missing files"), IconTheme.JabRefIcons.DOWNLOAD), OPEN_EXTERNAL_FILE(Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE), + EXTRACT_FILE_REFERENCES(Localization.lang("Extract references from file"), IconTheme.JabRefIcons.FILE_STAR), OPEN_URL(Localization.lang("Open URL or DOI"), IconTheme.JabRefIcons.WWW, KeyBinding.OPEN_URL_OR_DOI), SEARCH_SHORTSCIENCE(Localization.lang("Search ShortScience")), MERGE_WITH_FETCHED_ENTRY(Localization.lang("Get bibliographic data from %0", "DOI/ISBN/...")), diff --git a/src/main/java/org/jabref/gui/icon/IconTheme.java b/src/main/java/org/jabref/gui/icon/IconTheme.java index 470357981af..cc9bad0528b 100644 --- a/src/main/java/org/jabref/gui/icon/IconTheme.java +++ b/src/main/java/org/jabref/gui/icon/IconTheme.java @@ -193,6 +193,7 @@ public enum JabRefIcons implements JabRefIcon { DELETE_ENTRY(MaterialDesignD.DELETE), SEARCH(MaterialDesignM.MAGNIFY), FILE_SEARCH(MaterialDesignF.FILE_FIND), + FILE_STAR(MaterialDesignF.FILE_STAR), PDF_METADATA_READ(MaterialDesignF.FORMAT_ALIGN_TOP), PDF_METADATA_WRITE(MaterialDesignF.FORMAT_ALIGN_BOTTOM), ADVANCED_SEARCH(Color.CYAN, MaterialDesignM.MAGNIFY), diff --git a/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java new file mode 100644 index 00000000000..d46854c5f55 --- /dev/null +++ b/src/main/java/org/jabref/gui/maintable/ExtractReferencesAction.java @@ -0,0 +1,101 @@ +package org.jabref.gui.maintable; + +import java.nio.file.Path; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Callable; + +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; +import org.jabref.gui.actions.ActionHelper; +import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.importer.ImportEntriesDialog; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.util.GrobidService; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.preferences.PreferencesService; + +public class ExtractReferencesAction extends SimpleCommand { + private final int FILES_LIMIT = 10; + + private final DialogService dialogService; + private final StateManager stateManager; + private final PreferencesService preferencesService; + private final BibEntry entry; + private final LinkedFile linkedFile; + private final TaskExecutor taskExecutor; + + public ExtractReferencesAction(DialogService dialogService, + StateManager stateManager, + PreferencesService preferencesService, + TaskExecutor taskExecutor) { + this(dialogService, stateManager, preferencesService, null, null, taskExecutor); + } + + public ExtractReferencesAction(DialogService dialogService, + StateManager stateManager, + PreferencesService preferencesService, + BibEntry entry, + LinkedFile linkedFile, + TaskExecutor taskExecutor) { + this.dialogService = dialogService; + this.stateManager = stateManager; + this.preferencesService = preferencesService; + this.entry = entry; + this.linkedFile = linkedFile; + this.taskExecutor = taskExecutor; + + if (this.linkedFile == null) { + this.executable.bind( + ActionHelper.needsEntriesSelected(stateManager) + .and(ActionHelper.hasLinkedFileForSelectedEntries(stateManager)) + .and(this.preferencesService.getGrobidPreferences().grobidEnabledProperty()) + ); + } else { + this.setExecutable(true); + } + } + + @Override + public void execute() { + extractReferences(); + } + + private void extractReferences() { + stateManager.getActiveDatabase().ifPresent(databaseContext -> { + List selectedEntries = new LinkedList<>(); + if (entry == null) { + selectedEntries = stateManager.getSelectedEntries(); + } else { + selectedEntries.add(entry); + } + + List fileList = FileUtil.getListOfLinkedFiles(selectedEntries, databaseContext.getFileDirectories(preferencesService.getFilePreferences())); + if (fileList.size() > FILES_LIMIT) { + boolean continueOpening = dialogService.showConfirmationDialogAndWait(Localization.lang("Processing a large number of files"), + Localization.lang("You are about to process %0 files. Continue?", fileList.size()), + Localization.lang("Continue"), Localization.lang("Cancel")); + if (!continueOpening) { + return; + } + } + + Callable parserResultCallable = () -> new ParserResult( + new GrobidService(this.preferencesService.getGrobidPreferences()).processReferences(fileList, preferencesService.getImportFormatPreferences()) + ); + BackgroundTask task = BackgroundTask.wrap(parserResultCallable) + .withInitialMessage(Localization.lang("Processing PDF(s)")); + + task.onFailure(dialogService::showErrorDialogAndWait); + + ImportEntriesDialog dialog = new ImportEntriesDialog(stateManager.getActiveDatabase().get(), task); + dialog.setTitle(Localization.lang("Extract References")); + dialogService.showCustomDialogAndWait(dialog); + }); + } +} diff --git a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index 6e65da6ab13..aa8a5477902 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -75,6 +75,7 @@ public static ContextMenu create(BibEntryTableViewModel entry, factory.createMenuItem(StandardActions.ATTACH_FILE_FROM_URL, new AttachFileFromURLAction(dialogService, stateManager, taskExecutor, preferencesService)), factory.createMenuItem(StandardActions.OPEN_FOLDER, new OpenFolderAction(dialogService, stateManager, preferencesService, taskExecutor)), factory.createMenuItem(StandardActions.OPEN_EXTERNAL_FILE, new OpenExternalFileAction(dialogService, stateManager, preferencesService, taskExecutor)), + factory.createMenuItem(StandardActions.EXTRACT_FILE_REFERENCES, new ExtractReferencesAction(dialogService, stateManager, preferencesService, taskExecutor)), factory.createMenuItem(StandardActions.OPEN_URL, new OpenUrlAction(dialogService, stateManager, preferencesService)), factory.createMenuItem(StandardActions.SEARCH_SHORTSCIENCE, new SearchShortScienceAction(dialogService, stateManager, preferencesService)), diff --git a/src/main/java/org/jabref/logic/importer/util/GrobidService.java b/src/main/java/org/jabref/logic/importer/util/GrobidService.java index 514e43d0513..f3449f68be0 100644 --- a/src/main/java/org/jabref/logic/importer/util/GrobidService.java +++ b/src/main/java/org/jabref/logic/importer/util/GrobidService.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -84,6 +85,34 @@ public List processPDF(Path filePath, ImportFormatPreferences importFo String httpResponse = response.body(); + return getBibEntries(importFormatPreferences, httpResponse); + } + + public List processReferences(List pathList, ImportFormatPreferences importFormatPreferences) throws IOException, ParseException { + List entries = new ArrayList<>(); + for (Path filePath: pathList) { + entries.addAll(processReferences(filePath, importFormatPreferences)); + } + + return entries; + } + + public List processReferences(Path filePath, ImportFormatPreferences importFormatPreferences) throws IOException, ParseException { + Connection.Response response = Jsoup.connect(grobidPreferences.getGrobidURL() + "/api/processReferences") + .header("Accept", MediaTypes.APPLICATION_BIBTEX) + .data("input", filePath.toString(), Files.newInputStream(filePath)) + .data("consolidateCitations", String.valueOf(ConsolidateCitations.WITH_METADATA)) + .method(Connection.Method.POST) + .ignoreContentType(true) + .timeout(20000) + .execute(); + + String httpResponse = response.body(); + + return getBibEntries(importFormatPreferences, httpResponse); + } + + private static List getBibEntries(ImportFormatPreferences importFormatPreferences, String httpResponse) throws IOException, ParseException { if (httpResponse == null || "@misc{-1,\n author = {}\n}\n".equals(httpResponse)) { // This filters empty BibTeX entries throw new IOException("The GROBID server response does not contain anything."); } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 2b05bedc01a..9b6b0a31c45 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -311,6 +311,12 @@ Export\ preferences\ to\ file=Export preferences to file Export\ to\ clipboard=Export to clipboard Export\ to\ text\ file.=Export to text file. +Extract\ references\ from\ file=Extract references from file +Extract\ References=Extract References +Processing\ PDF(s)=Processing PDF(s) +Processing\ a\ large\ number\ of\ files=Processing a large number of files +You\ are\ about\ to\ process\ %0\ files.\ Continue?=You are about to process %0 files. Continue? + Exporting\ %0=Exporting %0 Could\ not\ export\ file\ '%0'\ (reason\:\ %1)=Could not export file '%0' (reason: %1) Unknown\ export\ format\ %0=Unknown export format %0 diff --git a/src/test/java/org/jabref/logic/importer/util/GrobidServiceTest.java b/src/test/java/org/jabref/logic/importer/util/GrobidServiceTest.java index 9898ec9fba2..daca2f700c2 100644 --- a/src/test/java/org/jabref/logic/importer/util/GrobidServiceTest.java +++ b/src/test/java/org/jabref/logic/importer/util/GrobidServiceTest.java @@ -4,6 +4,7 @@ import java.net.URISyntaxException; import java.nio.file.Path; import java.util.List; +import java.util.Objects; import java.util.Optional; import org.jabref.logic.importer.ImportFormatPreferences; @@ -100,4 +101,22 @@ public void processPdfTest() throws IOException, ParseException, URISyntaxExcept // assertEquals(Optional.of("Paper Title"), be0.getField(StandardField.TITLE)); // assertEquals(Optional.of("2014-10-05"), be0.getField(StandardField.DATE)); } + + @Test + public void extractsReferencesFromPdf() throws IOException, ParseException, URISyntaxException { + BibEntry ref1 = new BibEntry(StandardEntryType.Article) + .withField(StandardField.AUTHOR, "Kopp, O") + .withField(StandardField.ADDRESS, "Berlin Heidelberg") + .withField(StandardField.DATE, "2013") + .withField(StandardField.JOURNAL, "All links were last followed on October") + .withField(StandardField.PAGES, "700--704") + .withField(StandardField.PUBLISHER, "Springer") + .withField(StandardField.TITLE, "Winery -A Modeling Tool for TOSCA-based Cloud Applications") + .withField(StandardField.VOLUME, "8274") + .withField(StandardField.YEAR, "2013"); + + Path file = Path.of(Objects.requireNonNull(PdfGrobidImporterTest.class.getResource("LNCS-minimal.pdf")).toURI()); + List extractedReferences = grobidService.processReferences(file, importFormatPreferences); + assertEquals(List.of(ref1), extractedReferences); + } } diff --git a/src/test/resources/pdfs/IEEE/.gitignore b/src/test/resources/pdfs/IEEE/.gitignore new file mode 100644 index 00000000000..0509c598856 --- /dev/null +++ b/src/test/resources/pdfs/IEEE/.gitignore @@ -0,0 +1,314 @@ +# Created by https://www.toptal.com/developers/gitignore/api/latex +# Edit at https://www.toptal.com/developers/gitignore?templates=latex + +### LaTeX ### +## Core latex/pdflatex auxiliary files: +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.fmt +*.fot +*.cb +*.cb2 +.*.lb + +## Intermediate documents: +*.dvi +*.xdv +*-converted-to.* +# these rules might exclude image files for figures etc. +# *.ps +# *.eps +# *.pdf + +## Generated if empty string is given at "Please type another file name for output:" +.pdf + +## Bibliography auxiliary files (bibtex/biblatex/biber): +*.bbl +*.bcf +*.blg +*-blx.aux +*-blx.bib +*.run.xml + +## Build tool auxiliary files: +*.fdb_latexmk +*.synctex +*.synctex(busy) +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync + +## Build tool directories for auxiliary files +# latexrun +latex.out/ + +## Auxiliary and intermediate files from other packages: +# algorithms +*.alg +*.loa + +# achemso +acs-*.bib + +# amsthm +*.thm + +# beamer +*.nav +*.pre +*.snm +*.vrb + +# changes +*.soc + +# comment +*.cut + +# cprotect +*.cpt + +# elsarticle (documentclass of Elsevier journals) +*.spl + +# endnotes +*.ent + +# fixme +*.lox + +# feynmf/feynmp +*.mf +*.mp +*.t[1-9] +*.t[1-9][0-9] +*.tfm + +#(r)(e)ledmac/(r)(e)ledpar +*.end +*.?end +*.[1-9] +*.[1-9][0-9] +*.[1-9][0-9][0-9] +*.[1-9]R +*.[1-9][0-9]R +*.[1-9][0-9][0-9]R +*.eledsec[1-9] +*.eledsec[1-9]R +*.eledsec[1-9][0-9] +*.eledsec[1-9][0-9]R +*.eledsec[1-9][0-9][0-9] +*.eledsec[1-9][0-9][0-9]R + +# glossaries +*.acn +*.acr +*.glg +*.glo +*.gls +*.glsdefs +*.lzo +*.lzs +*.slg +*.slo +*.sls + +# uncomment this for glossaries-extra (will ignore makeindex's style files!) +# *.ist + +# gnuplot +*.gnuplot +*.table + +# gnuplottex +*-gnuplottex-* + +# gregoriotex +*.gaux +*.glog +*.gtex + +# htlatex +*.4ct +*.4tc +*.idv +*.lg +*.trc +*.xref + +# hyperref +*.brf + +# knitr +*-concordance.tex +# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files +# *.tikz +*-tikzDictionary + +# listings +*.lol + +# luatexja-ruby +*.ltjruby + +# makeidx +*.idx +*.ilg +*.ind + +# minitoc +*.maf +*.mlf +*.mlt +*.mtc[0-9]* +*.slf[0-9]* +*.slt[0-9]* +*.stc[0-9]* + +# minted +_minted* +*.pyg + +# morewrites +*.mw + +# newpax +*.newpax + +# nomencl +*.nlg +*.nlo +*.nls + +# pax +*.pax + +# pdfpcnotes +*.pdfpc + +# sagetex +*.sagetex.sage +*.sagetex.py +*.sagetex.scmd + +# scrwfile +*.wrt + +# svg +svg-inkscape/ + +# sympy +*.sout +*.sympy +sympy-plots-for-*.tex/ + +# pdfcomment +*.upa +*.upb + +# pythontex +*.pytxcode +pythontex-files-*/ + +# tcolorbox +*.listing + +# thmtools +*.loe + +# TikZ & PGF +*.dpth +*.md5 +*.auxlock + +# titletoc +*.ptc + +# todonotes +*.tdo + +# vhistory +*.hst +*.ver + +# easy-todo +*.lod + +# xcolor +*.xcp + +# xmpincl +*.xmpi + +# xindy +*.xdy + +# xypic precompiled matrices and outlines +*.xyc +*.xyd + +# endfloat +*.ttt +*.fff + +# Latexian +TSWLatexianTemp* + +## Editors: +# WinEdt +*.bak +*.sav + +# Texpad +.texpadtmp + +# LyX +*.lyx~ + +# Kile +*.backup + +# gummi +.*.swp + +# KBibTeX +*~[0-9]* + +# TeXnicCenter +*.tps + +# auto folder when using emacs and auctex +./auto/* +*.el + +# expex forward references with \gathertags +*-tags.tex + +# standalone packages +*.sta + +# Makeindex log files +*.lpz + +# xwatermark package +*.xwm + +# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib +# option is specified. Footnotes are the stored in a file with suffix Notes.bib. +# Uncomment the next line to have this generated file ignored. +#*Notes.bib + +### LaTeX Patch ### +# LIPIcs / OASIcs +*.vtc + +# glossaries +*.glstex + +# End of https://www.toptal.com/developers/gitignore/api/latex diff --git a/src/test/resources/pdfs/IEEE/extract-references-test.bib b/src/test/resources/pdfs/IEEE/extract-references-test.bib new file mode 100644 index 00000000000..eaddd5e3f66 --- /dev/null +++ b/src/test/resources/pdfs/IEEE/extract-references-test.bib @@ -0,0 +1,8 @@ +@Article{, + title = {JabRef Example for References Parsing}, + file = {:ieee-paper.pdf:PDF}, +} + +@Comment{jabref-meta: databaseType:bibtex;} + +@Comment{jabref-meta: fileDirectory:.;} diff --git a/src/test/resources/pdfs/IEEE/ieee-paper.bib b/src/test/resources/pdfs/IEEE/ieee-paper.bib new file mode 100644 index 00000000000..38533080cc0 --- /dev/null +++ b/src/test/resources/pdfs/IEEE/ieee-paper.bib @@ -0,0 +1,51 @@ +@Article{Alver2007, + author = {Alver, Morten Omholt and Tenn{\o}y, Torodd and Alfredsen, Jo Arve and {\O}ie, Gunvor}, + journal = {Aquacultural engineering}, + title = {Automatic measurement of rotifer Brachionus plicatilis densities in first feeding tanks}, + year = {2007}, + number = {2}, + pages = {115--121}, + volume = {36}, + publisher = {Elsevier}, +} + +@Article{Alver2007a, + author = {Alver, Morten Omholt and others}, + journal = {Aquaculture}, + title = {Estimating larval density in cod (Gadus morhua) first feeding tanks using measurements of feed density and larval growth rates}, + year = {2007}, + number = {1}, + pages = {216--226}, + volume = {268}, + publisher = {Elsevier}, +} + +@InProceedings{Kopp2018, + author = {Kopp, Oliver and Armbruster, Anita and Zimmermann, Olaf}, + booktitle = {ZEUS}, + title = {Markdown Architectural Decision Records: Format and Tool Support}, + year = {2018}, + publisher = {CEUR-WS.org}, +} + +@InProceedings{Kopp2012, + author = {Oliver Kopp and others}, + booktitle = {Business Process Model and Notation}, + title = {{BPMN4TOSCA:} {A} Domain-Specific Language to Model Management Plans for Composite Applications}, + year = {2012}, + publisher = {Springer}, + series = {LNCS}, + volume = {125}, + doi = {10.1007/978-3-642-33155-8_4}, +} + +@InProceedings{Koenig2023, + author = {Simone König and others}, + booktitle = {INDIN}, + title = {{BPMN4Cars}: A Car-Tailored Workflow Engine}, + year = {2023}, + publisher = {IEEE}, + doi = {10.1109/indin51400.2023.10218082}, +} + +@Comment{jabref-meta: databaseType:bibtex;} diff --git a/src/test/resources/pdfs/IEEE/ieee-paper.pdf b/src/test/resources/pdfs/IEEE/ieee-paper.pdf new file mode 100644 index 0000000000000000000000000000000000000000..408d16f8254552daaef2c9348b8d275e01f925ed GIT binary patch literal 73470 zcmbrm1zcUb(mss4v{-Q{?ykk%-J!+ZU5a~K+=^RqhvM$;ZpE!Q6f6Gi%_-;fo_pT+ z-v9S*ep$&%W+qQ&l38m{!X}j$7NupRV}&Cfo9do}VO%fP|_ ztX2bFo&Y=nwfGYS=YK%K{5uLprhkGV^r!5Pqz+#8jtCJn8sR6Ktm}K(u)_|0np>=2au+bJ~_L^e)LF6idB(eN7YRGMd(e_mcD z^EDUmQO9)4BpIsJUa6TWb{8{L1O)>VNGIyMpoL*DK*DV6(?~sXFph>VMTg_ zIPAck%}xo0-Kl9rCPn#r+R1#qsW5vZc%(%7rDsnH?J%cTlmgNYHU7-RUFP9c4fxrs z#MguVSQAuqW>E=*%wf{Dt>w|&)_dnvFN0F?dPa^G15dvajZ6+O^WKe%ds|m9XxK`N zMb5M!pH5BX4PE0(r*%OhI;67NDeen=`-W&>!=B~LAP^ji#7BEilO9^2J^205%gL&c z$-Y-l(bj!#O%oKUh{Zmut*X_Ic6ECU@zB_^w?IX-(MgQm^1Nj?ExNAfNBOWcnU#xm z=q_9|$&HV@?8JADi%CKX`B|_D5k+^*rKJIq@v6=SpY#CVhQov0!$S(w#&NH9?1aNg z9IK+L+smX^pZlVfH=V6B&=YAww56My9X6(<9QN6W3B*2&AvYVH+G|mFHK*RxZ)i2X zYWrblO}1O*;6h7`WbxW1*m~i(H6Y|<=SSO4QrvuZy>ZURZ>kdMc}sTKhjVbtRPSvF z-r&IBEoEA;3w|c{a7;Ec2{g?ME_j#Z*#U~KF#Et(?ZLxoR@NSPuwZQoEePLBjMV3x zb#j1wpNiTcp2ygbXk%)ImG#X6zdDP6AU{tHq@h4*0mkgR;dN+%o7@B&NOp-eMBrfN zyK!`>ztg@rV zTP`ayR%E$wsrhLL9=Vp7RyDjN+`wK!!D?kxj*VRL^cQxj%c(IQephFwtx=mYlqWdk zXYWvwN39Kd9N#D;!_LU(7gUJz_%4>uw93o~)nTE~WJ_K4eVc&LUqn^RpQoV)HKC>& zqfY~cE`@{rO%L0+LMYsED!WYG3K_d;-r%GO2{nU{^&Z=4hsNF1;^*v7Y;mt3_u(^8 z*oi2xY{}IdoZ4@QV`uQp3~N(ugI60~QgXtsC@DZP_}lUgJVkya3d@V!xU;$xuIEZBhGT!KlMZz$vr(IMOsYLk z7CqkOkU0-$Tw&Y>@1zGyQ$v6sUChbPW2`VMC;No17}h^`x`}vQ*biIofE*)E!YW%6 znC!>qC}2HDP~(S-q(yBPRV7ZrPGX`p`w=zXp;cB1?i*a9ieE8aL#)iX96QH%C5_QH zg6b&ppJg*7wA3YFW$@)~z0-qEU%)Fm3xHq|e;~i|Jtk=_s3nl~KIpHKF1`pWM3opj zvK0$L${ZksIlgWuRc-r-W%^~t1!WQoUNtUIY^gmiY-ci_skt^!{&VIM7>o#>sg3c> zL@WcOr9GG5G-p5?-P?J$H7Ojvb3+mFerYAq;1VvF?9u@i6+?9YTab(^&`3Orn~UNf zT|-K!K`i>@W5l!#Htu;4jPj=wl3r;+#Eda?)se|5&9o0!&X~F$AR(>8I6v>hGAQg# z<8CH3EZc>zx!8Tzis?x>h9xA~!|%D+Cjbatik74NRuu! zsO^x3E;d!zd&6a^S<{N8)la)lg=CvwZ}J+<(O)Z&n|K-fU9H{X$4=5OGR;9|Jz~hA zq^91@EL+=Sl`3K#L~mSPBBQ3MI&N~rGMApYhA|`;5V%G`9Fv9h!CbCU8<)tumDjJ( zJs6Q*zJ-i2k$$^X&+fKT`PzM$?7&#$oi!HPxeGj7*pDwn$*9q-w9;c;PDyT1jN!-E zSM*Y3OgW)hlEZ;vu~{R7KZb7$&o3%@llV)#nWKOm!>8;e!;(17oP&DWlVUWnb1K8 z6(n_x;WOxssVxrtc%9SC>YjyYX`>U4#k0)lT|XN{+p?*W0c|OV~h3(w#T8 zYmroAk&H?8q+zfQ@mWX@(dDZ4g!ohxBfwRwlXCSfAD!gTcO5PF95O(E#y^$CLlXhB z9>FwvRGu73nBVP<%{9qdUq=F^6sOp!>Gck4m*7PYF&8A5;EP_6CatDLPHf+{ih0G% zOVt!omk}~y8okNt5LpB|*$2Xr5o9p~T4^k0G`ImFj|B^=e!8fhlWT^bgH$q;QsfXF zr|X}nY*^iV%*^x&DB7?*rM;)WeiLIBnn&1TR=(TAQ%yu0bz0mKsinCM5-Lc-H zbuUlPP&pheu+xL5>v48M*iz93_h@Hz^7ko-ZcU9T9==YB;6AEFVL6rxUnA`BW* zEEQEa-S+;Y_Xw(8O4y##Vcf8xLk6N{hO{XZDKtaN=P^7q9bA&JSJ2F~93bRz?cEgH z#c3+k^AD5R@^uGqoF!OS)}sIm?2mn|BC`h z|7r}3({49iud$9{+FVopXhcr5X2_DB0#;Cg5#-2M(qOFhUtkS(Gkn1&&!Ir2V8IZG zd;-?9z55Lmy|RLg89#x6Zb66Tu{LfbEV}0j7ZQOHnOEt_)1j+I2`XWIW!h4SilH+u zyru6~>ZCIc>{=qxu>4xNjuj&b0kKb!jyMr}54P?@ItV@}C+=L;wp_m-L)kZitUn?# z|7doN(o2XLb*l7I`U7@rRE?=vX)$EWOu;K8Ma#?agFy^b)4_h-9B{{!uZVtMdCYg* zh~ZGa9%g68!`hyHADiwy?b@iUAbpAG@!32ewpZg@GykSuY9apsLnPK29?2ZqrzGVJ z|IJl>#WH!a2#z~ejRZ&VL&z`+w0mSf?%fxfygtyQly@EjdGDp)4z2~!?_zhss$QBV zk%)&VBYtdgof~De=z(NlNvSw@B?}N^fMYHZOUg@xP%B6r6tt|rLJPzT@G^^QUj^63 zw9>iPgm3($eDn4vx-ztkzx}1SsM;qE63OK#lBasMNc!oxn;~JCh)pM{ zPp?I|8#oD$w}PnMD|GMop>*d23k0AmxYz0lA$>+^FretNg23r5wckqWWyf|J3pBsB zMF|A)`;i7c`+9mdt$mF^T-KhFo;4&ewZ1OZ$TE_t#@#MN&!| zB_WWRan+-=viyUFZ%j7)x9#Qh(_^G)UoI6|7L+68x14nQ1vN&FgM>knser@ zvlkWRB%~Z@xIv>;U<>Z)?E=A)V1gz()q8~gTYCP2Vek=F#-OW|7>BB=J|g*_r8m+g z*rU{YycD!+S!A(l#$I1vhcwy%HgPPm0e3#klQPZFH+5lr! z1{KTAe}hl6XQ8J6NqBIU==%sqCFS`NN!K(upYs*W_zCH`bcx>Qp+I}V=D8>zSknmN zTTOi5j;(Q8N|5C#Bu9QnB~IS04n8ARn8T7)m@j7rWy5k1&E;chxmMXJmMMO7(Y$G+$;eO(}ekU1pJ1mf~^$o~^ra1dl=jXyH7E@BlL~Z%tBQ*?LN=2=;GyxU|Rg^r5*6^30j+T+%q@g<-35>2bB zlaf^O?kt^^YTktLwUbnGx3}g3jcm^F-DA#hnd*XNpS@vs{hE8LsqO?f*CWZQ(FtDL zEf#OBm*2I9GLOuUx{ka~GyM1m*e!z_YS&NS5fAH0G~9Y0^MCB#*1jt*}x*x zT6RhoFKa0t=s3IkeoMVFeAHr7yXh8decA;ZL=d}%%Be9+brHijU$}idh3wANfJmQ; zVM+Cd)WhlBwT;9f!^If!gKX8h%|u93_)VucZ{e%WcQ))1;U2q$UBs}}TVgP762wzA zhvFpJU6MS6-2C2>D4jaPukIRFtnZ=cKGJl0*BUl@M;2veWyI~=xVhrrmi2PteAdPzx9$VdZ3{V`66`-88E5_$bc~-Kn9G>C=$K{1~Hxy{uOI_ zM))KOj$X-0-|-2tw3)R99KE28y`ho)6BfpI^y2gq^g@6eK)?lnMG+7sB-CPOrek8^ z1oX^6$Hqa(!9d3ZzyheDV_{Mf z3XGG1j)MgVwk@m&JY0a)2M*y)%7tMu=z&!OXg==2vSD>DZjBPaX6 zasF=Pf9{io0RWqw^WQib|Cf>e>XeC@1>k2`|DE;s<@mQ=nHc~UVFXx&gOv_Y{&(Kz zX#GEql@Tzp7zr8KIO#Z9nf{HJ`F|PhbH9wNOaNc=Z>&#_^x64}0vzs>0}^HDAOy^7 zpdX|cWo98{ehv+DFcJc8|ET9+B4qxLpJU2SDpnEMYzPl6t$q;#n)YtQzPD_Rsc|*4scCN< z3LWVQ6#GWLKnj9?D|l={^3}-Q%8n|>TdcU}9{HAPz08wOqsXBaAey%${(=TJM7gIe zRxFR{#;>K^#*RqiQ<`lGOk(EB&T;7j{v;aKD{rp*o1@pLMZ(d#vTnkHaLfTP?>1K5 zohVh*sj7c2L(P_jo_g{rhv64q;#yWauNY7W&uXjXvsmdMJ_d9aBCd3Nc7TeGc0gF( znuC@ySQ^Z4m-R$fAmUV27L^EcpzJ)Mww&%?HKA&LU@QvEvTF0Fph2`>c7EJ8?df(j;Ks$JX|HJR~qsW;D`qZ^IG zy=kTSignxc)ri#M_d=PQ5wtk5W|+Kc>(MOUzs>k1X_l?t(>x`KA@`C?a_(lLqo5ir`p< zLpk*eGMEP2fUj*Y_tLS_L;npMWS+gC#J&VV=U8AjD$ti!kj&fIT zj=cer?u>L*>^gB&jW9R^Rpi_a&#%nhvv7w7UYF#gp}kvA3l#Wq7dQBs*Mh-VN968TVJNB%>Bw}$B6OgGv#iOXR*1h*)(2x{F`MK{i z)xQ{JRdlB6Vn1xyI?sDA$A+Wccl{}F`Qq#R6(i(#Zm8v^GlGwBhul~(0&3Mx4~Un+ zeEM(@PG#l0-24PhGVVbln!f9)wE|IaN>Oyx-1uH@+?L8i6rbx|g^aKfH^7Qgw8I|l zT32+~{7(DNjJ+GKU%3RY|NQrNHJ0B!+&{jnJuU5jc~=9xRRA2u|Kq#bUr!#Ezd5%j z2mrtI-#mGK-?{?$t7pePmjvq zS)aY}|Jd!{o_PPr%l1D&{{1=o&#wIc)lEM?leCyv82~TF9E7Y4fVb@bA~6FV`oP2n zcuGDeARi}SE0YcI^7fS2*a_JIkEG{&U>TTzd@O*0vjN&!SpjeJKsmqBKJz@`0JJ~f zKhXhX19^annFH{`1ALGHau$GGAU#m_Gk#znz%nrZeQdxoFg@1;R&nq@&W0AInc0w>K9P=BnPNlKmxS? zHNt0?qy?k^q`z989gfzseHee)ABgiABM`$c{j&j|0Km2kpKSxQ!&4bB2H=>V z?e~P?mpy^w``xzBbU0LT5i9iL)8RYo)Zx2vswP0|9uGpO$ba+@_;gbrN8t2ehFHENnANlj$I@{D!Wbi_}K@rC& zd`gi~K%yahvTghgt-*akUP^HE|sg7Ev~P7 zd3ovU%7tPsEUGBTL#zh3HGoR%VJ6hlBf$M+WomnI0DH@vgo#Dp_6^bZdQdMY$l$(h znE_ku zOfUiW_r40CFtOAi;rtySqDeRU>fXgLAfbK_V17}UEOht>@S=q6bR2ERGwmQU=%^r= z>}%=Q3@>Z~USSXi2@rY@Q#?>3S|_v0%d=MHks}0lN8azr;jJP1uU_)--;J)l&lvN{ z&uCS%g@A*5WQCgQmHGx2ShoQ_sqpCEp4j(brWlW=8n}#2FgoEV%CDBb6@x zD&+0qomJ293zW^kORzROgEqkzwwOmDG%(!S&<>2ezp}|1BJs+HQd<-zr!UUo2a2{?ybXO0ZBRH`HK8D`W z=M_>d5WFCvdJX>~E)EKepD>bzUsM$C#g9?$9^I~V-Unh;Xh<<|!$ezk~eg zeWxo2!L*7D*87%#MivsvGzCNc5wY6up7b3!CfeG@^@=tg4Q3t`dMtJL_?L`i{kCN9 z`Ine70elGNp>2ZS9`uKGoI`Zp3lYgP7_dN{?!)?rfPp;@2BTtifnTAuujXGbLUx*c zPj_OKf5CDdKSz?C4XSHnV+(?HRBT60$^QZ-Pm*Hjkv$P~U7vyl;}uxjFeaE!R&&1> z9t{iy=nI!qC?Q>OOx)YSt6rMx-n&`Ghp&%WbX~oRIOH8G5bdlNS2wlmdJNu}0hV_7 zh*2T--YC;f%NZSe-s1KK;@E!4$)gcwG*#7&4!Cy#;bt2LmJ2)~-F=izaZ%_AGpv}k*lt-KcobRwy z&t8o)c}6Ek@O^%Fsmo?%K@Ah(f!ic%#iV5J|Z_x(y3((YKlFilXjaVmr+cGEf2{38mX@! z_L5O5UD3yX2dQi#%rzziVo!#(`^XbVtViN8YB|-Ro(GGc&91-`uJ@7bk;PhRMdd4f z%g!yg(L`zX{VkYSGH;o}#~;?E&W6%ZC3jyLy}urHR^-U9RB=YVPBXS<50M1ViOGGJ zGmSu>c};<(GcX$pC)GnJ-PeGj5@esK*ATmrX{g$C9h|Fm5{?NXcDtx7#0 z%IB;I51OWol>?D4&9nFbWUVf4{DXc}eJ4Tx-RURoZ{0G;#E?Dr1l0p)7(VBPwc=4O zJ*HvqgP{aGCpuej3ImbLugl##MVeeL4|lsdW{PW26b0(>dMPt^!KHB-zrjbM)1Ht3?H<_8*1?7Emk$d#hr48*Cb zWN1)bh|q2&mBq|#<+8{++lk4gt4wl!K#d3q3W5Lt3!`{7Y7D<(FeoIiU0)pJL*67$ zEVVyedA#FiPJvKFe1i1Nl~Ko{1~~-8^{`A0aS)S;jhFkYM z-W5Lk_RoaO_TTbzCcUt`uP-@OP?D`%Eu&>v!8uScj~xUQJ?L(HBDZ<6mCKWE?xq0!1urt+-K$*emc8q0UZM@MVo}ZSL)Oz`Y z?p&iiUuj9tf!%I)g}u@SZCZrfqRr!h7b(X?YO$SxUJ>rVWB#KMeUYg<^));!x7~~Y z$nnl* zS{w^qdG}owE6?jx{%je;^R~p`OqGz~O3z`)Yuf3^+ykqK+#IiH^hr-&c88d76!@ko zevi12L7vfk*`_YoLMy{^6O#9r-P#v2#xclv=S~Wwpn;CqVsqP>@jWP->e=1x4e^Nn+6I%|y>E-$=?hbu5G!1)9D z>6N@y4TeA+GJY=R&UCk7-Fq$OuU@)dP~!@*ixjk!g0WsUYoe-K4!2a}6(N-M@Dhbt zyssE&8q56}HUH3r6LMgdGS)&<(aDT_)hmq|`&A657oL^Rr@c^#Y89Qw)eGN7Kapdk zTELGfTZIzlD7)an9|oWBwj<(Xd%;VJRG6OD%479(yCk!Hb=h9zyWADXSyT4O1BTS- zTEvxF;<0jzqY93ZESD^oy6a4oiwIj}{+Yj9%7T6VPmoJ%u?1aO1O?Khcczammg|vM zn<*pBC{^yw-7U+Msjco3!E-7v*H0bvUdeaf#f)y=zquB7x(7$q0_#7iR5egQyUE_q zPuB4o$aN3IBx`_1;mebz`3x(EHWAriM}U1>kC2*zx0#`u)pzF4e`cORJ*%8DCgcdc zOcUkaL9Nhvymq^>wc+$VON-L~y%$;OadZ=00Drh+>`VPKSi3+}&&IILo6n~%a@u!I z99P+O;Sh5294FcD{*2PN@u3%OM#O=88dt?O-n;Y4xJLtxv@9*fqe#63a$k<)Wp=>x8V6x3p~qdo z49ehSn-wHn4sFIz7hr2=_B(aHT5ch~t>W=2=y(u4q1v0=-K@^#JS&w>diXX)djAn! za%ssw%Sy~icsl1@_nK9x$J!#>N8ZxW*?3z0R>++VqZPsjDkH3F`1Bg_xq1U!e0yg` z)Z!CnM&v8+T)6>?xw>>$_!#or`eAddYbZP z$H|w}9V_jHHZ+}o%H?(9#U;x353r)%9BQi|4aPKb2YP60A=+L4!_w`>7+wNQ%u-`-ihRt# zfMc?#C?Z^Bdl)JtM{cI^!yLYAyvjmOv-{EhU0PF7&Z#@fhJ;Q2V4AVTp4vgAsXxRIuFNrZ$&WZ6qus2V?&S(cT)u+om2!3Z-Lv1e!l!zJRN)s2wWFL_Vw^+Zyq!IMAf2?t!2Eu>$0$cg!FsgXeq9;IK{N>?7Rk3D?TD1JE-AU_jW zJrMs5arNq3XWx#dW$@s8JI;$$wIs*p>#yxPdI@FI9_5m;S>hl0)L9>fmNFADoM_BS z^0iR%tMXe2a&=wtJ#!WzMcUKQ9D~QZMfs!LPd|^%G^a(`MHGVcJDh8Fa4u`{&)GFy zz9$g*)Ucv?TQ8)6>BeqBbx(IL5GnfR-qh`pr}ac+d-^RlBmVorX&H75%XYI2=!jtG zh|b}q_wPG|V46xcwRoKJurpO^evHqZy7YHU-fE|a$J{_;kr6m-z`j#0`rh3fURH(6 zr^`HAH<`uh88Wupn*t~Ep58ouWD~uAR8g*Zldy%K^SXYSXx(-+%ZV#sXiy?(d&w_2 z{W=hn?pQrIQAYqzZ%bnbg(tjABCe2>I}tLZhir}f@ZwgZ$RcM4lpSJbKh51v1JW5?OH5Kt_O8oy8o&k6BO9M zj)W4X*K2%eahhIIFmK}G3X3uWn}+g8VY@z*MTu^if@=EsS!daPxGlrKIEV7n`bZ?b zj>Y}=#DxK9385xcPc7+~^!f@pd2I09B7?7M-|D`nm7D6lbNblbd>fXeLo*Uq&vikj zuODF1Xl(gjm&sAGP1CnnNf9D8j#aKzV`SQ2_>!~4CoJ65oM__uG+xer#Nj-dKqXha z+J>FatK2+m_y}ykRAYY%AIbbI1eeXJDsjXh7wxrsTb9NJm6n$0XGxCsAmdAq{GC8-s?OtpeU;$#U80nP&%YBlr$7pA;plZJAUu zqtbVjajbORQ(5?IJ6$)@sjZxH1Se>d=6)h{Lp9ey7p*j=SOcYpB*^k~G$Mf)DsV}P z@KAGkFDH2Mx!4a5!8o2YPCJi81qYYYquHvP#wE2Uo|!f2O>p0PnQ#ipIOcnEj~v!_ zpAt?B!&4L)2Al^lO_%Q5CCy%!Tw#8ikLbkBe)ZKqf zu&=4@5K>(-Yr@QN=uN8CkqlBZ$E zGIqaT1#ftGQPOj6C{S6S89ocP??p~yKvLej>sw7?tZrN6A{+dzoav>X1~V6TQF6Q$ zY7A)d<5&G!%Diq?b&gU5h)y~kO}Vkz;VLAf&Fup}INh-aZBij~;?ZX!&A3^<5t0oD zk}bnwJ90>#ThgON7`3pSCpP4tIWc*1#&*YFry+VFRLETJBFpksy;|ybrc*2J@v+Mc z%|%Hvvu|5UkaRxG59fNUw+J#9)LZXb{c@VOiOQ)W7K4_xhtOZvELb>8l1$_&p9;Y( zr{)+$;o^nCh4r%2i=&ljV`$B-W*zq>5tWn;5wHA1dj>3qYIfC$hpVAUZsryo^hMet zl&_Fr;Go{|^x)x0s%9P@?Dg*1>A`C~!95-tEgc#EB|`xN+)C^I2y@~sCT)L~)DK%$ zaF!y2e)M*RLn%B`jdg=Jj*n5kIb`<~@_Rc0<-sG+u+$nNuFm2YN+1_g*Zlh*N!V(t zFHOWgy30t5cr&;do%P1Z6`UD%cM?0FSKh!{cmM2Xt2&qQgEytbLcWP3z+!h5lBk+I zJQc>jmV-#w-D7;X-C7X&k@TKrAEyFdc;nVOZg#;cCokd4DYwYyyrm)G5=%2B3Rp77 z#GmPGDPdH~R!tYock*)(qBGyZcR1OjH5r#b{MdI%`ZNndX1hx()gu>X$K{pqu=jo^ zn5O(?DI*EtPIt+P0UDAU?dRjn!1;6~oJB+(xjOUni?dSZLkhDTwfC~}i;{8SELoa0 z%Q@J-bvial~$bTA0pQ2T&99V zgVtT8ZHrQvUr!U(AG<6&jIdv9JKsjS=?DA6V0wYo&UnIOTxG{;uIKv%2o;w-ezPLT zm%#Rqu=1I_Am5fh3QY>8>qs6A)5pb#ym&ai&bbet$tg+CSGX$Nx5JEL;EM>{fv=;w z5<`Kil^Den*Cn~Fw{fUFGl@#f>PKZn^TPG~?rOk|D^*A{#8H6S<8j;h$6caVTBs>yN^W!^bj(&U^--TSj z(WaO9u+XH=XD~MR`lUv&hUCx~1t!CnU5K)fpnbt0+LidFG`$M-*aT}OT+r@juc&sJ zX$$@7Q^JYwKL#BaWL{^r9pmXo))=^m8sMt;%w5^i-7;(jgq{^VK)4G{9bg zSg9Jm>n!fI;0o4_0W=%LH{q#x__MMrPL&I*>*z3O592JYVLOav?J7kPRnCf2{K~axVGdrb8x%8J`LJcmYV+&7{IC`dtCJGj46L8 zPMe{^`P3rK2>fCw<~4t$T2hMA7$Om=p#+Y|v8LPwL)L9vOc`5lIduDiJmY0SjtK0# zt3;^~RBos}MunDZYI2M~7xMKMxVWpak8IFUN)@psADzOw>)1ZF!K>BB9ISYY*CtCE zB07>hHgHYWU({gJLwk=z#o@g>``mj$=dzzToz1zmRVsI^*;cS(3;$hS&qt3HJk23% z;{&;#EKi*y*919p!=UDp$KesOwdQPiYS?$KK=PznKW~QMfMbU*Mspli=rkQzdk5f{ z%`66cgA|(@#c4^ju$YADd0GT@MOsQsex)*e=kYlV2vMXHq>>Gj<}8XEVLbGA9z>D_ znOrP&_Q&u}%MZqhX)&s3)d$RwoQDtDy+r0DpPFev-TExskj#pxdE4b1}K2UTP0u+YZi@ALwTqRU^^ykpGOlNJt1Hit2D zGAs(WrXEJgAHgGwj$Zxy$uPjl?B`#{cs{%gJcA@`;8cxR=$l0-Wy` zQ<9RE5TzE9QPN-p>`4NCI>5w%5U@`Pq!4^M&QA+C^H0kO*e7OWWCZNdv$FyZ$2%AR zPxAwIdjbC&pl2&?WM*RO2sk^;3^-;5=mywE%g6~3BjKoLX=Wf`ZDMHzr~yOBWXeDA0Dj-Vh!C*(3_Qd3 z*Qqqte?P$rJfF+N_PoOiJXOQW#Podo``#1Dtb~MwN7Cy-2;IF0x#1#m z2tzaE3{Ov6&;>ud1E0&W*%yxnVoroFK&rvOX!vXRed@jrm}roifizb|lksDlB0Dlw zK<0BKFZd0Cz!5TM@Idr|l!I`AT0Yu=B={Z<3wXyt>3N2c!ht4$5F+t2j}_sJ4I_L% ziT{MP3gY+`T!64_6yybj?yjLevgaWv8Uo0C^8B0RN61wbYtRRrNPpCy(;Y`DSJ={! z2)=?tDf~$CZ5FENum#?$0h)>Yj{}IH&4r-4A!80a62-1fsaWaHwf8Ekz=@9M@z@d59{ zhK`8{YCdWoV#bvwJ6G?@^keU_4!OTKJh7P#6x*YPEmjE$NRxmsl(`!7+6ZGEO5hOs6@wI-~$l(jTMtB&GXD1&G2R5q1zuS z{Y$?^b66C+8E7Sa5!r&vl6#}Yr<8-V-}BaKdN?Ol|I3V;KMZ4IG7+rG&R%n(rkU8! zZC~H|T80Zov)CM%t0YFA+Nd!3pQ}-+I8&6`&ek>95cSCDthDEupLtv}Zy9KgSEV+W z>hwjq132nY$>HzJWt!{b@B?KgYdU>X)tYb1!Sn_0dQZ|PxiLoXf-0n6#=|=eKtzqM zh)A+k(Q}}B=bgdI_M^!6t?685@M+<(;Ys=qwu{_3UNEwS>j{$=+Ak^lnAV*HQ9atR zYWd`to7>D|cjLvGf>m)xq&GAtWjh#Yw153UBz*aGRj%WWGqlu0maV4m?$4!4w9g#n zDQ89FthfHzV~b1-L4}=|#I0L$);{$fzPXQVWENx%*XVc3B`1M3NaP*P_q+j%zSeaO)vrT|(dW8g zvLf7A=9b@hWbAo=DLU}h@7#dIqW*JnRK-JRmhn34`n^oqqnv6BH;Jh<*=EKP7+%h3 zi~C#G-QfG>&IQEK_Dbt170WCWQR#~$$TF6sdX^yMhSDSBK55SU#yxBc8#i9M_J6JpsW69=YKaV+Hg})frAE(2W7h(D-V;GT| z&7y*Dj3lW2r`3dAKlChz1=H)7J=l4RMiW=*nho7k@$;9jHITY_-^I?Z-yMk(SnEzj zdCn?C>$P7}W9je>#zO9Sx=u8f`6owszn?!KK4K$zOU-cg$%wHfNPefHtv#YnKaR(@8acPd*N1n!XmDL2Yxdn zQ-@Ud(4sl9Iht71q6$^DZw6f(|hf|>jZIp~!IjGALgF5!rDUkA&kny^o{s59p79>0X*UxG2` zbQWaBS^g3pyd&$6OfckgBBvd}OMKiCn$%EcyR9tc3!f|xE$TJBjVg>i>%J;xIqGCh z6Dk)k*ns8X+@gZA{}H9GK~9w@zjqmwIC?Ts(1L27395vZsLlZU78f7AIZiw!YgBo7 zIjCu_0@`e}@?3{`*+%mfTaRkaqcDSOOAW12A~L%5o?fYFVJ&tmcfFqtTCr-u$(U_}w)8=lW;X|W=>HBXMp}q`^jarWP#onjX7HH6r{4kAj8%<4V z{}49?nj1_E?~YIB8_<(b8A0#cW)rwgPRja8@z_eH#fn_gO=MWqBPP&&g&SW{^$ZU= zP!T@S{8Vl`AOAGj_~v__KJGy+D$#r4v*=zt(d^H8FRp@@#V?JJ;j03k<@v;cxkYFs7~>Ik@wM}>-=zOc6_dE zzBp;i2pK%2qKWDU_d?Z5Gd|R0XD;K>I5pp-_cIpFB|TR zOtrLUqJ)-TQHZw~b0QQHxqY;vzYn^4h0HWxp6bH6A_J4zkTWSoErSbP7q0fB%ZtaS z>(WGpMbGar337@5fxSsO_5OOuAW!3saUD9&<A>aE<8rbXkbA#4)YNJ0-bSBOaL`dvuyo{3LoCyuUW6>o3jfesvdanCYy3EK7=- zfLzyP8c^}tFEnoBqLpIke8jwc0{5WLWc$oUcuc?lM|6-zdPwf%!oKZmJ}7xm>-254 z^1DFwar5YU2T9!kh(Mxr*H;8q!fD`aKfRF~Zk7Rhf-1y1OdhceeZ9GOC%%4`yf|ej zT$3D1elGXjwz+$^ZQJhNZQHhO+qP}n zwr$(pv(LFR_nwoS$;?Xq`BF)x>Q^P}eb)1mtTKOE#&A*7AAO(+lrA#5iv-O>pl=92KcUJ_6cC3*FLiJC=S;Y?HklrY1O`39mb9)Tb%X= z@lqylujO+O%XL9A91f{3Z!aW?0cfFLN~?jn!i+i@V9=`&=& zYnDJt;%HFLB5-%Qne%ghtk!QMff4s+aVPIsFf|ri0w6#W{JT5HIJaEEn8G_l+sEaH zR+)UMDQc+$E2b(yY;@$-E(cCq8v3@bVdsT-k*e2Gbj_$?KbDIY(*{>CzYWVA7d%VJ zkX#mSM!$qb0h*+azebrpK#LwGY%&bLz=??zAwi$`+54~RP=j3!s~8*Y?^v%Ksah6z zw|lw|5FbECa#89o$KCpr=X^ZZ@8OK$jdohIyy%S|*QRofF}%PxVR_B?j&Bu0(e-km zG7XxQmS?cdc50!oVKW%cevDu&dhAdU;xzKV?Os9_fEf7j1{UHlXOJ)>&v@fWmqn3t zQ0`SbQ#)&UQHau}b>Kzcu1dqFhbs-{_A)Wqbi7Y6e!eggUOtkwHjJ3mZ7zGg`Rm)+ zbYl9X%J~Dmf9DGe$GAM=UnZg(4pD`zm(2ta zZEMbG^Dk9qYSms>SQP{~;8H@lv3h(5&ncLSI5dd#Fw(Sgg+GU;uAtQOJTnZMSxMR8 zTKpgZDKQ1a6>c(pTr-M7Yqtj(c!Sl*R{OBda=Ue_%_F=UaxlL}33M{kZ=H90-9gyt z=V!}?voexl>|JvF*4E0lPfDNKt>c^Hup;!lMiBnK&fky0Di@ZgyTq>1m}fb`PY8OOi@!9&GJEV+ovb(LY61oEXaid9SiX6|dZam55Ry ztCVLE5W(_>Qo`LI+nwWmoTWM*blE=xmRLD2Bin2$1&jiQ#Wa5oJWpkerqWC&S{`aU;bnLzc$c+mT6>2;zXha*T!Xz4~? z>If-Wy9@5{q0*Us)ue-HD`+D!6+;t-dXv6AxZa@tpq7vRt-w?q%u&)v|hSY43Wj9ev=g zIaW!$%sCsTS8dgY(a^ud*cHT&7&fc0mK z2Cal0)3dM9JbT7I$r4#7jpYM(&3{Rsy!_$ zIs7tJ8{<8Xi^zgmjCEQ=N$q*fgifa}Ca_wb!pjcYgXx(;wk)_-@)lzw1s3c5Y6ya0 z$wS4<1HgD|Kv*mfJVdC#;nHHUHDO=8rNn72qeVp{4MTNpoKZ-_Qp+4tHHIA3P&6;kiUg80t?tD^-E%uBves*?B|7G+FS zRK`2V0j~a7_)4LrTgy0Zpp2IW~X}) zsG?bXsMZF~UwhV-vA1l(s@w_R@E3jKAc^ktK_z<;mPK64MHat*E(v=s{F0WP3}m&< zM*_POvCq6t6J?*#M>JRDx9o0FZroM+$L`*9Era^|A_0$b(UEyH#ISokMz#{l*A7BN zY$W(jNJFlq+$lQC`(B(hEA*yM9t<26`uq{gEtizB-bM>;v{$1r+!sYS`m0vLeS*zP zybJDRBpf*5Hl!BJg}9h8G7a&1s*`je(w_?lv@en+Uo-8DP|eFPj|kSjGn$eEkXbG* zw2^vFxYY4IgcQEoAE-v(3M`<`n9O+jm0$xp%{HE9xu!wZ8S&FS>cTx+wB@-mvkP(`j4n_hSn{%_M?e7XPS6X_fW7ywxrG_RHo|-zanLrY{&?^U>@ z85Rm7uO3Ri2a)S4IROxQ;L5fMc6}RvQeYqAzA~EREFR@7H49Rb&0zWgvs@M^)-~hp z*hEI3VAoPl@|&CCzgW%gzT9_-e)XX11TrSZV%U+@s}H!IT&8HeTRqgY>V*Fq)9|g( znZ-`-QNeY}&CUE)WvJh4rc5vp;zMYEvr_XZJ+i71 z>;6ti+EMEiCQAQN9e{$n4~@Ta$BZ3)F6nWSr0J+lu6n9Ind{0c^O2VTXbR%gkq}NE zA-pQp-cxla^7NP$clsWOr&82ZMDH?r&DmeYGyotp~aa zu6S2PV0(qWMHnNI1cy5G8}ExMTV#fZjnJTKpTjvODwOE1Yc0vuQRSS&7^;4f8k33^ zIpo$#ZmDyN{3&KWyJJ~h&)TEMirk|U6>!~n&n1`}&d^YCF%EK`%XO#bl+a)A_>##U zgbveU?e(N-KE}oU7$8O({kL2BG_9v=jt^j6?5$dWrAyRylO9`$&LQ>*v>5b8+El-( zkDd&5Cj;L?Crqh^AZ63m@EIuop+<8U>;bWG zOu*E$0lDo?z9azJSVJ)F>r69pZgW?_v=BFo!$2i0x0}||FAzpK3e~R6=zB(78RZ4G zJz6~?n>~N40?->zjzZIpUtPt7fsJ9HvOGsdfuJ%domT2=e44LbU@+ezL-L?U#mW5? zwH4Py_$Y4#>-%?73Wa-fW=$>vGrw1+eS6ufBn-1^Uf1*lSZ`>Qzb&BXt~m&fg;i>w zg01nXvS{(LeFR(<_5_#Jx@PXb24ceI_= z3t#bU9ao#U;=eX0a8X)l@o8*0aiVXkoG*2&r=k2ky8zjZq$Md3ffQ6S z#9&>arz?@J{@?`{CdjBw?W-{mDB8a;#cU%%AuJ+DBSD~vW@Gt!ic48OYg6Z8j7tc* zv;flTT2yj#C1&D7N@CF?@@D|RBz8ps%*3_*T{fA>0goshBr@(BXF<;{=biB;VoYB2~73)*)xCyPZa15DW+Q*N%JzL=+3az{ek}x2UM! zha~uhWgC=N8)r`oR>lli$E1IKirs+un8~0aJryijiw1VZWb|9IrDkX)jhO9i1r=-52)TQp~qi zeG>-y%Ru9+I+pgOh3U8q^Vdt0bwV&K^kbx06;K;FjlvaWUk#aWlX;scyj!+;^|t3dB?6SoiKCE*jcI%)ris!c(W4|X zl;oI0L*7U85XYgL_DMhbmhnZnZjHPh9KHH*qv>VaCoT*_EJu)IiWO3UOGeVzOD=odaKM{KR zvia5T1$N?YV-W^P<-ZC4Y7^HTwp(+;*>_JeuerE3?|$x_ak)a=l;`VSxIz@M=OJQy zsTYAiAxs-%2wVoNzoaK0MXJMkWwi_#b(o($;)Pe?<8PIx9}@z6wfB9Kmb#wMEhlk@%}9SdACq}- zKGEqp=hl)71{30%2U)BP{A@J`+PN+y3WN^4kruxZX!e0tiNNvNR0CSuh~40}?zjIQ zdvlrXG9JKKRcP$bECOeP%byJ)&XZM&sd755g6cfZkjuFrBU@xqUu)RBo&{aTpsf0p zppX9@JIH_AUvLR1_qd0o+H~-b7iWB{khzdms zzcjv&IrbkT4@FbudQmDjG)T`L7B+8>Sg&GCtLjNHcTw7}NLc+u(M(g3vm&hm2hqXd zH{yNUkf03-Jv-^snvwxbz5}!P_5xv2=cQ+{J#Vd#H~vQHA)`4;GZUItsY$`)tJ=!d zkdJ_C$rb2h%2nCVP7q|dB~DR!O)b-!GfXZO-uMxZ_v&{K!LkDw`Mrn19Q@zgTp*F4BGwU9f&i682;>2DXe*hwEnv^XvbE4b={2Hi#NJOzT zDuU}p3u$bQyU4Cf-N`d8%hf<`CEz7J`d(rCwu$*7$WnGs{c-_+U!w;bNa7zgy%|^R zS_5hhB9ah)z<;}({81YRJPJ>7w*!r)EP^3sK zw-JO=KK)a7mG+l-FQL2**z(5Wk*yaL>w=I;n4*8XbfLNA*5n`J18d@QPl;S-9G=g{ zJ-iEHzMXq_lo+>%%?|IhL=GO7`3kahdBoFAmt@Jtha5eEk*KrZ*b+$wHD&Y?%@HX4 zaUPQxWB086x7$bieCp()2yefOzuyC1@p7fwpmUehq9|437?q3uu|EB>16jI(m{Ra|r`78Lk%Grpa9Q=+nnf zwRr}3!tiswWLL3`-F=94%1L|V)rBsEa~3YBhUTTSOGJ8R4WH&joO(xIKY%84AV4P~ zwIDK!8ox^nvQmP0xO7L;5v$%=4->wH$JAN8Px=Bvel3t!93hivPFaI(A8t;&7*#Dv z9WjJTjh08m0b2WcDxN2`8Xz`BtAnAPg;kgkq1QQa-WaKh*aW!i@E*N7B}Vxmoj16b z!_9{|f6&95D3N521kUed)SeBVm{s&2Lia%8-GzAxiV-{yt(n1}v50Vfji{c0W;HQ5 zD$NxiDLfLw!$+IStn_Rx=Q9_%2X$OHYWdt?xbIhy1m5Im)V7yYa0-h`%2$Dr69vYk z*le1xdT)3cdkU{EPokhrOE=KRDj4gh2^qd%D1irqMZWI|7) z8e5*PJ&%J*_swmu7~Ru=XJ5RCjIN?(aLChP%psAloUe8iH`?Qdbt{i~IO@}%?5BrO z|2(51(XfIhbD#Omd|+s6&a$X`M28CpHZ%=RV7p5)WL! zxV@GsFGN6>OZatnj~^RlUjC+fo2SM`T2zx`$6Vt+T~`qHB~-_h3^s%~||D3FvY$GUEN_-kuun}L)M{#w&-iy|PHhXIUkzsNI$@cD;8=H57F zEZ?yp4^~7YiOy6;%2BZR6jPyP^Mf~X!O(diV`+(bQzE|Sqn95|-h;Trbx<7p zIaVNOL`L~}nyt=t^85HgFkOBci@~dORpDW+P%95(6Ih!NFO){e5<(p?qssy~Xbh{l zBM$7LSy)$ktd28pxn35c+dk&k^=Z&0bj55Gr}GmyNqFIy7xFk!ew+Ncd)o^Q7JVn7 z6XdTJ9N)n2Z&?kOrieBFHFxtOoAgfaMpxz{z2|d)xL>riJPgz}TnN~J!WazGKv}Kr z;FA4|;G2ncfKzv^8kWpAFoSc-8#3EEI501ij;!v;(JWJ`*uqC|voN9bT_>RyK_#mW zgHU>HLIBk3;fGKrM&kBLwS+}y!HZF7z&~#z8TE(q9nMGRh+6p6-!b@WN?!aYFzFpv zv*#Vw!$kmN;r`vQZDgJeS@0!c? zR!PgM@6zY?X>+d``$it9+GxYbjteS6lE&;8Zu(Q-@{p2 zl8Pg6-ZaReMCjZsBy9`6oR$t#T0FE>3a<>KaqH%}Hl~$5jAnmW;+6H=r$0ZW_AqdR zlRmou;{Ey|#D|jqNFnSCWuv0`bybx1#R+JRE!IfBNQr`SQDBmFNfNmPY)#gT7Z~y% zUCcNfIOq(}>M6W7P-9A29g_RqX73YsMW~_jVl%rZkH zOr3M=;f(600}#26X{K~53uwcKYI$ehv$!+HbTbottAZl+LE-w9tSJF;U*Y?i5={LG zv0rQDrk#9JeCIsJ%TtXYoN?$;9XH_J1Vq!XIux9JsAktlfW1jKcZWjc+9iI`lB!9* z)*DYyfm8Pih2A;0@U(OpeIxCGCp8S#(CK6CRS>~_ahOBBTBY=0&c*^0xhafEq^?|# z+0uk6g}*E;$o7 zdsJTtq0v1nQv;lNLUdzVe2Cdw?%k~bTK%q#7;Yp+(%cswuOzLX^On^rM-PeluW_-vHbX(9q0t5$VwrgKmCy_SVNVianXQk*b@=58oM zh}L_Rdk12+XH+xPEA?C^Hz1EGu^RClV4oQ4ES~TVJMqjmpAJ*l!OsAa<7e`t_v;l` z^{5+VtBU}!CNN+z``qeev&}%CV$1wbNcO1~%HlOffm+IGvs}-MXdpjI|CYfMURJFwvyaPmQBT@xsGmN8^kgJazI!Pz z?dn#PLVW>4-!5ZGXb;`x-|!j=EtL=ll}~EhzCZnkMp69NX6-4xVTWJ%$xw->!j50! zXbO0%K;HJ&dbu<^?hur;xlboj$Ic-JC(jh{c-kiF#38~hmDm$YHvwAGpZ;95jI0-> zH_*NdTi?Hr<)P7vtSPp5@WQ=?6dGpr!u-DzGQXvNXN+zI!R%?z?ynoGXYkU`m)#g( zNX|3vF=Sw|lNU2#FZ1ny4zbYReD1wGwTnL`u=r;sP{t1$DYzLRwUn&Jcf42FA?_$A z%SLwoq7yqF{BwsSV`*o-A5zSln$}|*ITcQ;=aW9dF{lt{!}t3(uIhC0kNx<8aC?kx zD~lCc)w^a3;3m&XNYr$)M}zI}nL z=t9phDE#E;?`HS66>`30&60N&TG&S3++K@Z_JDC3r1A#3-y>=@yEA73$k$8{2jB5X zAbyLx1jnT}rS_~lrs{NW6EEyh2~J-n-eJ>amMD6Jl`WmMkuyc(m(w;HhA7vTQ3!au zF?}|V--aZSC?!c{tsP3+lKHaf8Cjh6++#rbl+u02YRWw)6s0m*go1AxEwlW@qjGeM0O7JV{W+C0C>CFMo%&UUIerbLHzhzucsfoGY z-$^Cok}+Ony;fKCk(^MA;XudB1`QpL4JfIj#BCjsg_5@Nlxe0F?ANs?!h;g&dQ_GA zj7`=bK4weW!7+!_^6uKORGN&PzHv!~FK48EB3bW8$(|T%s(DCzcTgU)J|47Ix-+ct zJLA$RRvaF$M-_A(qwfoBeAbgbDp%$2U_~6cGw67cxM>O&83gq4No8aGOXYxD>^^g95ad5b9i^Z&DudP$G7KEex$}KIlC`!@zT)Y&#N%ufT2*gTX2Sq}x zUj(9;Q9B-(6pizP+OigOOK~3qA%C9mUYpCFiLdL|>Y&$+3~wF>{oCl=tFyCgVwR_TgNV&@E8n+6Ac#?L$IIv{)D{+wTQt6t}9c#n)XO zY8gH5&8F)CjH6gMfg6MDi!=WCQZBpa*Gh@aowoH*7&>&qNMOaaU!%spih&0c=~1af z-S}OM@Q#xvfO5}_(`>)qEk9MoAQlZPqLa`_-}J+0ePN)@Dqa;c6ZOdkKF>kGS73n+ zX-&!M!V{#C=Im?%7q$@vkB%J|`$6gfYJYj77vfIkHv^dZCkp`_1xmlul;gWEI00P* z5>qTRDU@p6Smq5A!GvEoUT7r5lJ`%#d>N)pgxtpTqaEm1Tgr!-mC_It=?G~hJFCWm z5kCXJPi1t3sfvJ~S+P38*U>NI5<~oD?-ZV10a+Sq=s2VSt>{rzF3m@Xr?JKDE$g0B zWCsGjs`*K~I6dyvt`x1twR*9Q>#IeTpCGZ8oJcTu6#_emINUswwd-F=b<*{5w!!?6 zLT2SrHeI)Wy7$)`^gUAa%wXg6!guT%YsH;k3m&nl9aqszXojYenLHj71k`;8iW5+} z3$j;bwmIV-osHu8Lhu$O;6>f@S^6ujjhOK>3WrT|S_1Z>iFi(lm4IC#hh-pIKE&qo z6qF_;p%&Ou*0bn~LKm5^zX~M+6A8VacQRtvf8fn6%UAvb!TqnI&Hr+H{@)31aV2GS zWkE_AGb?>32L&4|85?#f>VMeS|1-@^|5tWS&G^?w_D}cNU#|OqaBdd%f5*B1G5$Bs z&F~MG)xYB0e{cR5%>7q$^)ECx%fHdw{Ir6!LVxA!Qnb>vGPH8EO0;^k`m_eL1~yh! zdbEbLMzls&hI$UBw8pe1w5GJCZnmaI*0g4{mb6y1*0eUXwzRfJ_GUJQw05-iv<|e6 zv`)0nv~K^#ga1=}{I5Ltzq-5sTgAn~^xpvZUr8Gy^M6!vvC#h&sr_^Pf8gNCCaMV+ z^}>K>1Y}*pXG-ml+8}3Lkb%GW2tea~1GhorD3pW%%ZR$d9=9VL0p$x{^5;Q1=e%Cl zPS#$sc~3I&#~!*GjF%@nExN^9tufOf%6#LZ5u|?s5&xo~B7&X`>Br@s8y0KjQ4moP zg9i#WH}ItcfJ^vww67q@?~6f(lm|D+X9$RfK*xs*1QZ$uC}d=4DAopGF6Kf7b$;m& zc$PNV>hX4X5dS?Tk&PMDu{;m$7P3^_#iv|M7zn4z;=orY)51>T`;m;@d3Lqnf0`NQn2p~YzJF8Qb7i6bN zVG4lY>m1^n+e#P83bFqUjRevvC}&HC73BfOHwR#Xg5cM;1qcS|OTr8rh z@lg0`4-T@=agtGUk0_qQdGKd)pG(cd=6b&}= zb>aMdj(jV-1-!MTx4JVox}*J53K-5Ws4LT7n6KZONdQCB!id7}ezMYON}L%BARotj zsVn=!OHT`N{sMaUosP+)iO5HX1ULZHi$FTh`7Q=SL*o0gqp0@ULwfJSzyA~7`-AHC z!)W(YJ;&QM_;dF0>0&M|iy?&%F#_9*?*Znk!Ndmw&|(45?yEB~j12eMWRsiwgA5rc zsJH5a`r%>&J&G?}L8Q0aW8iaJL6B*84iP2+74f4NoX?!MjZjO70&EY+2B_B>@nOX( z=u1e3fdbeE`T1dO$4^A$Ga#jvOTaovOY-Z-mjL2+2fNqox#N>lg{FzBREJ)hL8^^pL)hktlp368%3vWA7QFk2MW= z2m$`?z?MRR!a_oTtv9^9K3RC5Q6j*A0{%Wxv5;V)00+g__`y1KqsT%1VP77IyI4OD zpB~?Y2sRL*j{_)3ztE4iEGzy5Vs@Y=I(qN+l+8L05ZbsOCcy4VQ3 zAp12XhQ5vO=f>4(eZMg;?}6E&+-=kAAcUyM%t}uayNmRs=(K5Hx zMbVeshza2#FT%}N2HcU>BtEQ4~Y{hC=(_h@ZIksB%rq!w@+ zf0ktJ2tEhuf_$o}(s5`S|8lLRAP zw_Ju_@3^W^=4e`}@W^NW!A^!urQlQ2tW~(s*TrSLId))mIAy@>lk{yNI}!J=D6uI5 zUfFFf73|OyGJM%WPbdO~C&n?tyW?K!Gvs<)-W|RH#^WdTZ@i_NtmT1RhBI$4~HM*_F>xD_g5|5oQ95R+# zr#o;%?F_C>d^KM{_@Qqi$~tZx9T{Ij%Y3jRd#gKXTFaR8b^qZMQsW9*SCeq(Xz+t< z+KH7InzO%^32c59(uLg%H7W(QVWvAdb~(=rQa4OKQUHv~ZC`wcgE67lbI$0kJ=>z8wKgW`BkX>SEd!LL-cj<0DS{0_cG=dXf>_&7H#%z?&Jzp{-6!}DD! zXT4;i8^@J;!yS~jLk~n&FMas=U@>Ljd^KJl(HoY)>TH@t;>?DM`GS7?W7l@RDCw-n zFQek3hS6`c!`0s7Yey(<5>&bN9tanqZDZ@vI<-PH`S!` z8EgAi?4OR@#P6}OkK~K`8^0K}{b#1q@wJr{JKyRYYElX$tIaYR>gg-4gPy#w5g$tG zU4(~`qD*SQ3aiBqyZ^RQowGO1k2+c4{*=5~L;**9+#$1WCwn9LMcv*$x)@KKJx27j zTF`nM1iJp#6aeft(bt<5{Qct{NpRiZnOGLa%M~`Jii24#@Q0EnLjec_BL__tIMb(P zyYSKC7{ibBUF`_;&B3)=UEEHL>T&1ctd`ts3L{{O3yBWpEY70sOaEEF>p-k$lq;D< ze6rKZPLSAxc+_VLnEG@4t+rW^(T7QRd09BkFe?df^Xg9e{f4oTArO64-TNt*^Cif#+_GA*fo z718zA3^}!`3}xjYcm1~duqU%xfXV(|1e_XHurj0ajYZn(ZMtgi$v#Gkvl(w>)g^6h zHPf*=krX$9u{fX35=IMIFPhL1u?ViJ_?V27G$^*7t(Q7sPPMX~s-*|-uq9I1i8}KA z24K>ZzX*2fYz|DKl&amIY{Z`rTJdlTwcuA8FRRZZ#{j8$5hP?6X#*$eyV3QF^HL4i zC?mP7Q0&@S!gF+Ri}9pf_?k+1zrnSf8m3|Utw&0X_v4mP%%(Y++|lTzb1B<5q6^13 zul9y?pKWJ<(A`5o(|ISd_JUJ+hzoj-I?C^ylp4i?Gk$1@X(sReR0u| z?S^?uu)mDk~ImE*Sm-1YVH&!d>LRD>hp$l8>~F1iB9o7h_FVQQOO%K zc`7=}VDlF**ELV~@Vn-o#f|^ro@sJVo=)6OaIJvZfWm9SYAV39ypvny#H)Rm!oAts z7e$5YReSm=-`U@4WN_jg&Hj4HPk+ft8eU1iM<1c%?YtOdpOde!2i1Pirs^cARB?%h z9-^z!1~!V6Vo5j#~>)uZHOj7Tm+g@M75dW#|P4jE|jfJYG$g0wN z<7(jxUB6n34^|YYF_kLrk2dz*5=!YEBB{?IRSTT79wvm_lJMGXwSvEhq$&0+0cs|l z?Um1Es1ZR|$Tfkuh?K+{Cd4`66kewrScwfdE_teS+4;J)cW#z;nA@q10CdHhZ4g2~ z4KICcsa+oHf}-JqOwSV1;73Dc?pvrK-u@C3(jUnQOeC@^8GS|}*8ZQXWBGytZ=BnUPLKZxi1eS=s%6j z%fei2rG!T8CtIyss+u7*_BUu!XILrc*F(FHM~wF*dDGHSeB8<9@yUd*tZ+K+Gfd1BwLH`c`9;VuG_kQ899am&uX$PW((A}%{Xc`QIFJB zuE3X~sGXlq=BV$lHaX|arg>ebzmU1n{6>k9(X)*6ioK_(66(yc&Ppyma4(=-V>|Iq z;CPF(MVPaF9p)Y)50qwBguZ$#S(u+V2v(}`tISj8j2cVPkxr3)?p#rh>~86VuC}(V zjEY_2)_{7f{U}U*k%0OTL^uLMG>zx2(~RqTF<2!yG`YWw``zHQm=BC3-E@?lm2A#} zcVw|v`iqvEKFMo3jhiXFX%jRMa#|Dc`0c6E7dxbZmj_~69B-Y87&ZdAC9X{XvSAcT zubVCN>F0O5txTrT=hM2k;%K3Pd*w!<-=`ts#$I;E=It=nFi*tp$v`d=_0?zfvYx-yaAdxa_L{)%>h()eQa^yNZOMS9*kPK#<0e=D;LB}#e)jQ z+_wYj^vCV;X!hvn7MK*ikA`-3m0wMGwA7pRbrYk=O~Z;;v4V)v_Sq4Vu#%)Y6&xn7 z&K=`jM>4w3<@o#BI?rIF4|?QA&uq{SRmp^%cx5{*W}Q8&S)#W; z1^0NjZCL9dSd8`gk)6!S{OJ!&<}OX4;SmK<_vkbu@x-YNrOZnjdVK5MqjvJ0FOwH= z##;$~@3^-<16b;2@M%*hCNyBlRPv!P$q^CWN&~o+j1H&d>tiJRysU@V^^MK+EEJB# z^+ZD;L=HRZs@9l~pH<52XUU^+~A*Gl(ra5 zV4K>|hij=O{!PVWy92KJ3ay$VeR7AYl=^olU44{?d1s?7Yhp2%Xy_)GLV^{O|;P%eNffvs_KnUKmyR-q*v-u4mH<{BQRsnS35@7=5=r}Gsu8pz? zrTb|Z+MheL76DaoQ`!Y%v6Wrw^lJIZlPzG6kAM%8p#>1v&7r#vw5Z2OON@ua@AT*4 z!K9`5+g{OS5o}n_d)jOzG&Dn1UVZg^PMD=5z4%kD6&Oz)!f8xEmO3zHd)6$jl+moO zv+vCBcPc_Rq7&&7bp0j9bOxieq>W>(a^+gTdl8FNH#*J|@NH@R*~9#5D<-Ezr;Bxm zdJU>|&YH_07+9wF1|A2*J#2(gel&Kx78S2!6iqW{&<*hLjmqOs3k2msW=@8NBv=mP@ynQ1rAX^eZRh>{4d&sx=#kZ2<_doKWcO=N?UPOx2rf8 z#h|JuQ=_J9*-&=j4`~#Jk&i*CIpk z@iT7EV)^C*6@8h}ruF(Pc=)&^+a%huq@N4}JiSqc<4(p-Y?T0KJY9CpHy0i{M1(bN zcwgx+$YvkF{us`pb#YoQ&Y5RP@7@xN)nca6htu4CejkTQU6DEcHvf~kRM93(Fypi& zq-JpB++{un53bN;7J!4spL3dF>WQl2nmW0;u0#X8q``?gw3TbpZLh(b?MY_Bob3X$ zd*0WhZ_Kb6o1<7pgd-UnyKDShwWwQ?U^%B5dn%ZSOMFs7LmAp`uU`--jrEE(kf8R1 z<4s?_U06M7zu|@?=C;*se3ikMw55jH@-?EX+pkHW6P}8g=jGfr<0PIiqjsP|NfN{| zzhhzJHeaUP77G&yFEwTh75#PyY`FjtQn7`$2Eph!QmUC4jyt@P);PKi1k6el*&{r! zkraCI+q!s4Ta4^h?&4e>)!0jr1xZ_058xTRIlGP6ulnVXI2W3aLU{1PF)G1Xm~kB8 zOTMcPbu4x>46Z{1201?JjLh#K;X}rLzcD355bK2Oxft(Q#R)rz^YX>hUnIEdzPRYD z4b+E8KyKk=TBVeH6C>%^bLMJ-8<--r8|4Uay^x*7vd#%qF{_yf{_$#1`WmS)sg)3 zVCc}ro?;zrVrKM?Q3Hc1WUXW6oXh12>m2|(x0{s8Y9(Qx-O^_uZ|9vTb}Q{k)M`Om zwd^}~e+Zuh>9Wm`RkECB3cizPc4@qD!!^Bzj9M}thvfo2G>PaH4`k9R?Io}+?&kdY zd6uR`j|Po3j~>H-s#Lwd@%A)#L@`}6w1-}kvI(ixZ1dI44@=cl-L7+eG1lnDe)(8g z>9pT5g{mpb=|OYhhtfK6e#ZvBUQD;^nR?Sr)~}t*Ezsqr(->P!x0ooWW!w;OkaRal zJDi8Wl4yWQLBBx6psBX9i|ORgm68~B?tKmOG;T6uy{*AF9Q1Q#?wDm)EMUWR2DlE9 z1|f0$>?CsoNSSH5$SmJQ`0751M|+oTh+@Q;6t+Ob->BP2LX6JnnpGN0u?2$CtFFK7 zna>hyOyM}WOs3Vxp|Ern!C?nyc;O7v#zw$8*Ma$R7($f8jJ$D_Y~oW=I4q1DeipJ0T zW#7A~p1|bN*9iv1u|IcBLgI&v=~OLZ#PyPPSqoRKNH$m)E+UJfMEO)AC0$)?baiP7!y1^a2C`S3e>Yd0}jRM#r!=w z$m=smA*gea9b_fL3suH=WqYBxr8`oRC*z6L_=!sIT+7~DrkjaQW;d6>MeHue`CP5v z8Ouo4;q@rU8xs4myFE0D7UXE(P*CHeA~-iOquT%9Bbi1WGlq^`Jlv@_#6&#is z$LYcj`kt-YqCN0Zf)g8C&rf=eW1c(kL`M1oEdHKdX#a|8ysG7icwBi;{liok$Ut%l zftrwDkZ={f9=15Ec1wPr$|4U)`l^lnAnJ%5(Ju&GD*kwNEqrWs&m}LXol1msux6s! zMU&IIOrL_{+O5p4Q%@p4D!#d!ubz(;dQfjJNQO5c_HQ{1B9|)0A#kYmAWH)fm^ln}e;Xp?( zZCl*Gkr3|tPs_I^yv3DF3;Mc4Fc1Mtem({!RISECPJ)Pw#I8Yu|Gtn z3>C`WN_+e`4~i_0i(d=Yd3hTqg(3*V^~Wp~f@R$9uB_jm@)^5F#mqG6+Hb(1FssjE z<}xoUkg-j_?npquGwT`W&ER5)z=;h11CFznt@s~uo&VF~^^a!B z|5vUfuPi8{qVykfoqvQd|2f^Ungxf6Zk6QDpOfx-I`FlVSY(?EjknI(k@H zS^mD%e~tg<=wW02+ba5x_5TiKD%%wkjkcOea99{Gaht6+{$9qx43-J23QqXS78Y=iemuvkA9`^}6l4of>8sdj;cFXhvkdd}wt$CJR#BQ+Ntaef zm+VHSNm5fL zOf9y6a-|eyiVBJgpCyO*$i8tWKBnL992)5xfTQ2tt!eA4oU7^UZJ$A}Jp-%zt^G9BepP%x7wg_!?pt0Op6vp?Vk_|rUGBKGt75c& zB5$nS*I?<|Jg>|TZm&M&#D5okKtHHl=$o3Iz}V71*}jn`$C39A&ad?i&py~(_UOXN zB>zYZEUnE?fATWs0Ui}z7@jBEx z+Oagz7X;`TzGE+}9@$trNOEGl`xpz)Pfz0Yj^5g|g;fmroNh{GeJ>G&`xpn`+aLx1 zes*Tb20&J1#BgW#Na-O2MEP-{TKIa2``qEX^MUUBp?>`CiTluDf7hY8`?>J?u9?-Z zW?``pZuSA+-Qfhgbs5802mHRq^o7~Y5jHS5ef)W#Z)|G3`{Z@|Qn7Ga@G1H50Z4G9 z|J*?1=X~f{q?@Jx)G$4xGCtEY&(}TEw*;hPY<$~j@EKFOl9I}@q`H#&vHz%P1~SFO z#Q2Vo8S0;3UBAXhQvIo7XkvV;LH#Vc(0N;+EVU?|ppg5qLA&gMLH&mFj|eXr*xCFR zWv+kz{qt}O9~m3l1>T#H92W)5H!;}v`ttoH9hwj}^x}hjufrGlQ!E8G49DzZ4ex_y{4C+|$($oR%-^`m$jv%b1Cc1An)qYmIx@g4KfbG*H{ zIIxIeY_+zX$~j0u&A3$rN9^LNAk@*5g6PP$nAa#@R7#LL+&#ietsl}bzgxhPSZ$Vm zQL*K4Ho5n}7k+O0SDqQ%NjmgMfxnWHT3-X;7V&r2;+-y3y9R~lOOt!}!(ILuJ{1i+ zV14hpFh5><3>;+J5xfC;@rlt0piYAyfMP5G*gwb4To6Z{Q~`N5roF$xpavm9=GBtk>50 zN=6lS18TjP^!${eh0qAqmY&!pGLT1&g!t_;U3ie=f4fP>|@+*5A_fbW*eajIAYxV zJryZT!j5(h`oP53%mvWPQ|rtBZfXOE9LicmUkOw@rF5^88hH;ui6*bsTM_Ms$4!!S zy|l?LTU^aupUWT{!TRg$vZk_M+r-nX1>IZXEGM3JgHR*{A4eojJQ#>CbNmRYGL_?+ zo9E9(^UST44UWB2C&sTK4>hL@Mx{Wy{#DBE5IY>GOvz$~z(0i6i>QX;mVXd3gMF6D z+Dz*@P$(4uZUO9G&xQ{#vRJr|#UM3-hB2ydJ*B**t_PxcgUdgFi z4SBY6n9vNLnWz#W-oEMAd55vPvU-*Q8=lRiT(&cna#_u^3m9D&3u_@`sK;Zj?o*Tn z1QwA`+rLn-OztOt8FhM)tK0UDA5+MzWlbVfirGtk&bux{VH5q zu7`!8T!4Qnbx{^{w|{_e<}>o7lG8;4Q2=PPxJ!F;o@T4X$_@G6G zhDk*4c|-Gj6tRA%6mS3#Gj?{IIUabvdC-U(juW(Vm2^@aY(_oaqzFY!W%yWpCVpl+ zo2N3n4r}YMo|8@&Z)yH&BS>4Bo}+E^zW^aX-oH!s2XBddGS^8LolRW^HXbiL_hzeU znf$0?_=pYcjvy=t#Z*ww1eyLa)QR;WGA#9Te&nFjs6hu3F5dnq6-Owcj^80etavQ< za#E>Fpu+T;rmV91Qo4fdVW;5OtFeOf}Gcy|F z#^#xCgG>e}$XKKmF3l_!_G&cqY#l2#tIe-8zcoX4zzp?%y~%JR1=Z4rCzvB{h3bJ!K1N_i-`&%%|-5Jg@DctiClH3U}_CL&@S4 z;VA#*;eEFir7u3C5v86E&QI-J4XPF_1+JGD_`p=~GfA`uIFfL!`=CAv?~yV(gO_GC z+!_ilkrCj!p@1$BLJ6(++!B&JZS3`_x_tR zSxT3mFdPCGYYt=Mp%b7`mK-1gQ>ZD)+5Vz+YT~TZ#3}yUCTI=Jvm9Vb<;P5>uf4lF z$~yq57tr&=G_u4Wva?2MUz-j3v0<}gHF}W*Nna(@CV3DE!%%HvT;84!Y)@D*U%L7_ z(2j|lhV>soIJY}A34N01ea3ylW>Y;pf!|IA$}LUq{ZdABEY7>Q!=NWrF!+SOi84@; zD&Ghlg{iQM!;ZA7los`0MuzBtluAtLY+5!8NePYWO5#|xNCUWr2b5`o6Fsw?IDX3s zNc-AiO79w8I=2-s*@WzN3PnI5>|ps1f$=)AszI`t4nPMILK_LmX7< zxeN}9AfcT?3<^?JGFhw06?mWxk6)dQ5w(#X`nA;vY_vanr5i^U@D;uwbd7fN9a`91 ztwW+C^c=}of!(9(h0NmP_YZ$iF2jei>nPVnLzZ3#J`vKESpD9PFqM9JkM%J^U%KjH z`uwm=6Q8yGpfWYbGdmZ>f#EwPbuj~0M$rA%p#PkRb*kieBP#bXvZ0UPEAiFC8B~AF z?D-6SIC}SSON-YrS^Dx1UN;V*rFSSE_TY-2oc?)z>-|AMM$oOntOuTsnq)cum*? z!QqZJQ#~N=kA$~NKuWC(&-wZpE_nv-y=$L zhswWzbL^t$btj6%UHBSx`>#8~U#yghwpp#NK!bmynQJn^G~Mr)$w}`xa8t#a+?jSd zGi-N;`gfYlR}Lpz>h7htBOT-(7cAJX;yx`?&sDdFK@ z3Ja+edsJkfYor}VDf)7di{+H!J-1MPsSkOX%2%MGfH@}iNARdGpK&LK!t$m2BE#vl zhQ>T2vww(rLbQwL)fzAA2Hf{0eL`De;rh|NN?&ARQ@`XLrc@3ph$T=~R zO%w?Rs<2^WmuuCKXQ##v-^ZW$(cBDAN6bjnOztk_QpvdBI)ZLZNO)<+`HC4b<4}v7 z`>M+M>A;{dLD9<{m+xb^ZfC*vi^S`;>hZnwQ$7OWci=e2qBkS697vvCd6;b4$#^bS#oxr9Gz zrzfG>=)e&3r!hG@sj3MRPVq`>m;qma|9j(iwr_~zzDMlzE%a0iCYG@0WBR;}%lc{( z>pU#Lm~7-oUE1=hO+(gdG~)AXTRgagvt&KtbRh@;$*HC5F`PESW`irrzcOi;G(=2L zch-@2FS2sh?PzB=wuF5~SYDw-Pw6t&(bJ}>#vgaK*d>fvZdGP9=(Ng7UmMRH1@4YZH%`ob8Gnt*KGhiZtD z4e@xDAn{zKwV;`le2%?Yf%i;Ws4Mdi?$_~~AxIfRG{&eu?iAe@?t?R<)3_0wQ56`h zHjn>Sp88}`Hyx6#|5OzIu485X_?++*cFyLU%P*`4taN5C^%7^0DNkOWgRIqUa#N@3O7*@wAyr{w89H05{+QEhD&hN^h#8{lqeh(7fcPSo9m&{CM3B`vK(z(8d zJ>8~gk1~~oFpAhiUbFpB<|(l1TM$Q7!!DwKu-us))J>oKRFPIA_XhrvEWRbme-eX| zZ*C;C$9SmKgq$j4BrE&=vzL<6p^f0sGJg)dypXF}%ChdV+1P3VzH`+Dj@#R0-QaWe z8Ek9hTyV*6La+Grf(sb<6;JkYSbket~o*l#mp@H>;o2&84ukT!6~&- z#M@?4sUyi=!UTB_(!up0#%Dbyt*=m+^49~h?yG3D!+crJ!)}Xif_w9hKi2&Pu5;x5 z7#&i`;vs7*`c=QsN0hie3nfNt_*o+-?A0>OPDVbnW)zH>jc7TO0Dn<2omzs{4BU5l zQGr0uO~Vw+g+1x{<-u{5J}lc(tb4}$C2zAxdHC;qj1BJs2Y3Rr23&sEaf2g9e6LY;P#^*hARdtxrA(J$gUCI*i=B+n@}F(VLweS(Id5Pg1+0DH zt#>wXE;&om9D}{Of5O&t!weOKP><^Uu+UI#8^xCH)7BA;=qOm%ineY{c?L(&!4C~* z&cgEGNds)2d|b!n%bih#ol;?h&EeDzlp$wiJ}61$6^vB&~aq$a}XyNJdq{99w zKUWjrO@#-#vw`1gbLbh)*Opc`wH7K}oRCqPGikOy z%t`O_RHP4tPA2xKCe(|Z9YpO=&K8O!*O;3v+7Ds!@;wf&6EhR|(YDr>%fZeP7Spfo zBmjLqyf@4LOK4hEKpo%IBDA+x(Cw)rDxy6*gm=8U%RfiLbK!1^52#CG9vp8=^}|d{ ztgcp)&TmlXf<>*gyX?0I4l#|O)z`{x=~MG%y(wd=QVv%LuX62r0alWpx^1^CS{xYKhOMa+!P{yiLVAZ^s9zS##Qj#>D@1GOZhe1fWH?Mu}=>^g$ zRI^-Qz0wv!;CXj1`_M7gx|(XnKjYzFq>G^fe{B#%Hcr-|X6<~LVA_=r9n?$TS?Laf zD}Ij9ZM~@bmQS7Zk4bj>#mkZZqQkW;?QtD}A}W z!#|)p=>)+mEPLx;u7W5UY-WD#cyP$87bWk=Hic&D- z6{K|~8+z2bZr8|Ps+s%I<@l=*%$Iny=B(ewyF^Exc0J%Ho0f}3&rFY>1(?`NM2T^j zA9((r2h;iy_%{qa@ACrtT$j#K1{G|;MMucENFV#HBJKg@7sB`|$$HIK)J(MaOaXP~@LEz0=EkONU%TSzB!B*HpS3z;Q;wlXa* zt7xzS!OGckNk%qZAD6TtCoTs)K<2!Z8W05^4g@u3R3{&4NxP0HPqjgsAkT(2fDfqGt1Q))8n(=E zxgB9jhM57mPcD>RCF1!5-^+W)7$2shaMwi|ayD0B(A69_IWc$8^W?$lCj9eQvUcws zKmP#Yd9_Uz)&lzAd51nm3gpfw1@jxM3@mDGp!HVYyBDG)MO?l?QroBWPCl}pP$L#M z;vx(m)&0xJeTNc7rCQthqvSRm?O=-`-ouWqxue0a{wpFe(E;sE4y&$7B}!eV z3%#53Njdc!c)ULWtHJ{1Od79HOBu|7kse-)i9@=ue*-XW~9#7D}Z6?VEi$v1CL^xxQ~!=_(UpHa>w`G)JOrcu7eIW+`*J{Y$l zajl!-wukwwpv#^5?&4ZXoyT1sl|smGJ3Q8mQ7 zs$Gc#T$npzY51MFWOiOoD{+e8%wDr6iL`L~;xKJ!6SK;9Q~2k3xznVy!2U~tjWBE&nJ>u|sx z2l8;Z=Y`|fxJEAtl0WJdHD#8=+7fmR1EzX;S3{VzNH+~F8%D8XbrarLL#?8Gu@Zi^ zii9poD{p;HkxjA-ufv1*GDgpNM*0Y{kquXCu~_0Zi{QQNsO#r0o|%|@W`C^OD0$B0 zF$K;FPyPLo7g)>ydv%=8Oa(b^AZ$l|>Q-)Sr^K)M89jfEX}Bj?8{byoPr2;ImT%4* zGuak*NO?&yoQbY%cE$ofD6NASz9b0>+mVErGvIN^%w?wv@9sGVZ61H_8=0^--yug> zUdxGwk}tcLQGHXDD=pJU_J`aP0txwD*pt9BbgtyAgm^q@A9%%Ryy*BbCy|0B{Kw}MB z;SI=$f;c1vvYoGrR^&o?l4;BYh;>|*b0x@{ra0#EbSo%?xW&L+5BU&lm~YDn@~?+w zQgvomOl^81fuU7q8A@X_NwbIoNNdAjLX`70$$eRfYcxA#cW3+a1%(4SQM7QzB8}_y zi3$l$zf=n8sF;v!!vWbn)yutxcC^jnB^W=}AC07>{8xwf@GNqjjm8f zeIr$Lnn>Lp^3zsrvjdwXJupttMG4eREj1nuQKo!^z`^3R1x=ErUad!Q|0 z_4+seKxgH!h+O$+`jM^_*q6x}^Lt9`wybd&MA*%u1AWOKwv76K*OqQvmK?#+e#De# zg8S6Gb}9f3*VP$%PwGCMxSG>7eT+&5M7;>Y7~LPx^vQ0qdO|A^RP&7gvE8!%YM&$v zgkjUljwM_ZISqFg6~MgpHKGux|FcA?BK5RW#F%_SgKro};Tel}3H=KEgon<`Z(lnd z(s|ZEo?juL160;1jaR;8UU;zVKr?QuI0|Xg(&jQG7qswt8bZy)du}{aG{;Rcb`hKf zYYEvE1gXZ=AZNUZ?=OfSy@zE(G8KIXD;w0$?UEiG7uSb0QiS=@80rIXsxR^sob6ls z+_1DR`_}uya7C_YV0+Sw%$&2z#;I$r4mEvn3(_I+r|>I9{0IRnO3!9pP@dDV@j52?An-s$JhrXYKfD0=4oA|juY=9K92=QKp*P2 ziiRt2{_TDOT_^>vAd~3tNxkdq>*d3Zz!NLg9q%A$<@c)$lL>ZHyG`y3a%DF$fpqvX z1dln=IrT`(G8(T~$Ql-RLP|M|2E@Gmc7f=w$}J<)QV$CI{Z|=klA=VlC$6W(!y7A` zFyAmkdwQk2Au!N(Bb>yhbGkARU+tmdwP+i$$F3lv24DVumpz0g$I4dPCy1NA;II*?WT^hZ&O>_1%j|#{sp+U&&AKt#2M#r#jml}yC+`{xd%-{BO4U$U)euxX6 z);Fyi{DH60#l?doeunagiIv9gy$cu?Xv($*+px(N~pQCD;tVR(XIr(h!f#b~ls z^*bqW1kZI2)2?b=3(~(2x3*37!P^6l4bO`>e3>HfDT{g!riuUM)?_jT+ zL}K!bf`Uahw)*|#BLFO*C!AOy!Rag4aw+)}PQD_EF>EYyPfw_|9)}i8ZdA>QsriX0 zY*R$?h}sRLKr4vy6#W%`B@}0K6Q}rRxwvyDcA=)`Y*fy|0X>Vo&5HfG7_6)fXluWe z(C<7{tGX!Oue4+=^4EbfCJ<7{ls>_3hj@QuG@gzUrIbvuF>~NQ9XSyJ;?U!or#w&w z8j0E8D_@~ilfRk6WocFk5M1kt!?9g zs^knjy^3~w8Ohi;c2)}WPa$TKBMN!G#8U>IiBZn zZtqf3hI5A>i-f)h5gSRXlYmhPlw4gMH@!vtzF26_T=1NYlAA+=E@n0GK)8==WAhtm zmT32g)DzRe{@o?B2`s`3$)K>2>j-slI=g~H4Y4BaClDWKzD!oG+aX^to8wxb?J;Qd zQ&1)pcDTy5Y_FijTL|of+n@rYu&>k}9(VWy>s-hZv>DppHMoB_v{-may5a z$U3#?qJOsn^8B$-r5{0%Ny>pyi%T&M5360h2(E;L$9`eV2V~MoX zzFsO)>&dn`3PZ>H+61oNThi;7O9Wy0QH$$#{99~R&i)c)EomF*n6ws(0$8ahxEtqi z^}vH?4Bta>_EXkACum|XmeE%>xhn1TvrT@e!myZj@$0KreD`f_ipYa0_rDz7Xh2_CB3XlH4$eK2--@ z3LS{v=2!wP^VX{w*d?YTe>|HQS)aeM(UHzmWm!#Q4y47zadBF8r93ib60&VV&U^sB*G;2o{D#QhmDR(|#J z=cQ}jD{>r;y92muAn#DN-xnOA-Gcvf+}p2})=kI`=aQO6!QqX zio-G|FtG8teE7n`y0NJfVR$I&W#3>Z6a$byvbHTWawLgVUmWmVRoZkQk&;7`F@8K{TT(&kUJ=Q+m10oE?KOodRvVYK@VBiQ3($s z0g?Jh?&|G$TJlpCyFg`L_;{}m3G!j&Q#rTuX_Ohc<>BmhGZne;cXxjE{p~!&R`|gaRE0J{r+GXl z$=wy;c%jCA=N;I6MuIUvQsjIiph^NK1dR;mFVVcYrKO>0W46;!X&a};DvZbo@58Z? zR~~n?4Aq<53I-1#C=1U|oA%Wq6+aHhvz*NL-St4r#WtY2l~Nm$?8#DnHGB$tOmw8G ztI3d-tDP4JjkuvvrK7ZH)Zk?rcQ*`)Qag1vv3(@3D$IDD9ZUD`Rb%lBnRCo2m*qGT zX0mH`FY3-aNAflsL{7&2aMvBNBh)pNM?dv65jO)GWqieRb z;`E}Clk!wF05Ydt4Cv<^O?bq(biXT5 z%iEj9eShts*3Dm&b$H61GhL>rjedw`mOH{ma-5XKM()0kP?|}cH|lc~tSK6_&F_=? zOT6z*guK^j8N2i8@!!lhFFKg>=R?4F$MR^o?a>PrkC}ZC=@-S@trvl1)q;jOHb-Syzxe; zgm5t!;ev+=%I@e1hY;0wI(nEB-W3b4SGW%uY%x)37fp`e4G!s$1G{4A;ZpPdC#mio zHR4k^viVb;;3{Ul%pH9nUuHe{Y%##Z1{eCz2Vd_oBSka&MCVt#dd$gnM(xb!s&C5a z5Ow7FhfCtc+B+woSQXZ507o$4KX7?gA3$Qv`l$uqfHRTjq`p7P_2Bn$f*3YZ;aKZ5 zu7%S`AN@HSrvM$k>(tkGLrbYMsCCIb*g={Wu4>X_F;8^wu+>bIwhDbb!8apLTh}GK zH|`U|f$LPvS(HC8+J|P=01NRGh9z(C%!|*fQ9UEtH?kV?hIHyA=GDjmDT`QPIva_Q zA)kw#$_tFZ^0+L%kwl-((V$6FpJHrK}9}$ddGXiWHdYIP#EPHTgneXL1uHY z@L%~m9e`8iL`a5Qxc(m9(8ZT?q-N{I5p5yfKjdbTmZ5}4 zOS22DHk7jN0_*dOgJ%zz5|3oIwI7>k9M$ZQzsrQ&g=5LFo*mLUgKH8YZWY~~X`glS zyZ+rJ72q#Z@{F(Q*aWcMK^tVze-h$OQ*dymj6Ow8t&IrcIou|R65AmW2VmAxcN!A}|@hS13|^dZTIW{f#N3=*wSobPQueS3#MzLAvC z5L0l(@8AqHJngV+$At-$syiD;3U8;h4W3<)O7-rf2UA+2`d6^=#6Vmi%4z_qBG(1! zgFt6}@Zrn82zU~A#lQPHr zv59m6NCMmT&~*dPqW1yd+(g*`=h*-=7zFQo+R-_YfRN14kD=g8>k;9RN`y$v;b@p8vJ!;HO8w;=#PCkq@)seP~4vU+TJ-U9O zzd(X+L5}RD&eF?*JY~0!z0HvD%D%0V7S)ss>ltm7*LwJIS9;aBg)HKJ{egM_5nZ7O z>YO(>!mnyp8}U|46QfIIuXl#QU)U`s_8&adn7J6Qh)9V=owjIvQNLQIVFU?NiJ6k+<^6%~6J zi#iAUGofES!w4fMR$fEkMJ#ECMh?1H21Scsn>TU?T^}B-Q8vf8<8L*UO@hQXZ==&s zjAp2cC~Bw!bHs3MU0IB=iUSG*Du&-fq?%J?R*4(gIei8bki`h~QXzO%i&bk(mYp~p zdDeFwiL-W|WsLHN7~w4+{`kloBz~zboa!r@O1qMLAvVRARzzFkwr7v-sYgFLAW<^RY$0wKxrt_)kg>zPlG zOBl{GRsp=!pG?NhiRP)!vld9^-e!-1eV7^O1~+z+_WR_jpWO@NHrM>bN8DH`!vhta z)ox4@gN}=;E((pvrly$#Pr);q7)VxY{bL41L;jU;w?@Tc8nzrcSmX^_{-oNMv~nwu zU8@`Vr2yh73!Dx%Xi8F*V2__|ir8I8mYMKMP1GWWK(JlkCY~+9(so_S|8j39{*G91 z6FilSB~PCMp>>y{6fkCeR9+K~yH%BQ9FaF~eTvpF<#A z)XXLbCZ2aYkQwD8b$?t0q1ox}yzq!UtAb}&Z21%G&+mDgf-yA=o|v0~Ng|j2n%v6w z62%Na1Z17j*iR9QUqa_0l8g4#geGwklY)HPZZ7N#r;qdKPJEv@;3DV{RS-W`S!|>=;|KJy1%SCmxA$KS$uLX}zN?0-X90N}a)$r(~B<&IlkY z!+!WJDNH*3q?<<|wL{w($ObvS3*Xc{nq_JaK9TbiQF@g*Q5lxsZfoO)sTc!2gb#T! zl{&}EBi=m>3^jZvfE^D#0YlcOTo?5n!l;UCd>ET3aUaPHnBj7`VRXDA?TmA`YU})i z2{={|-Z)`PQ->yB;OSxn_*bqViaodGdiDpQmVrR8yCp-Ls-Gh9GtjFGPCoWzgfIYV* z68-UjAoL13UUl9`{!njabLGGbH3Ficz)A0i<}EvAK!F<&%q67p;$b-HI3`?5&EzLK;)f&x8glbiuLNWt2=rW1n#Rf)k|F04afQVw z#I`{}chW*MMhafCt%td*f{8?t1lc$haw%SIVYonu8xcknP5J4TKAtca-(0;g@3F<| z+Br9%Ec)XOS8*9f@M{5ZPily4Y91PAAxlpKRqVkGv5~zMtkTM5dYmOe%7O9RhW;p+ zh_!c*{|Btxk2eVG6oNzE>R09)+NQfi7-xfVgf!78`yC{==B|MRtLmv@P(dhIFm^-QWkg}@H4=cCxJY_+53_-Jn}8Ev*=^=7XnlfCDS7OC%P z;d{<$uHo~^?xs!N@CX#Si|b{z4MCDAfn<{o{HY(3N3fZaVIU~u=$0DE;*@j9r^GL1 zhRsmb)2=pHpCVj-lRbiPjRhcmt4jW3M;z{zaoF4Dv*Hg(Qy9?Ss|WUS{p0sUF2!zC zBv0Tf>&BWDX^*^8*^qS|)Vx(I0aB7J8$*p&-QuEc{54w2#DFlNrS(9b=RL1QT;#e> zE8f1Xi#sa!YtrJm2Ns?1U0KQ%u6&3TH7PO`9cYCGn3v1ro33y=@KC{@WXkDBqBlM` zZ@{&zeoDH2&b5jiMUS@lq2^4-L^OI)!Q&lu_7Z4W4=!YK9@gE=aNpK>^&xv&z z%_fbG>knOqy|s*UAYlnDr8nT0^B%ay(CDnjkH)IbbH-B=)TJfKBJ+_QkRyb~iuCOo z!^pg8Nu7&LN(cJj`$vPahV^D?-BLa!UZVN@m$Fg=gxg9=jo6)TvO=iJt@^x!7~h ze@uS5nR5E|Q-Y?NT2P9)Y2Ch4$wo#S$_s75ep7v1XWq6|p-iAgjP&)VmS!Wo54N#h8yX98%r7H}*8=_Gg!t zk|~;E{K$w#5?zz@Tn!nd3v0#O-B1j~s0IKfz^8`2!ZBLVapTD+%#d2WVenpMii*{I zfnD2C?XGl03Ghj+-&Hb0{nC#`=Y*k2%{+T={yC;sK@FDZJEZX>8Z$zc6Lt?xgL+)q zD>==cF^GQKA`3^nFsL4Y#qzXt$avT$tdp`OE8R=pWpl{h|{55M&AzJMukb{Jr=UA~?4m|U=fpN36`xI*7GV*B1+no#Au_ca*PUou{h{2jt`oyfXjQwjd&{P_*oQxI$-(d#kO+;Xf zZ+U;*MgHqdUHuh?}jt3t1Hgh1-=cy*c0O8PEK} z2`rcbw?=Sj^IN$ZiK{4N65RyGNr^>Su=o4S?zS&Xl}7o8Qb7irjOom0Nrm(3s>ark zE_MRMtDw{5%v=zd?1%Vu7(Ub5(rAJY2@OagqaOrHdx-!=G(kw=8bv^2jPit3Qg0#0R#^8{Hyv|QawiKSH+9gf&NyIDnpaSEZ$?D*hps~8 z$8~cMn6a(&S*NN$ViexHjSzzl^{!0QRGoz<#uSxN*0-2bEf|JScL1!#@I5H zM3F48-JSd+rMAve^4tBrtp)^8;o?Ya@quKY9BPq^+=pzMszL})%s$$q56w@K#4c4Y zL|0jt>o{yS!vXUWa$x?me#E=ouj7Cn0U_SaiO#!_-@0&Lkzu?H7A=;Q`NQU=*ZT}} z`8$EIz(XpZ)PyszqThNLr>E@-V50~+tQMiZtsk*rJ30n96`NNwyIRLTSq{)I5!iNI z-1M#S`H?7WRgI_h6;pCMuw?3DBB~P!RLet_=RFXMP5l^+V1CJllP&+=Ai0aAfR(Bt z9*nD+ehk^+ZI>BE_C{6}#t-%K8k|djKydc$hweC)(b6t6cHN8Ty#g%5EFb;Q-9ODe zKl7?F>02HP=nAaxGe;|pUzE&#BBQeVjd##D{$wZ~IT4SYwZMJ#cuMEv@fFI%DpU2g z4T3GuB(;DGw;&_Neq8^$EmP;sD);sC8Ci|NL@bWtUd2t#6G z(c-4)ZebxZA|QA{+;dSR=ewi;>WNW;HY?UFBGrf%@cx-=b$kmUB^`aqm_TJ=YieBqn;{~S+`>Q~^@AUe*!yeE zUZOWHv~5B_6snc>o)qNS*s@m7?We?jSL9iKBc@=dQ+BV#K-}5PKD#oi-p^EiJA-n7 zL4Laz;~HGd_n}y#QKU{jD#C!q#4N#Q$EDqV1G6C;FF^)oz~ebSQ-$4>(4)jq@?ubH z>Z+D#uu<4xNxsnaQ!WkG?zBZ>9SZ2C*{KWs2&XkU_570hvdb1O#*DJb!>>7GfpYx z^@W*EcMjA(Cy|Yaiz^p~AACPx0I6t6du@;bA&zqO6hJjO+eoWljnZ&px#~zXJtG(3 z&(R*KEue-^W6f0+_II|Nb*BVwM29cmmzP~TkhEX}Zb5P;ob#uMbfMhZcr+QOut#u) zjwNI#7to z=ST@%LmacnSb;^SZLbV;|LB3}Ktmezb&!|UjlVf7Q>nwP5>3_%s^$mo2(DGg)Fy&V z?fAoYgwip<60#S6ASXDYb$XuU-h6*|V=<_fUv0{M6D7=CUW=yuK@df~yL-(%Gjf-? zp$lj>j75)NHb-%W7S(@&H4{Eh6DadBY;}ML)nuKx=ic{CFI9=eh2vrn18ke?Lh=hL z9T^ZiV`SH5*-o*=6}LsE74DlX2;{&7>n4+F1cB$*A*Hd3_>H^*Nrqo{k3BJS#~#6O zVyYK;|B%V{BUi+(JJtyxMMOatRI>6&4I>yZXNLg-SRW0wPK2G`sV?^`gBG<|S@M`R zjH`r*dyCIb(`j?9MDZ{?d3=2BGd;aM2u8vX1~=RsN%>=AoNtM0vGpO{1$ zc~)!csv)SVtI`dhLdIuT9+C{VwwGzqS5D6Es zzZdnvP`W4d?bZw=!p~oBBi)RLpZ(SREiDr$6Fz(__B@sHD1XDm>c1A6FjThk_+bNI zoa`_Ul)8E)%Yv~LT_DNYB)@lEp)%_GC<)PzX=#!j;hq{5lb5lv#u9;h?%5(d2MG8j zOBe#Ea7rZfV977ro!VY8I;YdAVVaja&Txw8h+R37BX86Fnec=C?AnblE#o6op1BSw*HU}l$&cdzn)9C0H9_diFTn|ua zPYvNVu|Y0UIUXgE+o=jw)@1{TUNFqgb=np&^zRS62BZkkyz|6blrRi{+|w#kZ;MOx zcImT3*O3!ju=WeX>@t;_H!BT_Cp$>5zwQ3n5hJxSWSp^!s$keWXF6vI370x(2(~JF zL7iKy+El63ZMjma%ipz5zBXpGZ8a~uKMBU@8qt>CE*&NLs4FJpi*iyYEai3`p9{e@ z!o+WNw=q(1TgxOfXj%M zMgXs!)=U!@jqSBN4<)aLGj%z4lv7cQj&Pl|;VvBFX_su!$U$*RY-2FBX z=fxn7;&P2*m!MwoY}Sm<86vijXsU)T;8u6xE%O#RN7`$3~G zdGaW+9ygNh*{jnwJ)`>+_L9~?Uhg*T_gb$^#;V$%XfaA8-33dehyZIl&yNzK?S8)n zyE$sdNiAN9LjE{d(>fjf99nl}-UtS|LMM5j^Z#U}G1>%Ri_4Q%5KK}o54B7Sr0*lb z)inUa*k1D=$Y)r7O^0OnZ`Y|GdyT4n$o(1PiQhOkMw5QFB>)%XhZ~2D-Z}D1KoLpG_#Y zIqr0_QjuW6+^>)*_K+rwcW73VK*xuW54J-@FMBL|zb7#*bu%M{^y#hrxBJ><;O!F3ZA*g({&Ph$Mb!yB z+QKT1~9{9O?xZwt=<7oi7U_?>{~EN@B-VAEOGvjPld+pNv8*vDG^W;zx= z-#;DeV_mjc?`>1luSk*@JdIF!f4)XLy92}|Lmmc*zemcu|aTB+&s;8gWoItSebQ#Ic2I%yRo^U837Zru4%-&ahiTPeDJ>- zR;w;<3iqo1eP1}DyR&h$gaG$;lzv5KT}!sGrFis+K2r8(N60PN$QB_MprKVE&%E<> zu4a`oC6%h;ly`Cdmwsh+Dw7fUhk27K3%0sLW6tV zOZ?2XVAvU|^#b!B5XX8s#Iv8xCY!FYdw}IO_?vg~^$(6B^EK`meptc*0I5sO`E!~a za{%%*V#WJcb}dVn+XKeuY)R|=5bhb0OMYu4Dh$Y_`*nh9Qfj^H1{5>Cu^edAKn|Ty zKc)+#d4I_e475S&0w0l6A!3(}(YT$L2(#Cf1K>;zE36aJOkGhf5RCmr$1XefgTDh! z?f2sS6f>05oRH6}#RqDJx~MI%>ZuZ|INIQuj@{BxNoSl6QKWtFgJ=xdCs)aAJ3%4E z2fH3JlZMV|3>lj=@BBOWuDOcCbybHp1F@<0ORc@;o|1oT>P+qIqO~{9m+cPlrsxn=^`D8^N0? zp!R5-nAx&lgs0W)_2dpQ5mORUuciht>)}b03hLN+`WcsGc0-rY`2Ik1FJoeN|Yfxv6tOz!87bd$+SqoyRT?~%)?8MS`V5JIC;g5WW}CbqKVqpZ2MfZ4Xbc_NA84Saomafi9-%XyH3N z*qhiQgk>lXWRBC5a8;OZ({bnpy2o<(_yMIzsW2I{q;m2~s+psuMvx_q&;tvrOW9+u zRX3FH2sTpHhas`Y2(EUu%A`J3b9hDEGtpE^Ff*xKH3~GKC>wuT2}6^ql}_}_6W0Hz z`N-i+ph3@k5g8YnNN2QY$2i4kio0huE;i1)t2E`g9S6>{+=-s{lcDS}*lbvGDICzV z+LjQFQ{_j1we}GdyHBGz)I(MkVm5UCeI?ywBtYiidR+y~yl_!S^YOyd>L-bTiKTh> zyvP3|Pa}GQv-ANPGB5UF#G&oOlZ!5!(jiF)-ifsGL<|3t9Jv#{cQ}E*$ae#3m1hCD zwh~EgrT)V(3PFhSu)B|6w`+-xHeuq%*`1-K*nV$X*j+OR>_v?^*0#)aCe;qXL6`k~ znIS$jsP)%V>8;ydC9NT@fm(U`2o3pEG%jrG-#!ru_SO)r!^D* zeGP8ravx7KQYMaPXSL&Wqry=!|8l4u;*ltGigf^oxdDi``ZO6Hw~+|*QQ;ti(Yp6* z85dBonWNErj3}e3vf`+F*Cq04BaR#-O}CN-6EPTp2Q0|t$nd3+C z&<7~@wKY`|os06tC+zOKV5;-|weC-X@hj^4Ow8IF>XhMWvhO2w0zHdwbMTSg5y=Z- zB1`B6<&cFlt6(UR=$LEyzQZ?`Ztx2qqrPR#t&6;#&dWya zI}Zgbtgz?x(aXz2l}NkOj7flBS%RJrBi57C8CKt3Ei$WLp|Q_u3R{al=$j^HHzKgb zaE&Y6sL3rYL>S_=r7GYOye$i(!}9L~;7;q3y<$L_`QT1tYTf7`;=>|R$uQnAIHARr zpGWJ05O7fzbpMt<<{V;zyD7rv>OlQ+Me4yA$5R2w^|M_Pnwy#g44nz}6*XslxipX|IPZgZ=e|o0++|r4h z1tV@wnt%h)bjpWZ*1f9gibuhul*_~GJpvAa3>4lQ1^_+owcHi)-8yeU3vjLH8^1I? z-gwC*SUyH$CQ2kg|B55n!idM|?IP;JhA4$NWavXvHml`8AD5LzOvg8DT`TbGB-;{L z$##tN`4n~gmJn72IPaJs7t1lr)aYh3&%;mBis>E%Gz68rTLHpJ_gDI6@ZHHOd!fY z6`(r&2q|3$cuEjGJ6(NqLk9~(BlxH9|Jz;Fah)r~!rJQZj3EZ5hIWSbruJ8%;dlSEpXt8#=+$mDroQz%!FLnAjN_0v(*KuXB_*16_cwKsQ4> z>t7DWx;h6-{-=YnIKV%R{XYj|u|xkl35ylL$#HcW*PrKN!Pwy!|BvTl6+0QKYEH&N zHaBHZ(eWpkk)53@^L=RqoJpM%k^Q)RXErxk8qx82aG6vA@0*_hj1C6lC`u4rlT zV;phMKjtwh%4-Ki;WPs#ErI(8o=+hHD9Fg$+X(Li0|UV%h@Wu_XfZ9!mv86{4SqIu z-4ENyOyJi*P|mm%$ly(Hd?(|R*n7_kfQmMNx3KU{YNx?z0L2%9#R=epHG+uzRCySr zs2GO|>U_C+V?j^`Fk+8KTB3}t0}!~0i424Jt0HjVl6;umx`}{h`x@nvzB_q7S@QE8 z9dw%@Kc5+6=u=c&xZha3hjy7GFNtwy5+y zdxlGh;yN{Z6u}#3Dv+j?<_iK1+}tKp5*FLs;?Y`X(zrz2$%-u=x>}5 zVBGv)c>E1)=N7jQHLfP0E-mbzIWBX5FK3Zvl*c@ae6Fj|OUzXl-%SIWJH18A9 z{&tibGfOAqRvQ@Ss7ok*=Z*qh-flsQFmy@tkT`$Z52ObEjfE(-uGr{07t@7ZJ6iNG=rY2{Jy*YDjX3XLmTT z6F?s_eUVTu_)8L^bWnRQ+mD|(ulZl}obbagi7vPUa^p7M2(3??k)3&`Hul)*;5@BB z8M$eKT2}7h!dKg{UM({ONJ1Jx-(&jrWchTx<>NjuDT^vqirK)dE$JgZjO zz%aw!r#u82aNFmX8gJk*=Wz}UkD?_+KI(mVQiQv9seYCn8|&wSP&-<6GXQa&$4|32 zap#x|V|V-b!n^50wB@|FqA2ec<~ky1X>Mk24uz-Ta_7w6@C3;IT1FsbKa=mXQ@O)y zFRQvs)-zw<641z-Id5tI<&Mkpi#2>70SW-1bqJu`H zA2yFFRTxQW)%G%&s-S0_-zB-s-LZXupNE~Z&(Uw7{5+_=}tC zwb(}?9&0o!VXILa(n$w168Ba_5;0;*c2eqR(RHjCxo%~EVkB%!G~T{k{>DYYQyJ$a zjf3_AqfVB14?X^6el$u@`bF#bSE9NPi#ZY}7ZQ)4bF81u(Qndy>SZ!|~`mz#%v%XA&Z#a{EG{Efp!L#kEseHvMIMekk#j{nJfJXt>6+!I;<~=^j+swSerx zczLxqMfp$L#_EP!4&EGn4lC?q4HtmwE3IGaH^Fk`P=H5T$~jnqHeu}$+4!N zSj!%^Aa{*Dn7w}x5%FTyNxK`e-r<;b2x|21qYq27A&XtzDqI)a?Xj8yQCtmYcE-Bb?YM8VM&ZL&=%`e!U-7$- z4f0FlnE8XuU2Pn!+X-@%khNr+U&B*t(Dqga^}HRwOI=}S4nNvmgzQtu%^ohWkebKd-*RhH7M#^@-3WE6)=nWRR7})SMf94& zQ!#3l_>z0qHkz`=rwE^}D#YU0s(L$7lbd8UM^1c7!L{3w1r5DNJ|DVe$eAP8L$+iH zNDpH=IMDkGD?qEVxNJRC!k3dRBZqHQF2NANH8QN)=LsM;#W;d_b`eSJ&1Ea!Vw0So# zMiz8)w87aG+Q`RZG0|_&@=dc@+wl580bP3RDvOSfs1hvKH74uFIAb?AaLtLmq#*1ETkf>7GK^gfn`{?8=7% zqFPqpLfwzTn+MhF+bB9y2yLB_SgWx}xoz6-`_qFT^=B4{ zeoFkdD`1+^E$;8IZRME8yE8m2r1N&~A>%n|vl;u?aaFX2W2|kl!Rko^?15->zvSn; z@+E=f{uV5KTNi(4Z9DI&m_5PhgtM>d_v{ za9-4A@P^xm(fU0K#92by;C;L8q#{7DQW*2LRL!*hZ5t;kx*ji`7qrTEFANq+cKBXI zFk_#2-n2g6)H@XLe0o>?xqH8-^jk!^+H7U6ucB6j@o6--gy)O0=^|b3Uh=)V)XRRy zH}_>lAQsWrr|MJI&1Rf2HvHlEv%28EdmG?+6oQwnQQRIGUFBIBg;=T3o7#9SbhXA% zKlj^FeREuGA2lp&gT4f}A6xZsHI2F^yw5y&vvOCE;x>7x3Jx~aD^iK0di0o{T6P5_ zF7b31&LQ`BoLvg3!)Y|@g!{*R_JwU5gg|*TmY}Wx4#v_~i{?v=(w<`e&u}7x)}lu*&basXhz0@6ya_K;hRp}t#a&Hd{F`03 zUzTof4Bb&WmAQc&{wY*Sb8WwRD23K4`D;Yw>utYjLfl+fVpMl0A4z%ox6#KcTlRYj zRUYl)9isz}TQ{^T7r<#%QYd_$z4|vdnb!_-X$qbYat6^)1Z|RZ;Uf?PVz#0|q~sx* znycMg<_rie*C4ru7Wb_2S2Xlv=LE&HRMOtdih-|>!zvBiLq9p?hf(MHExlY%a>|Kl z9gTaAJ{!KqoFEw~JmuLRC6e~w7FJ!VMIp1sE!l|Pvb76QA+@50Pp^0L7RXmR*jtfl z1L8vf0#WcBiPO31huta^f`~TWrIK%b5qZcDO!`H$EgD-4Bb*QC?_2UEq&2S{g6zeV zBQb4L(>n-@)d+)!15$j~nu35UM_whe4?hqMAOsMWbVkc2?D6z=5Rl607j$W}zCLaM z?ocQ>-o8(73)02&Yx=@^7Qh(pGcFq_ekgj{F&xUa)#qj)ybnv~rO=iEIGoif(i}%_ zC;GSFcWOM_UUHT zS|1SDz^_ViX!klPz<_X>gjQnfu2tL{6&B`GYEwd9vpa?ZaxL+s$4|owY`}DOl=d=Y zBt^LDiVgQ^GZaZwY=qBATN<;9%C)MV(7Ww)-Xy|rp)sW*j<@B>8JP9WZo)04sK?|& zEz(Q4U;c$yw&z&I@okoIW&9__7sV`r<-8702K%1F#GtD&-d>a?x|ozBx8r!ti*r(f zh-c@wg)l#JMzD)_dlREdkQEMv0|fE%(p4MryOTK15V$NqV)}8(t7of$ozaY=2*GRn z>(#YwI4kK#O50;PpgevL`=gZdY^3g~@6xzohhF^x@Yg#qq1rn_Uc*bmX7$7e)p>NXM9tgsm<2Q zcn%&CO5)bhLqFSNzqwg}Iq-YLG>;28?sGnjW?VDU8F3BIMlNtRFT)*uv zcLi_l!=&N}g!fa5NX1#E7`I_^X3)YsL8_bLja5CW5|+~t(~2fJTl=qs->->#I~1W_n5S z`0?I&pwBMqqLR7%+zl!6@-!vi3Y-I9gh#rgs5e9cLfr#6-`~ZH)q}|Q#%vL&mWU?F z$m!)7F;Rt(3Wp^Z81MO8;7R{T&wWa4Kmvt zobGaaOdGl)ywFEuIvLIH`XOelklB^@t=*U!GkY}*D%I|8g!TRo-hlsCJ?SQlKL4sR zS6v*n807uwGhuvIg^?V`hq&d`6Gk;`4<~RR-`I9xs^z!rTs<zBK;HQSeO+0AcXX7~tCB<3 zhUkVxoo7;qS-mIzF?{nVX&xLNH{V3P2@LC3oOhswX`OAB5TW;%*v`t8o(tUse8@_? zU0WN_%Koi2f5NU4CRLUu*0ys&eePSZdCU~U0PV&$d-yijT~_OLJf`qMNQ*MMyALHQ zIz02{yBIDnZtYHs8;&y)7#e~xabI2_-wxY6hrb)f5{(;vhsqBzhS(=pYjm%KXU-3W zB+Ey-UTl@HJfa<}oPy@^@pD`jH({jk26;?7T2&BU*Q9!xn{y~tEBZxNQUm$w1~s_Z zzO2&#oyM$0tY}FdyIWkA$Pdc{w_%StErV3m<^YG8oH5W^z6tEGa5!K#)x01q?@W<^ zHGDFddMXr{qh%L5y2fzBFC{6t2~4tVRB`-`VauNMLW4m#cxnfcN;|5^X_uSJ@3mH- zPKs~FlhjXJ3{B0=cHGu)-NW5TJo#j5i>AabZhE>{s?IlM%P72SN`Q5hsDJz%a1+|! zn1gBTR&wWv7h?7>nfejRyWmHQ-PLYZ0-gI@vuNI_tadI!bt`0yZZm zX)`CcYVIcN&NH0MFcrR)NiP;g>w3j`_YIjuCCOf%8zm%+QLm#geNsokyEL8h#X@kgv$X~bTDOKWQ9Qy8xu3l{Z zokYy{TM=rxH;fACn+o-m@W$mzybN95bO=E!3T=37WoLZjj{JODcO$R~6*D6v(z2nX-b_+$dt(>ZE7k8!(GxrgKU+4ise?()!bs|~ zIN}ZA-8v1U#~l~wFW=Nok|;e4CCUz2&#%!=`gChFbnweTe^-%&6=53}lBJlJm_U~B zg5Zs+*L3D1*3!;Z-y~KkY|cKN=;eJGhCW%tI?x}P(`6s`XSv~Qj!VjV=bnB%(`L?$ z|87|1kZN&FMnu5#sQv=8Bi_yW(>rz4<)Ic9FO(k><8_HQ&)W`Zf z!h7|j?d($X$YPE=cE3vJjMhTJnRCv?LmFhEF}LM4g|iHKGjl}_)i3d!Z{pKNGGfUE z5Nf&XUYrIyzAu9&yBaGgXt*6dF%ZVO@4=OnjOGLydK}dV7JrcN@yYJw<|D%lO#L^c ztfn$m75ob8u)*jZ#$A+~Rq6GL%R}i}6|e4@3Wu2Fj|y+yQOqif&q_0@DOiUBeN@>RrjqyzS zBUMyy>w1~qL5vze-wqaq6(U!YiX1fus@E3k#;s&iM5A)U z`|bo|fJN2`yW3*5Q?^o_UM<9JSC35@#aoJ?KzaKi=)OgK5}MIyHpQwa^ZnU5$+?%9 zx^eYtvQ#Xd_m^rKS&OP}R}7fHW`vv^?00*Lacr=+j`NAW4bK(OaXI4Zz1VwkWHFL= zqse(ld{`;zsHOYBIUaKt*KAgcu?>2U>D*{AfE?RFpF*>1j-&R@-D%_GK)h%Bgmep1 z0D_=|70$g5j>B*U#E1lg`Ke`Goru%uZ}b@bzUz6iy!($~hFGuoE+oUakz##3^aH^~zUM2GUVp5qxnKofPLj#R@gcRs8bok_e&|(#$#B}Avo9;PD*LG=r#~ir_>Tj zUt|b27^m3eo|nduEDZ)aGVT>P+i*QSG8*q#kl;3Gw~tl9Rmw{ok4q1oan_0mqG9DC zfqYFbkPNrjaEhj=irbaP3M_u8utd95{{c00C3_=GGwTCNra#xB!RyQZmu0sJdT?i< zL;h6$`GN-6QwefB&rn&LUX`kj=a3T030V>7b_-3?g1iUaYWe`1J}&)_MdgJ2PMI`! zip>W`o(4hhlE07i|FV%HGP$i3Y*)3<7s#;5K=g=ZUZxoPf>{ZB58LE(i^%yD1AcIH zm8-}e%7i@aD+l9@hnJB~A7|Q|QT=$D! zF%!T^O0aZczGThx(gP+r=-)3+dju$G8>iVjEi`X6fAz)aCa?FJ@KQ|r#X}AYc6w)q z=C>xjiHWy}9);esaZo=Ha)g`_tPZKWA2usFQrRw!RR&g|Bo~QWxs}JB+c0g9Bunxc z3ANqq`gnVF|8c|mtjO8at!)S!T7FqwZvWY*YQsj$2c7;eil^K+pWm=d)tE(}1#Bmp zuDxlR9MFmSuv^#vZOJ#V%`Gk_GCfx1vC&*D%r48sxlXPA<9bV$`v-Ro`>NUVI9OZJ zWfHR8ggI5gT{8QIA&Ak$hi@}(SxFW%)_O8eN1IihU8vbgTrY`umxr}$=D!3o`>pm` z_SYDlZVQFqZne}KAdT7)U3#K9^@c_q$+k8cNgqkMit%eZMb5iHiP*BSF+{ynDMAa= zjqu*&wrC~nqkD{4!@+!1cReqa>bFjm17`HQv#2WNt3Q)l#8-nx(|ny;hoYaVSm7*F z9~kwnEYGXPsnR6$=IpoVyi1}DY>d|+&7gc3qk`5)S6kKMPC_d0lGULcK4H;HHSadF z!4$`?FG}MY=P0+l^<1tvuJ&G>N2pgDAouo|_Upkklcw@}R=4dVHVU|0${tL+W7ly# ze?VwA0X+BMVK!Ty=8sz3#!z<}{zgAeKqdO1hKP_SkVb!hDQzMXF#M=}!)vRJRg_ag zk}g@Kd2(HDxoqWRP}a71KdY;%zLiurX#ri0W{OEAe@(JyaMLMf`TSl#xW$<#JfL=C?6a`dNx{rw5Oe9K zXWQ)IgPOf_xAJhNN5hCxZvBcTV5!yU5XHMInsq&=`Cfk0=>xbPEDp2!pG-f_O|KM) z8m1-(5O*+eGnJ@%x6!IHRh#5Sra(wAtP!(wIzA1un=qb@B$*hI#xC%B@#p)MlW+0t z$OBucp2;H)vq+W9bRv`;zYC*DwY0yZU?t4$MfQ2vwM|hEOru2*bAES})BMl{VHEZ` zoFEjJfI1cZwOF`H+~!KacsJuwmD$~8$g=~eKik|eS;dwgE*{^K0)?Qajm`Ih%Q)WomMAH8ur(i=l*~}6qWFdF(1n?n7KY{bqPpA05+!2{ z`0BnIatws#L8e5zq$*yN1eSP^d@yiw^WB0DUokZzL}6FTiyN$HSTHQ@I74163>N|JfI4FvOk&8Sgxh56ZhI+$fVzNO zOPj1e-E(VYjVQ9Dj!oyUb@Odf42TP6nVXlNdi`=C*`s-g)T^b1S*sF(l6>pu9h}3u z-X7Es^7p++?EOiCb!~xPZIW*Zp?<@3mVfAu)wX8T?C=m2C0+j^PfDvwk`UKJM|Lm% zWKDh7mvLiJ?F`3~`pA*VnH)E&;N4SO!16F5!@0mrFq@>71@j9dP#jJ-V2;-4>*z`s z&}?KY!)0&56A8U5j^Zc}iSfWYWC5IS?05q>`}O|YPDTZA@)o0?@+XR>*ptz zV#L}!N(=X6;?-J&Kz>*=WP`0*7A)?j5pokOqTsLS~Wa zv4M$|0Vz0OcdQ!UT$-5jG|)qj(n?|frDP)Gw%PMImUUaA%p`noW&T;hYH2A)B&7?x z78hFTI$A#_v1v-D#*zu6(ao}P3M0z)zKF~de+mXxpGQq$R*28lZKH77d!$O@PQO)0 zm>)_Us`G$jbMsat)HuDf^SasGT-w$T@`Z@mmiX#AKn!|wiZgF?lT$P%W-Ax_Ut`>R zLZ~G3d8dFIZHJrh6kFlmG36~+)c0%|X*CMA%y=JPwDj(=_~qUUFS5$>O+vUOqElF;C#0zap3=mfLMif4m%(G!NZxz{pH4(oC_l0X4s?Kvuu`WBwAf zWX+YHO=u8|^ZC7yUub#*A*Q9R4fdWbBNV!Y+ec>dbz6nX244inNj}*MBWF)CJO<-U zQ?r7(=X(T7RSFJtw^$5%I=VDjheNXv3ElDn(or($n1rwQ8zLCg7$Zk|U1l>7;roWU zq1hTQ>NQNb#TSIzas4Y`TaI0w9xjL4v~P;<_Yo%lj&Z~qjQq{AIg^u1VEd@@|93>7?Kl+G`Ojl1wXc1Yd_O(~L zM%$*WW3vVggHQ~FA70r@_9%jKo=RkgW!@b5^ImW~;{YMrt&TT9swWtu?B43qG{i|S zLEnZ`?HQ8M^w&@ESeDj%0>xsjzL4bl>$gcKByzL2c&Eq~r|pwx&OY5!_L{y&{q?ZX zqs=&9L8zWr#MV4MU6lwd+9ezOv4BKjT{b(lA=2Nz+utaVfGcs8*_(k3q7sBbHlit( z83|&?&ge68>3yK#@JQ3dUmT6@u_3oQo>gIL0J%ypUl&Awm(J@aN}guOuAUiM_r$nCmxvLn%JLQ{RJ?1Kdtr^jLWo19MCerwtZ5 zFCpb#d^=}vVX;iJ%5x$GJwud#7qegMJ8)q`A#u~e>!pyLv+T?4W)3Qr;;s20@Gd5%-ge*3AhDjBvb?2TII3MWPOhct;JGuS z<1yRu(3r;sVp!vJctT)!y#;AqLFID`wX-00Ngt)FsWuJ6v;F(KI=I+mTzIuNb~~TE3yGy_GCBW<)GtE1)b-^gI9p8UDC8^gJy0w_ z`MvJ3Cc`BndC(~Pzwqo>f8tvGH=doUf~1_9{J-$*{_oir!5lCEGbD| z)Lqf(^nnI&Zk#dD6wY_E0$KzA=CWO-Rq-!L(G8wjSrgtzAy z7ZLvp&qET~xq4TVd#$s3FlzpY$?>Y_;aAadLLTI;X?}cj7xWk0Yff#>)W*Hbvm5LY zY@rJ;X7>_e`80{q3BCRNIL--P%$MD3z}Od+NcPzufu54La^Bv(MFeh$b-&ZPR(0(ymRgnRujBq_=G7@?}Cw;LiP){?U`nmx0GjE(2ml?@Qv@ubEvLPtTG-#LCdq zSD;H7pj3Oag(>LG!1%-MvMe1&6f=arW)_6^5Cl}2o^pYexw4f$Wl3g&8AO*%>b;~1 zHk=gdEWt9hFFtPBJt-&yXnf|Ra#XM0b4PF>h=+JDX@5htdwG*<6BYm)Amxw%v(31bxSV3 zNxc%jsoC|PbxL(J+Uw|z^4cE+Rm38Kliy<)^oQve39H``KIVCj3~gqU=rG=O>#{n! zcu4Z8Y=daFOU>82J%waJF|AZLFl?)|m5Jy{4QU#4W+Ia88Yc?0)06z`xFq?&+lfRe ziz;i$os@5k9md5HU2`n;H&DgZU6FRH!zrbIEz>M9)T$ z?UX0jsYlteSapN7+OXPY6fT$U01L6F<({2yIUY1XM=$HUE)JCr8Y)LGrwlJoxlZ{% ztzTxnzVz?8z@a(ij~~6HG`I|=Ic)&DCgj(4Kdel%f?QP1(^uEsopeq494E9zR7dDS z6$GBjc-ws8qqQe)J-fg2aX4CfhrdMiE5)C&hwa~E4?N4Rqn?ASjUnJV9WdO#3+dV$ zidkDZ0D$5Ok}{9P7=@)3)WC3>^_B1{WZ0R)saJLY2s_+K$iu~^cJ>ZeK>;3h;K_NV zbpQSY4=O6A1`a0n0LT@|7+zML44&7%l@FFHd<_+vcaR2~JEG>Nw`PH@v6i-zpDR%ndxT z;)H0OW_8sKrT92%EKlN9!*5;fU#^OFXPd4y-jZ|wgf3O^HJ|i6EORghQ()&{p1pcL zpVy&LFFPOj)|E6EN7;#3Dl=#dOZqh5sQMu>nP`4Vm*2yXF#4`$nO)JoWfRRvXK6%ib3G*Zi7x1RaC+JY8K(=&DpK zcan&aNhHf3t>Oi>NBH1w2$y^zB=CuNPxt}DL)&D~F^+iPVHXCKi$f3UagxK|bA!tCEQ6BU(*eKsahE0iXYMydS>v!Bb@SQ_mm zWFD4Fx&*}&gLd5$i}0S6Da-IwbN8gVAodrF`*b(p)QBa`rr&_feCY{#Pg+j6%#DvA zWpUW>$#6%x_i`W6i8Jbjh-Ad^tCl^FhlC0#<+zqit^thB> zD@jzG-Y=NrpSDUoC3yLQ_2?G$o}KZ(Ko#;66#iKz{wJtDQc_k|7XEFqC~at9`X_8T zzC-aJ*s`<#jM}T0Fn*wx{rkHDz`xdCfa|r{uQ2}Z1o-%_T5*M5j-PG+0b}U@%hCZ3 z<5Ynw7>^6*F3h+PV`|^;#7Chz?xP8NUZiq&h+ZBOZC?1Ie%ENoc1-bg|GK`>RBC1C zl!yD6%n0h-nUgebd>XNp2!u*T2!iVQpPbNwq@lzZ|lGE&cdFWx<3Y9qeCWDt%;xo69 z`8$q3!*Totj^n;79GlCz(_#FBV;(q;&;P-3@7_0d?5$TDEJ=LW6?an(lqR(e@oISw z&r|)8^ZXa(2R8*KO{VVEJXiPvwD+v_cHK(HaJ3YX9UR;o*kXXLkEswdz11!fRbxRK z9=fb$E015tNt_6<+b6?eW0Slw6e5YTbQ7^v-a_6jK$n#YAaeS2jr}7sfQsd28S;am zIT-XadWyP%Mp=M-tQg@7HWVprggb=>Ng+O%ysy*T4OU{ZpMZ&Vqy-SiDhvp-w1|^5 z0*Wc?M4YPheDrQS)d~ZJ>GpjLReh3H=(UCN4e^YNN!|s&gAV=Nm0W8W7k-5Eor(VP z0pBMYp(-H?p=>RTp_J;&>#xvtD~x!86`Rj!npT32n?rVFC(wu9$eNe-ws`CJnc$BK zzPDa#LMN7PEjPM&Hq%94z4AZ~Q{OS(zIa+W>O4bOW#th2EHr^+S1pvyq%6oiXuCNm z7Kxv!*FfH+>_Sc;HFB{*iEP(&0FQkn!dxv{3mqFP0VgQ5igqvVqh?LQo#e^| z7nJnEWg)MxhyQOlb~}ELQ?v`}Q)+a^oxX8&kvin5so**MJnYh=L5t<_D~iq;_=%(r z4*!DVpF`UJhU3S|!eS~)|AONmH+{c_V9wvM%yIRi&fgLE>J_TLBJkBKUU2CCCoEq< z_m_}-PNypg ziw7NxIrkIq%196M(b<<)(CADKxjF}IDuDJU%-kN^n~b?_-xy$AK&DZ2REYHY*!%S% zN|wv1E$O7~7m8|~a=99J+x*6F@cZ+%3)J@EEUtl&_REQ`O)4gmw>(E(>opXya$nV zGRBxmWC*PC?I1kFJ8lNB!%uxw!VP*XA5wg7yfts96R2SURibGKyM?aXeMEpegc?F! z!se`Sg7SJ!h4WFI1*9WK)!gj|@sIIOcDYV8g=-T8jcml*{f91h+P}s(5 zsYIy1%y%4?EZTBY+{v`A>^u)0RJe0?vw*;^@?W6*^ILNN8Dx`1G*=+jg5HwJFXrJZ$RwdI>{#a9ig6O>u4 z)p84*fRo(ZAiTH6pq9KYSYL03-5@BcE);8+Gj;^v$klrCM{Smw9%_}T7OWD~@kBLq zo7bS{<6Og7X_#0I*;>}~atR=K4dZbIXv(3NvyIt_Wl8j5?^c}xWohI}Vf7gKsn%kU z*6CNjz#+*Bx=zEHv6D<^nuOTIa~L#Rleew8t1H$AG8gOXD(F*Am>N%^o79Dd*3Y-V zB@IUW6e5YpgczFb(i7?6mk*~6FlEQ*;(c6=K#W5Y5M#JQa~~ia&9qDb2lI+b*G*Ld zIUgtHJMiW8%_TU?=pishtAr#g3Be%oeuN&0gVX&XW8)(J8eb2D2JCRci~v>%A#ZK* zL`aMI9Rn0+2PVgvVdXiZImJ259SOaVAsDy0kZSRrUdlmGLnx?P_X4DMM&RtXzy4)x zjcrE-lT`XXH`-yhCg*HUJLgbW1AZDzlsFwZO!rlkUCI7jHi70`|$lvXk(=|4< zhwsW-JHl_-;eJEX)Zl7w^x6*i7U-3NU$*xC)FA8VU}0)?rR3Ub!1p`zYoh_+AGZMC z)kA*TBfS3RZ&OU+?@a?(f2=VC0pRb31Ldw>ZFT@~{AjA2ouLzabMm)FT?>BI1paZ8 z0@w7znt#>wV^=}O(8b{|OaJkE;9p$!wSnLtqaz6T(X4;d`or|^N`JTYm-%z8^GDMJ z0pE-MYmPKI`@AP^KTzwYVZ<+|`MuDj^R7n0V7R;I?RAXYYd z0Q>i;^<9&swT%rc2+Rr>Ul0D@<*&t@aPf5;wBQ&pwKR43&8S~=aMJ%gPvH+S{%ns8 ze6F*A;kNy#>-zIm-9H*(Z>aBJYHh`ERR(-LFaWwAvMbRK!~c@NFZ}g77I^F6&rPlo z^N+oMRrC3-$vj3{{%F@5)#CJenkFPx7x?A9X z{$sEHQ^7Ai`twAC@8iJ-1Nt2g|I9_`pLZ<6x(>P)*6?R9*Nz5o?SbGckiVS9-`M`% zbm)(%epOf+zRw3#vN!yzDEuNDLo31SA%Z`Hxa#Mh{0a05KYz7Z&d%DvQQy!GKqqY~ zsc5JQurV-#|6vBRu!C4wA@B!Cf{qR*)^>FCK*cK*!vDgzG0ot^5C2ct&QKS=K}jd2 zdv&EVoE!^)KLIkdvokcJ2TB^cI$PTr*wevvTo;7T7ZF_tLjau!*VX)n?_sioU=R)n zlo7;A3j)!?%fTD+--;;O>Dt&B8UTP|x)%0^@Xk^cRb>%#w6M_AwX(WuhoXt8Jplgu zy*>50RlidFhGC*05~8JHVD87@VD&Bd0zbhtp1X5 zfUdmnFES_y%Kk?=5a?>%`Aazv2z&*lUu7JitD*Tt1_g0&z^BeHGWfMH_?Z4CW99s# z4i4~@FZ!h%6a<0(ZZinP#`=4`AQ1bN3;*R>5C<6cy9^3qhy776h#dlV3BO(o?}9&U z=3s^Vi@vLf^!v5!f9iw&X)`;F?AccKB%jE`xBwUGuNDK%nfu z_bG^j?aE91S`Gxg0_d+YxVqo&&@Daaq@xRDG5ZE7m%E1YLIQd&S4k#G< zdmV85{uoR6Mj_|#?S+C^|C}38jz7l(%JIiMy=pGpqy1_#6bk!O#(DKLP)(4G!Pm^+DL#|G*^#0$2ZA9UO32 z{vm@`@rR6q{m*NmY#hJa$HB>VwRHKV9UL&WKl%>}V*3L#P!Q)I{uBz22)|~u{n4DB#@cmO~JQ#Zq_7yy*Fwsrtq z$7gu-ePm^14Y-b&S06+G8t|hz1i|px$sq<30t<3N;DatKDh2|HfLI|y@Zk~W1N`?Q bS^)S;!2W8zcpZDLK+A?fO)V-bhVg#@lqgIJ literal 0 HcmV?d00001 diff --git a/src/test/resources/pdfs/IEEE/ieee-paper.tex b/src/test/resources/pdfs/IEEE/ieee-paper.tex new file mode 100644 index 00000000000..62de1f28d8d --- /dev/null +++ b/src/test/resources/pdfs/IEEE/ieee-paper.tex @@ -0,0 +1,39 @@ +\documentclass[conference,a4paper,english]{IEEEtran}[2015/08/26] + +\usepackage{lipsum} +\usepackage{hyperref} +\usepackage[keeplastbox]{flushend} + +\begin{document} +\title{JabRef Example for Reference Parsing} +\author{% + \IEEEauthorblockN{First Author} + \IEEEauthorblockA{University of Examples, Germany\\ + \{lastname\}@example.org} +} + +\maketitle + +\begin{abstract} +\lipsum[1] +\end{abstract} + +\section{Introduction} +\lipsum[2] + +\section{Related Work} +\lipsum[3] +\cite{Alver2007,Alver2007a,Kopp2012,Kopp2018,Koenig2023} + +\section{Contribution} +\lipsum[4-7] + +\section{Conclusion and Outlook} +\lipsum[4] + +\atColsEnd{\vfil} + +\bibliographystyle{IEEEtran} +\bibliography{IEEEabrv,ieee-paper} + +\end{document} From 6f57cfe82fe0a0182745ce812b216455c46f2dfe Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 12 Mar 2024 22:41:29 +0100 Subject: [PATCH 14/26] Fix "Other fields" tab respecting custom tabs (#11012) * The entry editor respects the configured additional tabs when showing "Other fields". * Streamline code of OtherFieldsTab#determineFieldsToShow * Fix wording * Update CHANGELOG.md --- CHANGELOG.md | 1 + .../gui/entryeditor/OtherFieldsTab.java | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea79b7a3dc9..333e46dc805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We fixed an issue where the preview panel showing the wrong entry (an entry that is not selected in the entry table). [#9172](https://github.com/JabRef/jabref/issues/9172) - We fixed an issue where HTML-reserved characters like '&' and '<', in addition to HTML entities like '&' were not rendered correctly in entry preview. [#10677](https://github.com/JabRef/jabref/issues/10677) - The last page of a PDF is now indexed by the full text search. [#10193](https://github.com/JabRef/jabref/issues/10193) +- The entry editor respects the configured custom tabs when showing "Other fields". [#11012](https://github.com/JabRef/jabref/pull/11012) - The default owner of an entry can be changed again. [#10924](https://github.com/JabRef/jabref/issues/10924) - We fixed an issue where the duplicate check did not take umlauts or other LaTeX-encoded characters into account. [#10744](https://github.com/JabRef/jabref/pull/10744) - We fixed the colors of the icon on hover for unset special fields. [#10431](https://github.com/JabRef/jabref/issues/10431) diff --git a/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java index 4ed6b1434ad..52882b29f5a 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/OtherFieldsTab.java @@ -26,7 +26,6 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibEntryTypesManager; -import org.jabref.model.entry.field.BibField; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.InternalField; import org.jabref.model.entry.field.StandardField; @@ -36,7 +35,7 @@ public class OtherFieldsTab extends FieldsEditorTab { public static final String NAME = "Other fields"; - private final List customTabFieldNames; + private final List customTabsFieldNames; private final BibEntryTypesManager entryTypesManager; public OtherFieldsTab(BibDatabaseContext databaseContext, @@ -63,8 +62,8 @@ public OtherFieldsTab(BibDatabaseContext databaseContext, indexingTaskManager); this.entryTypesManager = entryTypesManager; - this.customTabFieldNames = new ArrayList<>(); - preferences.getEntryEditorPreferences().getDefaultEntryEditorTabs().values().forEach(customTabFieldNames::addAll); + this.customTabsFieldNames = new ArrayList<>(); + preferences.getEntryEditorPreferences().getEntryEditorTabs().values().forEach(customTabsFieldNames::addAll); setText(Localization.lang("Other fields")); setTooltip(new Tooltip(Localization.lang("Show remaining fields"))); @@ -76,15 +75,21 @@ protected SequencedSet determineFieldsToShow(BibEntry entry) { BibDatabaseMode mode = databaseContext.getMode(); Optional entryType = entryTypesManager.enrich(entry.getType(), mode); if (entryType.isPresent()) { + // Get all required and optional fields configured for the entry Set allKnownFields = entryType.get().getAllFields(); + // Remove all fields being required or optional SequencedSet otherFields = entry.getFields().stream() - .filter(field -> !allKnownFields.contains(field) && - !(field.equals(StandardField.COMMENT) || field instanceof UserSpecificCommentField)) + .filter(field -> !allKnownFields.contains(field)) .collect(Collectors.toCollection(LinkedHashSet::new)); - otherFields.removeAll(entryType.get().getDeprecatedFields(mode)); - otherFields.removeAll(entryType.get().getOptionalFields().stream().map(BibField::field).toList()); + // The key field is in the required tab, but has a special treatment otherFields.remove(InternalField.KEY_FIELD); - customTabFieldNames.forEach(otherFields::remove); + // Remove all fields contained in JabRef's tab "Deprecated" + otherFields.removeAll(entryType.get().getDeprecatedFields(mode)); + // Remove all fields contained in the custom tabs + customTabsFieldNames.forEach(otherFields::remove); + // Remove all user-comment fields (tab org.jabref.gui.entryeditor.CommentsTab) + otherFields.removeIf(field -> field.equals(StandardField.COMMENT)); + otherFields.removeIf(field -> field instanceof UserSpecificCommentField); return otherFields; } else { // Entry type unknown -> treat all fields as required (thus no other fields) From 85b3a766e022fda616ced461cdb68053d8c0fb79 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Tue, 12 Mar 2024 22:41:48 +0100 Subject: [PATCH 15/26] Remove obsolete "Comments" tab configuration (#11011) * Remove obsolete "Comments" tab configuration * Fix typo * Add migration * Add version info --------- Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> --- .../jabref/migrations/PreferencesMigrations.java | 13 ++++++++++++- .../org/jabref/preferences/JabRefPreferences.java | 11 ++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/jabref/migrations/PreferencesMigrations.java b/src/main/java/org/jabref/migrations/PreferencesMigrations.java index e9a673f4b3b..e9f30abc050 100644 --- a/src/main/java/org/jabref/migrations/PreferencesMigrations.java +++ b/src/main/java/org/jabref/migrations/PreferencesMigrations.java @@ -16,6 +16,8 @@ import javafx.scene.control.TableColumn; +import org.jabref.gui.entryeditor.CommentsTab; +import org.jabref.gui.entryeditor.EntryEditor; import org.jabref.gui.maintable.ColumnPreferences; import org.jabref.gui.maintable.MainTableColumnModel; import org.jabref.logic.citationkeypattern.GlobalCitationKeyPattern; @@ -57,13 +59,14 @@ public static void runMigrations(JabRefPreferences preferences, BibEntryTypesMan addCrossRefRelatedFieldsForAutoComplete(preferences); upgradePreviewStyle(preferences); // changeColumnVariableNamesFor51 needs to be run before upgradeColumnPre50Preferences to ensure - // backwardcompatibility, as it copies the old values to new variable names and keeps th old sored with the old + // backward compatibility, as it copies the old values to new variable names and keeps th old sored with the old // variable names. However, the variables from 5.0 need to be copied to the new variable name too. changeColumnVariableNamesFor51(preferences); upgradeColumnPreferences(preferences); restoreVariablesForBackwardCompatibility(preferences); upgradeCleanups(preferences); moveApiKeysToKeyring(preferences); + removeCommentsFromCustomEditorTabs(preferences); } /** @@ -545,4 +548,12 @@ static void moveApiKeysToKeyring(JabRefPreferences preferences) { } } } + + /** + * The tab "Comments" is hard coded using {@link CommentsTab} since v5.10 (and thus hard-wired in {@link EntryEditor#createTabs()}. + * Thus, the configuration ih the preferences is obsolete + */ + static void removeCommentsFromCustomEditorTabs(JabRefPreferences preferences) { + preferences.getEntryEditorPreferences().getEntryEditorTabs().remove("Comments"); + } } diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index d4a9e64016c..1da58ce0989 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.SequencedMap; import java.util.Set; import java.util.TreeSet; import java.util.UUID; @@ -835,10 +836,6 @@ public void setLanguageDependentDefaultValues() { defaults.put(CUSTOM_TAB_FIELDS + "_def1", StandardField.ABSTRACT.getName()); defaults.put(CUSTOM_TAB_NAME + "_def1", Localization.lang("Abstract")); - // Entry editor tab 2: Comments Field - used for research comments, etc. - defaults.put(CUSTOM_TAB_FIELDS + "_def2", StandardField.COMMENT.getName()); - defaults.put(CUSTOM_TAB_NAME + "_def2", Localization.lang("Comments")); - defaults.put(EMAIL_SUBJECT, Localization.lang("References")); } @@ -1508,7 +1505,7 @@ private Map> getEntryEditorTabs() { List tabFields = getSeries(CUSTOM_TAB_FIELDS); if (tabNames.isEmpty() || (tabNames.size() != tabFields.size())) { - // Nothing set, so we use the default values + // Nothing set (or wrong configuration), then we use default values tabNames = getSeries(CUSTOM_TAB_NAME + "_def"); tabFields = getSeries(CUSTOM_TAB_FIELDS + "_def"); } @@ -1543,8 +1540,8 @@ private void storeEntryEditorTabs(Map> customTabs) { getEntryEditorTabs(); } - private Map> getDefaultEntryEditorTabs() { - Map> customTabsMap = new LinkedHashMap<>(); + private SequencedMap> getDefaultEntryEditorTabs() { + SequencedMap> customTabsMap = new LinkedHashMap<>(); int defNumber = 0; while (true) { From ab490d05f84f7d82368f37edc16ff88cf81be8eb Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Wed, 13 Mar 2024 13:03:33 +0100 Subject: [PATCH 16/26] Fix test names (#11014) --- .../org/jabref/model/entry/BibEntryTypesManagerTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java b/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java index e736a1b8b90..c47f9abde47 100644 --- a/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java +++ b/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java @@ -140,7 +140,7 @@ void standardTypeIsStillAccessibleIfOverwritten(BibDatabaseMode mode) { @ParameterizedTest @EnumSource(BibDatabaseMode.class) - void sModifyingArticle(BibDatabaseMode mode) { + void modifyingArticle(BibDatabaseMode mode) { overwrittenStandardType = new BibEntryType( StandardEntryType.Article, List.of(new BibField(StandardField.TITLE, FieldPriority.IMPORTANT), @@ -155,7 +155,7 @@ void sModifyingArticle(BibDatabaseMode mode) { @ParameterizedTest @EnumSource(BibDatabaseMode.class) - void sModifyingArticleWithParsing(BibDatabaseMode mode) { + void modifyingArticleWithParsing(BibDatabaseMode mode) { overwrittenStandardType = new BibEntryType( StandardEntryType.Article, List.of(new BibField(StandardField.TITLE, FieldPriority.IMPORTANT), @@ -173,7 +173,7 @@ void sModifyingArticleWithParsing(BibDatabaseMode mode) { @ParameterizedTest @EnumSource(BibDatabaseMode.class) - void sModifyingArticleWithParsingKeepsListOrder(BibDatabaseMode mode) { + void modifyingArticleWithParsingKeepsListOrder(BibDatabaseMode mode) { overwrittenStandardType = new BibEntryType( StandardEntryType.Article, List.of(new BibField(StandardField.TITLE, FieldPriority.IMPORTANT), From 45035df5cb2dd2636c24f48a03baa574d50f4ef7 Mon Sep 17 00:00:00 2001 From: xinhangzhou <123058040+xinhangzhou@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:16:11 +0800 Subject: [PATCH 17/26] chore: remove repetitive words (#11015) Signed-off-by: xinhangzhou --- config/README.md | 2 +- .../java/org/jabref/logic/importer/fetcher/DBLPFetcher.java | 2 +- src/main/java/org/jabref/model/entry/BibEntry.java | 2 +- src/main/java/org/jabref/model/entry/field/OrFields.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/README.md b/config/README.md index d0a6bf89e97..0bea959d5f2 100644 --- a/config/README.md +++ b/config/README.md @@ -20,7 +20,7 @@ Style-checks are done for each pull request and installing this code style confi # Eclipse: The Eclipse code formatter style is stored in the `eclipse.gradle` file and gets imported automatically. -In case the formatter style needs to be adapted, configure it and export in in eclipse. +In case the formatter style needs to be adapted, configure it and export in eclipse. 1. Right click on the eclipse project "JabRef" 2. Select "Export > General > Preferences" diff --git a/src/main/java/org/jabref/logic/importer/fetcher/DBLPFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/DBLPFetcher.java index 1eef08ff5f6..2c67443bb6a 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/DBLPFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/DBLPFetcher.java @@ -68,7 +68,7 @@ public void doPostCleanup(BibEntry entry) { FieldFormatterCleanups cleanups = new FieldFormatterCleanups(true, List.of( new FieldFormatterCleanup(StandardField.TIMESTAMP, new ClearFormatter()), - // unescape the the contents of the URL field, e.g., some\_url\_part becomes some_url_part + // unescape the contents of the URL field, e.g., some\_url\_part becomes some_url_part new FieldFormatterCleanup(StandardField.URL, new LayoutFormatterBasedFormatter(new RemoveLatexCommandsFormatter())) )); cleanups.applySaveActions(entry); diff --git a/src/main/java/org/jabref/model/entry/BibEntry.java b/src/main/java/org/jabref/model/entry/BibEntry.java index a8c940fb3bb..56fc9302796 100644 --- a/src/main/java/org/jabref/model/entry/BibEntry.java +++ b/src/main/java/org/jabref/model/entry/BibEntry.java @@ -572,7 +572,7 @@ public Optional getFieldOrAlias(Field field) { } /** - * Return the LaTeX-free contents of the given field or its alias an an Optional + * Return the LaTeX-free contents of the given field or its alias an Optional *

* For details see also {@link #getFieldOrAlias(Field)} * diff --git a/src/main/java/org/jabref/model/entry/field/OrFields.java b/src/main/java/org/jabref/model/entry/field/OrFields.java index 5a49202d718..f0b456916d2 100644 --- a/src/main/java/org/jabref/model/entry/field/OrFields.java +++ b/src/main/java/org/jabref/model/entry/field/OrFields.java @@ -10,7 +10,7 @@ /** * Represents a choice between two (or more) fields or any combination of them. *

- * Example is that a BibEntry requires either an author or an editor, but both can be be present. + * Example is that a BibEntry requires either an author or an editor, but both can be present. */ public class OrFields implements Comparable { From ff1195d093cf5f0111f6aa34fbb18e70f6305338 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Wed, 13 Mar 2024 23:03:56 +0100 Subject: [PATCH 18/26] Change to rolling logs (#11023) * Change to rolling logs Co-authored-by: Carl Christian Snethlage * Add link to PR --------- Co-authored-by: Carl Christian Snethlage --- CHANGELOG.md | 1 + src/main/java/org/jabref/Launcher.java | 18 ++++++++---------- src/main/resources/l10n/JabRef_en.properties | 2 -- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 333e46dc805..96db4a75227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We changed the duplicate handling in the Import entries dialog. Potential duplicate entries are marked with an icon and importing will now trigger the merge dialog [#10914](https://github.com/JabRef/jabref/pull/10914) - We made the command "Push to TexShop" more robust to allow cite commands with a character before the first slash. [forum#2699](https://discourse.jabref.org/t/push-to-texshop-mac/2699/17?u=siedlerchr) - We only show the notification "Saving library..." if the library contains more than 2000 entries. [#9803](https://github.com/JabRef/jabref/issues/9803) +- JabRef now keeps previous log files upon start. [#11023](https://github.com/JabRef/jabref/pull/11023) - We enhanced the dialog for adding new fields in the content selector with a selection box containing a list of standard fields. [#10912](https://github.com/JabRef/jabref/pull/10912) - We store the citation relations in an LRU cache to avoid bloating the memory and out-of-memory exceptions. [#10958](https://github.com/JabRef/jabref/issues/10958) - Keywords filed are now displayed as tags. [#10910](https://github.com/JabRef/jabref/pull/10910) diff --git a/src/main/java/org/jabref/Launcher.java b/src/main/java/org/jabref/Launcher.java index ba015659d03..a8926ae2e3e 100644 --- a/src/main/java/org/jabref/Launcher.java +++ b/src/main/java/org/jabref/Launcher.java @@ -11,7 +11,6 @@ import java.util.List; import java.util.Map; -import org.jabref.architecture.AllowedToUseStandardStreams; import org.jabref.cli.ArgumentProcessor; import org.jabref.cli.JabRefCLI; import org.jabref.gui.Globals; @@ -19,7 +18,6 @@ import org.jabref.logic.UiCommand; import org.jabref.logic.journals.JournalAbbreviationLoader; import org.jabref.logic.journals.predatory.PredatoryJournalListLoader; -import org.jabref.logic.l10n.Localization; import org.jabref.logic.net.ProxyAuthenticator; import org.jabref.logic.net.ProxyPreferences; import org.jabref.logic.net.ProxyRegisterer; @@ -48,7 +46,6 @@ * - Handle the command line arguments * - Start the JavaFX application (if not in cli mode) */ -@AllowedToUseStandardStreams("Direct output to the user") public class Launcher { private static Logger LOGGER; private static boolean isDebugEnabled; @@ -140,11 +137,13 @@ private static void addLogToDisk() { // The "Shared File Writer" is explained at // https://tinylog.org/v2/configuration/#shared-file-writer Map configuration = Map.of( - "writerFile", "shared file", - "writerFile.level", isDebugEnabled ? "debug" : "info", "level", isDebugEnabled ? "debug" : "info", - "writerFile.file", directory.resolve("log.txt").toString(), - "writerFile.charset", "UTF-8"); + "writerFile", "rolling file", + "writerFile.level", isDebugEnabled ? "debug" : "info", + "writerFile.file", directory.resolve("log_{date:yyyy-MM-dd_HH-mm-ss}.txt").toString(), + "writerFile.charset", "UTF-8", + "writerFile.policies", "startup", + "writerFile.backups", "30"); configuration.forEach(Configuration::set); initializeLogger(); @@ -169,9 +168,8 @@ private static boolean handleMultipleAppInstances(String[] args, RemotePreferenc LOGGER.debug("Passing arguments passed on to running JabRef..."); if (remoteClient.sendCommandLineArguments(args)) { // So we assume it's all taken care of, and quit. - LOGGER.debug("Arguments passed on to running JabRef instance."); - // Used for script-use output etc. to the user - System.out.println(Localization.lang("Arguments passed on to running JabRef instance. Shutting down.")); + // Output to both to the log and the screen. Therefore, we do not have an additional System.out.println. + LOGGER.info("Arguments passed on to running JabRef instance. Shutting down."); return false; } else { LOGGER.warn("Could not communicate with other running JabRef instance."); diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 9b6b0a31c45..e6c1342058c 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -77,8 +77,6 @@ Application\ to\ push\ entries\ to=Application to push entries to Apply=Apply -Arguments\ passed\ on\ to\ running\ JabRef\ instance.\ Shutting\ down.=Arguments passed on to running JabRef instance. Shutting down. - Assign\ the\ original\ group's\ entries\ to\ this\ group?=Assign the original group's entries to this group? Assigned\ %0\ entries\ to\ group\ "%1".=Assigned %0 entries to group "%1". From 2207b79b73e62729b4136bfeed85bce510fabec5 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Thu, 14 Mar 2024 17:34:06 +0100 Subject: [PATCH 19/26] Fix log file path on Windows (#11028) --- src/main/java/org/jabref/Launcher.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jabref/Launcher.java b/src/main/java/org/jabref/Launcher.java index a8926ae2e3e..8f1ea3229c7 100644 --- a/src/main/java/org/jabref/Launcher.java +++ b/src/main/java/org/jabref/Launcher.java @@ -140,7 +140,8 @@ private static void addLogToDisk() { "level", isDebugEnabled ? "debug" : "info", "writerFile", "rolling file", "writerFile.level", isDebugEnabled ? "debug" : "info", - "writerFile.file", directory.resolve("log_{date:yyyy-MM-dd_HH-mm-ss}.txt").toString(), + // We need to manually join the path, because ".resolve" does not work on Windows, because ":" is not allowed in file names on Windows + "writerFile.file", directory + File.separator + "log_{date:yyyy-MM-dd_HH-mm-ss}.txt", "writerFile.charset", "UTF-8", "writerFile.policies", "startup", "writerFile.backups", "30"); From 5cb70df94c91503744744013562457fc07aa390d Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Thu, 14 Mar 2024 18:25:24 +0100 Subject: [PATCH 20/26] Clean up defintions of entry types (#11013) * Clean up defintions of entry types Also reorderer to required/optionalImport/optionalDetail * More comments * Add "Publisher" * Adapt naming important/detail also to methods * Add test * Add log output when using customized type * Fix test method names * Add another test case * Fix names * Revert adding "publisher" to article * Reorder with... * Fix test --- ...2Tab.java => DetailOptionalFieldsTab.java} | 24 +- .../jabref/gui/entryeditor/EntryEditor.java | 26 +- ...b.java => ImportantOptionalFieldsTab.java} | 24 +- .../entryeditor/OptionalFieldsTabBase.java | 12 +- .../org/jabref/model/entry/BibEntryType.java | 26 +- .../model/entry/BibEntryTypeBuilder.java | 59 +++- .../model/entry/BibEntryTypesManager.java | 6 + .../model/entry/field/FieldPriority.java | 6 +- .../jabref/model/entry/field/OrFields.java | 6 + .../types/BiblatexEntryTypeDefinitions.java | 313 ++++++++---------- .../BiblatexSoftwareEntryTypeDefinitions.java | 18 +- .../types/BibtexEntryTypeDefinitions.java | 3 + .../model/entry/types/EntryTypeFactory.java | 2 +- .../types/IEEETranEntryTypeDefinitions.java | 2 +- .../model/entry/BibEntryTypeBuilderTest.java | 41 +++ .../model/entry/BibEntryTypesManagerTest.java | 6 + .../BiblatexAPAEntryTypeDefinitionsTest.java | 13 + .../BiblatexEntryTypeDefinitionsTest.java | 13 + ...latexSoftwareEntryTypeDefinitionsTest.java | 12 + .../types/BibtexEntryTypeDefinitionsTest.java | 26 ++ .../IEEETranEntryTypeDefinitionsTest.java | 12 + ...reReviewStudyEntryTypeDefinitionsTest.java | 12 + 22 files changed, 400 insertions(+), 262 deletions(-) rename src/main/java/org/jabref/gui/entryeditor/{OptionalFields2Tab.java => DetailOptionalFieldsTab.java} (57%) rename src/main/java/org/jabref/gui/entryeditor/{OptionalFieldsTab.java => ImportantOptionalFieldsTab.java} (56%) create mode 100644 src/test/java/org/jabref/model/entry/BibEntryTypeBuilderTest.java create mode 100644 src/test/java/org/jabref/model/entry/types/BiblatexAPAEntryTypeDefinitionsTest.java create mode 100644 src/test/java/org/jabref/model/entry/types/BiblatexEntryTypeDefinitionsTest.java create mode 100644 src/test/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitionsTest.java create mode 100644 src/test/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitionsTest.java create mode 100644 src/test/java/org/jabref/model/entry/types/IEEETranEntryTypeDefinitionsTest.java create mode 100644 src/test/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitionsTest.java diff --git a/src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java b/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java similarity index 57% rename from src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java rename to src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java index c63c145cfba..e884d5b297d 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OptionalFields2Tab.java +++ b/src/main/java/org/jabref/gui/entryeditor/DetailOptionalFieldsTab.java @@ -14,21 +14,21 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.preferences.PreferencesService; -public class OptionalFields2Tab extends OptionalFieldsTabBase { +public class DetailOptionalFieldsTab extends OptionalFieldsTabBase { public static final String NAME = "Optional fields 2"; - public OptionalFields2Tab(BibDatabaseContext databaseContext, - SuggestionProviders suggestionProviders, - UndoManager undoManager, - DialogService dialogService, - PreferencesService preferences, - StateManager stateManager, - ThemeManager themeManager, - IndexingTaskManager indexingTaskManager, - BibEntryTypesManager entryTypesManager, - TaskExecutor taskExecutor, - JournalAbbreviationRepository journalAbbreviationRepository) { + public DetailOptionalFieldsTab(BibDatabaseContext databaseContext, + SuggestionProviders suggestionProviders, + UndoManager undoManager, + DialogService dialogService, + PreferencesService preferences, + StateManager stateManager, + ThemeManager themeManager, + IndexingTaskManager indexingTaskManager, + BibEntryTypesManager entryTypesManager, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository) { super( Localization.lang("Optional fields 2"), false, diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 436f62cd303..e31448fbb1e 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -249,45 +249,45 @@ private void navigateToNextEntry() { private List createTabs() { entryEditorTabs.add(new PreviewTab(databaseContext, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), taskExecutor)); - // Required, optional, deprecated, and "other" fields + // Required, optional (important+detail), deprecated, and "other" fields entryEditorTabs.add(new RequiredFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); - entryEditorTabs.add(new OptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); - entryEditorTabs.add(new OptionalFields2Tab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); + entryEditorTabs.add(new ImportantOptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); + entryEditorTabs.add(new DetailOptionalFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); entryEditorTabs.add(new DeprecatedFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); entryEditorTabs.add(new OtherFieldsTab(databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), bibEntryTypesManager, taskExecutor, journalAbbreviationRepository)); // Comment Tab: Tab for general and user-specific comments entryEditorTabs.add(new CommentsTab(preferencesService, databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), taskExecutor, journalAbbreviationRepository)); - // General fields from preferences - // First, remove all tabs that are already handled above or below; except for the source tab (which has different titles for BibTeX and BibLaTeX mode) + // The preferences allow to configure tabs to show (e.g.,"General", "Abstract") + // These should be shown. Already hard-coded ones should be removed. Map> entryEditorTabList = new HashMap<>(entryEditorPreferences.getEntryEditorTabs()); entryEditorTabList.remove(PreviewTab.NAME); entryEditorTabList.remove(RequiredFieldsTab.NAME); - entryEditorTabList.remove(OptionalFieldsTab.NAME); - entryEditorTabList.remove(OptionalFields2Tab.NAME); + entryEditorTabList.remove(ImportantOptionalFieldsTab.NAME); + entryEditorTabList.remove(DetailOptionalFieldsTab.NAME); entryEditorTabList.remove(DeprecatedFieldsTab.NAME); + entryEditorTabList.remove(OtherFieldsTab.NAME); entryEditorTabList.remove(CommentsTab.NAME); entryEditorTabList.remove(MathSciNetTab.NAME); entryEditorTabList.remove(FileAnnotationTab.NAME); + entryEditorTabList.remove(SciteTab.NAME); + // CitationRelationsTab entryEditorTabList.remove(RelatedArticlesTab.NAME); + // SourceTab -- not listed, because it has different names for BibTeX and biblatex mode entryEditorTabList.remove(LatexCitationsTab.NAME); entryEditorTabList.remove(FulltextSearchResultsTab.NAME); - entryEditorTabList.remove(SciteTab.NAME); - entryEditorTabList.remove("Comments"); - // Then show the remaining configured + for (Map.Entry> tab : entryEditorTabList.entrySet()) { entryEditorTabs.add(new UserDefinedFieldsTab(tab.getKey(), tab.getValue(), databaseContext, libraryTab.getSuggestionProviders(), undoManager, dialogService, preferencesService, stateManager, themeManager, libraryTab.getIndexingTaskManager(), taskExecutor, journalAbbreviationRepository)); } - // "Special" tabs entryEditorTabs.add(new MathSciNetTab()); entryEditorTabs.add(new FileAnnotationTab(libraryTab.getAnnotationCache())); entryEditorTabs.add(new SciteTab(preferencesService, taskExecutor, dialogService)); entryEditorTabs.add(new CitationRelationsTab(entryEditorPreferences, dialogService, databaseContext, undoManager, stateManager, fileMonitor, preferencesService, libraryTab, taskExecutor)); entryEditorTabs.add(new RelatedArticlesTab(entryEditorPreferences, preferencesService, dialogService, taskExecutor)); - sourceTab = new SourceTab( databaseContext, undoManager, @@ -299,9 +299,7 @@ private List createTabs() { bibEntryTypesManager, keyBindingRepository); entryEditorTabs.add(sourceTab); - entryEditorTabs.add(new LatexCitationsTab(databaseContext, preferencesService, taskExecutor, dialogService)); - entryEditorTabs.add(new FulltextSearchResultsTab(stateManager, preferencesService, dialogService, taskExecutor)); return entryEditorTabs; diff --git a/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/ImportantOptionalFieldsTab.java similarity index 56% rename from src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java rename to src/main/java/org/jabref/gui/entryeditor/ImportantOptionalFieldsTab.java index ad522d2411f..5258477d3f4 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/ImportantOptionalFieldsTab.java @@ -14,21 +14,21 @@ import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.preferences.PreferencesService; -public class OptionalFieldsTab extends OptionalFieldsTabBase { +public class ImportantOptionalFieldsTab extends OptionalFieldsTabBase { public static final String NAME = "Optional fields"; - public OptionalFieldsTab(BibDatabaseContext databaseContext, - SuggestionProviders suggestionProviders, - UndoManager undoManager, - DialogService dialogService, - PreferencesService preferences, - StateManager stateManager, - ThemeManager themeManager, - IndexingTaskManager indexingTaskManager, - BibEntryTypesManager entryTypesManager, - TaskExecutor taskExecutor, - JournalAbbreviationRepository journalAbbreviationRepository) { + public ImportantOptionalFieldsTab(BibDatabaseContext databaseContext, + SuggestionProviders suggestionProviders, + UndoManager undoManager, + DialogService dialogService, + PreferencesService preferences, + StateManager stateManager, + ThemeManager themeManager, + IndexingTaskManager indexingTaskManager, + BibEntryTypesManager entryTypesManager, + TaskExecutor taskExecutor, + JournalAbbreviationRepository journalAbbreviationRepository) { super( Localization.lang("Optional fields"), true, diff --git a/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTabBase.java b/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTabBase.java index 5f61a0279ab..5a95c3ddb8b 100644 --- a/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTabBase.java +++ b/src/main/java/org/jabref/gui/entryeditor/OptionalFieldsTabBase.java @@ -27,10 +27,10 @@ public class OptionalFieldsTabBase extends FieldsEditorTab { private final BibEntryTypesManager entryTypesManager; - private final boolean isPrimaryOptionalFields; + private final boolean isImportantOptionalFields; public OptionalFieldsTabBase(String title, - boolean isPrimaryOptionalFields, + boolean isImportantOptionalFields, BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders, UndoManager undoManager, @@ -54,7 +54,7 @@ public OptionalFieldsTabBase(String title, journalAbbreviationRepository, indexingTaskManager); this.entryTypesManager = entryTypesManager; - this.isPrimaryOptionalFields = isPrimaryOptionalFields; + this.isImportantOptionalFields = isImportantOptionalFields; setText(title); setTooltip(new Tooltip(Localization.lang("Show optional fields"))); setGraphic(IconTheme.JabRefIcons.OPTIONAL.getGraphicNode()); @@ -65,10 +65,10 @@ protected SequencedSet determineFieldsToShow(BibEntry entry) { BibDatabaseMode mode = databaseContext.getMode(); Optional entryType = entryTypesManager.enrich(entry.getType(), mode); if (entryType.isPresent()) { - if (isPrimaryOptionalFields) { - return entryType.get().getPrimaryOptionalFields(); + if (isImportantOptionalFields) { + return entryType.get().getImportantOptionalFields(); } else { - return entryType.get().getSecondaryOptionalNotDeprecatedFields(mode); + return entryType.get().getDetailOptionalNotDeprecatedFields(mode); } } else { // Entry type unknown -> treat all fields as required (thus no optional fields) diff --git a/src/main/java/org/jabref/model/entry/BibEntryType.java b/src/main/java/org/jabref/model/entry/BibEntryType.java index b804d8255e7..164029fa799 100644 --- a/src/main/java/org/jabref/model/entry/BibEntryType.java +++ b/src/main/java/org/jabref/model/entry/BibEntryType.java @@ -22,8 +22,8 @@ public class BibEntryType implements Comparable { private final EntryType type; + private final SequencedSet allFields; private final SequencedSet requiredFields; - private final SequencedSet fields; /** * Provides an enriched EntryType with information about defined standards as mandatory fields etc. @@ -31,12 +31,12 @@ public class BibEntryType implements Comparable { * A builder is available at {@link BibEntryTypeBuilder} * * @param type The EntryType this BibEntryType is wrapped around. - * @param fields A BibFields list of all fields, including the required fields + * @param allFields A BibFields list of all fields, including the required fields * @param requiredFields A OrFields list of just the required fields */ - public BibEntryType(EntryType type, Collection fields, Collection requiredFields) { + public BibEntryType(EntryType type, Collection allFields, Collection requiredFields) { this.type = Objects.requireNonNull(type); - this.fields = new LinkedHashSet<>(fields); + this.allFields = new LinkedHashSet<>(allFields); this.requiredFields = new LinkedHashSet<>(requiredFields); } @@ -70,21 +70,21 @@ public SequencedSet getRequiredFields() { * Returns all defined fields. */ public SequencedSet getAllBibFields() { - return Collections.unmodifiableSequencedSet(fields); + return Collections.unmodifiableSequencedSet(allFields); } public Set getAllFields() { - return fields.stream().map(BibField::field).collect(Collectors.toCollection(LinkedHashSet::new)); + return allFields.stream().map(BibField::field).collect(Collectors.toCollection(LinkedHashSet::new)); } - public SequencedSet getPrimaryOptionalFields() { + public SequencedSet getImportantOptionalFields() { return getOptionalFields().stream() .filter(field -> field.priority() == FieldPriority.IMPORTANT) .map(BibField::field) .collect(Collectors.toCollection(LinkedHashSet::new)); } - public SequencedSet getSecondaryOptionalFields() { + public SequencedSet getDetailOptionalFields() { return getOptionalFields().stream() .filter(field -> field.priority() == FieldPriority.DETAIL) .map(BibField::field) @@ -108,8 +108,8 @@ public Set getDeprecatedFields(BibDatabaseMode mode) { return deprecatedFields; } - public SequencedSet getSecondaryOptionalNotDeprecatedFields(BibDatabaseMode mode) { - SequencedSet optionalFieldsNotPrimaryOrDeprecated = new LinkedHashSet<>(getSecondaryOptionalFields()); + public SequencedSet getDetailOptionalNotDeprecatedFields(BibDatabaseMode mode) { + SequencedSet optionalFieldsNotPrimaryOrDeprecated = new LinkedHashSet<>(getDetailOptionalFields()); optionalFieldsNotPrimaryOrDeprecated.removeAll(getDeprecatedFields(mode)); return optionalFieldsNotPrimaryOrDeprecated; } @@ -139,12 +139,12 @@ public boolean equals(Object o) { BibEntryType that = (BibEntryType) o; return type.equals(that.type) && Objects.equals(requiredFields, that.requiredFields) && - Objects.equals(fields, that.fields); + Objects.equals(allFields, that.allFields); } @Override public int hashCode() { - return Objects.hash(type, requiredFields, fields); + return Objects.hash(type, requiredFields, allFields); } /** @@ -159,7 +159,7 @@ public int hashCode() { public String toString() { return "BibEntryType{" + "type=" + type + - ", allFields=" + fields + + ", allFields=" + allFields + ", requiredFields=" + requiredFields + '}'; } diff --git a/src/main/java/org/jabref/model/entry/BibEntryTypeBuilder.java b/src/main/java/org/jabref/model/entry/BibEntryTypeBuilder.java index 91656a6c50d..b1ef5643348 100644 --- a/src/main/java/org/jabref/model/entry/BibEntryTypeBuilder.java +++ b/src/main/java/org/jabref/model/entry/BibEntryTypeBuilder.java @@ -1,7 +1,10 @@ package org.jabref.model.entry; import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.SequencedCollection; import java.util.SequencedSet; import java.util.Set; @@ -20,8 +23,11 @@ public class BibEntryTypeBuilder { private EntryType type = StandardEntryType.Misc; - private SequencedSet fields = new LinkedHashSet<>(); + private SequencedSet requiredFields = new LinkedHashSet<>(); + private SequencedSet optionalFields = new LinkedHashSet<>(); + + private Set seenFields = new HashSet<>(); public BibEntryTypeBuilder withType(EntryType type) { this.type = type; @@ -29,8 +35,13 @@ public BibEntryTypeBuilder withType(EntryType type) { } public BibEntryTypeBuilder withImportantFields(SequencedSet newFields) { - this.fields = Streams.concat(fields.stream(), newFields.stream().map(field -> new BibField(field, FieldPriority.IMPORTANT))) - .collect(Collectors.toCollection(LinkedHashSet::new)); + List containedFields = containedInSeenFields(newFields); + if (!containedFields.isEmpty()) { + throw new IllegalArgumentException("Fields " + containedFields + " already added"); + } + this.seenFields.addAll(newFields); + this.optionalFields = Streams.concat(optionalFields.stream(), newFields.stream().map(field -> new BibField(field, FieldPriority.IMPORTANT))) + .collect(Collectors.toCollection(LinkedHashSet::new)); return this; } @@ -39,8 +50,13 @@ public BibEntryTypeBuilder withImportantFields(Field... newFields) { } public BibEntryTypeBuilder withDetailFields(SequencedCollection newFields) { - this.fields = Streams.concat(fields.stream(), newFields.stream().map(field -> new BibField(field, FieldPriority.DETAIL))) - .collect(Collectors.toCollection(LinkedHashSet::new)); + List containedFields = containedInSeenFields(newFields); + if (!containedFields.isEmpty()) { + throw new IllegalArgumentException("Fields " + containedFields + " already added"); + } + this.seenFields.addAll(newFields); + this.optionalFields = Streams.concat(optionalFields.stream(), newFields.stream().map(field -> new BibField(field, FieldPriority.DETAIL))) + .collect(Collectors.toCollection(LinkedHashSet::new)); return this; } @@ -49,33 +65,38 @@ public BibEntryTypeBuilder withDetailFields(Field... fields) { } public BibEntryTypeBuilder withRequiredFields(SequencedSet requiredFields) { - this.requiredFields = requiredFields; + return addRequiredFields(requiredFields); + } + + public BibEntryTypeBuilder addRequiredFields(SequencedSet requiredFields) { + Set fieldsToAdd = requiredFields.stream().map(OrFields::getFields).flatMap(Set::stream).collect(Collectors.toSet()); + List containedFields = containedInSeenFields(fieldsToAdd); + if (!containedFields.isEmpty()) { + throw new IllegalArgumentException("Fields " + containedFields + " already added"); + } + this.seenFields.addAll(fieldsToAdd); + this.requiredFields.addAll(requiredFields); return this; } public BibEntryTypeBuilder addRequiredFields(OrFields... requiredFields) { - this.requiredFields.addAll(Arrays.asList(requiredFields)); - return this; + return addRequiredFields(Arrays.asList(requiredFields).stream().collect(Collectors.toCollection(LinkedHashSet::new))); } public BibEntryTypeBuilder addRequiredFields(Field... requiredFields) { - this.requiredFields.addAll(Arrays.stream(requiredFields).map(OrFields::new).toList()); - return this; + return addRequiredFields(Arrays.stream(requiredFields).map(OrFields::new).collect(Collectors.toCollection(LinkedHashSet::new))); } public BibEntryTypeBuilder withRequiredFields(Field... requiredFields) { - this.requiredFields = Arrays.stream(requiredFields).map(OrFields::new).collect(Collectors.toCollection(LinkedHashSet::new)); - return this; + return addRequiredFields(requiredFields); } public BibEntryTypeBuilder withRequiredFields(OrFields first, Field... requiredFields) { - this.requiredFields = Stream.concat(Stream.of(first), Arrays.stream(requiredFields).map(OrFields::new)).collect(Collectors.toCollection(LinkedHashSet::new)); - return this; + return addRequiredFields(Stream.concat(Stream.of(first), Arrays.stream(requiredFields).map(OrFields::new)).collect(Collectors.toCollection(LinkedHashSet::new))); } public BibEntryTypeBuilder withRequiredFields(SequencedSet first, Field... requiredFields) { - this.requiredFields = Stream.concat(first.stream(), Arrays.stream(requiredFields).map(OrFields::new)).collect(Collectors.toCollection(LinkedHashSet::new)); - return this; + return addRequiredFields(Stream.concat(first.stream(), Arrays.stream(requiredFields).map(OrFields::new)).collect(Collectors.toCollection(LinkedHashSet::new))); } public BibEntryType build() { @@ -84,7 +105,11 @@ public BibEntryType build() { .map(OrFields::getFields) .flatMap(Set::stream) .map(field -> new BibField(field, FieldPriority.IMPORTANT)); - SequencedSet allFields = Stream.concat(fields.stream(), requiredAsImportant).collect(Collectors.toCollection(LinkedHashSet::new)); + SequencedSet allFields = Stream.concat(optionalFields.stream(), requiredAsImportant).collect(Collectors.toCollection(LinkedHashSet::new)); return new BibEntryType(type, allFields, requiredFields); } + + private List containedInSeenFields(Collection fields) { + return fields.stream().filter(seenFields::contains).toList(); + } } diff --git a/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java b/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java index eb25ad23972..71a077acf01 100644 --- a/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java +++ b/src/main/java/org/jabref/model/entry/BibEntryTypesManager.java @@ -18,7 +18,12 @@ import org.jabref.model.entry.types.EntryTypeFactory; import org.jabref.model.entry.types.IEEETranEntryTypeDefinitions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class BibEntryTypesManager { + private static final Logger LOGGER = LoggerFactory.getLogger(BibEntryTypesManager.class); + private final InternalEntryTypes BIBTEX_ENTRYTYPES = new InternalEntryTypes( Stream.concat(BibtexEntryTypeDefinitions.ALL.stream(), IEEETranEntryTypeDefinitions.ALL.stream()) .collect(Collectors.toList())); @@ -132,6 +137,7 @@ private Optional enrich(EntryType type) { .filter(typeEquals(type)) .findFirst(); if (enrichedType.isPresent()) { + LOGGER.debug("Using customized entry type for {}", type.getName()); return enrichedType; } else { return standardTypes.stream() diff --git a/src/main/java/org/jabref/model/entry/field/FieldPriority.java b/src/main/java/org/jabref/model/entry/field/FieldPriority.java index 199151eeaf0..7c44b53af28 100644 --- a/src/main/java/org/jabref/model/entry/field/FieldPriority.java +++ b/src/main/java/org/jabref/model/entry/field/FieldPriority.java @@ -3,10 +3,10 @@ import java.util.Locale; /** - * Determines whether the field is in the Optional1 or Optional2 tab + * Determines whether the field is in the {@link org.jabref.gui.entryeditor.ImportantOptionalFieldsTab} or {@link org.jabref.gui.entryeditor.DetailOptionalFieldsTab} tab * - * See {@link org.jabref.model.entry.BibEntryType#getPrimaryOptionalFields()} - * and {@link org.jabref.model.entry.BibEntryType#getSecondaryOptionalFields()}. + * See {@link org.jabref.model.entry.BibEntryType#getImportantOptionalFields()} + * and {@link org.jabref.model.entry.BibEntryType#getDetailOptionalFields()}. */ public enum FieldPriority { IMPORTANT, diff --git a/src/main/java/org/jabref/model/entry/field/OrFields.java b/src/main/java/org/jabref/model/entry/field/OrFields.java index f0b456916d2..a8de739eb7d 100644 --- a/src/main/java/org/jabref/model/entry/field/OrFields.java +++ b/src/main/java/org/jabref/model/entry/field/OrFields.java @@ -10,6 +10,12 @@ /** * Represents a choice between two (or more) fields or any combination of them. *

+ * The idea of OrFields originates from BibLaTeX, where the manual lists following + *
+ * Required fields: author, title, journaltitle, year/date + *
+ * The class OrFields is used to model "year/date" in this case. + *

* Example is that a BibEntry requires either an author or an editor, but both can be present. */ public class OrFields implements Comparable { diff --git a/src/main/java/org/jabref/model/entry/types/BiblatexEntryTypeDefinitions.java b/src/main/java/org/jabref/model/entry/types/BiblatexEntryTypeDefinitions.java index 87db17e110d..40c79b8b31d 100644 --- a/src/main/java/org/jabref/model/entry/types/BiblatexEntryTypeDefinitions.java +++ b/src/main/java/org/jabref/model/entry/types/BiblatexEntryTypeDefinitions.java @@ -10,322 +10,297 @@ /** * This class defines entry types for biblatex support. - * - * @see biblatex documentation + * It is based on the biblatex documentation + *

+ * The definitions for BibTeX are done at {@link BibtexEntryTypeDefinitions} */ public class BiblatexEntryTypeDefinitions { private static final BibEntryType ARTICLE = new BibEntryTypeBuilder() .withType(StandardEntryType.Article) + .withRequiredFields( + StandardField.AUTHOR, StandardField.TITLE, StandardField.JOURNALTITLE, StandardField.DATE) .withImportantFields( StandardField.SUBTITLE, StandardField.EDITOR, StandardField.SERIES, StandardField.VOLUME, StandardField.NUMBER, StandardField.EID, StandardField.ISSUE, StandardField.PAGES, StandardField.NOTE, StandardField.ISSN, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) .withDetailFields( - StandardField.TRANSLATOR, StandardField.ANNOTATOR, StandardField.COMMENTATOR, StandardField.SUBTITLE, - StandardField.TITLEADDON, StandardField.EDITOR, StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, + StandardField.TRANSLATOR, StandardField.ANNOTATOR, StandardField.COMMENTATOR, + StandardField.TITLEADDON, StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, StandardField.JOURNALSUBTITLE, StandardField.ISSUETITLE, StandardField.ISSUESUBTITLE, StandardField.LANGUAGE, - StandardField.ORIGLANGUAGE, StandardField.SERIES, StandardField.VOLUME, StandardField.NUMBER, StandardField.EID, - StandardField.ISSUE, StandardField.PAGES, StandardField.VERSION, StandardField.NOTE, - StandardField.ISSN, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields( - StandardField.AUTHOR, StandardField.TITLE, StandardField.JOURNALTITLE, StandardField.DATE) + StandardField.ORIGLANGUAGE, StandardField.VERSION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType BOOK = new BibEntryTypeBuilder() .withType(StandardEntryType.Book) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.DATE) .withImportantFields(StandardField.EDITOR, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.MAINTITLE, StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.VOLUME, StandardField.EDITION, StandardField.PUBLISHER, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.EDITOR, StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, + .withDetailFields(StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, StandardField.TRANSLATOR, StandardField.ANNOTATOR, StandardField.COMMENTATOR, StandardField.INTRODUCTION, - StandardField.FOREWORD, StandardField.AFTERWORD, StandardField.SUBTITLE, StandardField.TITLEADDON, - StandardField.MAINTITLE, StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.LANGUAGE, - StandardField.ORIGLANGUAGE, StandardField.VOLUME, StandardField.PART, StandardField.EDITION, StandardField.VOLUMES, - StandardField.SERIES, StandardField.NUMBER, StandardField.NOTE, StandardField.PUBLISHER, StandardField.LOCATION, - StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.ADDENDUM, - StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, - StandardField.URL, StandardField.URLDATE) + StandardField.FOREWORD, StandardField.AFTERWORD, + StandardField.LANGUAGE, + StandardField.ORIGLANGUAGE, StandardField.PART, StandardField.VOLUMES, + StandardField.SERIES, StandardField.NUMBER, StandardField.NOTE, StandardField.LOCATION, + StandardField.ADDENDUM, + StandardField.PUBSTATE) .build(); private static final BibEntryType MVBOOK = new BibEntryTypeBuilder() .withType(StandardEntryType.MvBook) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.DATE) .withImportantFields(StandardField.EDITOR, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.EDITION, StandardField.PUBLISHER, StandardField.ISBN, StandardField.PAGETOTAL, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.EDITOR, StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, + .withDetailFields(StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, StandardField.TRANSLATOR, StandardField.ANNOTATOR, StandardField.COMMENTATOR, StandardField.INTRODUCTION, - StandardField.FOREWORD, StandardField.AFTERWORD, StandardField.SUBTITLE, StandardField.TITLEADDON, - StandardField.LANGUAGE, StandardField.ORIGLANGUAGE, StandardField.EDITION, StandardField.VOLUMES, StandardField.SERIES, - StandardField.NUMBER, StandardField.NOTE, StandardField.PUBLISHER, StandardField.LOCATION, StandardField.ISBN, - StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + StandardField.FOREWORD, StandardField.AFTERWORD, + StandardField.LANGUAGE, StandardField.ORIGLANGUAGE, StandardField.VOLUMES, StandardField.SERIES, + StandardField.NUMBER, StandardField.NOTE, StandardField.LOCATION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType INBOOK = new BibEntryTypeBuilder() .withType(StandardEntryType.InBook) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.BOOKTITLE, StandardField.DATE) .withImportantFields( StandardField.BOOKAUTHOR, StandardField.EDITOR, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.MAINTITLE, StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.BOOKSUBTITLE, StandardField.BOOKTITLEADDON, StandardField.VOLUME, StandardField.EDITION, StandardField.PUBLISHER, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.BOOKTITLE, StandardField.DATE) - .withDetailFields(StandardField.BOOKAUTHOR, StandardField.EDITOR, StandardField.EDITORA, StandardField.EDITORB, + .withDetailFields(StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, StandardField.TRANSLATOR, StandardField.ANNOTATOR, StandardField.COMMENTATOR, - StandardField.INTRODUCTION, StandardField.FOREWORD, StandardField.AFTERWORD, StandardField.SUBTITLE, - StandardField.TITLEADDON, StandardField.MAINTITLE, StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, - StandardField.BOOKSUBTITLE, StandardField.BOOKTITLEADDON, StandardField.LANGUAGE, StandardField.ORIGLANGUAGE, - StandardField.VOLUME, StandardField.PART, StandardField.EDITION, StandardField.VOLUMES, StandardField.SERIES, - StandardField.NUMBER, StandardField.NOTE, StandardField.PUBLISHER, StandardField.LOCATION, StandardField.ISBN, - StandardField.CHAPTER, StandardField.PAGES, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, - StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + StandardField.INTRODUCTION, StandardField.FOREWORD, StandardField.AFTERWORD, + StandardField.LANGUAGE, StandardField.ORIGLANGUAGE, + StandardField.PART, StandardField.VOLUMES, StandardField.SERIES, + StandardField.NUMBER, StandardField.NOTE, StandardField.LOCATION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType BOOKINBOOK = new BibEntryTypeBuilder() .withType(StandardEntryType.BookInBook) - .withImportantFields(INBOOK.getPrimaryOptionalFields()) - .withDetailFields(INBOOK.getSecondaryOptionalFields()) .withRequiredFields(INBOOK.getRequiredFields()) + .withImportantFields(INBOOK.getImportantOptionalFields()) + .withDetailFields(INBOOK.getDetailOptionalFields()) .build(); private static final BibEntryType SUPPBOOK = new BibEntryTypeBuilder() .withType(StandardEntryType.SuppBook) - .withImportantFields(INBOOK.getPrimaryOptionalFields()) - .withDetailFields(INBOOK.getSecondaryOptionalFields()) + .withImportantFields(INBOOK.getImportantOptionalFields()) + .withDetailFields(INBOOK.getDetailOptionalFields()) .withRequiredFields(INBOOK.getRequiredFields()) .build(); private static final BibEntryType BOOKLET = new BibEntryTypeBuilder() .withType(StandardEntryType.Booklet) + .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE) .withImportantFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.HOWPUBLISHED, StandardField.CHAPTER, StandardField.PAGES, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.HOWPUBLISHED, - StandardField.TYPE, StandardField.NOTE, StandardField.LOCATION, StandardField.CHAPTER, StandardField.PAGES, - StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, + StandardField.TYPE, StandardField.NOTE, StandardField.LOCATION, + StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType COLLECTION = new BibEntryTypeBuilder() .withType(StandardEntryType.Collection) + .withRequiredFields(StandardField.EDITOR, StandardField.TITLE, StandardField.DATE) .withImportantFields( StandardField.TRANSLATOR, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.MAINTITLE, StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.VOLUME, StandardField.EDITION, StandardField.PUBLISHER, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.EDITOR, StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, StandardField.TRANSLATOR, + .withDetailFields(StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, StandardField.ANNOTATOR, StandardField.COMMENTATOR, StandardField.INTRODUCTION, StandardField.FOREWORD, - StandardField.AFTERWORD, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.MAINTITLE, - StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.LANGUAGE, StandardField.ORIGLANGUAGE, - StandardField.VOLUME, StandardField.PART, StandardField.EDITION, StandardField.VOLUMES, StandardField.SERIES, - StandardField.NUMBER, StandardField.NOTE, StandardField.PUBLISHER, StandardField.LOCATION, StandardField.ISBN, - StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE, - StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, - StandardField.URLDATE) + StandardField.AFTERWORD, + StandardField.LANGUAGE, StandardField.ORIGLANGUAGE, + StandardField.PART, StandardField.VOLUMES, StandardField.SERIES, + StandardField.NUMBER, StandardField.NOTE, StandardField.LOCATION, + StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType MVCOLLECTION = new BibEntryTypeBuilder() .withType(StandardEntryType.MvCollection) + .withRequiredFields(StandardField.EDITOR, StandardField.TITLE, StandardField.DATE) .withImportantFields(StandardField.TRANSLATOR, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.EDITION, StandardField.PUBLISHER, StandardField.ISBN, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.EDITOR, StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, StandardField.TRANSLATOR, + .withDetailFields(StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, StandardField.ANNOTATOR, StandardField.COMMENTATOR, StandardField.INTRODUCTION, StandardField.FOREWORD, - StandardField.AFTERWORD, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, - StandardField.ORIGLANGUAGE, StandardField.EDITION, StandardField.VOLUMES, StandardField.SERIES, StandardField.NUMBER, - StandardField.NOTE, StandardField.PUBLISHER, StandardField.LOCATION, StandardField.ISBN, StandardField.PAGETOTAL, - StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, - StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + StandardField.AFTERWORD, StandardField.LANGUAGE, + StandardField.ORIGLANGUAGE, StandardField.VOLUMES, StandardField.SERIES, StandardField.NUMBER, + StandardField.NOTE, StandardField.LOCATION, StandardField.PAGETOTAL, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType INCOLLECTION = new BibEntryTypeBuilder() .withType(StandardEntryType.InCollection) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.BOOKTITLE, StandardField.DATE) .withImportantFields(StandardField.TRANSLATOR, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.MAINTITLE, StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.BOOKSUBTITLE, StandardField.BOOKTITLEADDON, StandardField.VOLUME, StandardField.EDITION, StandardField.PUBLISHER, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.BOOKTITLE, StandardField.DATE) .withDetailFields(StandardField.EDITOR, StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, - StandardField.TRANSLATOR, StandardField.ANNOTATOR, StandardField.COMMENTATOR, StandardField.INTRODUCTION, - StandardField.FOREWORD, StandardField.AFTERWORD, StandardField.SUBTITLE, StandardField.TITLEADDON, - StandardField.MAINTITLE, StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.BOOKSUBTITLE, - StandardField.BOOKTITLEADDON, StandardField.LANGUAGE, StandardField.ORIGLANGUAGE, StandardField.VOLUME, - StandardField.PART, StandardField.EDITION, StandardField.VOLUMES, StandardField.SERIES, StandardField.NUMBER, - StandardField.NOTE, StandardField.PUBLISHER, StandardField.LOCATION, StandardField.ISBN, StandardField.CHAPTER, - StandardField.PAGES, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + StandardField.ANNOTATOR, StandardField.COMMENTATOR, StandardField.INTRODUCTION, + StandardField.FOREWORD, StandardField.AFTERWORD, + StandardField.LANGUAGE, StandardField.ORIGLANGUAGE, + StandardField.PART, StandardField.VOLUMES, StandardField.SERIES, StandardField.NUMBER, + StandardField.NOTE, StandardField.LOCATION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType SUPPCOLLECTION = new BibEntryTypeBuilder() .withType(StandardEntryType.SuppCollection) - .withImportantFields(INCOLLECTION.getPrimaryOptionalFields()) - .withDetailFields(INCOLLECTION.getSecondaryOptionalFields()) .withRequiredFields(INCOLLECTION.getRequiredFields()) + .withImportantFields(INCOLLECTION.getImportantOptionalFields()) + .withDetailFields(INCOLLECTION.getDetailOptionalFields()) .build(); private static final BibEntryType MANUAL = new BibEntryTypeBuilder() .withType(StandardEntryType.Manual) + .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE) .withImportantFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.EDITION, StandardField.PUBLISHER, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.EDITION, + .withDetailFields(StandardField.LANGUAGE, StandardField.TYPE, StandardField.SERIES, StandardField.NUMBER, StandardField.VERSION, StandardField.NOTE, - StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.LOCATION, StandardField.ISBN, StandardField.CHAPTER, - StandardField.PAGES, StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, - StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + StandardField.ORGANIZATION, StandardField.LOCATION, + StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType MISC = new BibEntryTypeBuilder() .withType(StandardEntryType.Misc) + .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE) .withImportantFields( StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.HOWPUBLISHED, StandardField.LOCATION, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.HOWPUBLISHED, - StandardField.TYPE, StandardField.VERSION, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.LOCATION, - StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, + StandardField.TYPE, StandardField.VERSION, StandardField.NOTE, StandardField.ORGANIZATION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType ONLINE = new BibEntryTypeBuilder() .withType(StandardEntryType.Online) + .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE, StandardField.URL) .withImportantFields( StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.URLDATE) - .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE, StandardField.URL) - .withDetailFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.VERSION, - StandardField.NOTE, StandardField.ORGANIZATION, StandardField.ADDENDUM, StandardField.PUBSTATE, - StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, StandardField.VERSION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType PATENT = new BibEntryTypeBuilder() .withType(IEEETranEntryType.Patent) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.NUMBER, StandardField.DATE) .withImportantFields(StandardField.HOLDER, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.NUMBER, StandardField.DATE) - .withDetailFields(StandardField.HOLDER, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.TYPE, + .withDetailFields(StandardField.TYPE, StandardField.VERSION, StandardField.LOCATION, StandardField.NOTE, StandardField.ADDENDUM, - StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, - StandardField.URL, StandardField.URLDATE) + StandardField.PUBSTATE) .build(); private static final BibEntryType PERIODICAL = new BibEntryTypeBuilder() .withType(IEEETranEntryType.Periodical) + .withRequiredFields(StandardField.EDITOR, StandardField.TITLE, StandardField.DATE) .withImportantFields( StandardField.SUBTITLE, StandardField.ISSUETITLE, StandardField.ISSUESUBTITLE, StandardField.ISSN, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.EDITOR, StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, StandardField.SUBTITLE, - StandardField.ISSUETITLE, StandardField.ISSUESUBTITLE, StandardField.LANGUAGE, StandardField.SERIES, + .withDetailFields(StandardField.EDITORA, StandardField.EDITORB, StandardField.EDITORC, + StandardField.LANGUAGE, StandardField.SERIES, StandardField.VOLUME, StandardField.NUMBER, StandardField.ISSUE, StandardField.NOTE, - StandardField.ISSN, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType SUPPPERIODICAL = new BibEntryTypeBuilder() .withType(StandardEntryType.SuppPeriodical) - .withImportantFields(ARTICLE.getPrimaryOptionalFields()) - .withDetailFields(ARTICLE.getSecondaryOptionalFields()) .withRequiredFields(ARTICLE.getRequiredFields()) + .withImportantFields(ARTICLE.getImportantOptionalFields()) + .withDetailFields(ARTICLE.getDetailOptionalFields()) .build(); private static final BibEntryType PROCEEDINGS = new BibEntryTypeBuilder() .withType(StandardEntryType.Proceedings) + .withRequiredFields(StandardField.TITLE, StandardField.DATE) .withImportantFields( StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.MAINTITLE, StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.EVENTTITLE, StandardField.VOLUME, StandardField.PUBLISHER, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.EDITOR, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.MAINTITLE, - StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.EVENTTITLE, StandardField.EVENTTITLEADDON, - StandardField.EVENTDATE, StandardField.VENUE, StandardField.LANGUAGE, StandardField.VOLUME, StandardField.PART, + .withDetailFields(StandardField.EDITOR, StandardField.EVENTTITLEADDON, + StandardField.EVENTDATE, StandardField.VENUE, StandardField.LANGUAGE, StandardField.PART, StandardField.VOLUMES, StandardField.SERIES, StandardField.NUMBER, StandardField.NOTE, StandardField.ORGANIZATION, - StandardField.PUBLISHER, StandardField.LOCATION, StandardField.ISBN, - StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE, - StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, - StandardField.URLDATE) + StandardField.LOCATION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType MVPROCEEDINGS = new BibEntryTypeBuilder() .withType(StandardEntryType.MvProceedings) + .withRequiredFields(StandardField.TITLE, StandardField.DATE) .withImportantFields( StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.MAINTITLE, StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.EVENTTITLE, StandardField.VOLUME, StandardField.PUBLISHER, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.EDITOR, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.EVENTTITLE, + .withDetailFields(StandardField.EDITOR, StandardField.EVENTTITLEADDON, StandardField.EVENTDATE, StandardField.VENUE, StandardField.LANGUAGE, StandardField.VOLUMES, StandardField.SERIES, StandardField.NUMBER, StandardField.NOTE, StandardField.ORGANIZATION, - StandardField.PUBLISHER, StandardField.LOCATION, StandardField.ISBN, StandardField.PAGETOTAL, - StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, - StandardField.EPRINTTYPE, StandardField.URL, - StandardField.URLDATE) + StandardField.LOCATION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType INPROCEEDINGS = new BibEntryTypeBuilder() .withType(StandardEntryType.InProceedings) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.BOOKTITLE, StandardField.DATE) .withImportantFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.MAINTITLE, StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.BOOKSUBTITLE, StandardField.BOOKTITLEADDON, StandardField.EVENTTITLE, StandardField.VOLUME, StandardField.PUBLISHER, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.BOOKTITLE, StandardField.DATE) - .withDetailFields(StandardField.EDITOR, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.MAINTITLE, - StandardField.MAINSUBTITLE, StandardField.MAINTITLEADDON, StandardField.BOOKSUBTITLE, StandardField.BOOKTITLEADDON, - StandardField.EVENTTITLE, StandardField.EVENTTITLEADDON, StandardField.EVENTDATE, StandardField.VENUE, - StandardField.LANGUAGE, StandardField.VOLUME, StandardField.PART, StandardField.VOLUMES, StandardField.SERIES, - StandardField.NUMBER, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.LOCATION, - StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, StandardField.ADDENDUM, - StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, - StandardField.URL, StandardField.URLDATE) + .withDetailFields(StandardField.EDITOR, + StandardField.EVENTTITLEADDON, StandardField.EVENTDATE, StandardField.VENUE, + StandardField.LANGUAGE, StandardField.PART, StandardField.VOLUMES, StandardField.SERIES, + StandardField.NUMBER, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.LOCATION, + StandardField.ADDENDUM, + StandardField.PUBSTATE) .build(); private static final BibEntryType REFERENCE = new BibEntryTypeBuilder() .withType(StandardEntryType.Reference) - .withImportantFields(COLLECTION.getPrimaryOptionalFields()) - .withDetailFields(COLLECTION.getSecondaryOptionalFields()) .withRequiredFields(COLLECTION.getRequiredFields()) + .withImportantFields(COLLECTION.getImportantOptionalFields()) + .withDetailFields(COLLECTION.getDetailOptionalFields()) .build(); private static final BibEntryType MVREFERENCE = new BibEntryTypeBuilder() .withType(StandardEntryType.MvReference) - .withImportantFields(MVCOLLECTION.getPrimaryOptionalFields()) - .withDetailFields(MVCOLLECTION.getSecondaryOptionalFields()) .withRequiredFields(MVCOLLECTION.getRequiredFields()) + .withImportantFields(MVCOLLECTION.getImportantOptionalFields()) + .withDetailFields(MVCOLLECTION.getDetailOptionalFields()) .build(); private static final BibEntryType INREFERENCE = new BibEntryTypeBuilder() .withType(StandardEntryType.InReference) - .withImportantFields(INCOLLECTION.getPrimaryOptionalFields()) - .withDetailFields(INCOLLECTION.getSecondaryOptionalFields()) .withRequiredFields(INCOLLECTION.getRequiredFields()) + .withImportantFields(INCOLLECTION.getImportantOptionalFields()) + .withDetailFields(INCOLLECTION.getDetailOptionalFields()) .build(); private static final BibEntryType REPORT = new BibEntryTypeBuilder() .withType(StandardEntryType.Report) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.TYPE, StandardField.INSTITUTION, StandardField.DATE) .withImportantFields( StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.NUMBER, StandardField.ISRN, StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.TYPE, StandardField.INSTITUTION, StandardField.DATE) - .withDetailFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.NUMBER, - StandardField.VERSION, StandardField.NOTE, StandardField.LOCATION, StandardField.ISRN, - StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE, - StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, - StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, + StandardField.VERSION, StandardField.NOTE, StandardField.LOCATION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType SET = new BibEntryTypeBuilder() @@ -335,107 +310,101 @@ public class BiblatexEntryTypeDefinitions { private static final BibEntryType THESIS = new BibEntryTypeBuilder() .withType(StandardEntryType.Thesis) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.TYPE, StandardField.INSTITUTION, StandardField.DATE) .withImportantFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.TYPE, StandardField.INSTITUTION, StandardField.DATE) - .withDetailFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.NOTE, - StandardField.LOCATION, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, - StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, StandardField.NOTE, + StandardField.LOCATION, StandardField.ISBN, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType UNPUBLISHED = new BibEntryTypeBuilder() .withType(StandardEntryType.Unpublished) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.DATE) .withImportantFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.HOWPUBLISHED, StandardField.PUBSTATE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.HOWPUBLISHED, - StandardField.NOTE, StandardField.LOCATION, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.EVENTTITLE, - StandardField.EVENTDATE, StandardField.VENUE, StandardField.URL, StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, + StandardField.NOTE, StandardField.LOCATION, StandardField.ADDENDUM, StandardField.EVENTTITLE, + StandardField.EVENTDATE, StandardField.VENUE) .build(); private static final BibEntryType CONFERENCE = new BibEntryTypeBuilder() .withType(StandardEntryType.Conference) - .withImportantFields(INPROCEEDINGS.getPrimaryOptionalFields()) - .withDetailFields(INPROCEEDINGS.getSecondaryOptionalFields()) .withRequiredFields(INPROCEEDINGS.getRequiredFields()) + .withImportantFields(INPROCEEDINGS.getImportantOptionalFields()) + .withDetailFields(INPROCEEDINGS.getDetailOptionalFields()) .build(); private static final BibEntryType ELECTRONIC = new BibEntryTypeBuilder() .withType(IEEETranEntryType.Electronic) - .withImportantFields(ONLINE.getPrimaryOptionalFields()) - .withDetailFields(ONLINE.getSecondaryOptionalFields()) .withRequiredFields(ONLINE.getRequiredFields()) + .withImportantFields(ONLINE.getImportantOptionalFields()) + .withDetailFields(ONLINE.getDetailOptionalFields()) .build(); private static final BibEntryType MASTERSTHESIS = new BibEntryTypeBuilder() .withType(StandardEntryType.MastersThesis) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.INSTITUTION, StandardField.DATE) .withImportantFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.TYPE, StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.INSTITUTION, StandardField.DATE) - .withDetailFields(StandardField.TYPE, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.NOTE, - StandardField.LOCATION, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, - StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, StandardField.NOTE, + StandardField.LOCATION, StandardField.ISBN, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType PHDTHESIS = new BibEntryTypeBuilder() .withType(StandardEntryType.PhdThesis) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.INSTITUTION, StandardField.DATE) .withImportantFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.TYPE, StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.INSTITUTION, StandardField.DATE) - .withDetailFields(StandardField.TYPE, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.NOTE, - StandardField.LOCATION, StandardField.ISBN, StandardField.CHAPTER, StandardField.PAGES, - StandardField.PAGETOTAL, StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, StandardField.NOTE, + StandardField.LOCATION, StandardField.ISBN, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType TECHREPORT = new BibEntryTypeBuilder() .withType(StandardEntryType.TechReport) + .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.INSTITUTION, StandardField.DATE) .withImportantFields( StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.TYPE, StandardField.NUMBER, StandardField.ISRN, StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.INSTITUTION, StandardField.DATE) - .withDetailFields(StandardField.TYPE, StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, - StandardField.NUMBER, StandardField.VERSION, StandardField.NOTE, StandardField.LOCATION, - StandardField.ISRN, StandardField.CHAPTER, StandardField.PAGES, StandardField.PAGETOTAL, StandardField.ADDENDUM, - StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, - StandardField.URL, StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, + StandardField.VERSION, StandardField.NOTE, StandardField.LOCATION, + StandardField.ADDENDUM, + StandardField.PUBSTATE) .build(); private static final BibEntryType WWW = new BibEntryTypeBuilder() .withType(StandardEntryType.WWW) - .withImportantFields(ONLINE.getPrimaryOptionalFields()) - .withDetailFields(ONLINE.getSecondaryOptionalFields()) .withRequiredFields(ONLINE.getRequiredFields()) + .withImportantFields(ONLINE.getImportantOptionalFields()) + .withDetailFields(ONLINE.getDetailOptionalFields()) .build(); private static final BibEntryType SOFTWARE = new BibEntryTypeBuilder() .withType(StandardEntryType.Software) + .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE) .withImportantFields( StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.HOWPUBLISHED, StandardField.LOCATION, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.HOWPUBLISHED, - StandardField.TYPE, StandardField.VERSION, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.LOCATION, - StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, + StandardField.TYPE, StandardField.VERSION, StandardField.NOTE, StandardField.ORGANIZATION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); private static final BibEntryType DATASET = new BibEntryTypeBuilder() .withType(StandardEntryType.Dataset) + .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE) .withImportantFields( StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.HOWPUBLISHED, StandardField.LOCATION, StandardField.DOI, StandardField.EPRINT, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) - .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.DATE) - .withDetailFields(StandardField.SUBTITLE, StandardField.TITLEADDON, StandardField.LANGUAGE, StandardField.EDITION, StandardField.HOWPUBLISHED, - StandardField.TYPE, StandardField.VERSION, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.LOCATION, - StandardField.ADDENDUM, StandardField.PUBSTATE, StandardField.DOI, StandardField.EPRINT, - StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, StandardField.URL, StandardField.URLDATE) + .withDetailFields(StandardField.LANGUAGE, StandardField.EDITION, + StandardField.TYPE, StandardField.VERSION, StandardField.NOTE, StandardField.ORGANIZATION, + StandardField.ADDENDUM, StandardField.PUBSTATE) .build(); public static final List ALL = Arrays.asList(ARTICLE, BOOK, MVBOOK, INBOOK, BOOKINBOOK, SUPPBOOK, diff --git a/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitions.java b/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitions.java index 6a2e7f0851e..ff5e3e6599f 100644 --- a/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitions.java +++ b/src/main/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitions.java @@ -12,42 +12,38 @@ public class BiblatexSoftwareEntryTypeDefinitions { private static final BibEntryType SOFTWARE = new BibEntryTypeBuilder() .withType(StandardEntryType.Software) + .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.URL, StandardField.VERSION, StandardField.YEAR) .withImportantFields(StandardField.DATE, StandardField.DOI, StandardField.EPRINTTYPE, StandardField.EPRINTCLASS, StandardField.EPRINT, - StandardField.EDITOR, StandardField.FILE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, + StandardField.FILE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, BiblatexSoftwareField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.RELATED, - BiblatexSoftwareField.RELATEDSTRING, BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.URLDATE, StandardField.VERSION) - .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.URL, StandardField.VERSION, StandardField.YEAR) + BiblatexSoftwareField.RELATEDSTRING, BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.URLDATE) .build(); private static final BibEntryType SOFTWAREVERSION = new BibEntryTypeBuilder() .withType(BiblatexSoftwareEntryType.SoftwareVersion) - .withImportantFields(StandardField.DATE, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, - StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, BiblatexSoftwareField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, - StandardField.PUBLISHER, StandardField.RELATED, BiblatexSoftwareField.RELATEDTYPE, BiblatexSoftwareField.RELATEDSTRING, - BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.SUBTITLE, StandardField.URLDATE) .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.URL, StandardField.YEAR, StandardField.VERSION) - .withDetailFields(StandardField.DATE, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, + .withImportantFields(StandardField.DATE, StandardField.EPRINTCLASS, StandardField.EPRINTTYPE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, BiblatexSoftwareField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.RELATED, BiblatexSoftwareField.RELATEDTYPE, BiblatexSoftwareField.RELATEDSTRING, BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.SUBTITLE, StandardField.URLDATE) - .withRequiredFields(new OrFields(StandardField.AUTHOR, StandardField.EDITOR), StandardField.TITLE, StandardField.URL, StandardField.YEAR) .build(); + private static final BibEntryType SOFTWAREMODULE = new BibEntryTypeBuilder() .withType(BiblatexSoftwareEntryType.SoftwareModule) + .withRequiredFields(StandardField.AUTHOR, StandardField.SUBTITLE, StandardField.URL, StandardField.YEAR) .withImportantFields(StandardField.DATE, StandardField.DOI, StandardField.EPRINTTYPE, StandardField.EPRINTCLASS, StandardField.EPRINT, StandardField.EDITOR, StandardField.FILE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, BiblatexSoftwareField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.RELATED, BiblatexSoftwareField.RELATEDSTRING, BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.URLDATE, StandardField.VERSION) - .withRequiredFields(StandardField.AUTHOR, StandardField.SUBTITLE, StandardField.URL, StandardField.YEAR) .build(); private static final BibEntryType CODEFRAGMENT = new BibEntryTypeBuilder() .withType(BiblatexSoftwareEntryType.CodeFragment) + .withRequiredFields(StandardField.URL) .withImportantFields(StandardField.DATE, StandardField.DOI, StandardField.EPRINTTYPE, StandardField.EPRINTCLASS, StandardField.EPRINT, StandardField.EDITOR, StandardField.FILE, BiblatexSoftwareField.HALID, BiblatexSoftwareField.HALVERSION, StandardField.INSTITUTION, BiblatexSoftwareField.INTRODUCEDIN, BiblatexSoftwareField.LICENSE, StandardField.MONTH, StandardField.NOTE, StandardField.ORGANIZATION, StandardField.PUBLISHER, StandardField.RELATED, BiblatexSoftwareField.RELATEDSTRING, BiblatexSoftwareField.REPOSITORY, BiblatexSoftwareField.SWHID, StandardField.URLDATE, StandardField.VERSION) - .withRequiredFields(StandardField.URL) .build(); public static final List ALL = Arrays.asList(SOFTWAREVERSION, SOFTWARE, SOFTWAREMODULE, CODEFRAGMENT); diff --git a/src/main/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitions.java b/src/main/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitions.java index d2109ebc8d4..885235958f8 100644 --- a/src/main/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitions.java +++ b/src/main/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitions.java @@ -10,6 +10,9 @@ /** * This class represents all supported BibTeX entry types. + * It is based on the information of BibTeXing, a manual + * by the original BibTeX author. Also enriched by new fields not existing back then (e.g., ISSN). + *

* The BibLaTeX entry types are defined at {@link BiblatexEntryTypeDefinitions}. */ public class BibtexEntryTypeDefinitions { diff --git a/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java b/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java index ae879fabafb..0f30ca5e6bf 100644 --- a/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java +++ b/src/main/java/org/jabref/model/entry/types/EntryTypeFactory.java @@ -28,7 +28,7 @@ public static boolean nameAndFieldsAreEqual(BibEntryType type1, BibEntryType typ return Objects.equals(type1.getType(), type2.getType()) && Objects.equals(type1.getRequiredFields(), type2.getRequiredFields()) && Objects.equals(type1.getOptionalFields(), type2.getOptionalFields()) - && Objects.equals(type1.getSecondaryOptionalFields(), type2.getSecondaryOptionalFields()); + && Objects.equals(type1.getDetailOptionalFields(), type2.getDetailOptionalFields()); } } diff --git a/src/main/java/org/jabref/model/entry/types/IEEETranEntryTypeDefinitions.java b/src/main/java/org/jabref/model/entry/types/IEEETranEntryTypeDefinitions.java index 600c9f67bdb..790dea24746 100644 --- a/src/main/java/org/jabref/model/entry/types/IEEETranEntryTypeDefinitions.java +++ b/src/main/java/org/jabref/model/entry/types/IEEETranEntryTypeDefinitions.java @@ -62,7 +62,7 @@ public class IEEETranEntryTypeDefinitions { .withType(IEEETranEntryType.Patent) .withRequiredFields(new OrFields(StandardField.YEAR, StandardField.YEARFILED), StandardField.NATIONALITY, StandardField.NUMBER) .withImportantFields(StandardField.AUTHOR, StandardField.TITLE, StandardField.LANGUAGE, StandardField.ASSIGNEE, StandardField.ADDRESS, - StandardField.TYPE, StandardField.NUMBER, StandardField.DAY, StandardField.DAYFILED, StandardField.MONTH, + StandardField.TYPE, StandardField.DAY, StandardField.DAYFILED, StandardField.MONTH, StandardField.MONTHFILED, StandardField.NOTE, StandardField.URL) .build(); diff --git a/src/test/java/org/jabref/model/entry/BibEntryTypeBuilderTest.java b/src/test/java/org/jabref/model/entry/BibEntryTypeBuilderTest.java new file mode 100644 index 00000000000..44794f55412 --- /dev/null +++ b/src/test/java/org/jabref/model/entry/BibEntryTypeBuilderTest.java @@ -0,0 +1,41 @@ +package org.jabref.model.entry; + +import java.util.LinkedHashSet; +import java.util.List; + +import org.jabref.model.entry.field.StandardField; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class BibEntryTypeBuilderTest { + + @Test + void fieldAlreadySeenSameCategory() { + assertThrows(IllegalArgumentException.class, () -> + new BibEntryTypeBuilder() + .withImportantFields(StandardField.AUTHOR) + .withImportantFields(StandardField.AUTHOR) + .build()); + } + + @Test + void detailOptionalWorks() { + BibEntryType bibEntryType = new BibEntryTypeBuilder() + .withImportantFields(StandardField.AUTHOR) + .withDetailFields(StandardField.NOTE) + .build(); + assertEquals(new LinkedHashSet<>(List.of(StandardField.NOTE)), bibEntryType.getDetailOptionalFields()); + } + + @Test + void fieldAlreadySeenDifferentCategories() { + assertThrows(IllegalArgumentException.class, () -> + new BibEntryTypeBuilder() + .withRequiredFields(StandardField.AUTHOR) + .withImportantFields(StandardField.AUTHOR) + .build()); + } +} diff --git a/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java b/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java index c47f9abde47..e227b226393 100644 --- a/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java +++ b/src/test/java/org/jabref/model/entry/BibEntryTypesManagerTest.java @@ -188,4 +188,10 @@ void modifyingArticleWithParsingKeepsListOrder(BibDatabaseMode mode) { assertEquals(overwrittenStandardType.getOptionalFields(), type.get().getOptionalFields()); } + + @Test + void translatorDetailOptionalAtArticle() { + BibEntryType entryType = entryTypesManager.enrich(StandardEntryType.Article, BibDatabaseMode.BIBLATEX).get(); + assertTrue(entryType.getDetailOptionalFields().contains(StandardField.TRANSLATOR)); + } } diff --git a/src/test/java/org/jabref/model/entry/types/BiblatexAPAEntryTypeDefinitionsTest.java b/src/test/java/org/jabref/model/entry/types/BiblatexAPAEntryTypeDefinitionsTest.java new file mode 100644 index 00000000000..3ca45fbaa3f --- /dev/null +++ b/src/test/java/org/jabref/model/entry/types/BiblatexAPAEntryTypeDefinitionsTest.java @@ -0,0 +1,13 @@ +package org.jabref.model.entry.types; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class BiblatexAPAEntryTypeDefinitionsTest { + + @Test + void all() { + assertNotNull(BiblatexAPAEntryTypeDefinitions.ALL); + } +} diff --git a/src/test/java/org/jabref/model/entry/types/BiblatexEntryTypeDefinitionsTest.java b/src/test/java/org/jabref/model/entry/types/BiblatexEntryTypeDefinitionsTest.java new file mode 100644 index 00000000000..a2511daa940 --- /dev/null +++ b/src/test/java/org/jabref/model/entry/types/BiblatexEntryTypeDefinitionsTest.java @@ -0,0 +1,13 @@ +package org.jabref.model.entry.types; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class BiblatexEntryTypeDefinitionsTest { + + @Test + void all() { + assertNotNull(BiblatexEntryTypeDefinitions.ALL); + } +} diff --git a/src/test/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitionsTest.java b/src/test/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitionsTest.java new file mode 100644 index 00000000000..cb8f1789101 --- /dev/null +++ b/src/test/java/org/jabref/model/entry/types/BiblatexSoftwareEntryTypeDefinitionsTest.java @@ -0,0 +1,12 @@ +package org.jabref.model.entry.types; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class BiblatexSoftwareEntryTypeDefinitionsTest { + @Test + void all() { + assertNotNull(BiblatexSoftwareEntryTypeDefinitions.ALL); + } +} diff --git a/src/test/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitionsTest.java b/src/test/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitionsTest.java new file mode 100644 index 00000000000..05aef2a503f --- /dev/null +++ b/src/test/java/org/jabref/model/entry/types/BibtexEntryTypeDefinitionsTest.java @@ -0,0 +1,26 @@ +package org.jabref.model.entry.types; + +import org.jabref.model.entry.BibEntryType; +import org.jabref.model.entry.field.StandardField; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class BibtexEntryTypeDefinitionsTest { + + @Test + void all() { + assertNotNull(BibtexEntryTypeDefinitions.ALL); + } + + @Test + void languageContained() { + BibEntryType articleEntryType = BiblatexEntryTypeDefinitions.ALL.stream() + .filter(type -> type.getType().equals(StandardEntryType.Article)) + .findFirst() + .get(); + assertTrue(articleEntryType.getDetailOptionalFields().contains(StandardField.LANGUAGE)); + } +} diff --git a/src/test/java/org/jabref/model/entry/types/IEEETranEntryTypeDefinitionsTest.java b/src/test/java/org/jabref/model/entry/types/IEEETranEntryTypeDefinitionsTest.java new file mode 100644 index 00000000000..2bb58af26b7 --- /dev/null +++ b/src/test/java/org/jabref/model/entry/types/IEEETranEntryTypeDefinitionsTest.java @@ -0,0 +1,12 @@ +package org.jabref.model.entry.types; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class IEEETranEntryTypeDefinitionsTest { + @Test + void all() { + assertNotNull(IEEETranEntryTypeDefinitions.ALL); + } +} diff --git a/src/test/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitionsTest.java b/src/test/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitionsTest.java new file mode 100644 index 00000000000..553b5125ad9 --- /dev/null +++ b/src/test/java/org/jabref/model/entry/types/SystematicLiteratureReviewStudyEntryTypeDefinitionsTest.java @@ -0,0 +1,12 @@ +package org.jabref.model.entry.types; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class SystematicLiteratureReviewStudyEntryTypeDefinitionsTest { + @Test + void all() { + assertNotNull(SystematicLiteratureReviewStudyEntryTypeDefinitions.ALL); + } +} From e23030a43f3315d8df7145635153250d15cafbdd Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Fri, 15 Mar 2024 03:20:58 +0100 Subject: [PATCH 21/26] Update CSL styles (#11031) Co-authored-by: Siedlerchr <320228+Siedlerchr@users.noreply.github.com> --- src/main/resources/csl-locales | 2 +- src/main/resources/csl-styles | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/csl-locales b/src/main/resources/csl-locales index f6f859c2123..7b5a477f2d9 160000 --- a/src/main/resources/csl-locales +++ b/src/main/resources/csl-locales @@ -1 +1 @@ -Subproject commit f6f859c2123087ac51c574cd1ed3a64204ae73c7 +Subproject commit 7b5a477f2d9a8882b52bcecdc50f08d4422cc822 diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index 1bb9097598f..713bf5738ac 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit 1bb9097598f2d85d3e5997702bddc2d73ecfb584 +Subproject commit 713bf5738ac0b13c502e364cded9445c48d18193 From 6a0544e8df66f59837f26d1b7b6978cac78a2e84 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Fri, 15 Mar 2024 06:36:01 +0100 Subject: [PATCH 22/26] Remove non-existing recipe (#11029) --- rewrite.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/rewrite.yml b/rewrite.yml index 41738ae1eca..fd8ccb02f1b 100644 --- a/rewrite.yml +++ b/rewrite.yml @@ -103,8 +103,6 @@ recipeList: - org.openrewrite.java.migrate.lang.StringFormatted - org.openrewrite.java.migrate.util.SequencedCollection - - org.openrewrite.java.recipes.UseJavaParserBuilderInJavaTemplate - - org.openrewrite.java.RemoveObjectsIsNull - org.openrewrite.java.ShortenFullyQualifiedTypeReferences From a6eee32639b8956654515e873055b203a3037ee5 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Fri, 15 Mar 2024 12:33:01 +0100 Subject: [PATCH 23/26] Update teaching.md --- docs/teaching.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/teaching.md b/docs/teaching.md index 93e5fb79e25..f5c4b567097 100644 --- a/docs/teaching.md +++ b/docs/teaching.md @@ -83,8 +83,6 @@ Course: BSc Computer Science Individual Project * Summary: Students experience the procedure of finding and fixing small and medium issues in an open source project. * Successfully run in 2022/2023 -Do you have additions/suggestions for improvement? - #### Northern Arizona University (NAU), USA Course [CS499 - Open Source Software Development](https://github.com/igorsteinmacher/CS499-OSS) @@ -129,7 +127,7 @@ Course "Programming and Software Development" as part of the [BSc Software Engin Course [DD2480 Software Engineering Fundamentals](https://www.kth.se/student/kurser/kurs/DD2480?l=en) * Summary: Groups of students from three to five persons experienced the whole software engineering process within a week: From the requirements' specification to the final pull request. -* Successfully run in 2020 +* Successfully run in 2020, 2024, and other years. ### Portuguese From f5f92f9373da99e47f469cc8512978443bf6e57f Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Fri, 15 Mar 2024 16:28:23 +0100 Subject: [PATCH 24/26] Convert RemoveBracesFormatterTest to @ParameterizedTest (#11033) * Convert to @ParameterizedTest * Convert to csvsource --------- Co-authored-by: Carl Christian Snethlage <50491877+calixtus@users.noreply.github.com> --- .../RemoveBracesFormatterTest.java | 83 +++++-------------- 1 file changed, 21 insertions(+), 62 deletions(-) diff --git a/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveBracesFormatterTest.java b/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveBracesFormatterTest.java index 45a418094bc..37eff160e67 100644 --- a/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveBracesFormatterTest.java +++ b/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveBracesFormatterTest.java @@ -1,7 +1,8 @@ package org.jabref.logic.formatter.bibtexfields; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -10,67 +11,25 @@ */ public class RemoveBracesFormatterTest { - private RemoveBracesFormatter formatter; - - @BeforeEach - public void setUp() { - formatter = new RemoveBracesFormatter(); - } - - @Test - public void formatRemovesSingleEnclosingBraces() { - assertEquals("test", formatter.format("{test}")); - } - - @Test - public void formatKeepsUnmatchedBracesAtBeginning() { - assertEquals("{test", formatter.format("{test")); - } - - @Test - public void formatKeepsUnmatchedBracesAtEnd() { - assertEquals("test}", formatter.format("test}")); - } - - @Test - public void formatKeepsShortString() { - assertEquals("t", formatter.format("t")); - } - - @Test - public void formatRemovesBracesOnly() { - assertEquals("", formatter.format("{}")); - } - - @Test - public void formatKeepsEmptyString() { - assertEquals("", formatter.format("")); - } - - @Test - public void formatRemovesDoubleEnclosingBraces() { - assertEquals("test", formatter.format("{{test}}")); - } - - @Test - public void formatRemovesTripleEnclosingBraces() { - assertEquals("test", formatter.format("{{{test}}}")); - } - - @Test - public void formatKeepsNonMatchingBraces() { - assertEquals("{A} and {B}", formatter.format("{A} and {B}")); - } - - @Test - public void formatRemovesOnlyMatchingBraces() { - assertEquals("{A} and {B}", formatter.format("{{A} and {B}}")); - } - - @Test - public void formatDoesNotRemoveBracesInBrokenString() { - // We opt here for a conservative approach although one could argue that "A} and {B}" is also a valid return - assertEquals("{A} and {B}}", formatter.format("{A} and {B}}")); + private final RemoveBracesFormatter formatter = new RemoveBracesFormatter(); + + @ParameterizedTest + @CsvSource({ + "test, {test}", // formatRemovesSingleEnclosingBraces + "{test, {test", // formatKeepsUnmatchedBracesAtBeginning + "test}, test}", // formatKeepsUnmatchedBracesAtEnd + "t, t", // formatKeepsShortString + "'', {}", // formatRemovesBracesOnly + "test, {{test}}", // formatKeepsEmptyString + "test, {{{test}}}", // formatRemovesDoubleEnclosingBraces + "{A} and {B}, {A} and {B}", // formatRemovesTripleEnclosingBraces + "{A} and {B}, {{A} and {B}}", // formatKeepsNonMatchingBraces + "{A} and {B}}, {A} and {B}}", // formatRemovesOnlyMatchingBraces + "Vall{\\'e}e Poussin, {Vall{\\'e}e Poussin}", // formatDoesNotRemoveBracesInBrokenString + "Vall{\\'e}e Poussin, Vall{\\'e}e Poussin" + }) + public void format(String expected, String input) { + assertEquals(expected, formatter.format(input)); } @Test From d303aff3a8cded12cf943e391254ddb42a6f52a5 Mon Sep 17 00:00:00 2001 From: Emil Hultcrantz <90456354+Frequinzy@users.noreply.github.com> Date: Sun, 17 Mar 2024 18:53:12 +0100 Subject: [PATCH 25/26] Importing of BibDesk Groups and Linked Files (#10968) * Add test to check parsing of BibDesk Static Groups * Add test to check parsing of BibDesk Static Groups * Change isExpanded attribute to false in expected groups * remove extra blank line * Add tests to check parsing of BibDesk Smart and mixed groups * Add parsing of BibDesk Files * Attempts at plist * Now parses bdsk-file and shows it as a file in JabRef * Add test for parsing a bdsk-file field * Fix formatting * Add dd-plist library to documentation --------- Co-authored-by: Tian0602 <646432316@qq.com> * Add creation of static JabRef group from a BibDesk file * Creates an empty ExplicitGroup from BibDesk comment * Adds citations to new groups modifies group creations to support multiple groups in the same BibDeskFile * Fix requested changes Refactor imports since they did not match with main Add safety check in addBibDeskGroupEntriesToJabRefGroups --------- Co-authored-by: Filippa Nilsson * Refactor newline to match main branch Co-authored-by: Filippa Nilsson * Add changes to CHANGELOG.md * Reformat indentation to match previous * Revert external libraries Adjust groups serializing * checkstyle and optional magic * fix * fix tests * fix * fix dangling do * better group tree metadata setting * merge group trees, prevent duplicate group assignment in entry Add new BibDesk group Fix IOB for change listeing * fix tests, and extract constant * return early * fixtest and checkstyle --------- Co-authored-by: Anna Maartensson <120831475+annamaartensson@users.noreply.github.com> Co-authored-by: Tian0602 <646432316@qq.com> Co-authored-by: LottaJohnsson <35195355+LottaJohnsson@users.noreply.github.com> Co-authored-by: Filippa Nilsson Co-authored-by: Filippa Nilsson <75281470+filippanilsson@users.noreply.github.com> Co-authored-by: Oliver Kopp Co-authored-by: Siedlerchr --- CHANGELOG.md | 1 + build.gradle | 3 + licenses/com.googlecode.plist_ddplist.txt | 20 + src/main/java/module-info.java | 1 + .../bibtex/comparator/BibDatabaseDiff.java | 6 +- .../importer/fileformat/BibtexParser.java | 195 ++++++++-- .../MetadataSerializationConfiguration.java | 5 + .../jabref/model/groups/AllEntriesGroup.java | 6 + .../jabref/model/groups/GroupTreeNode.java | 12 + .../org/jabref/model/metadata/MetaData.java | 1 + .../importer/fileformat/BibtexParserTest.java | 349 +++++++++++++++++- 11 files changed, 561 insertions(+), 38 deletions(-) create mode 100644 licenses/com.googlecode.plist_ddplist.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 96db4a75227..cdd097ee56d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - When pasting HTML into the abstract or a comment field, the hypertext is automatically converted to Markdown. [#10558](https://github.com/JabRef/jabref/issues/10558) - We added the possibility to redownload files that had been present but are no longer in the specified location. [#10848](https://github.com/JabRef/jabref/issues/10848) - We added the citation key pattern `[camelN]`. Equivalent to the first N words of the `[camel]` pattern. +- We added importing of static groups and linked files from BibDesk .bib files. [#10381](https://github.com/JabRef/jabref/issues/10381) - We added ability to export in CFF (Citation File Format) [#10661](https://github.com/JabRef/jabref/issues/10661). - We added ability to push entries to TeXworks. [#3197](https://github.com/JabRef/jabref/issues/3197) - We added the ability to zoom in and out in the document viewer using Ctrl + Scroll. [#10964](https://github.com/JabRef/jabref/pull/10964) diff --git a/build.gradle b/build.gradle index a2e998897ae..61eeaafc33f 100644 --- a/build.gradle +++ b/build.gradle @@ -242,6 +242,9 @@ dependencies { // Because of GraalVM quirks, we need to ship that. See https://github.com/jspecify/jspecify/issues/389#issuecomment-1661130973 for details implementation 'org.jspecify:jspecify:0.3.0' + // parse plist files + implementation 'com.googlecode.plist:dd-plist:1.23' + testImplementation 'io.github.classgraph:classgraph:4.8.168' testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' testImplementation 'org.junit.platform:junit-platform-launcher:1.10.2' diff --git a/licenses/com.googlecode.plist_ddplist.txt b/licenses/com.googlecode.plist_ddplist.txt new file mode 100644 index 00000000000..ab9e4668533 --- /dev/null +++ b/licenses/com.googlecode.plist_ddplist.txt @@ -0,0 +1,20 @@ +dd-plist - An open source library to parse and generate property lists +Copyright (C) 2016 Daniel Dreibrodt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 17a0f833a57..fd4dbe1b0c2 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -144,4 +144,5 @@ requires org.libreoffice.uno; requires de.saxsys.mvvmfx.validation; requires com.jthemedetector; + requires dd.plist; } diff --git a/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java b/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java index 7645ed85d1c..b807124cfc3 100644 --- a/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java +++ b/src/main/java/org/jabref/logic/bibtex/comparator/BibDatabaseDiff.java @@ -49,6 +49,11 @@ private static EntryComparator getEntryComparator() { private static List compareEntries(List originalEntries, List newEntries, BibDatabaseMode mode) { List differences = new ArrayList<>(); + // Prevent IndexOutOfBoundException + if (newEntries.isEmpty()) { + return differences; + } + // Create a HashSet where we can put references to entries in the new // database that we have matched. This is to avoid matching them twice. Set used = new HashSet<>(newEntries.size()); @@ -88,7 +93,6 @@ private static List compareEntries(List originalEntries, } } } - BibEntry bestEntry = newEntries.get(bestMatchIndex); if (bestMatch > MATCH_THRESHOLD || hasEqualCitationKey(originalEntry, bestEntry) diff --git a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java index 0a32dd95989..fa535dab56b 100644 --- a/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java +++ b/src/main/java/org/jabref/logic/importer/fileformat/BibtexParser.java @@ -1,10 +1,13 @@ package org.jabref.logic.importer.fileformat; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PushbackReader; import java.io.Reader; import java.io.StringWriter; +import java.nio.file.Path; +import java.util.Base64; import java.util.Collection; import java.util.Deque; import java.util.HashMap; @@ -16,12 +19,17 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import java.util.regex.Pattern; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + import org.jabref.logic.bibtex.FieldContentFormatter; import org.jabref.logic.bibtex.FieldWriter; import org.jabref.logic.exporter.BibtexDatabaseWriter; import org.jabref.logic.exporter.SaveConfiguration; +import org.jabref.logic.groups.DefaultGroupsFactory; import org.jabref.logic.importer.ImportFormatPreferences; import org.jabref.logic.importer.Importer; import org.jabref.logic.importer.ParseException; @@ -35,17 +43,31 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryType; import org.jabref.model.entry.BibtexString; +import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.FieldProperty; import org.jabref.model.entry.field.StandardField; import org.jabref.model.entry.types.EntryTypeFactory; +import org.jabref.model.groups.ExplicitGroup; +import org.jabref.model.groups.GroupHierarchyType; +import org.jabref.model.groups.GroupTreeNode; import org.jabref.model.metadata.MetaData; import org.jabref.model.util.DummyFileUpdateMonitor; import org.jabref.model.util.FileUpdateMonitor; +import com.dd.plist.BinaryPropertyListParser; +import com.dd.plist.NSDictionary; +import com.dd.plist.NSString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import static org.jabref.logic.util.MetadataSerializationConfiguration.GROUP_QUOTE_CHAR; +import static org.jabref.logic.util.MetadataSerializationConfiguration.GROUP_TYPE_SUFFIX; /** * Class for importing BibTeX-files. @@ -68,8 +90,8 @@ */ public class BibtexParser implements Parser { private static final Logger LOGGER = LoggerFactory.getLogger(BibtexParser.class); - private static final Integer LOOKAHEAD = 1024; + private static final String BIB_DESK_ROOT_GROUP_NAME = "BibDeskGroups"; private final FieldContentFormatter fieldContentFormatter; private final Deque pureTextFromFile = new LinkedList<>(); private final ImportFormatPreferences importFormatPreferences; @@ -80,11 +102,16 @@ public class BibtexParser implements Parser { private int line = 1; private ParserResult parserResult; private final MetaDataParser metaDataParser; + private final Map parsedBibdeskGroups; + + private GroupTreeNode bibDeskGroupTreeNode; + private final DocumentBuilderFactory builder = DocumentBuilderFactory.newInstance(); public BibtexParser(ImportFormatPreferences importFormatPreferences, FileUpdateMonitor fileMonitor) { this.importFormatPreferences = Objects.requireNonNull(importFormatPreferences); this.fieldContentFormatter = new FieldContentFormatter(importFormatPreferences.fieldPreferences()); this.metaDataParser = new MetaDataParser(fileMonitor); + this.parsedBibdeskGroups = new HashMap<>(); } public BibtexParser(ImportFormatPreferences importFormatPreferences) { @@ -209,28 +236,51 @@ private ParserResult parseFileContent() throws IOException { // Try to read the entry type String entryType = parseTextToken().toLowerCase(Locale.ROOT).trim(); - if ("preamble".equals(entryType)) { - database.setPreamble(parsePreamble()); - // Consume a new line which separates the preamble from the next part (if the file was written with JabRef) - skipOneNewline(); - // the preamble is saved verbatim anyway, so the text read so far can be dropped - dumpTextReadSoFarToString(); - } else if ("string".equals(entryType)) { - parseBibtexString(); - } else if ("comment".equals(entryType)) { - parseJabRefComment(meta); - } else { - // Not a comment, preamble, or string. Thus, it is an entry - parseAndAddEntry(entryType); + switch (entryType) { + case "preamble" -> { + database.setPreamble(parsePreamble()); + // Consume a new line which separates the preamble from the next part (if the file was written with JabRef) + skipOneNewline(); + // the preamble is saved verbatim anyway, so the text read so far can be dropped + dumpTextReadSoFarToString(); + } + case "string" -> + parseBibtexString(); + case "comment" -> + parseJabRefComment(meta); + default -> + // Not a comment, preamble, or string. Thus, it is an entry + parseAndAddEntry(entryType); } skipWhitespace(); } + addBibDeskGroupEntriesToJabRefGroups(); + try { - parserResult.setMetaData(metaDataParser.parse( + MetaData metaData = metaDataParser.parse( meta, - importFormatPreferences.bibEntryPreferences().getKeywordSeparator())); + importFormatPreferences.bibEntryPreferences().getKeywordSeparator()); + if (bibDeskGroupTreeNode != null) { + metaData.getGroups().ifPresentOrElse(existingGroupTree -> { + var existingGroups = meta.get(MetaData.GROUPSTREE); + // We only have one Group BibDeskGroup with n children + // instead of iterating through the whole group structure every time we just search in the metadata for the group name + var groupsToAdd = bibDeskGroupTreeNode.getChildren() + .stream(). + filter(Predicate.not(groupTreeNode -> existingGroups.contains(GROUP_TYPE_SUFFIX + groupTreeNode.getName() + GROUP_QUOTE_CHAR))); + groupsToAdd.forEach(existingGroupTree::addChild); + }, + // metadata does not contain any groups, so we need to create an AllEntriesGroup and add the other groups as children + () -> { + GroupTreeNode rootNode = new GroupTreeNode(DefaultGroupsFactory.getAllEntriesGroup()); + bibDeskGroupTreeNode.moveTo(rootNode); + metaData.setGroups(rootNode); + } + ); + } + parserResult.setMetaData(metaData); } catch (ParseException exception) { parserResult.addException(exception); } @@ -282,7 +332,6 @@ private void parseAndAddEntry(String type) { } catch (IOException ex) { // This makes the parser more robust: // If an exception is thrown when parsing an entry, drop the entry and try to resume parsing. - LOGGER.warn("Could not parse entry", ex); parserResult.addWarning(Localization.lang("Error occurred when parsing entry") + ": '" + ex.getMessage() + "'. " + "\n\n" + Localization.lang("JabRef skipped the entry.")); @@ -330,6 +379,73 @@ private void parseJabRefComment(Map meta) { // custom entry types are always re-written by JabRef and not stored in the file dumpTextReadSoFarToString(); + } else if (comment.startsWith(MetaData.BIBDESK_STATIC_FLAG)) { + try { + parseBibDeskComment(comment, meta); + } catch (ParseException ex) { + parserResult.addException(ex); + } + } + } + + /** + * Adds BibDesk group entries to the JabRef database + */ + private void addBibDeskGroupEntriesToJabRefGroups() { + for (String groupName : parsedBibdeskGroups.keySet()) { + String[] citationKeys = parsedBibdeskGroups.get(groupName).split(","); + for (String citation : citationKeys) { + Optional bibEntry = database.getEntryByCitationKey(citation); + Optional groupValue = bibEntry.flatMap(entry -> entry.getField(StandardField.GROUPS)); + if (groupValue.isEmpty()) { // if the citation does not belong to a group already + bibEntry.flatMap(entry -> entry.setField(StandardField.GROUPS, groupName)); + } else if (!groupValue.get().contains(groupName)) { + // if the citation does belong to a group already and is not yet assigned to the same group, we concatenate + String concatGroup = groupValue.get() + "," + groupName; + bibEntry.flatMap(entryByCitationKey -> entryByCitationKey.setField(StandardField.GROUPS, concatGroup)); + } + } + } + } + + /** + * Parses comment types found in BibDesk, to migrate BibDesk Static Groups to JabRef. + */ + private void parseBibDeskComment(String comment, Map meta) throws ParseException { + String xml = comment.substring(MetaData.BIBDESK_STATIC_FLAG.length() + 1, comment.length() - 1); + try { + // Build a document to handle the xml tags + Document doc = builder.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes())); + doc.getDocumentElement().normalize(); + + NodeList dictList = doc.getElementsByTagName("dict"); + meta.putIfAbsent(MetaData.DATABASE_TYPE, "bibtex;"); + bibDeskGroupTreeNode = GroupTreeNode.fromGroup(new ExplicitGroup(BIB_DESK_ROOT_GROUP_NAME, GroupHierarchyType.INDEPENDENT, importFormatPreferences.bibEntryPreferences().getKeywordSeparator())); + + // Since each static group has their own dict element, we iterate through them + for (int i = 0; i < dictList.getLength(); i++) { + Element dictElement = (Element) dictList.item(i); + NodeList keyList = dictElement.getElementsByTagName("key"); + NodeList stringList = dictElement.getElementsByTagName("string"); + + String groupName = null; + String citationKeys = null; + + // Retrieves group name and group entries and adds these to the metadata + for (int j = 0; j < keyList.getLength(); j++) { + if (keyList.item(j).getTextContent().matches("group name")) { + groupName = stringList.item(j).getTextContent(); + var staticGroup = new ExplicitGroup(groupName, GroupHierarchyType.INDEPENDENT, importFormatPreferences.bibEntryPreferences().getKeywordSeparator()); + bibDeskGroupTreeNode.addSubgroup(staticGroup); + } else if (keyList.item(j).getTextContent().matches("keys")) { + citationKeys = stringList.item(j).getTextContent(); // adds group entries + } + } + // Adds the group name and citation keys to the field so all the entries can be added in the groups once parsed + parsedBibdeskGroups.putIfAbsent(groupName, citationKeys); + } + } catch (ParserConfigurationException | IOException | SAXException e) { + throw new ParseException(e); } } @@ -618,13 +734,29 @@ private void parseField(BibEntry entry) throws IOException { // it inconvenient // for users if JabRef did not accept it. if (field.getProperties().contains(FieldProperty.PERSON_NAMES)) { - entry.setField(field, entry.getField(field).get() + " and " + content); + entry.setField(field, entry.getField(field).orElse("") + " and " + content); } else if (StandardField.KEYWORDS == field) { // multiple keywords fields should be combined to one entry.addKeyword(content, importFormatPreferences.bibEntryPreferences().getKeywordSeparator()); } } else { - entry.setField(field, content); + // If a BibDesk File Field is encountered + if (field.getName().length() > 10 && field.getName().startsWith("bdsk-file-")) { + byte[] decodedBytes = Base64.getDecoder().decode(content); + try { + // Parse the base64 encoded binary plist to get the relative (to the .bib file) path + NSDictionary plist = (NSDictionary) BinaryPropertyListParser.parse(decodedBytes); + NSString relativePath = (NSString) plist.objectForKey("relativePath"); + Path path = Path.of(relativePath.getContent()); + + LinkedFile file = new LinkedFile("", path, ""); + entry.addFile(file); + } catch (Exception e) { + throw new IOException(); + } + } else { + entry.setField(field, content); + } } } } @@ -774,7 +906,6 @@ private String fixKey() throws IOException { /** * returns a new StringBuilder which corresponds to toRemove without whitespaces - * */ private StringBuilder removeWhitespaces(StringBuilder toRemove) { StringBuilder result = new StringBuilder(); @@ -919,20 +1050,16 @@ private StringBuilder parseBracketedFieldContent() throws IOException { // Check for "\},\n" - Example context: ` path = {c:\temp\},\n` // On Windows, it could be "\},\r\n", thus we rely in OS.NEWLINE.charAt(0) (which returns '\r' or '\n'). // In all cases, we should check for '\n' as the file could be encoded with Linux line endings on Windows. - if ((nextTwoCharacters[0] == ',') && ((nextTwoCharacters[1] == OS.NEWLINE.charAt(0)) || (nextTwoCharacters[1] == '\n'))) { - // We hit '\}\r` or `\}\n` - // Heuristics: Unwanted escaping of } - // - // Two consequences: - // - // 1. Keep `\` as read - // This is already done - // - // 2. Treat `}` as closing bracket - isClosingBracket = true; - } else { - isClosingBracket = false; - } + // We hit '\}\r` or `\}\n` + // Heuristics: Unwanted escaping of } + // + // Two consequences: + // + // 1. Keep `\` as read + // This is already done + // + // 2. Treat `}` as closing bracket + isClosingBracket = (nextTwoCharacters[0] == ',') && ((nextTwoCharacters[1] == OS.NEWLINE.charAt(0)) || (nextTwoCharacters[1] == '\n')); } else { isClosingBracket = true; } diff --git a/src/main/java/org/jabref/logic/util/MetadataSerializationConfiguration.java b/src/main/java/org/jabref/logic/util/MetadataSerializationConfiguration.java index 8ca88821f12..db99eb45882 100644 --- a/src/main/java/org/jabref/logic/util/MetadataSerializationConfiguration.java +++ b/src/main/java/org/jabref/logic/util/MetadataSerializationConfiguration.java @@ -18,6 +18,11 @@ public class MetadataSerializationConfiguration { */ public static final char GROUP_QUOTE_CHAR = '\\'; + /** + * Group Type suffix (part of the GroupType) + */ + public static final String GROUP_TYPE_SUFFIX = ":"; + /** * For separating units (e.g. name and hierarchic context) in the string representation */ diff --git a/src/main/java/org/jabref/model/groups/AllEntriesGroup.java b/src/main/java/org/jabref/model/groups/AllEntriesGroup.java index 320b3306ce8..1e3ef85fa53 100644 --- a/src/main/java/org/jabref/model/groups/AllEntriesGroup.java +++ b/src/main/java/org/jabref/model/groups/AllEntriesGroup.java @@ -23,6 +23,12 @@ public boolean equals(Object o) { return o instanceof AllEntriesGroup aeg && Objects.equals(aeg.getName(), getName()); } + /** + * Always returns true for any BibEntry! + * + * @param entry The @{@link BibEntry} to check + * @return Always returns true + */ @Override public boolean contains(BibEntry entry) { return true; diff --git a/src/main/java/org/jabref/model/groups/GroupTreeNode.java b/src/main/java/org/jabref/model/groups/GroupTreeNode.java index efe4d233fee..d5954181d9d 100644 --- a/src/main/java/org/jabref/model/groups/GroupTreeNode.java +++ b/src/main/java/org/jabref/model/groups/GroupTreeNode.java @@ -135,6 +135,13 @@ public int hashCode() { return Objects.hash(group); } + /** + * Get only groups containing all the entries or just groups containing any of the + * + * @param entries List of {@link BibEntry} to search for + * @param requireAll Whether to return only groups that must contain all entries + * @return List of {@link GroupTreeNode} containing the matches. {@link AllEntriesGroup} is always contained} + */ public List getContainingGroups(List entries, boolean requireAll) { List groups = new ArrayList<>(); @@ -197,6 +204,11 @@ public List getEntriesInGroup(List entries) { return result; } + /** + * Get the name of the underlying group + * + * @return String the name of the group + */ public String getName() { return group.getName(); } diff --git a/src/main/java/org/jabref/model/metadata/MetaData.java b/src/main/java/org/jabref/model/metadata/MetaData.java index 0479400337a..5c7293487ad 100644 --- a/src/main/java/org/jabref/model/metadata/MetaData.java +++ b/src/main/java/org/jabref/model/metadata/MetaData.java @@ -48,6 +48,7 @@ public class MetaData { public static final String FILE_DIRECTORY_LATEX = "fileDirectoryLatex"; public static final String PROTECTED_FLAG_META = "protectedFlag"; public static final String SELECTOR_META_PREFIX = "selector_"; + public static final String BIBDESK_STATIC_FLAG = "BibDesk Static Groups"; public static final char ESCAPE_CHARACTER = '\\'; public static final char SEPARATOR_CHARACTER = ';'; diff --git a/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java b/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java index 9ec42a04d89..3c84de494c2 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/BibtexParserTest.java @@ -31,6 +31,8 @@ import org.jabref.logic.importer.ParseException; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.util.OS; +import org.jabref.model.TreeNode; +import org.jabref.model.database.BibDatabase; import org.jabref.model.database.BibDatabaseMode; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryType; @@ -56,6 +58,7 @@ import org.jabref.model.metadata.SaveOrder; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; @@ -72,10 +75,10 @@ /** * Tests for reading whole bib files can be found at {@link org.jabref.logic.importer.fileformat.BibtexImporterTest} *

- * Tests cannot be executed concurrently, because Localization is used at {@link BibtexParser#parseAndAddEntry(String)} + * Tests cannot be executed concurrently, because Localization is used at {@link BibtexParser#sparseAndAddEntry(String)} */ class BibtexParserTest { - + private static final String BIB_DESK_ROOT_GROUP_NAME = "BibDeskGroups"; private ImportFormatPreferences importFormatPreferences; private BibtexParser parser; @@ -87,7 +90,7 @@ void setUp() { } @Test - void parseWithNullThrowsNullPointerException() throws Exception { + void parseWithNullThrowsNullPointerException() { Executable toBeTested = () -> parser.parse(null); assertThrows(NullPointerException.class, toBeTested); } @@ -1380,6 +1383,311 @@ void integrationTestGroupTree() throws IOException, ParseException { ((ExplicitGroup) root.getChildren().get(2).getGroup()).getLegacyEntryKeys()); } + /** + * Checks that BibDesk Static Groups are available after parsing the library + */ + @Test + void integrationTestBibDeskStaticGroup() throws Exception { + ParserResult result = parser.parse(new StringReader(""" + @article{Swain:2023aa, + author = {Subhashree Swain and P. Shalima and K.V.P. Latha}, + date-added = {2023-09-14 20:09:08 +0200}, + date-modified = {2023-09-14 20:09:08 +0200}, + eprint = {2309.06758}, + month = {09}, + title = {Unravelling the Nuclear Dust Morphology of NGC 1365: A Two Phase Polar - RAT Model for the Ultraviolet to Infrared Spectral Energy Distribution}, + url = {https://arxiv.org/pdf/2309.06758.pdf}, + year = {2023}, + bdsk-url-1 = {https://arxiv.org/pdf/2309.06758.pdf}, + bdsk-url-2 = {https://arxiv.org/abs/2309.06758}} + + @article{Heyl:2023aa, + author = {Johannes Heyl and Joshua Butterworth and Serena Viti}, + date-added = {2023-09-14 20:09:08 +0200}, + date-modified = {2023-09-14 20:09:08 +0200}, + eprint = {2309.06784}, + month = {09}, + title = {Understanding Molecular Abundances in Star-Forming Regions Using Interpretable Machine Learning}, + url = {https://arxiv.org/pdf/2309.06784.pdf}, + year = {2023}, + bdsk-url-1 = {https://arxiv.org/pdf/2309.06784.pdf}, + bdsk-url-2 = {https://arxiv.org/abs/2309.06784}} + + @comment{BibDesk Static Groups{ + + + + + + group name + firstTestGroup + keys + Swain:2023aa,Heyl:2023aa + + + group name + secondTestGroup + keys + Swain:2023aa + + + + }} + """)); + + GroupTreeNode root = result.getMetaData().getGroups().get(); + assertEquals(new AllEntriesGroup("All entries"), root.getGroup()); + assertEquals(Optional.of(BIB_DESK_ROOT_GROUP_NAME), root.getFirstChild().map(GroupTreeNode::getName)); + + ExplicitGroup firstTestGroupExpected = new ExplicitGroup("firstTestGroup", GroupHierarchyType.INDEPENDENT, ','); + firstTestGroupExpected.setExpanded(true); + + assertEquals(Optional.of(firstTestGroupExpected), root.getFirstChild().flatMap(TreeNode::getFirstChild).map(GroupTreeNode::getGroup)); + + ExplicitGroup secondTestGroupExpected = new ExplicitGroup("secondTestGroup", GroupHierarchyType.INDEPENDENT, ','); + secondTestGroupExpected.setExpanded(true); + assertEquals(Optional.of(secondTestGroupExpected), root.getFirstChild().flatMap(TreeNode::getLastChild).map(GroupTreeNode::getGroup)); + + BibDatabase db = result.getDatabase(); + + assertEquals(List.of(root.getGroup(), firstTestGroupExpected), root.getContainingGroups(db.getEntries(), true).stream().map(GroupTreeNode::getGroup).toList()); + assertEquals(List.of(root.getGroup(), firstTestGroupExpected), root.getContainingGroups(db.getEntryByCitationKey("Heyl:2023aa").stream().toList(), false).stream().map(GroupTreeNode::getGroup).toList()); + } + + /** + * Checks that BibDesk Smart Groups are available after parsing the library + */ + @Test + @Disabled("Not yet supported") + void integrationTestBibDeskSmartGroup() throws Exception { + ParserResult result = parser.parse(new StringReader(""" + @article{Kraljic:2023aa, + author = {Katarina Kraljic and Florent Renaud and Yohan Dubois and Christophe Pichon and Oscar Agertz and Eric Andersson and Julien Devriendt and Jonathan Freundlich and Sugata Kaviraj and Taysun Kimm and Garreth Martin and S{\\'e}bastien Peirani and {\\'A}lvaro Segovia Otero and Marta Volonteri and Sukyoung K. Yi}, + date-added = {2023-09-14 20:09:10 +0200}, + date-modified = {2023-09-14 20:09:10 +0200}, + eprint = {2309.06485}, + month = {09}, + title = {Emergence and cosmic evolution of the Kennicutt-Schmidt relation driven by interstellar turbulence}, + url = {https://arxiv.org/pdf/2309.06485.pdf}, + year = {2023}, + bdsk-url-1 = {https://arxiv.org/pdf/2309.06485.pdf}, + bdsk-url-2 = {https://arxiv.org/abs/2309.06485}} + + @article{Swain:2023aa, + author = {Subhashree Swain and P. Shalima and K.V.P. Latha}, + date-added = {2023-09-14 20:09:08 +0200}, + date-modified = {2023-09-14 20:09:08 +0200}, + eprint = {2309.06758}, + month = {09}, + title = {Unravelling the Nuclear Dust Morphology of NGC 1365: A Two Phase Polar - RAT Model for the Ultraviolet to Infrared Spectral Energy Distribution}, + url = {https://arxiv.org/pdf/2309.06758.pdf}, + year = {2023}, + bdsk-url-1 = {https://arxiv.org/pdf/2309.06758.pdf}, + bdsk-url-2 = {https://arxiv.org/abs/2309.06758}} + + @article{Heyl:2023aa, + author = {Johannes Heyl and Joshua Butterworth and Serena Viti}, + date-added = {2023-09-14 20:09:08 +0200}, + date-modified = {2023-09-14 20:09:08 +0200}, + eprint = {2309.06784}, + month = {09}, + title = {Understanding Molecular Abundances in Star-Forming Regions Using Interpretable Machine Learning}, + url = {https://arxiv.org/pdf/2309.06784.pdf}, + year = {2023}, + bdsk-url-1 = {https://arxiv.org/pdf/2309.06784.pdf}, + bdsk-url-2 = {https://arxiv.org/abs/2309.06784}} + + @comment{BibDesk Smart Groups{ + + + + + + conditions + + + comparison + 4 + key + BibTeX Type + value + article + version + 1 + + + comparison + 2 + key + Title + value + the + version + 1 + + + conjunction + 0 + group name + article + + + conditions + + + comparison + 3 + key + Author + value + Swain + version + 1 + + + conjunction + 0 + group name + Swain + + + + }} + """)); + + GroupTreeNode root = result.getMetaData().getGroups().get(); + assertEquals(new AllEntriesGroup("All entries"), root.getGroup()); + assertEquals(2, root.getNumberOfChildren()); + ExplicitGroup firstTestGroupExpected = new ExplicitGroup("article", GroupHierarchyType.INDEPENDENT, ','); + firstTestGroupExpected.setExpanded(false); + assertEquals(firstTestGroupExpected, root.getChildren().get(0).getGroup()); + ExplicitGroup secondTestGroupExpected = new ExplicitGroup("Swain", GroupHierarchyType.INDEPENDENT, ','); + secondTestGroupExpected.setExpanded(false); + assertEquals(secondTestGroupExpected, root.getChildren().get(1).getGroup()); + + BibDatabase db = result.getDatabase(); + List firstTestGroupEntriesExpected = new ArrayList<>(); + firstTestGroupEntriesExpected.add(db.getEntryByCitationKey("Kraljic:2023aa").get()); + firstTestGroupEntriesExpected.add(db.getEntryByCitationKey("Swain:2023aa").get()); + assertTrue(root.getChildren().get(0).getGroup().containsAll(firstTestGroupEntriesExpected)); + assertFalse(root.getChildren().get(1).getGroup().contains(db.getEntryByCitationKey("Swain:2023aa").get())); + } + + /** + * Checks that both BibDesk Static Groups and Smart Groups are available after parsing the library + */ + @Test + @Disabled("Not yet supported") + void integrationTestBibDeskMultipleGroup() throws Exception { + ParserResult result = parser.parse(new StringReader(""" + @article{Kraljic:2023aa, + author = {Katarina Kraljic and Florent Renaud and Yohan Dubois and Christophe Pichon and Oscar Agertz and Eric Andersson and Julien Devriendt and Jonathan Freundlich and Sugata Kaviraj and Taysun Kimm and Garreth Martin and S{\\'e}bastien Peirani and {\\'A}lvaro Segovia Otero and Marta Volonteri and Sukyoung K. Yi}, + date-added = {2023-09-14 20:09:10 +0200}, + date-modified = {2023-09-14 20:09:10 +0200}, + eprint = {2309.06485}, + month = {09}, + title = {Emergence and cosmic evolution of the Kennicutt-Schmidt relation driven by interstellar turbulence}, + url = {https://arxiv.org/pdf/2309.06485.pdf}, + year = {2023}, + bdsk-url-1 = {https://arxiv.org/pdf/2309.06485.pdf}, + bdsk-url-2 = {https://arxiv.org/abs/2309.06485}} + + @article{Swain:2023aa, + author = {Subhashree Swain and P. Shalima and K.V.P. Latha}, + date-added = {2023-09-14 20:09:08 +0200}, + date-modified = {2023-09-14 20:09:08 +0200}, + eprint = {2309.06758}, + month = {09}, + title = {Unravelling the Nuclear Dust Morphology of NGC 1365: A Two Phase Polar - RAT Model for the Ultraviolet to Infrared Spectral Energy Distribution}, + url = {https://arxiv.org/pdf/2309.06758.pdf}, + year = {2023}, + bdsk-url-1 = {https://arxiv.org/pdf/2309.06758.pdf}, + bdsk-url-2 = {https://arxiv.org/abs/2309.06758}} + + @article{Heyl:2023aa, + author = {Johannes Heyl and Joshua Butterworth and Serena Viti}, + date-added = {2023-09-14 20:09:08 +0200}, + date-modified = {2023-09-14 20:09:08 +0200}, + eprint = {2309.06784}, + month = {09}, + title = {Understanding Molecular Abundances in Star-Forming Regions Using Interpretable Machine Learning}, + url = {https://arxiv.org/pdf/2309.06784.pdf}, + year = {2023}, + bdsk-url-1 = {https://arxiv.org/pdf/2309.06784.pdf}, + bdsk-url-2 = {https://arxiv.org/abs/2309.06784}} + + @comment{BibDesk Static Groups{ + + + + + + group name + firstTestGroup + keys + Swain:2023aa,Heyl:2023aa + + + + }} + + @comment{BibDesk Smart Groups{ + + + + + + conditions + + + comparison + 4 + key + BibTeX Type + value + article + version + 1 + + + comparison + 2 + key + Title + value + the + version + 1 + + + conjunction + 0 + group name + article + + + + }} + """)); + + GroupTreeNode root = result.getMetaData().getGroups().get(); + assertEquals(new AllEntriesGroup("All entries"), root.getGroup()); + assertEquals(2, root.getNumberOfChildren()); + ExplicitGroup firstTestGroupExpected = new ExplicitGroup("firstTestGroup", GroupHierarchyType.INDEPENDENT, ','); + firstTestGroupExpected.setExpanded(false); + assertEquals(firstTestGroupExpected, root.getChildren().get(0).getGroup()); + ExplicitGroup secondTestGroupExpected = new ExplicitGroup("article", GroupHierarchyType.INDEPENDENT, ','); + secondTestGroupExpected.setExpanded(false); + assertEquals(secondTestGroupExpected, root.getChildren().get(1).getGroup()); + + BibDatabase db = result.getDatabase(); + assertTrue(root.getChildren().get(0).getGroup().containsAll(db.getEntries())); + List smartGroupEntriesExpected = new ArrayList<>(); + smartGroupEntriesExpected.add(db.getEntryByCitationKey("Kraljic:2023aa").get()); + smartGroupEntriesExpected.add(db.getEntryByCitationKey("Swain:2023aa").get()); + assertTrue(root.getChildren().get(0).getGroup().containsAll(smartGroupEntriesExpected)); + } + /** * Checks that a TexGroup finally gets the required data, after parsing the library. */ @@ -1863,4 +2171,39 @@ void parseDuplicateKeywordsWithTwoEntries() throws Exception { ParserResult result = parser.parse(new StringReader(entries)); assertEquals(List.of(expectedEntryFirst, expectedEntrySecond), result.getDatabase().getEntries()); } + + @Test + void parseBibDeskLinkedFiles() throws IOException { + + BibEntry expectedEntry = new BibEntry(StandardEntryType.Article); + expectedEntry.withCitationKey("Kovakkuni:2023aa") + .withField(StandardField.AUTHOR, "Navyasree Kovakkuni and Federico Lelli and Pierre-alain Duc and M{\\'e}d{\\'e}ric Boquien and Jonathan Braine and Elias Brinks and Vassilis Charmandaris and Francoise Combes and Jeremy Fensch and Ute Lisenfeld and Stacy McGaugh and J. Chris Mihos and Marcel. S. Pawlowski and Yves. Revaz and Peter. M. Weilbacher") + .withField(new UnknownField("date-added"), "2023-09-14 20:09:12 +0200") + .withField(new UnknownField("date-modified"), "2023-09-14 20:09:12 +0200") + .withField(StandardField.EPRINT, "2309.06478") + .withField(StandardField.MONTH, "09") + .withField(StandardField.TITLE, "Molecular and Ionized Gas in Tidal Dwarf Galaxies: The Spatially Resolved Star-Formation Relation") + .withField(StandardField.URL, "https://arxiv.org/pdf/2309.06478.pdf") + .withField(StandardField.YEAR, "2023") + .withField(new UnknownField("bdsk-url-1"), "https://arxiv.org/abs/2309.06478") + .withField(StandardField.FILE, ":../../Downloads/2309.06478.pdf:"); + + ParserResult result = parser.parse(new StringReader(""" + @article{Kovakkuni:2023aa, + author = {Navyasree Kovakkuni and Federico Lelli and Pierre-alain Duc and M{\\'e}d{\\'e}ric Boquien and Jonathan Braine and Elias Brinks and Vassilis Charmandaris and Francoise Combes and Jeremy Fensch and Ute Lisenfeld and Stacy McGaugh and J. Chris Mihos and Marcel. S. Pawlowski and Yves. Revaz and Peter. M. Weilbacher}, + date-added = {2023-09-14 20:09:12 +0200}, + date-modified = {2023-09-14 20:09:12 +0200}, + eprint = {2309.06478}, + month = {09}, + title = {Molecular and Ionized Gas in Tidal Dwarf Galaxies: The Spatially Resolved Star-Formation Relation}, + url = {https://arxiv.org/pdf/2309.06478.pdf}, + year = {2023}, + bdsk-file-1 = {YnBsaXN0MDDSAQIDBFxyZWxhdGl2ZVBhdGhZYWxpYXNEYXRhXxAeLi4vLi4vRG93bmxvYWRzLzIzMDkuMDY0NzgucGRmTxEBUgAAAAABUgACAAAMTWFjaW50b3NoIEhEAAAAAAAAAAAAAAAAAAAA4O/yLkJEAAH/////DjIzMDkuMDY0NzgucGRmAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////hKRkeAAAAAAAAAAAAAgACAAAKIGN1AAAAAAAAAAAAAAAAAAlEb3dubG9hZHMAAAIAKy86VXNlcnM6Y2hyaXN0b3BoczpEb3dubG9hZHM6MjMwOS4wNjQ3OC5wZGYAAA4AHgAOADIAMwAwADkALgAwADYANAA3ADgALgBwAGQAZgAPABoADABNAGEAYwBpAG4AdABvAHMAaAAgAEgARAASAClVc2Vycy9jaHJpc3RvcGhzL0Rvd25sb2Fkcy8yMzA5LjA2NDc4LnBkZgAAEwABLwAAFQACABH//wAAAAgADQAaACQARQAAAAAAAAIBAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAGb}, + bdsk-url-1 = {https://arxiv.org/abs/2309.06478}} + } + """)); + BibDatabase database = result.getDatabase(); + + assertEquals(Collections.singletonList(expectedEntry), database.getEntries()); + } } From 197f73026bb42001f9a4d5075c64832158a24b08 Mon Sep 17 00:00:00 2001 From: Oliver Kopp Date: Sun, 17 Mar 2024 18:58:18 +0100 Subject: [PATCH 26/26] Speed up failure reporting (#11030) --- .github/workflows/tests.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3070c02b51c..1016762e4aa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -171,11 +171,15 @@ jobs: run: xvfb-run --auto-servernum ./gradlew check -x checkstyleJmh -x checkstyleMain -x checkstyleTest -x modernizer env: CI: "true" + - name: Prepare format failed test results + if: failure() + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: xml-twig-tools xsltproc + version: 1.0 - name: Format failed test results if: failure() - run: | - sudo apt-get install -qq -y xml-twig-tools xsltproc - scripts/after-failure.sh + run: scripts/after-failure.sh databasetests: name: Database tests runs-on: ubuntu-latest