diff --git a/.github/workflows/add-greeting-to-issue.yml b/.github/workflows/add-greeting-to-issue.yml index 497a43d3697..b94650f1a62 100644 --- a/.github/workflows/add-greeting-to-issue.yml +++ b/.github/workflows/add-greeting-to-issue.yml @@ -20,6 +20,10 @@ jobs: with: issue-number: ${{ github.event.issue.number || github.event.pull_request.number }} body: | - As a general advice for newcomers: check out [Contributing](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md) for a start. Also, [guidelines for setting up a local workspace](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace) is worth having a look at. + Welcome to the vibrant world of open-source development with JabRef! - Feel free to ask here at GitHub, if you have any issue related questions. If you have questions about how to setup your workspace use JabRef's [Gitter](https://gitter.im/JabRef/jabref) chat. Try to open a (draft) pull-request early on, so that people can see you are working on the issue and so that they can see the direction the pull request is heading towards. This way, you will likely receive valuable feedback. + Newcomers, we're excited to have you on board. Start by exploring our [Contributing](https://github.com/JabRef/jabref/blob/main/CONTRIBUTING.md) guidelines, and don't forget to check out our [workspace setup guidelines](https://devdocs.jabref.org/getting-into-the-code/guidelines-for-setting-up-a-local-workspace) to get started smoothly. + + Having any questions or issues? Feel free to ask here on GitHub. Need help setting up your local workspace? Join the conversation on [JabRef's Gitter chat](https://gitter.im/JabRef/jabref). And don't hesitate to open a (draft) pull request early on to show the direction it is heading towards. This way, you will receive valuable feedback. + + Happy coding! 🚀 diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index bad9c1a6943..ccb056e9bec 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -31,7 +31,7 @@ jobs: restore-keys: cache-lychee- - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@v1.9.3 + uses: lycheeverse/lychee-action@v1.10.0 with: fail: true args: --accept '200,201,202,203,204,403,429,500' --max-concurrency 1 --cache --no-progress --exclude-all-private './**/*.md' diff --git a/.github/workflows/deployment-arm64.yml b/.github/workflows/deployment-arm64.yml index 69c4ac284a5..a7337738223 100644 --- a/.github/workflows/deployment-arm64.yml +++ b/.github/workflows/deployment-arm64.yml @@ -82,14 +82,14 @@ jobs: security delete-keychain signing_temp.keychain ${{runner.temp}}/keychain/notarization.keychain || true - name: Setup OSX key chain on macOS-arm if: (steps.checksecrets.outputs.secretspresent == 'YES') - uses: apple-actions/import-codesign-certs@v2 + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d with: p12-file-base64: ${{ secrets.OSX_SIGNING_CERT }} p12-password: ${{ secrets.OSX_CERT_PWD }} keychain-password: jabref - name: Setup OSX key chain on OSX for app id cert if: (steps.checksecrets.outputs.secretspresent == 'YES') - uses: apple-actions/import-codesign-certs@v2 + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d with: p12-file-base64: ${{ secrets.OSX_SIGNING_CERT_APPLICATION }} p12-password: ${{ secrets.OSX_CERT_PWD }} @@ -130,7 +130,14 @@ jobs: --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref.merged.module \ --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref.merged.module \ - --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module + --java-options --add-opens=javafx.controls/javafx.scene.control.skin=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref - name: Build pkg (macOS) if: (steps.checksecrets.outputs.secretspresent == 'YES') shell: bash @@ -160,7 +167,13 @@ jobs: --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref.merged.module \ --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref.merged.module \ - --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref - name: Rename files with arm64 suffix as well if: (steps.checksecrets.outputs.secretspresent == 'YES') shell: bash diff --git a/.github/workflows/deployment-jdk-ea.yml b/.github/workflows/deployment-jdk-ea.yml index eadf8903387..63d8e2a1dd5 100644 --- a/.github/workflows/deployment-jdk-ea.yml +++ b/.github/workflows/deployment-jdk-ea.yml @@ -200,35 +200,33 @@ jobs: run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" prepareModulesDir - name: Set up macOS key chain if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') - uses: apple-actions/import-codesign-certs@v2 + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d with: p12-file-base64: ${{ secrets.OSX_SIGNING_CERT }} p12-password: ${{ secrets.OSX_CERT_PWD }} keychain-password: jabref - name: Set up macOS key chain for app id cert if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') - uses: apple-actions/import-codesign-certs@v2 + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d with: p12-file-base64: ${{ secrets.OSX_SIGNING_CERT_APPLICATION }} p12-password: ${{ secrets.OSX_CERT_PWD }} create-keychain: false keychain-password: jabref - name: Build runtime image and installer - if: (steps.checksecrets.outputs.secretspresent == 'YES') shell: bash run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" jpackage jlinkZip - name: Package application image - if: (steps.checksecrets.outputs.secretspresent == 'YES') shell: bash run: ${{ matrix.archivePortable }} - name: Rename files - if: (matrix.os != 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') + if: (matrix.os != 'macos-latest') shell: pwsh run: | get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace "${{ steps.gitversion.outputs.AssemblySemVer }}","${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}"} get-childitem -Path build/distribution/* | rename-item -NewName {$_.name -replace "portable","${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}-portable"} - name: Repack deb file for Debian - if: (matrix.os == 'ubuntu-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') + if: (matrix.os == 'ubuntu-latest') shell: bash run: | cd build/distribution @@ -239,7 +237,6 @@ jobs: rm debian-binary control.tar.* data.tar.* mv -f jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64_repackaged.deb jabref_${{ steps.gitversion.outputs.Major }}.${{ steps.gitversion.outputs.Minor }}_amd64.deb - name: Rename files with JDK version - if: (steps.checksecrets.outputs.secretspresent == 'YES') shell: bash run: | for file in build/distribution/*.*; do diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index c61282d61c0..030790397d1 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -100,14 +100,14 @@ jobs: run: ./gradlew -i -PprojVersion="${{ steps.gitversion.outputs.AssemblySemVer }}" -PprojVersionInfo="${{ steps.gitversion.outputs.InformationalVersion }}" prepareModulesDir - name: Setup macOS key chain if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') - uses: apple-actions/import-codesign-certs@v2 + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d with: p12-file-base64: ${{ secrets.OSX_SIGNING_CERT }} p12-password: ${{ secrets.OSX_CERT_PWD }} keychain-password: jabref - name: Setup macOS key chain for app id cert if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') - uses: apple-actions/import-codesign-certs@v2 + uses: slidoapp/import-codesign-certs@1923310662e8682dd05b76b612b53301f431cd5d with: p12-file-base64: ${{ secrets.OSX_SIGNING_CERT_APPLICATION }} p12-password: ${{ secrets.OSX_CERT_PWD }} @@ -142,7 +142,14 @@ jobs: --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref.merged.module \ --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref.merged.module \ - --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module + --java-options --add-opens=javafx.controls/javafx.scene.control.skin=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref - name: Build pkg (macOS) if: (matrix.os == 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') shell: bash @@ -172,7 +179,14 @@ jobs: --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref.merged.module \ --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref.merged.module \ - --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module + --java-options --add-opens=javafx.controls/javafx.scene.control.skin=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref.merged.module \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-exports=javafx.base/com.sun.javafx.event=org.jabref \ + --java-options --add-exports=javafx.controls/com.sun.javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.graphics/javafx.scene=org.jabref \ + --java-options --add-opens=javafx.controls/javafx.scene.control=org.jabref \ + --java-options --add-opens=javafx.controls/com.sun.javafx.scene.control=org.jabref - name: Build runtime image and installer (linux, Windows) if: (matrix.os != 'macos-latest') && (steps.checksecrets.outputs.secretspresent == 'YES') shell: bash diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fa4bc3f0276..9f2625a7228 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -335,3 +335,12 @@ jobs: show-progress: 'false' - name: Merge Conflict finder uses: olivernybroe/action-conflict-finder@v4.0 + other_than_main: + name: Source branch is other than "main" + runs-on: ubuntu-latest + steps: + - if: github.head_ref == 'main' + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Pull requests should come from a branch other than "main"\n\n👉 Please read https://devdocs.jabref.org/contributing again carefully. 👈') diff --git a/CHANGELOG.md b/CHANGELOG.md index 5681827b24f..98029604043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,22 +16,31 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv - We added value selection (such as for month) for content selectors in custom entry types. [#11109](https://github.com/JabRef/jabref/issues/11109) - We added a duplicate checker for the Citation Relations tab. [#10414](https://github.com/JabRef/jabref/issues/10414) - We added tooltip on main table cells that shows cell content or cell content and entry preview if set in preferences. [10925](https://github.com/JabRef/jabref/issues/10925) +- Added a formatter to remove word enclosing braces. [#11222](https://github.com/JabRef/jabref/issues/11222) - We added the ability to add a keyword/crossref when typing the separator character (e.g., comma) in the keywords/crossref fields. [#11178](https://github.com/JabRef/jabref/issues/11178) - We added an exporter and improved the importer for Endnote XML format. [#11137](https://github.com/JabRef/jabref/issues/11137) - We added a button in gui to give users an easy option to select and add local bst files to available styles in preview layout list. [#11102](https://github.com/JabRef/jabref/issues/11102) +- We added support for automatically update LaTeX citations when a LaTeX file is created, removed, or modified. [#10585](https://github.com/JabRef/jabref/issues/10585) ### Changed - We replaced the word "Key bindings" with "Keyboard shortcuts" in the Preferences tab. [#11153](https://github.com/JabRef/jabref/pull/11153) -- We slightly improved the duplicate check if ISBN numbers are present. [#8885](https://github.com/JabRef/jabref/issues/8885) +- We slightly improved the duplicate check if ISBNs are present. [#8885](https://github.com/JabRef/jabref/issues/8885) +- JabRef no longer downloads HTML files of websites when a PDF was not found. [#10149](https://github.com/JabRef/jabref/issues/10149) ### Fixed -- We fixed an issue where entry type with duplicate fields prevented opening existing libraries with custom entry types [#11127](https://github.com/JabRef/jabref/issues/11127) +- We fixed an issue where entry type with duplicate fields prevented opening existing libraries with custom entry types. [#11127](https://github.com/JabRef/jabref/issues/11127) +- We fixed an issue where Markdown rendering removed braces from the text. [#10928](https://github.com/JabRef/jabref/issues/10928) - We fixed an issue when the file was flagged as changed on disk in the case of content selectors or groups. [#9064](https://github.com/JabRef/jabref/issues/9064) -- We fixed crash on opening the entry editor when auto completion is enabled. [#11188](https://github.com/JabRef/jabref/issues/11188) +- We fixed crash on opening the entry editor when auto-completion is enabled. [#11188](https://github.com/JabRef/jabref/issues/11188) - We fixed the usage of the key binding for "Clear search" (default: Escape). [#10764](https://github.com/JabRef/jabref/issues/10764) - We fixed an issue where library shown as unsaved and marked (*) after accepting changes made externally to the file. [#11027](https://github.com/JabRef/jabref/issues/11027) +- We fixed an issue where drag and dropping entries from one library to another was not always working. [#11254](https://github.com/JabRef/jabref/issues/11254) +- We fixed an issue where drag and dropping entries created a shallow copy. [#11160](https://github.com/JabRef/jabref/issues/11160) +- We fixed an issue where imports to a custom group would only work for the first entry [#11085](https://github.com/JabRef/jabref/issues/11085), [#11269](https://github.com/JabRef/jabref/issues/11269) +- We fixed an issue where a new entry was not added to the selected group [#8933](https://github.com/JabRef/jabref/issues/8933) +- We fixed an issue where the horizontal position of the Entry Preview inside the entry editor was not remembered across restarts [#11281](https://github.com/JabRef/jabref/issues/11281) ### Removed diff --git a/build.gradle b/build.gradle index a0d3ede962a..a545093ff9e 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ plugins { id 'idea' - id 'org.openrewrite.rewrite' version '6.11.2' + id 'org.openrewrite.rewrite' version '6.12.0' } // Enable following for debugging @@ -162,15 +162,15 @@ dependencies { implementation "org.apache.lucene:lucene-analysis-common:$luceneVersion" implementation "org.apache.lucene:lucene-highlighter:$luceneVersion" - implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.10.0' + implementation group: 'org.apache.commons', name: 'commons-csv', version: '1.11.0' implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.14.0' - implementation group: 'org.apache.commons', name: 'commons-text', version: '1.11.0' + implementation group: 'org.apache.commons', name: 'commons-text', version: '1.12.0' implementation 'com.h2database:h2-mvstore:2.2.224' // required for reading write-protected PDFs - see https://github.com/JabRef/jabref/pull/942#issuecomment-209252635 - implementation 'org.bouncycastle:bcprov-jdk18on:1.78' + implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1' - implementation 'commons-cli:commons-cli:1.6.0' + implementation 'commons-cli:commons-cli:1.7.0' implementation 'org.libreoffice:unoloader:7.6.4' implementation 'org.libreoffice:libreoffice:7.6.4' @@ -184,8 +184,8 @@ dependencies { implementation group: 'org.eclipse.jgit', name: 'org.eclipse.jgit', version: '6.9.0.202403050737-r' - implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.17.0' - implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.17.0' + implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.17.1' + implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.17.1' implementation 'com.fasterxml:aalto-xml:1.3.2' @@ -218,19 +218,22 @@ dependencies { } implementation 'org.fxmisc.flowless:flowless:0.7.2' implementation 'org.fxmisc.richtext:richtextfx:0.11.2' - implementation (group: 'com.dlsc.gemsfx', name: 'gemsfx', version: '2.8.0') { + implementation (group: 'com.dlsc.gemsfx', name: 'gemsfx', version: '2.16.0') { exclude module: 'javax.inject' // Split package, use only jakarta.inject exclude module: 'commons-lang3' exclude group: 'org.openjfx' exclude group: 'org.apache.logging.log4j' + exclude group: 'tech.units' } + // Required by gemsfx + implementation 'tech.units:indriya:2.2' implementation 'org.controlsfx:controlsfx:11.2.1' implementation 'org.jsoup:jsoup:1.17.2' implementation 'com.konghq:unirest-java:3.14.5' - implementation 'org.slf4j:slf4j-api:2.0.12' + implementation 'org.slf4j:slf4j-api:2.0.13' implementation "org.tinylog:tinylog-api:2.7.0" implementation "org.tinylog:slf4j-tinylog:2.7.0" implementation "org.tinylog:tinylog-impl:2.7.0" @@ -240,7 +243,7 @@ dependencies { // route all requests to log4j to SLF4J implementation 'org.apache.logging.log4j:log4j-to-slf4j:2.23.1' - implementation('de.undercouch:citeproc-java:3.0.0') { + implementation('de.undercouch:citeproc-java:3.1.0') { exclude group: 'org.antlr' } @@ -265,7 +268,7 @@ dependencies { implementation group: 'org.jooq', name: 'jool', version: '0.9.15' // JAX-RS implemented by Jersey // API - implementation 'jakarta.ws.rs:jakarta.ws.rs-api:3.1.0' + implementation 'jakarta.ws.rs:jakarta.ws.rs-api:4.0.0' // Implementation of the API implementation 'org.glassfish.jersey.core:jersey-server:3.1.6' // injection framework @@ -293,25 +296,27 @@ dependencies { // YAML formatting implementation 'org.yaml:snakeyaml:2.2' - testImplementation 'io.github.classgraph:classgraph:4.8.170' + implementation 'commons-io:commons-io:2.16.1' + + testImplementation 'io.github.classgraph:classgraph:4.8.172' testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' testImplementation 'org.junit.platform:junit-platform-launcher:1.10.2' - testImplementation 'org.mockito:mockito-core:5.11.0' - testImplementation 'org.xmlunit:xmlunit-core:2.9.1' - testImplementation 'org.xmlunit:xmlunit-matchers:2.9.1' + testImplementation 'org.mockito:mockito-core:5.12.0' + testImplementation 'org.xmlunit:xmlunit-core:2.10.0' + testImplementation 'org.xmlunit:xmlunit-matchers:2.10.0' testRuntimeOnly 'com.tngtech.archunit:archunit-junit5-engine:1.3.0' testImplementation 'com.tngtech.archunit:archunit-junit5-api:1.3.0' testImplementation "org.testfx:testfx-core:4.0.16-alpha" testImplementation "org.testfx:testfx-junit5:4.0.16-alpha" testImplementation "org.hamcrest:hamcrest-library:2.2" - checkstyle 'com.puppycrawl.tools:checkstyle:10.15.0' + checkstyle 'com.puppycrawl.tools:checkstyle:10.16.0' // xjc needs the runtime as well for the ant task, otherwise it fails xjc group: 'org.glassfish.jaxb', name: 'jaxb-xjc', version: '3.0.2' xjc group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '3.0.2' - rewrite(platform("org.openrewrite.recipe:rewrite-recipe-bom:2.9.0")) + rewrite(platform("org.openrewrite.recipe:rewrite-recipe-bom:2.11.0")) rewrite("org.openrewrite.recipe:rewrite-static-analysis") rewrite("org.openrewrite.recipe:rewrite-logging-frameworks") rewrite("org.openrewrite.recipe:rewrite-testing-frameworks") @@ -419,6 +424,9 @@ tasks.register('generateCitaviSource', XjcTask) { tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' + + // hint by https://docs.gradle.org/current/userguide/performance.html#run_the_compiler_as_a_separate_process + options.fork = true } compileJava { @@ -520,16 +528,32 @@ testlogger { showStackTraces false } +tasks.withType(Test) { + reports.html.outputLocation.set(file("${reporting.baseDir}/${name}")) + // Enable parallel tests. See https://docs.gradle.org/8.1/userguide/performance.html#execute_tests_in_parallel for details. + maxParallelForks = Runtime.runtime.availableProcessors() - 1 +} + tasks.register('databaseTest', Test) { useJUnitPlatform { includeTags 'DatabaseTest' } + + testLogging { + // set options for log level LIFECYCLE + events = ["FAILED"] + exceptionFormat "full" + } + + maxParallelForks = 1 } tasks.register('fetcherTest', Test) { useJUnitPlatform { includeTags 'FetcherTest' } + + maxParallelForks = 1 } tasks.register('guiTest', Test) { @@ -542,6 +566,8 @@ tasks.register('guiTest', Test) { events = ["FAILED"] exceptionFormat "full" } + + maxParallelForks = 1 } // Test result tasks @@ -551,10 +577,6 @@ tasks.register('copyTestResources', Copy) { } processTestResources.dependsOn copyTestResources -tasks.withType(Test) { - reports.html.outputLocation.set(file("${reporting.baseDir}/${name}")) -} - tasks.register('jacocoPrepare') { doFirst { // Ignore failures of tests @@ -570,16 +592,18 @@ databaseTest.mustRunAfter jacocoPrepare fetcherTest.mustRunAfter jacocoPrepare jacocoTestReport { + dependsOn jacocoPrepare, test, fetcherTest, databaseTest + executionData files( - "$buildDir/jacoco/test.exec", - "$buildDir/jacoco/databaseTest.exec", - "$buildDir/jacoco/fetcherTest.exec").filter { it.exists() } - dependsOn jacocoPrepare, test, databaseTest, fetcherTest + layout.buildDirectory.file('jacoco/test.exec').get().asFile, + layout.buildDirectory.file('jacoco/fetcherTest.exec').get().asFile, + layout.buildDirectory.file('jacoco/databaseTest.exec').get().asFile) reports { - csv.getRequired().set(true) - html.getRequired().set(true) - xml.getRequired().set(true) // coveralls plugin depends on xml format report + csv.required = true + html.required = true + // coveralls plugin depends on xml format report + xml.required = true } } diff --git a/docs/code-howtos/testing.md b/docs/code-howtos/testing.md index 88a9f6d3dbc..88f8ab84d12 100644 --- a/docs/code-howtos/testing.md +++ b/docs/code-howtos/testing.md @@ -3,20 +3,7 @@ parent: Code Howtos --- # Testing JabRef -## Background on Java testing - -In JabRef, we mainly rely on basic JUnit tests to increase code coverage. There are other ways to test: - -| Type | Techniques | Tool (Java) | Kind of tests | Used In JabRef | -| -------------- | ------------------------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------- | -| Functional | Dynamics, black box, positive and negative | [JUnit-QuickCheck](https://github.com/pholser/junit-quickcheck) | Random data generation | No, not intended, because other test kinds seem more helpful. | -| Functional | Dynamics, black box, positive and negative | [GraphWalker](https://graphwalker.github.io) | Model-based | No, because the BibDatabase doesn't need to be tests | -| Functional | Dynamics, black box, positive and negative | [TestFX](https://github.com/TestFX/TestFX) | GUI Tests | Yes | -| Functional | Dynamics, white box, negative | [PIT](https://pitest.org) | Mutation | No | -| Functional | Dynamics, white box, positive and negative | [Mockito](https://site.mockito.org) | Mocking | Yes | -| Non-functional | Dynamics, black box, positive and negative | [JETM](http://jetm.void.fm), [Apache JMeter](https://jmeter.apache.org) | Performance (performance testing vs load testing respectively) | No | -| Structural | Static, white box | [CheckStyle](https://checkstyle.sourceforge.io) | Constient formatting of the source code | Yes | -| Structural | Dynamics, white box | [SpotBugs](https://spotbugs.github.io) | Reocurreing bugs (based on experience of other projects) | No | +In JabRef, we mainly rely on basic [JUnit](https://junit.org/junit5/docs/current/user-guide/) unit tests to increase code coverage. ## General hints on tests @@ -27,10 +14,10 @@ Imagine you want to test the method `format(String value)` in the class `BracesF * _Test only one thing per test:_ tests should be short and test only one small part of the method. So instead of ```java - testFormat() { - assertEqual("test", format("test")); - assertEqual("{test", format("{test")); - assertEqual("test", format("test}}")); + void format() { + assertEqual("test", format("test")); + assertEqual("{test", format("{test")); + assertEqual("test", format("test}}")); } ``` @@ -38,18 +25,42 @@ Imagine you want to test the method `format(String value)` in the class `BracesF * Do _not just test happy paths_, but also wrong/weird input. * It is recommended to write tests _before_ you actually implement the functionality (test driven development). * _Bug fixing:_ write a test case covering the bug and then fix it, leaving the test as a security that the bug will never reappear. -* Do not catch exceptions in tests, instead use the `assertThrows(Exception.class, ()->doSomethingThrowsEx())` feature of [junit-jupiter](https://junit.org/junit5/docs/current/user-guide/) to the test method. +* Do not catch exceptions in tests, instead use the `assertThrows(Exception.class, () -> doSomethingThrowsEx())` feature of [junit-jupiter](https://junit.org/junit5/docs/current/user-guide/) to the test method. + +## Coverage + +IntelliJ has build in test coverage reports. Choose "Run with coverage". + +For a full coverage report as HTML, execute the gradle task `jacocoTestReport` (available in the "verification" folder in IntelliJ). +Then, you will find which shows the coverage of the tests. ## Lists in tests -* Use `assertEquals(Collections.emptyList(), actualList);` instead of `assertEquals(0, actualList.size());` to test whether a list is empty. -* Similarly, use `assertEquals(Arrays.asList("a", "b"), actualList);` to compare lists instead of +Instead of - ```java - assertEquals(2, actualList.size()); - assertEquals("a", actualList.get(0)); - assertEquals("b", actualList.get(1)); - ``` +```java +assertTrue(actualList.isEmpty()); +``` + +use + +```java +assertEquals(List.of(), actualList); +``` + +Similarly, to compare lists, instead of following code: + +```java +assertEquals(2, actualList.size()); +assertEquals("a", actualList.get(0)); +assertEquals("b", actualList.get(1)); +``` + +use the following code: + +```java +assertEquals(List.of("a", "b"), actualList); +``` ## BibEntries in tests @@ -57,19 +68,18 @@ Imagine you want to test the method `format(String value)` in the class `BracesF ## Files and folders in tests -* If you need a temporary file in tests, then add the following Annotation before the class: +If you need a temporary file in tests, use the `@TempDir` annotation: - ```java - @ExtendWith(TempDirectory.class) - class TestClass{ +```java +class TestClass{ - @BeforeEach - void setUp(@TempDirectory.TempDir Path temporaryFolder){ - } - } - ``` + @Test + void deletionWorks(@TempDir Path tempDir) { + } +} +``` - to the test class. A temporary file is now created by `Files.createFile(path)`. Using this pattern automatically ensures that the test folder is deleted after the tests are run. See the [junit-pioneer doc](https://junit-pioneer.org/docs/temp-directory/) for more details. +to the test class. A temporary file is now created by `Files.createFile(path)`. Using this pattern automatically ensures that the test folder is deleted after the tests are run. See for more details. ## Loading Files from Resources @@ -129,3 +139,18 @@ docker run -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=jabref -p 3800:3307 mys ``` Set the environment variable `DBMS` to `mysql`. + +## Advanced testing and further reading + +On top of basic unit testing, there are more ways to test a software: + +| Type | Techniques | Tool (Java) | Kind of tests | Used In JabRef | +| -------------- | ------------------------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------- | +| Functional | Dynamics, black box, positive and negative | [JUnit-QuickCheck](https://github.com/pholser/junit-quickcheck) | Random data generation | No, not intended, because other test kinds seem more helpful. | +| Functional | Dynamics, black box, positive and negative | [GraphWalker](https://graphwalker.github.io) | Model-based | No, because the BibDatabase doesn't need to be tests | +| Functional | Dynamics, black box, positive and negative | [TestFX](https://github.com/TestFX/TestFX) | GUI Tests | Yes | +| Functional | Dynamics, white box, negative | [PIT](https://pitest.org) | Mutation | No | +| Functional | Dynamics, white box, positive and negative | [Mockito](https://site.mockito.org) | Mocking | Yes | +| Non-functional | Dynamics, black box, positive and negative | [JETM](http://jetm.void.fm), [Apache JMeter](https://jmeter.apache.org) | Performance (performance testing vs load testing respectively) | No | +| Structural | Static, white box | [CheckStyle](https://checkstyle.sourceforge.io) | Constient formatting of the source code | Yes | +| Structural | Dynamics, white box | [SpotBugs](https://spotbugs.github.io) | Reocurreing bugs (based on experience of other projects) | No | diff --git a/docs/decisions/0028-http-return-bibtex-string.md b/docs/decisions/0028-http-return-bibtex-string.md index e60a931d54a..bc8ff9394eb 100644 --- a/docs/decisions/0028-http-return-bibtex-string.md +++ b/docs/decisions/0028-http-return-bibtex-string.md @@ -2,8 +2,6 @@ nav_order: 28 parent: Decision Records --- - - # Return BibTeX string and CSL Item JSON in the API ## Context and Problem Statement diff --git a/docs/decisions/0029-cff-export-multiple-entries.md b/docs/decisions/0029-cff-export-multiple-entries.md index 179ceab745d..ec1dab8cfc0 100644 --- a/docs/decisions/0029-cff-export-multiple-entries.md +++ b/docs/decisions/0029-cff-export-multiple-entries.md @@ -1,10 +1,7 @@ --- -nav_order: 28 +nav_order: 29 parent: Decision Records --- - - - # Exporting multiple entries to CFF ## Context and Problem Statement diff --git a/docs/decisions/0030-use-apache-commons-io-for-directory-monitoring.md b/docs/decisions/0030-use-apache-commons-io-for-directory-monitoring.md new file mode 100644 index 00000000000..42a762132c8 --- /dev/null +++ b/docs/decisions/0030-use-apache-commons-io-for-directory-monitoring.md @@ -0,0 +1,48 @@ +--- +nav_order: 30 +parent: Decision Records +--- +# Use Apache Commons IO for directory monitoring + +## Context and Problem Statement + +In JabRef, there is a need to add a directory monitor that will listen for changes in a specified directory. + +Currently, the monitor is used to automatically update the [LaTeX Citations](https://docs.jabref.org/advanced/entryeditor/latex-citations) when a LaTeX file in the LaTeX directory is created, removed, or modified ([#10585](https://github.com/JabRef/jabref/issues/10585)). +Additionally, this monitor will be used to create a dynamic group that mirrors the file system structure ([#10930](https://github.com/JabRef/jabref/issues/10930)). + +## Considered Options + +* Use [java.nio.file.WatchService](https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/nio/file/WatchService.html) +* Use [io.methvin.watcher.DirectoryWatcher](https://github.com/gmethvin/directory-watcher) +* Use [org.apache.commons.io.monitor](https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/monitor/package-summary.html) + +## Decision Outcome + +Chosen option: "Use [org.apache.commons.io.monitor](https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/monitor/package-summary.html)", because comes out best (see below). + +## Pros and Cons of the Options + +### java.nio.file.WatchService + +* Good, because it is a standard Java API for watching directories. +* Good, because it does not need polling, it is event-based for most operating systems. +* Bad, because: + 1. Does not detect files coming together with a new folder (JDK issue: [JDK-8162948](https://bugs.openjdk.org/browse/JDK-8162948)). + 2. Deleting a subdirectory does not detect deleted files in that directory. + 3. Access denied when trying to delete the recursively watched directory on Windows (JDK issue: [JDK-6972833](https://bugs.openjdk.org/browse/JDK-6972833)). + 4. Implemented on macOS by the generic `PollingWatchService`. (JDK issue: [JDK-8293067](https://bugs.openjdk.org/browse/JDK-8293067)) + +### io.methvin.watcher.DirectoryWatcher + +* Good, because it implemented on top of the `java.nio.file.WatchService`, which is a standard Java API for watching directories. +* Good, because it resolves some of the issues of the `java.nio.file.WatchService`. + * Uses`ExtendedWatchEventModifier.FILE_TREE` on Windows, which resolves issues (1, 3) of the `java.nio.file.WatchService`. + * On macOS have native implementation based on the Carbon File System Events API, this resolves issue (4) of the `java.nio.file.WatchService`. +* Bad, because issue (2) of the `java.nio.file.WatchService` is not resolved. + +### org.apache.commons.io.monitor + +* Good, because there are no observed issues. +* Good, because can handle huge amount of files without overflowing. +* Bad, because it uses a polling mechanism at fixed intervals, which can waste CPU cycles if no change occurs. diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-open-module-settings.png b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-open-module-settings.png deleted file mode 100644 index deef5f86734..00000000000 Binary files a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/guidelines-intellij-open-module-settings.png and /dev/null differ diff --git a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md index 1dfb4f01f58..3ffe0b5ca9c 100644 --- a/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md +++ b/docs/getting-into-the-code/guidelines-for-setting-up-a-local-workspace/intellij-12-build.md @@ -32,12 +32,6 @@ After clicking "Download", IntelliJ installs Eclipse Temurin: ![IntelliJ installs Eclipse Temurin](guidelines-intellij-installs-temurin.png) {% endfigure %} -Open the module settings: Right click on "JabRef" and select "Open Module Settings": - -{% figure caption:"Open IntelliJ Module Settings" %} -![IntelliJ Module Settings](guidelines-intellij-open-module-settings.png) -{% endfigure %} - Navigate to **Project Settings > Project** and ensure that the projects' SDK is Java 21. {% figure caption:"Project SDK is pinned to the downloaded SDK (showing JDK 21 as example)" %} diff --git a/external-libraries.md b/external-libraries.md index 16b5165fe82..a4e483bb84d 100644 --- a/external-libraries.md +++ b/external-libraries.md @@ -294,6 +294,12 @@ Project: Apache Commons Digester URL: https://commons.apache.org/proper/commons-digester/ ``` +```yaml +Id: commons-io:commons-io +Project: Apache Commons IO +URL: https://commons.apache.org/proper/commons-io/ +``` + ```yaml Id: de.rototor.jeuclid:jeuclid-core Project: JEuclid @@ -537,7 +543,7 @@ Id: com.ibm.icu:* Project: International Components for Unicode URL: https://icu.unicode.org/ License: Unicode License (https://www.unicode.org/copyright.html) -Note: Our own fork https://github.com/JabRef/icu. Upstream PR: https://github.com/unicode-org/icu/pull/2127 +Note: Our own fork https://github.com/JabRef/icu. [Upstream PR](https://github.com/unicode-org/icu/pull/2127) Path: lib/icu4j.jar SourcePath: lib/ic4j-src.jar ``` @@ -579,49 +585,49 @@ License: LGPL-2.1-or-later ```yaml Id: org.openjfx:javafx-base -Project JavaFX +Project: JavaFX URL: https://openjfx.io/ License: GPL-2.0 WITH Classpath-exception-2.0 ``` ```yaml Id: org.openjfx:javafx-controls -Project JavaFX +Project: JavaFX URL: https://openjfx.io/ License: GPL-2.0 WITH Classpath-exception-2.0 ``` ```yaml Id: org.openjfx:javafx-fxml -Project JavaFX +Project: JavaFX URL: https://openjfx.io/ License: GPL-2.0 WITH Classpath-exception-2.0 ``` ```yaml Id: org.openjfx:javafx-graphics -Project JavaFX +Project: JavaFX URL: https://openjfx.io/ License: GPL-2.0 WITH Classpath-exception-2.0 ``` ```yaml Id: org.openjfx:javafx-media -Project JavaFX +Project: JavaFX URL: https://openjfx.io/ License: GPL-2.0 WITH Classpath-exception-2.0 ``` ```yaml Id: org.openjfx:javafx-swing -Project JavaFX +Project: JavaFX URL: https://openjfx.io/ License: GPL-2.0 WITH Classpath-exception-2.0 ``` ```yaml Id: org.openjfx:javafx-web -Project JavaFX +Project: JavaFX URL: https://openjfx.io/ License: GPL-2.0 WITH Classpath-exception-2.0 ``` @@ -691,9 +697,13 @@ License: BSD-3-Clause ## Sorted list of runtime dependencies output by gradle -1. `./gradlew dependencies > build/dependencies.txt` -2. Manually edit depedencies.txt to contain the tree of "compileClasspath" and "implementation" only. Otherwise, libraries such as "Apache Commons Lang 3" are missed. Ensure LF as line ending. -3. (on WSL) `sed 's/[^a-z]*//' < build/dependencies.txt | sed "s/\(.*\) .*/\1/" | grep -v "\->" | sort | uniq > build/dependencies-for-external-libraries.txt` +1. `./gradlew dependencyReport --configuration compileClasspath` +2. Fix `build/reports/project/dependencies.txt` + + - Change line endings to `LF` + - Remove text above and below the tree + +3. (on WSL) `sed 's/[^a-z]*//' < build/reports/project/dependencies.txt | sed "s/\(.*\) .*/\1/" | grep -v "\->" | sort | uniq > build/dependencies-for-external-libraries.txt` ```text at.favre.lib:hkdf:1.1.0 @@ -761,6 +771,7 @@ commons-cli:commons-cli:1.6.0 commons-codec:commons-codec:1.16.0 commons-collections:commons-collections:3.2.2 commons-digester:commons-digester:2.1 +commons-io:commons-io:2.16.1 commons-logging:commons-logging:1.2 commons-validator:commons-validator:1.7 de.rototor.jeuclid:jeuclid-core:3.1.11 @@ -809,7 +820,7 @@ org.apache.pdfbox:fontbox:3.0.2 org.apache.pdfbox:pdfbox-io:3.0.2 org.apache.pdfbox:pdfbox:3.0.2 org.apache.pdfbox:xmpbox:3.0.2 -org.bouncycastle:bcprov-jdk18on:1.77 +org.bouncycastle:bcprov-jdk18on:1.78 org.checkerframework:checker-qual:3.42.0 org.codehaus.woodstox:stax2-api:4.2 org.controlsfx:controlsfx:11.2.1 @@ -818,24 +829,24 @@ org.fxmisc.flowless:flowless:0.7.2 org.fxmisc.richtext:richtextfx:0.11.2 org.fxmisc.undo:undofx:2.1.1 org.fxmisc.wellbehaved:wellbehavedfx:0.3.3 -org.glassfish.grizzly:grizzly-framework:4.0.1 -org.glassfish.grizzly:grizzly-http-server:4.0.1 -org.glassfish.grizzly:grizzly-http:4.0.1 +org.glassfish.grizzly:grizzly-framework:4.0.2 +org.glassfish.grizzly:grizzly-http-server:4.0.2 +org.glassfish.grizzly:grizzly-http:4.0.2 org.glassfish.hk2.external:aopalliance-repackaged:3.1.0 org.glassfish.hk2:hk2-api:3.1.0 -org.glassfish.hk2:hk2-locator:3.0.5 +org.glassfish.hk2:hk2-locator:3.0.6 org.glassfish.hk2:hk2-utils:3.1.0 org.glassfish.hk2:osgi-resource-locator:1.0.3 org.glassfish.jaxb:jaxb-core:4.0.3 org.glassfish.jaxb:jaxb-runtime:4.0.3 org.glassfish.jaxb:txw2:4.0.3 -org.glassfish.jersey.containers:jersey-container-grizzly2-http:3.1.5 -org.glassfish.jersey.core:jersey-client:3.1.5 -org.glassfish.jersey.core:jersey-common:3.1.5 -org.glassfish.jersey.core:jersey-server:3.1.5 -org.glassfish.jersey.inject:jersey-hk2:3.1.5 +org.glassfish.jersey.containers:jersey-container-grizzly2-http:3.1.6 +org.glassfish.jersey.core:jersey-client:3.1.6 +org.glassfish.jersey.core:jersey-common:3.1.6 +org.glassfish.jersey.core:jersey-server:3.1.6 +org.glassfish.jersey.inject:jersey-hk2:3.1.6 org.jabref:afterburner.fx:2.0.0 -org.javassist:javassist:3.29.2-GA +org.javassist:javassist:3.30.2-GA org.jbibtex:jbibtex:1.0.20 org.jetbrains:annotations:24.0.1 org.jooq:jool:0.9.15 @@ -850,18 +861,18 @@ org.kordamp.ikonli:ikonli-materialdesign2-pack:12.3.1 org.libreoffice:libreoffice:7.6.4 org.libreoffice:unoloader:7.6.4 org.mariadb.jdbc:mariadb-java-client:2.7.9 -org.openjfx:javafx-base:22 -org.openjfx:javafx-controls:22 -org.openjfx:javafx-fxml:22 -org.openjfx:javafx-graphics:22 -org.openjfx:javafx-media:22 -org.openjfx:javafx-swing:22 -org.openjfx:javafx-web:22 +org.openjfx:javafx-base:22.0.1 +org.openjfx:javafx-controls:22.0.1 +org.openjfx:javafx-fxml:22.0.1 +org.openjfx:javafx-graphics:22.0.1 +org.openjfx:javafx-media:22.0.1 +org.openjfx:javafx-swing:22.0.1 +org.openjfx:javafx-web:22.0.1 org.postgresql:postgresql:42.7.3 org.reactfx:reactfx:2.0-M5 org.scala-lang:scala-library:2.13.8 -org.slf4j:jul-to-slf4j:2.0.12 -org.slf4j:slf4j-api:2.0.12 +org.slf4j:jul-to-slf4j:2.0.13 +org.slf4j:slf4j-api:2.0.13 org.tinylog:slf4j-tinylog:2.7.0 org.tinylog:tinylog-api:2.7.0 org.tinylog:tinylog-impl:2.7.0 diff --git a/gradle.properties b/gradle.properties index 8e7f633fecd..7de2be111f2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,12 @@ org.gradle.vs.watch=true + +# hint by https://docs.gradle.org/current/userguide/performance.html#increase_the_heap_size org.gradle.jvmargs=-Xmx4096M + +# hint by https://docs.gradle.org/current/userguide/performance.html#enable_configuration_cache +# Does not work: +# - Task `:compileJava` of type `org.gradle.api.tasks.compile.JavaCompile`: cannot serialize object of type 'org.gradle.api.internal.project.DefaultProject', a subtype of 'org.gradle.api.Project', as these are not supported with the configuration cache. +# org.gradle.configuration-cache=false + +# hint by https://docs.gradle.org/current/userguide/performance.html#enable_the_build_cache +org.gradle.caching=true diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 597270bf308..352ef69c95b 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -54,6 +54,9 @@ requires java.prefs; requires com.fasterxml.aalto; + // YAML + requires org.yaml.snakeyaml; + // Annotations (@PostConstruct) requires jakarta.annotation; requires jakarta.inject; @@ -88,13 +91,14 @@ uses org.mariadb.jdbc.credential.CredentialPlugin; // Apache Commons and other (similar) helper libraries + requires com.google.common; + requires io.github.javadiffutils; + requires java.string.similarity; requires org.apache.commons.cli; requires org.apache.commons.csv; + requires org.apache.commons.io; requires org.apache.commons.lang3; requires org.apache.commons.text; - requires com.google.common; - requires io.github.javadiffutils; - requires java.string.similarity; requires com.github.tomtung.latex2unicode; requires fastparse; @@ -146,5 +150,4 @@ requires de.saxsys.mvvmfx.validation; requires dd.plist; requires mslinks; - requires org.yaml.snakeyaml; } diff --git a/src/main/java/org/jabref/cli/JabRefCLI.java b/src/main/java/org/jabref/cli/JabRefCLI.java index 3d613468af5..cf78f1a2da2 100644 --- a/src/main/java/org/jabref/cli/JabRefCLI.java +++ b/src/main/java/org/jabref/cli/JabRefCLI.java @@ -189,9 +189,6 @@ private static Options getOptions() { options.addOption("v", "version", false, Localization.lang("Display version")); options.addOption(null, "debug", false, Localization.lang("Show debug level messages")); - // The "-console" option is handled by the install4j launcher - options.addOption(null, "console", false, Localization.lang("Show console output (only when the launcher is used)")); - options.addOption(Option .builder("i") .longOpt("import") @@ -372,25 +369,4 @@ protected static String alignStringTable(List> table) { return sb.toString(); } - - /** - * Creates and wraps a multi-line and colon-seperated string from a List of Strings. - */ - protected static String wrapStringList(List list, int firstLineIndentation) { - StringBuilder builder = new StringBuilder(); - int lastBreak = -firstLineIndentation; - - for (String line : list) { - if (((builder.length() + 2 + line.length()) - lastBreak) > WIDTH) { - builder.append(",\n"); - lastBreak = builder.length(); - builder.append(WRAPPED_LINE_PREFIX); - } else if (builder.length() > 0) { - builder.append(", "); - } - builder.append(line); - } - - return builder.toString(); - } } diff --git a/src/main/java/org/jabref/gui/Globals.java b/src/main/java/org/jabref/gui/Globals.java index 4bbb5dbb2d8..bb8a45f8d8b 100644 --- a/src/main/java/org/jabref/gui/Globals.java +++ b/src/main/java/org/jabref/gui/Globals.java @@ -5,6 +5,7 @@ import org.jabref.gui.remote.CLIMessageHandler; import org.jabref.gui.telemetry.Telemetry; import org.jabref.gui.undo.CountingUndoManager; +import org.jabref.gui.util.DefaultDirectoryMonitor; import org.jabref.gui.util.DefaultFileUpdateMonitor; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.TaskExecutor; @@ -14,6 +15,7 @@ import org.jabref.logic.remote.server.RemoteListenerServerManager; import org.jabref.logic.util.BuildInfo; import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.util.DirectoryMonitor; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; @@ -65,6 +67,7 @@ public class Globals { private static KeyBindingRepository keyBindingRepository; private static DefaultFileUpdateMonitor fileUpdateMonitor; + private static DefaultDirectoryMonitor directoryMonitor; private Globals() { } @@ -92,6 +95,13 @@ public static synchronized FileUpdateMonitor getFileUpdateMonitor() { return fileUpdateMonitor; } + public static DirectoryMonitor getDirectoryMonitor() { + if (directoryMonitor == null) { + directoryMonitor = new DefaultDirectoryMonitor(); + } + return directoryMonitor; + } + // Background tasks public static void startBackgroundTasks() { // TODO Currently deactivated due to incompatibilities in XML @@ -109,6 +119,11 @@ public static void shutdownThreadPools() { if (fileUpdateMonitor != null) { fileUpdateMonitor.shutdown(); } + + if (directoryMonitor != null) { + directoryMonitor.shutdown(); + } + JabRefExecutorService.INSTANCE.shutdownEverything(); } diff --git a/src/main/java/org/jabref/gui/LibraryTab.java b/src/main/java/org/jabref/gui/LibraryTab.java index 48231b2f967..ff2c9e4ba20 100644 --- a/src/main/java/org/jabref/gui/LibraryTab.java +++ b/src/main/java/org/jabref/gui/LibraryTab.java @@ -84,6 +84,7 @@ import org.jabref.model.entry.field.Field; import org.jabref.model.entry.field.FieldFactory; import org.jabref.model.entry.field.StandardField; +import org.jabref.model.util.DirectoryMonitorManager; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; @@ -127,9 +128,9 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } // Indicates whether the tab is loading data using a dataloading task // The constructors take care to the right true/false assignment during start. - private SimpleBooleanProperty loading = new SimpleBooleanProperty(false); + private final SimpleBooleanProperty loading = new SimpleBooleanProperty(false); - // initally, the dialog is loading, not saving + // initially, the dialog is loading, not saving private boolean saving = false; private PersonNameSuggestionProvider searchAutoCompleter; @@ -151,6 +152,7 @@ private enum PanelMode { MAIN_TABLE, MAIN_TABLE_AND_ENTRY_EDITOR } private final IndexingTaskManager indexingTaskManager; private final TaskExecutor taskExecutor; + private final DirectoryMonitorManager directoryMonitorManager; private LibraryTab(BibDatabaseContext bibDatabaseContext, LibraryTabContainer tabContainer, @@ -171,6 +173,7 @@ private LibraryTab(BibDatabaseContext bibDatabaseContext, this.entryTypesManager = entryTypesManager; this.indexingTaskManager = new IndexingTaskManager(taskExecutor); this.taskExecutor = taskExecutor; + this.directoryMonitorManager = new DirectoryMonitorManager(Globals.getDirectoryMonitor()); bibDatabaseContext.getDatabase().registerListener(this); bibDatabaseContext.getMetaData().registerListener(this); @@ -832,6 +835,11 @@ private void onClosed(Event event) { } catch (RuntimeException e) { LOGGER.error("Problem when closing change monitor", e); } + try { + directoryMonitorManager.unregister(); + } catch (RuntimeException e) { + LOGGER.error("Problem when closing directory monitor", e); + } try { PdfIndexerManager.shutdownIndexer(bibDatabaseContext); } catch (RuntimeException e) { @@ -849,6 +857,8 @@ private void onClosed(Event event) { } catch (RuntimeException e) { LOGGER.error("Problem when shutting down backup manager", e); } + // clean up the groups map + stateManager.clearSelectedGroups(bibDatabaseContext); } /** @@ -864,6 +874,10 @@ public BibDatabaseContext getBibDatabaseContext() { return this.bibDatabaseContext; } + public DirectoryMonitorManager getDirectoryMonitorManager() { + return directoryMonitorManager; + } + public boolean isSaving() { return saving; } @@ -1021,7 +1035,7 @@ public void listen(EntriesAddedEvent addedEntriesEvent) { // Automatically add new entries to the selected group (or set of groups) if (preferencesService.getGroupsPreferences().shouldAutoAssignGroup()) { - stateManager.getSelectedGroup(bibDatabaseContext).forEach( + stateManager.getSelectedGroups(bibDatabaseContext).forEach( selectedGroup -> selectedGroup.addEntriesToGroup(addedEntriesEvent.getBibEntries())); } } diff --git a/src/main/java/org/jabref/gui/StateManager.java b/src/main/java/org/jabref/gui/StateManager.java index 2c842cd213d..9513c3fe3f1 100644 --- a/src/main/java/org/jabref/gui/StateManager.java +++ b/src/main/java/org/jabref/gui/StateManager.java @@ -57,13 +57,13 @@ public class StateManager { 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(); + private final ObservableMap> selectedGroups = FXCollections.observableHashMap(); private final OptionalObjectProperty activeSearchQuery = OptionalObjectProperty.empty(); private final OptionalObjectProperty activeGlobalSearchQuery = OptionalObjectProperty.empty(); private final IntegerProperty globalSearchResultSize = new SimpleIntegerProperty(0); private final ObservableMap searchResultMap = FXCollections.observableHashMap(); private final OptionalObjectProperty focusOwner = OptionalObjectProperty.empty(); - private final ObservableList, Task>> backgroundTasks = FXCollections.observableArrayList(task -> new Observable[]{task.getValue().progressProperty(), task.getValue().runningProperty()}); + private final ObservableList, Task>> backgroundTasks = FXCollections.observableArrayList(task -> new Observable[] {task.getValue().progressProperty(), task.getValue().runningProperty()}); private final EasyBinding anyTaskRunning = EasyBind.reduce(backgroundTasks, tasks -> tasks.map(Pair::getValue).anyMatch(Task::isRunning)); private final EasyBinding anyTasksThatWillNotBeRecoveredRunning = EasyBind.reduce(backgroundTasks, tasks -> tasks.anyMatch(task -> !task.getKey().willBeRecoveredAutomatically() && task.getValue().isRunning())); private final EasyBinding tasksProgress = EasyBind.reduce(backgroundTasks, tasks -> tasks.map(Pair::getValue).filter(Task::isRunning).mapToDouble(Task::getProgress).average().orElse(1)); @@ -75,7 +75,7 @@ public class StateManager { private final ObservableList searchHistory = FXCollections.observableArrayList(); public StateManager() { - activeGroups.bind(Bindings.valueAt(selectedGroups, activeDatabase.orElseOpt(null))); + activeGroups.bind(Bindings.valueAt(selectedGroups, activeDatabase.orElseOpt(null).map(BibDatabaseContext::getUid))); } public ObservableList getVisibleSidePaneComponents() { @@ -140,16 +140,16 @@ public void setSelectedEntries(List newSelectedEntries) { public void setSelectedGroups(BibDatabaseContext database, List newSelectedGroups) { Objects.requireNonNull(newSelectedGroups); - selectedGroups.put(database, FXCollections.observableArrayList(newSelectedGroups)); + selectedGroups.put(database.getUid(), FXCollections.observableArrayList(newSelectedGroups)); } - public ObservableList getSelectedGroup(BibDatabaseContext database) { - ObservableList selectedGroupsForDatabase = selectedGroups.get(database); + public ObservableList getSelectedGroups(BibDatabaseContext context) { + ObservableList selectedGroupsForDatabase = selectedGroups.get(context.getUid()); return selectedGroupsForDatabase != null ? selectedGroupsForDatabase : FXCollections.observableArrayList(); } public void clearSelectedGroups(BibDatabaseContext database) { - selectedGroups.remove(database); + selectedGroups.remove(database.getUid()); } public Optional getActiveDatabase() { @@ -190,7 +190,7 @@ public ObservableList> getBackgroundTasks() { } public void addBackgroundTask(BackgroundTask backgroundTask, Task task) { - this.backgroundTasks.add(0, new Pair<>(backgroundTask, task)); + this.backgroundTasks.addFirst(new Pair<>(backgroundTask, task)); } public EasyBinding getAnyTaskRunning() { diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index e2621d4cec0..759c46fc0ad 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -55,6 +55,7 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.field.Field; +import org.jabref.model.util.DirectoryMonitorManager; import org.jabref.model.util.FileUpdateMonitor; import org.jabref.preferences.PreferencesService; @@ -73,6 +74,8 @@ *

* EntryEditor also registers itself to the event bus, receiving events whenever a field of the entry changes, enabling * the text fields to update themselves if the change is made from somewhere else. + *

+ * The editors for fields are created via {@link org.jabref.gui.fieldeditors.FieldEditors}. */ public class EntryEditor extends BorderPane { @@ -82,6 +85,7 @@ public class EntryEditor extends BorderPane { private final BibDatabaseContext databaseContext; private final EntryEditorPreferences entryEditorPreferences; private final ExternalFilesEntryLinker fileLinker; + private final DirectoryMonitorManager directoryMonitorManager; private Subscription typeSubscription; @@ -121,6 +125,7 @@ public class EntryEditor extends BorderPane { public EntryEditor(LibraryTab libraryTab) { this.libraryTab = libraryTab; this.databaseContext = libraryTab.getBibDatabaseContext(); + this.directoryMonitorManager = libraryTab.getDirectoryMonitorManager(); ViewLoader.view(this) .root(this) @@ -307,7 +312,7 @@ private List createTabs() { bibEntryTypesManager, keyBindingRepository); entryEditorTabs.add(sourceTab); - entryEditorTabs.add(new LatexCitationsTab(databaseContext, preferencesService, taskExecutor, dialogService)); + entryEditorTabs.add(new LatexCitationsTab(databaseContext, preferencesService, dialogService, directoryMonitorManager)); entryEditorTabs.add(new FulltextSearchResultsTab(stateManager, preferencesService, dialogService, taskExecutor)); return entryEditorTabs; diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java index ae0fda0194c..fcca2b100ad 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditorPreferences.java @@ -49,6 +49,7 @@ public static JournalPopupEnabled fromString(String status) { private final ObjectProperty enablementStatus; private final BooleanProperty shouldShowSciteTab; private final BooleanProperty showUserCommentsFields; + private final DoubleProperty previewWidthDividerPosition; public EntryEditorPreferences(Map> entryEditorTabList, Map> defaultEntryEditorTabList, @@ -62,7 +63,8 @@ public EntryEditorPreferences(Map> entryEditorTabList, boolean autolinkFilesEnabled, JournalPopupEnabled journalPopupEnabled, boolean showSciteTab, - boolean showUserCommentsFields) { + boolean showUserCommentsFields, + double previewWidthDividerPosition) { this.entryEditorTabList = new SimpleMapProperty<>(FXCollections.observableMap(entryEditorTabList)); this.defaultEntryEditorTabList = new SimpleMapProperty<>(FXCollections.observableMap(defaultEntryEditorTabList)); @@ -77,6 +79,7 @@ public EntryEditorPreferences(Map> entryEditorTabList, this.enablementStatus = new SimpleObjectProperty<>(journalPopupEnabled); this.shouldShowSciteTab = new SimpleBooleanProperty(showSciteTab); this.showUserCommentsFields = new SimpleBooleanProperty(showUserCommentsFields); + this.previewWidthDividerPosition = new SimpleDoubleProperty(previewWidthDividerPosition); } public ObservableMap> getEntryEditorTabs() { @@ -226,4 +229,19 @@ public BooleanProperty showUserCommentsFieldsProperty() { public void setShowUserCommentsFields(boolean showUserCommentsFields) { this.showUserCommentsFields.set(showUserCommentsFields); } + + public void setPreviewWidthDividerPosition(double previewWidthDividerPosition) { + this.previewWidthDividerPosition.set(previewWidthDividerPosition); + } + + /** + * Holds the horizontal divider position when the Preview is shown in the entry editor + */ + public DoubleProperty previewWidthDividerPositionProperty() { + return previewWidthDividerPosition; + } + + public Double getPreviewWidthDividerPosition() { + return previewWidthDividerPosition.get(); + } } diff --git a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index 20e54396bfe..97348f5838a 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -41,6 +41,7 @@ import org.jabref.preferences.PreferencesService; import com.tobiasdiez.easybind.EasyBind; +import com.tobiasdiez.easybind.Subscription; /** * A single tab displayed in the EntryEditor holding several FieldEditors. @@ -61,6 +62,7 @@ abstract class FieldsEditorTab extends EntryEditorTab { private PreviewPanel previewPanel; private final UndoManager undoManager; private Collection fields = new ArrayList<>(); + private Subscription dividerPositionSubscription; public FieldsEditorTab(boolean compressed, BibDatabaseContext databaseContext, @@ -258,9 +260,22 @@ private void initPanel() { container.getItems().remove(previewPanel); } else { container.getItems().add(1, previewPanel); + container.setDividerPositions(preferences.getEntryEditorPreferences().getPreviewWidthDividerPosition()); } }); + + // save position + dividerPositionSubscription = EasyBind.valueAt(container.getDividers(), 0) + .mapObservable(SplitPane.Divider::positionProperty) + .subscribeToValues(this::savePreviewWidthDividerPosition); setContent(container); } } + + private void savePreviewWidthDividerPosition(Number position) { + if (!preferences.getPreviewPreferences().shouldShowPreviewAsExtraTab()) { + preferences.getEntryEditorPreferences().setPreviewWidthDividerPosition(position.doubleValue()); + } + } } + diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java index 2272316bb8f..0be86194c87 100644 --- a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTab.java @@ -17,10 +17,10 @@ import org.jabref.gui.DialogService; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.texparser.CitationsDisplay; -import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; +import org.jabref.model.util.DirectoryMonitorManager; import org.jabref.preferences.PreferencesService; import com.tobiasdiez.easybind.EasyBind; @@ -33,9 +33,17 @@ public class LatexCitationsTab extends EntryEditorTab { private final ProgressIndicator progressIndicator; private final CitationsDisplay citationsDisplay; - public LatexCitationsTab(BibDatabaseContext databaseContext, PreferencesService preferencesService, - TaskExecutor taskExecutor, DialogService dialogService) { - this.viewModel = new LatexCitationsTabViewModel(databaseContext, preferencesService, taskExecutor, dialogService); + public LatexCitationsTab(BibDatabaseContext databaseContext, + PreferencesService preferencesService, + DialogService dialogService, + DirectoryMonitorManager directoryMonitorManager) { + + this.viewModel = new LatexCitationsTabViewModel( + databaseContext, + preferencesService, + dialogService, + directoryMonitorManager); + this.searchPane = new GridPane(); this.progressIndicator = new ProgressIndicator(); this.citationsDisplay = new CitationsDisplay(); @@ -66,6 +74,11 @@ private void setSearchPane() { searchPane.setId("citationsPane"); setContent(searchPane); + HBox latexDirectoryBox = getLatexDirectoryBox(); + VBox citationsPane = getCitationsPane(); + VBox notFoundPane = getNotFoundPane(); + VBox errorPane = getErrorPane(); + EasyBind.subscribe(viewModel.statusProperty(), status -> { searchPane.getChildren().clear(); switch (status) { @@ -73,36 +86,35 @@ private void setSearchPane() { searchPane.add(progressIndicator, 0, 0); break; case CITATIONS_FOUND: - searchPane.add(getCitationsPane(), 0, 0); + searchPane.add(citationsPane, 0, 0); break; case NO_RESULTS: - searchPane.add(getNotFoundPane(), 0, 0); + searchPane.add(notFoundPane, 0, 0); break; case ERROR: - searchPane.add(getErrorPane(), 0, 0); + searchPane.add(errorPane, 0, 0); break; } - searchPane.add(getLatexDirectoryBox(), 0, 1); + searchPane.add(latexDirectoryBox, 0, 1); }); } private HBox getLatexDirectoryBox() { Text latexDirectoryText = new Text(Localization.lang("Current search directory:")); - Text latexDirectoryPath = new Text(viewModel.directoryProperty().get().toString()); + Text latexDirectoryPath = new Text(); + latexDirectoryPath.textProperty().bind(viewModel.directoryProperty().asString()); latexDirectoryPath.setStyle("-fx-font-family:monospace;-fx-font-weight: bold;"); Button latexDirectoryButton = new Button(Localization.lang("Set LaTeX file directory")); latexDirectoryButton.setGraphic(IconTheme.JabRefIcons.LATEX_FILE_DIRECTORY.getGraphicNode()); latexDirectoryButton.setOnAction(event -> viewModel.setLatexDirectory()); - Button latexDirectoryRefreshButton = new Button(Localization.lang("Refresh")); - latexDirectoryRefreshButton.setGraphic(IconTheme.JabRefIcons.REFRESH.getGraphicNode()); - latexDirectoryRefreshButton.setOnAction(event -> viewModel.refreshLatexDirectory()); - HBox latexDirectoryBox = new HBox(10, latexDirectoryText, latexDirectoryPath, latexDirectoryButton, latexDirectoryRefreshButton); + HBox latexDirectoryBox = new HBox(10, latexDirectoryText, latexDirectoryPath, latexDirectoryButton); latexDirectoryBox.setAlignment(Pos.CENTER); return latexDirectoryBox; } private VBox getCitationsPane() { VBox citationsBox = new VBox(30, citationsDisplay); + VBox.setVgrow(citationsDisplay, Priority.ALWAYS); citationsBox.setStyle("-fx-padding: 0;"); return citationsBox; } @@ -122,7 +134,8 @@ private VBox getNotFoundPane() { private VBox getErrorPane() { Label titleLabel = new Label(Localization.lang("Error")); titleLabel.setStyle("-fx-font-size: 1.5em;-fx-font-weight: bold;-fx-text-fill: -fx-accent;"); - Text errorMessageText = new Text(viewModel.searchErrorProperty().get()); + Text errorMessageText = new Text(); + errorMessageText.textProperty().bind(viewModel.searchErrorProperty()); VBox errorMessageBox = new VBox(30, titleLabel, errorMessageText); errorMessageBox.setStyle("-fx-padding: 30 0 0 30;"); return errorMessageBox; @@ -130,7 +143,7 @@ private VBox getErrorPane() { @Override protected void bindToEntry(BibEntry entry) { - viewModel.init(entry); + viewModel.bindToEntry(entry); } @Override diff --git a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java index 833ce40ef63..7f6d74cc94f 100644 --- a/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java +++ b/src/main/java/org/jabref/gui/entryeditor/LatexCitationsTabViewModel.java @@ -1,11 +1,15 @@ package org.jabref.gui.entryeditor; +import java.io.File; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collection; import java.util.Optional; -import java.util.concurrent.Future; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.ReadOnlyListWrapper; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; @@ -14,23 +18,29 @@ import org.jabref.gui.AbstractViewModel; import org.jabref.gui.DialogService; -import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.gui.util.DirectoryDialogConfiguration; -import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.l10n.Localization; -import org.jabref.logic.texparser.CitationFinder; +import org.jabref.logic.texparser.DefaultLatexParser; import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.texparser.Citation; +import org.jabref.model.texparser.LatexParserResult; +import org.jabref.model.texparser.LatexParserResults; +import org.jabref.model.util.DirectoryMonitorManager; import org.jabref.preferences.PreferencesService; +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.apache.commons.io.filefilter.IOFileFilter; +import org.apache.commons.io.monitor.FileAlterationListener; +import org.apache.commons.io.monitor.FileAlterationObserver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LatexCitationsTabViewModel extends AbstractViewModel { - enum Status { + public enum Status { IN_PROGRESS, CITATIONS_FOUND, NO_RESULTS, @@ -39,114 +49,187 @@ enum Status { private static final Logger LOGGER = LoggerFactory.getLogger(LatexCitationsTabViewModel.class); private static final String TEX_EXT = ".tex"; + private static final IOFileFilter FILE_FILTER = FileFilterUtils.or(FileFilterUtils.suffixFileFilter(TEX_EXT), FileFilterUtils.directoryFileFilter()); private final BibDatabaseContext databaseContext; private final PreferencesService preferencesService; - private final TaskExecutor taskExecutor; private final DialogService dialogService; private final ObjectProperty directory; - private CitationFinder citationFinder; private final ObservableList citationList; private final ObjectProperty status; private final StringProperty searchError; - private Future searchTask; + private final BooleanProperty updateStatusOnCreate; + private final DefaultLatexParser latexParser; + private final LatexParserResults latexFiles; + private final DirectoryMonitorManager directoryMonitorManager; + private final FileAlterationListener listener; + private FileAlterationObserver observer; private BibEntry currentEntry; public LatexCitationsTabViewModel(BibDatabaseContext databaseContext, PreferencesService preferencesService, - TaskExecutor taskExecutor, - DialogService dialogService) { + DialogService dialogService, + DirectoryMonitorManager directoryMonitorManager) { + this.databaseContext = databaseContext; this.preferencesService = preferencesService; - this.taskExecutor = taskExecutor; this.dialogService = dialogService; this.directory = new SimpleObjectProperty<>(databaseContext.getMetaData().getLatexFileDirectory(preferencesService.getFilePreferences().getUserAndHost()) .orElse(FileUtil.getInitialDirectory(databaseContext, preferencesService.getFilePreferences().getWorkingDirectory()))); - this.citationFinder = new CitationFinder(directory.get()); this.citationList = FXCollections.observableArrayList(); this.status = new SimpleObjectProperty<>(Status.IN_PROGRESS); this.searchError = new SimpleStringProperty(""); + this.directoryMonitorManager = directoryMonitorManager; + this.updateStatusOnCreate = new SimpleBooleanProperty(false); + this.listener = getListener(); + + this.latexParser = new DefaultLatexParser(); + this.latexFiles = new LatexParserResults(); } - public void init(BibEntry entry) { - cancelSearch(); + private FileAlterationListener getListener() { + return new FileAlterationListener() { + @Override + public void onStart(FileAlterationObserver observer) { + if (!updateStatusOnCreate.get()) { + DefaultTaskExecutor.runInJavaFXThread(() -> status.set(Status.IN_PROGRESS)); + } + } + + @Override + public void onStop(FileAlterationObserver observer) { + if (!updateStatusOnCreate.get()) { + updateStatusOnCreate.set(true); + updateStatus(); + } + } + + @Override + public void onFileCreate(File file) { + Path path = file.toPath(); + LatexParserResult result = latexParser.parse(path).get(); + latexFiles.add(path, result); + + Optional citationKey = currentEntry.getCitationKey(); + if (citationKey.isPresent()) { + Collection citations = result.getCitationsByKey(citationKey.get()); + DefaultTaskExecutor.runInJavaFXThread(() -> citationList.addAll(citations)); + } + + if (updateStatusOnCreate.get()) { + updateStatus(); + } + } + + @Override + public void onFileDelete(File file) { + LatexParserResult result = latexFiles.remove(file.toPath()); + + Optional citationKey = currentEntry.getCitationKey(); + if (citationKey.isPresent()) { + Collection citations = result.getCitationsByKey(citationKey.get()); + DefaultTaskExecutor.runInJavaFXThread(() -> citationList.removeAll(citations)); + updateStatus(); + } + } + + @Override + public void onFileChange(File file) { + onFileDelete(file); + onFileCreate(file); + updateStatus(); + } + + @Override + public void onDirectoryChange(File directory) { + } + + @Override + public void onDirectoryCreate(File directory) { + } + + @Override + public void onDirectoryDelete(File directory) { + } + }; + } + + public void bindToEntry(BibEntry entry) { + checkAndUpdateDirectory(); currentEntry = entry; - Optional citeKey = entry.getCitationKey(); + Optional citationKey = entry.getCitationKey(); + + if (observer == null) { + observer = new FileAlterationObserver(directory.get().toFile(), FILE_FILTER); + directoryMonitorManager.addObserver(observer, listener); + } - if (citeKey.isPresent()) { - startSearch(citeKey.get()); + if (citationKey.isPresent()) { + citationList.setAll(latexFiles.getCitationsByKey(citationKey.get())); + if (!status.get().equals(Status.IN_PROGRESS)) { + updateStatus(); + } } else { searchError.set(Localization.lang("Selected entry does not have an associated citation key.")); status.set(Status.ERROR); } } - public ObjectProperty directoryProperty() { - return directory; - } - - public ObservableList getCitationList() { - return new ReadOnlyListWrapper<>(citationList); - } - - public ObjectProperty statusProperty() { - return status; - } + public void setLatexDirectory() { + DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() + .withInitialDirectory(directory.get()).build(); - public StringProperty searchErrorProperty() { - return searchError; - } + dialogService.showDirectorySelectionDialog(directoryDialogConfiguration).ifPresent(selectedDirectory -> + databaseContext.getMetaData().setLatexFileDirectory(preferencesService.getFilePreferences().getUserAndHost(), selectedDirectory.toAbsolutePath())); - private void startSearch(String citeKey) { - // we need to check whether the user meanwhile set the LaTeX file directory or the database changed locations checkAndUpdateDirectory(); - - searchTask = BackgroundTask.wrap(() -> citationFinder.searchAndParse(citeKey)) - .onRunning(() -> status.set(Status.IN_PROGRESS)) - .onSuccess(result -> { - citationList.setAll(result); - status.set(citationList.isEmpty() ? Status.NO_RESULTS : Status.CITATIONS_FOUND); - }) - .onFailure(error -> { - searchError.set(error.getMessage()); - status.set(Status.ERROR); - }) - .executeWith(taskExecutor); - } - - private void cancelSearch() { - if (searchTask == null || searchTask.isCancelled() || searchTask.isDone()) { - return; - } - - status.set(Status.IN_PROGRESS); - searchTask.cancel(true); } - public void checkAndUpdateDirectory() { + private void checkAndUpdateDirectory() { Path newDirectory = databaseContext.getMetaData().getLatexFileDirectory(preferencesService.getFilePreferences().getUserAndHost()) .orElse(FileUtil.getInitialDirectory(databaseContext, preferencesService.getFilePreferences().getWorkingDirectory())); if (!newDirectory.equals(directory.get())) { + status.set(Status.IN_PROGRESS); + updateStatusOnCreate.set(false); + citationList.clear(); + latexFiles.clear(); + + directoryMonitorManager.removeObserver(observer); directory.set(newDirectory); - citationFinder = new CitationFinder(newDirectory); + observer = new FileAlterationObserver(directory.get().toFile(), FILE_FILTER); + directoryMonitorManager.addObserver(observer, listener); } } - public void setLatexDirectory() { - DirectoryDialogConfiguration directoryDialogConfiguration = new DirectoryDialogConfiguration.Builder() - .withInitialDirectory(directory.get()).build(); + private void updateStatus() { + DefaultTaskExecutor.runInJavaFXThread(() -> { + if (!Files.exists(directory.get())) { + searchError.set(Localization.lang("Current search directory does not exist: %0", directory.get())); + status.set(Status.ERROR); + } else if (citationList.isEmpty()) { + status.set(Status.NO_RESULTS); + } else { + status.set(Status.CITATIONS_FOUND); + } + }); + } - dialogService.showDirectorySelectionDialog(directoryDialogConfiguration).ifPresent(selectedDirectory -> - databaseContext.getMetaData().setLatexFileDirectory(preferencesService.getFilePreferences().getUserAndHost(), selectedDirectory.toAbsolutePath())); + public ObjectProperty directoryProperty() { + return directory; + } + + public ObservableList getCitationList() { + return new ReadOnlyListWrapper<>(citationList); + } - init(currentEntry); + public ObjectProperty statusProperty() { + return status; } - public void refreshLatexDirectory() { - citationFinder = new CitationFinder(directory.get()); - init(currentEntry); + public StringProperty searchErrorProperty() { + return searchError; } public boolean shouldShow() { diff --git a/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java b/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java index 321f31bc31f..7197f9c2422 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java +++ b/src/main/java/org/jabref/gui/externalfiles/DownloadFullTextAction.java @@ -141,8 +141,7 @@ private void addLinkedFileFromURL(BibDatabaseContext databaseContext, URL url, B taskExecutor, dialogService, preferences); - - onlineFile.download(); + onlineFile.download(true); } else { dialogService.notify(Localization.lang("Full text document for entry %0 already linked.", entry.getCitationKey().orElse(Localization.lang("undefined")))); diff --git a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java index de4ac03f686..96ad7e46e4d 100644 --- a/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java +++ b/src/main/java/org/jabref/gui/externalfiles/ImportHandler.java @@ -194,7 +194,7 @@ public void importCleanedEntries(List entries) { bibDatabaseContext.getDatabase().insertEntries(entries); generateKeys(entries); setAutomaticFields(entries); - addToGroups(entries, stateManager.getSelectedGroup(bibDatabaseContext)); + addToGroups(entries, stateManager.getSelectedGroups(bibDatabaseContext)); } public void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext, BibEntry entry) { @@ -276,7 +276,7 @@ public void downloadLinkedFiles(BibEntry entry) { taskExecutor, dialogService, preferencesService - ).download() + ).download(false) ); } } @@ -405,13 +405,13 @@ public void importEntriesWithDuplicateCheck(BibDatabaseContext database, List linkedFile.edit(); case OPEN_FILE -> linkedFile.open(); case OPEN_FOLDER -> linkedFile.openFolder(); - case DOWNLOAD_FILE -> linkedFile.download(); + case DOWNLOAD_FILE -> linkedFile.download(true); case REDOWNLOAD_FILE -> linkedFile.redownload(); case RENAME_FILE_TO_PATTERN -> linkedFile.renameToSuggestion(); case RENAME_FILE_TO_NAME -> linkedFile.askForNameAndRename(); diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java index 91d0dbf6690..005ef6e75ff 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditorViewModel.java @@ -279,7 +279,7 @@ private void addFromURLAndDownload(URL url) { dialogService, preferences); files.add(onlineFile); - onlineFile.download(); + onlineFile.download(true); } public void deleteFile(LinkedFileViewModel file) { diff --git a/src/main/java/org/jabref/gui/frame/FrameDndHandler.java b/src/main/java/org/jabref/gui/frame/FrameDndHandler.java index 75fd0aebb77..424b3512488 100644 --- a/src/main/java/org/jabref/gui/frame/FrameDndHandler.java +++ b/src/main/java/org/jabref/gui/frame/FrameDndHandler.java @@ -26,8 +26,12 @@ import org.jabref.model.groups.GroupTreeNode; import com.tobiasdiez.easybind.EasyBind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class FrameDndHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(FrameDndHandler.class); + private final TabPane tabPane; private final Supplier scene; private final Supplier openDatabaseAction; @@ -81,45 +85,65 @@ private void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, T tabDragEvent.setDropCompleted(true); tabDragEvent.consume(); } else { - if (stateManager.getActiveDatabase().isEmpty() - || stateManager.getActiveDatabase().get().getMetaData().getGroups().isEmpty()) { + if (stateManager.getActiveDatabase().isEmpty()) { + LOGGER.warn("Active library is empty when dropping entries"); return; } + LibraryTab destinationLibraryTab = null; for (Tab libraryTab : tabPane.getTabs()) { if (libraryTab.getId().equals(destinationTabNode.getId()) && !tabPane.getSelectionModel().getSelectedItem().equals(libraryTab)) { - LibraryTab destinationLibraryTab = (LibraryTab) libraryTab; - if (hasGroups(dragboard)) { - List groupPathToSources = getGroups(dragboard); - - copyRootNode(destinationLibraryTab); - - GroupTreeNode destinationLibraryGroupRoot = destinationLibraryTab - .getBibDatabaseContext() - .getMetaData() - .getGroups().get(); - - GroupTreeNode groupsTreeNode = stateManager.getActiveDatabase().get() - .getMetaData() - .getGroups() - .get(); - - for (String pathToSource : groupPathToSources) { - GroupTreeNode groupTreeNodeToCopy = groupsTreeNode - .getChildByPath(pathToSource) - .get(); - copyGroupTreeNode((LibraryTab) libraryTab, destinationLibraryGroupRoot, groupTreeNodeToCopy); - } - return; - } - destinationLibraryTab.dropEntry(stateManager.getLocalDragboard().getBibEntries()); + destinationLibraryTab = (LibraryTab) libraryTab; + break; } } + + if (destinationLibraryTab == null) { + LOGGER.warn("Failed to find library tab to drop into"); + return; + } + + if (hasEntries(dragboard)) { + List entryCopies = stateManager.getLocalDragboard().getBibEntries() + .stream().map(entry -> (BibEntry) entry.clone()) + .toList(); + destinationLibraryTab.dropEntry(entryCopies); + } else if (hasGroups(dragboard)) { + dropGroups(dragboard, destinationLibraryTab); + } + tabDragEvent.consume(); } } + private void dropGroups(Dragboard dragboard, LibraryTab destinationLibraryTab) { + List groupPathToSources = getGroups(dragboard); + + copyRootNode(destinationLibraryTab); + + GroupTreeNode destinationLibraryGroupRoot = destinationLibraryTab + .getBibDatabaseContext() + .getMetaData() + .getGroups().get(); + + GroupTreeNode groupsTreeNode = stateManager.getActiveDatabase().get() + .getMetaData() + .getGroups() + .get(); + + for (String pathToSource : groupPathToSources) { + GroupTreeNode groupTreeNodeToCopy = groupsTreeNode + .getChildByPath(pathToSource) + .get(); + copyGroupTreeNode(destinationLibraryTab, destinationLibraryGroupRoot, groupTreeNodeToCopy); + } + } + + private boolean hasEntries(Dragboard dragboard) { + return dragboard.hasContent(DragAndDropDataFormats.ENTRIES); + } + private void onTabDragOver(DragEvent event, DragEvent tabDragEvent, Tab dndIndicator) { if (hasBibFiles(tabDragEvent.getDragboard()) || hasGroups(tabDragEvent.getDragboard())) { tabDragEvent.acceptTransferModes(TransferMode.ANY); diff --git a/src/main/java/org/jabref/gui/frame/JabRefFrame.java b/src/main/java/org/jabref/gui/frame/JabRefFrame.java index 84a34f53966..e154ae6f7e6 100644 --- a/src/main/java/org/jabref/gui/frame/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -9,6 +9,7 @@ import java.util.function.Supplier; import javafx.application.Platform; +import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; import javafx.beans.binding.StringBinding; import javafx.collections.transformation.FilteredList; @@ -212,7 +213,9 @@ private void initLayout() { splitPane.getItems().addAll(tabbedPane); SplitPane.setResizableWithParent(sidePane, false); - EasyBind.subscribe(sidePane.widthProperty(), c -> updateSidePane()); + sidePane.widthProperty().addListener(c -> updateSidePane()); + sidePane.getChildren().addListener((InvalidationListener) c -> updateSidePane()); + updateSidePane(); setCenter(splitPane); } diff --git a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java index bd113bf249e..dccd1fef498 100644 --- a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java @@ -262,7 +262,7 @@ private void refreshGroup() { DefaultTaskExecutor.runInJavaFXThread(() -> { updateMatchedEntries(); // Update the entries matched by the group // "Re-add" to the selected groups if it were selected, this refreshes the entries the user views - ObservableList selectedGroups = this.stateManager.getSelectedGroup(this.databaseContext); + ObservableList selectedGroups = this.stateManager.getSelectedGroups(this.databaseContext); if (selectedGroups.remove(this.groupNode)) { selectedGroups.add(this.groupNode); } diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java index 4efab42033b..de0c579699b 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java @@ -153,11 +153,11 @@ private void onActiveDatabaseChanged(Optional newDatabase) { .orElse(GroupNodeViewModel.getAllEntriesGroup(newDatabase.get(), stateManager, taskExecutor, localDragboard, preferences)); rootGroup.setValue(newRoot); - if (stateManager.getSelectedGroup(newDatabase.get()).isEmpty()) { + if (stateManager.getSelectedGroups(newDatabase.get()).isEmpty()) { stateManager.setSelectedGroups(newDatabase.get(), Collections.singletonList(newRoot.getGroupNode())); } selectedGroups.setAll( - stateManager.getSelectedGroup(newDatabase.get()).stream() + stateManager.getSelectedGroups(newDatabase.get()).stream() .map(selectedGroup -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, selectedGroup, localDragboard, preferences)) .collect(Collectors.toList())); } else { diff --git a/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java b/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java index 08c9240b0a2..e95af5e25a6 100644 --- a/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/AttachFileFromURLAction.java @@ -68,7 +68,7 @@ public void execute() { taskExecutor, dialogService, preferencesService); - onlineFile.download(); + onlineFile.download(true); } catch (MalformedURLException exception) { dialogService.showErrorDialogAndWait(Localization.lang("Invalid URL"), exception); } diff --git a/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java b/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java index 53684fe43f7..6df76576628 100644 --- a/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/DownloadLinkedFileAction.java @@ -6,6 +6,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import javax.net.ssl.HostnameVerifier; @@ -55,6 +57,7 @@ public class DownloadLinkedFileAction extends SimpleCommand { private final String downloadUrl; private final FilePreferences filePreferences; private final TaskExecutor taskExecutor; + private final boolean keepHtmlLink; private final BibDatabaseContext databaseContext; @@ -68,7 +71,8 @@ public DownloadLinkedFileAction(BibDatabaseContext databaseContext, DialogService dialogService, FilePreferences filePreferences, TaskExecutor taskExecutor, - String suggestedName) { + String suggestedName, + boolean keepHtmlLink) { this.databaseContext = databaseContext; this.entry = entry; this.linkedFile = linkedFile; @@ -77,10 +81,14 @@ public DownloadLinkedFileAction(BibDatabaseContext databaseContext, this.dialogService = dialogService; this.filePreferences = filePreferences; this.taskExecutor = taskExecutor; + this.keepHtmlLink = keepHtmlLink; this.linkedFileHandler = new LinkedFileHandler(linkedFile, entry, databaseContext, filePreferences); } + /** + * Downloads the given linked file to the first existing file directory. It keeps HTML files as URLs. + */ public DownloadLinkedFileAction(BibDatabaseContext databaseContext, BibEntry entry, LinkedFile linkedFile, @@ -88,12 +96,12 @@ public DownloadLinkedFileAction(BibDatabaseContext databaseContext, DialogService dialogService, FilePreferences filePreferences, TaskExecutor taskExecutor) { - this(databaseContext, entry, linkedFile, downloadUrl, dialogService, filePreferences, taskExecutor, ""); + this(databaseContext, entry, linkedFile, downloadUrl, dialogService, filePreferences, taskExecutor, "", true); } @Override public void execute() { - LOGGER.info("Downloading file from " + downloadUrl); + LOGGER.info("Downloading file from {}", downloadUrl); if (downloadUrl.isEmpty() || !LinkedFile.isOnlineLink(downloadUrl)) { throw new UnsupportedOperationException("In order to download the file, the url has to be an online link"); } @@ -132,51 +140,64 @@ private void doDownload(Path targetDirectory, URLDownload urlDownload) { taskExecutor.execute(downloadTask); } - private void onSuccess(Path targetDirectory, Path destination) { + /** + * @param targetDirectory The directory to store the file into. Is an absolute path. + */ + private void onSuccess(Path targetDirectory, Path downloadedFile) { + assert targetDirectory.isAbsolute(); + boolean isDuplicate; boolean isHtml; try { - isDuplicate = FileNameUniqueness.isDuplicatedFile(targetDirectory, destination.getFileName(), dialogService); + isDuplicate = FileNameUniqueness.isDuplicatedFile(targetDirectory, downloadedFile.getFileName(), dialogService); } catch (IOException e) { LOGGER.error("FileNameUniqueness.isDuplicatedFile failed", e); return; } if (isDuplicate) { - destination = targetDirectory.resolve( - FileNameUniqueness.eraseDuplicateMarks(destination.getFileName())); - - linkedFile.setLink(FileUtil.relativize(destination, - databaseContext.getFileDirectories(filePreferences)).toString()); + // We do not add duplicate files. + // The downloaded file was deleted in {@link org.jabref.logic.util.io.FileNameUniqueness.isDuplicatedFile]} + LOGGER.info("File {} already exists in target directory {}.", downloadedFile.getFileName(), targetDirectory); + return; + } - isHtml = linkedFile.getFileType().equals(StandardExternalFileType.URL.getName()); + // we need to call LinkedFileViewModel#fromFile, because we need to make the path relative to the configured directories + LinkedFile newLinkedFile = LinkedFilesEditorViewModel.fromFile( + downloadedFile, + databaseContext.getFileDirectories(filePreferences), + filePreferences); + if (newLinkedFile.getDescription().isEmpty() && !linkedFile.getDescription().isEmpty()) { + newLinkedFile.setDescription((linkedFile.getDescription())); + } + if (linkedFile.getSourceUrl().isEmpty() && LinkedFile.isOnlineLink(linkedFile.getLink())) { + newLinkedFile.setSourceURL(linkedFile.getLink()); } else { - // we need to call LinkedFileViewModel#fromFile, because we need to make the path relative to the configured directories - LinkedFile newLinkedFile = LinkedFilesEditorViewModel.fromFile( - destination, - databaseContext.getFileDirectories(filePreferences), - filePreferences); - if (newLinkedFile.getDescription().isEmpty() && !linkedFile.getDescription().isEmpty()) { - newLinkedFile.setDescription((linkedFile.getDescription())); - } - if (linkedFile.getSourceUrl().isEmpty() && LinkedFile.isOnlineLink(linkedFile.getLink())) { - newLinkedFile.setSourceURL(linkedFile.getLink()); - } else { - newLinkedFile.setSourceURL(linkedFile.getSourceUrl()); - } - entry.replaceDownloadedFile(linkedFile.getLink(), newLinkedFile); - isHtml = newLinkedFile.getFileType().equals(StandardExternalFileType.URL.getName()); + newLinkedFile.setSourceURL(linkedFile.getSourceUrl()); } - // Notify in bar when the file type is HTML. + isHtml = newLinkedFile.getFileType().equals(StandardExternalFileType.URL.getName()); if (isHtml) { - dialogService.notify(Localization.lang("Downloaded website as an HTML file.")); - LOGGER.debug("Downloaded website {} as an HTML file at {}", linkedFile.getLink(), destination); + if (this.keepHtmlLink) { + dialogService.notify(Localization.lang("Download '%0' was a HTML file. Keeping URL.", downloadUrl)); + } else { + dialogService.notify(Localization.lang("Download '%0' was a HTML file. Removed.", downloadUrl)); + List newFiles = new ArrayList<>(entry.getFiles()); + newFiles.remove(linkedFile); + entry.setFiles(newFiles); + try { + Files.delete(downloadedFile); + } catch (IOException e) { + LOGGER.error("Could not delete downloaded file {}.", downloadedFile, e); + } + } + } else { + entry.replaceDownloadedFile(linkedFile.getLink(), newLinkedFile); } } private void onFailure(URLDownload urlDownload, Exception ex) { - LOGGER.error("Error downloading from URL: " + urlDownload, ex); + LOGGER.error("Error downloading from URL: {}", urlDownload, ex); String fetcherExceptionMessage = ex.getMessage(); String failedTitle = Localization.lang("Failed to download from URL"); int statusCode; @@ -261,7 +282,7 @@ private Optional inferFileTypeFromMimeType(URLDownload urlDown String mimeType = urlDownload.getMimeType(); if (mimeType != null) { - LOGGER.debug("MIME Type suggested: " + mimeType); + LOGGER.debug("MIME Type suggested: {}", mimeType); return ExternalFileTypes.getExternalFileTypeByMimeType(mimeType, filePreferences); } else { return Optional.empty(); diff --git a/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java b/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java index f08933976cb..3a69061b10f 100644 --- a/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java +++ b/src/main/java/org/jabref/gui/linkedfile/RedownloadMissingFilesAction.java @@ -56,6 +56,9 @@ public void execute() { } } + /** + * @implNote Similar method {@link org.jabref.gui.fieldeditors.LinkedFileViewModel#redownload} + */ private void redownloadMissing(BibDatabaseContext databaseContext) { LOGGER.info("Redownloading missing files"); databaseContext.getEntries().forEach(entry -> { @@ -71,7 +74,7 @@ private void redownloadMissing(BibDatabaseContext databaseContext) { String fileName = Path.of(linkedFile.getLink()).getFileName().toString(); DownloadLinkedFileAction downloadAction = new DownloadLinkedFileAction(this.databaseContext, entry, - linkedFile, linkedFile.getSourceUrl(), dialogService, filePreferences, taskExecutor, fileName); + linkedFile, linkedFile.getSourceUrl(), dialogService, filePreferences, taskExecutor, fileName, true); downloadAction.execute(); }); }); diff --git a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java index de9319c93a3..5c18e02243a 100644 --- a/src/main/java/org/jabref/gui/search/GlobalSearchBar.java +++ b/src/main/java/org/jabref/gui/search/GlobalSearchBar.java @@ -238,7 +238,7 @@ public GlobalSearchBar(LibraryTabContainer tabContainer, query -> setSearchTerm(query.map(SearchQuery::getQuery).orElse(""))); this.searchQueryProperty.addListener((obs, oldValue, newValue) -> newValue.ifPresent(this::updateSearchResultsForQuery)); - this.searchQueryProperty.addListener((obs, oldValue, newValue) -> searchQueryProperty.get().ifPresent(this::updateSearchResultsForQuery)); + this.stateManager.activeDatabaseProperty().addListener(obs -> searchQueryProperty.get().ifPresent(this::updateSearchResultsForQuery)); /* * The listener tracks a change on the focus property value. * This happens, from active (user types a query) to inactive / focus diff --git a/src/main/java/org/jabref/gui/util/DefaultDirectoryMonitor.java b/src/main/java/org/jabref/gui/util/DefaultDirectoryMonitor.java new file mode 100644 index 00000000000..522a63632b0 --- /dev/null +++ b/src/main/java/org/jabref/gui/util/DefaultDirectoryMonitor.java @@ -0,0 +1,54 @@ +package org.jabref.gui.util; + +import org.jabref.model.util.DirectoryMonitor; + +import org.apache.commons.io.monitor.FileAlterationListener; +import org.apache.commons.io.monitor.FileAlterationMonitor; +import org.apache.commons.io.monitor.FileAlterationObserver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DefaultDirectoryMonitor implements DirectoryMonitor { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDirectoryMonitor.class); + private static final int POLL_INTERVAL = 1000; + + private final FileAlterationMonitor monitor; + + public DefaultDirectoryMonitor() { + monitor = new FileAlterationMonitor(POLL_INTERVAL); + start(); + } + + @Override + public void addObserver(FileAlterationObserver observer, FileAlterationListener listener) { + if (observer != null) { + observer.addListener(listener); + monitor.addObserver(observer); + } + } + + @Override + public void removeObserver(FileAlterationObserver observer) { + if (observer != null) { + monitor.removeObserver(observer); + } + } + + public void start() { + try { + monitor.start(); + } catch (Exception e) { + LOGGER.error("Error starting directory monitor", e); + } + } + + @Override + public void shutdown() { + try { + monitor.stop(); + } catch (Exception e) { + LOGGER.error("Error stopping directory monitor", e); + } + } +} diff --git a/src/main/java/org/jabref/logic/formatter/Formatters.java b/src/main/java/org/jabref/logic/formatter/Formatters.java index bcc7c202295..fb827f9ab8e 100644 --- a/src/main/java/org/jabref/logic/formatter/Formatters.java +++ b/src/main/java/org/jabref/logic/formatter/Formatters.java @@ -25,7 +25,8 @@ import org.jabref.logic.formatter.bibtexfields.NormalizeUnicodeFormatter; import org.jabref.logic.formatter.bibtexfields.OrdinalsToSuperscriptFormatter; import org.jabref.logic.formatter.bibtexfields.RegexFormatter; -import org.jabref.logic.formatter.bibtexfields.RemoveBracesFormatter; +import org.jabref.logic.formatter.bibtexfields.RemoveEnclosingBracesFormatter; +import org.jabref.logic.formatter.bibtexfields.RemoveWordEnclosingAndOuterEnclosingBracesFormatter; import org.jabref.logic.formatter.bibtexfields.ShortenDOIFormatter; import org.jabref.logic.formatter.bibtexfields.UnicodeToLatexFormatter; import org.jabref.logic.formatter.bibtexfields.UnitsToLatexFormatter; @@ -82,7 +83,8 @@ public static List getOthers() { new NormalizeNamesFormatter(), new NormalizePagesFormatter(), new OrdinalsToSuperscriptFormatter(), - new RemoveBracesFormatter(), + new RemoveEnclosingBracesFormatter(), + new RemoveWordEnclosingAndOuterEnclosingBracesFormatter(), new UnitsToLatexFormatter(), new EscapeUnderscoresFormatter(), new EscapeAmpersandsFormatter(), diff --git a/src/main/java/org/jabref/logic/formatter/bibtexfields/RemoveBracesFormatter.java b/src/main/java/org/jabref/logic/formatter/bibtexfields/RemoveEnclosingBracesFormatter.java similarity index 97% rename from src/main/java/org/jabref/logic/formatter/bibtexfields/RemoveBracesFormatter.java rename to src/main/java/org/jabref/logic/formatter/bibtexfields/RemoveEnclosingBracesFormatter.java index c29b081cc48..31b071aac12 100644 --- a/src/main/java/org/jabref/logic/formatter/bibtexfields/RemoveBracesFormatter.java +++ b/src/main/java/org/jabref/logic/formatter/bibtexfields/RemoveEnclosingBracesFormatter.java @@ -6,7 +6,7 @@ import org.jspecify.annotations.NullMarked; @NullMarked -public class RemoveBracesFormatter extends Formatter { +public class RemoveEnclosingBracesFormatter extends Formatter { @Override public String getName() { @@ -18,6 +18,16 @@ public String getKey() { return "remove_braces"; } + @Override + public String getDescription() { + return Localization.lang("Removes braces encapsulating the complete field content."); + } + + @Override + public String getExampleInput() { + return "{In CDMA}"; + } + @Override public String format(String value) { String formatted = value; @@ -37,16 +47,6 @@ public String format(String value) { return formatted; } - @Override - public String getDescription() { - return Localization.lang("Removes braces encapsulating the complete field content."); - } - - @Override - public String getExampleInput() { - return "{In CDMA}"; - } - /** * Check if a string at any point has had more ending } braces than opening { ones. * Will e.g. return true for the string "DNA} text {EPA" diff --git a/src/main/java/org/jabref/logic/formatter/bibtexfields/RemoveWordEnclosingAndOuterEnclosingBracesFormatter.java b/src/main/java/org/jabref/logic/formatter/bibtexfields/RemoveWordEnclosingAndOuterEnclosingBracesFormatter.java new file mode 100644 index 00000000000..c70c7315ebe --- /dev/null +++ b/src/main/java/org/jabref/logic/formatter/bibtexfields/RemoveWordEnclosingAndOuterEnclosingBracesFormatter.java @@ -0,0 +1,110 @@ +package org.jabref.logic.formatter.bibtexfields; + +import java.util.Objects; +import java.util.StringJoiner; + +import org.jabref.logic.cleanup.Formatter; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.strings.StringUtil; + +import org.jspecify.annotations.NullMarked; + +/** + * Removes start and end brace both at the complete string and at beginning/end of a word + *

+ * E.g., + *

    + *
  • {Vall{\'e}e Poussin} -> Vall{\'e}e Poussin
  • + *
  • {Vall{\'e}e} {Poussin} -> Vall{\'e}e Poussin
  • + *
  • Vall{\'e}e Poussin -> Vall{\'e}e Poussin
  • + *
+ */ +@NullMarked +public class RemoveWordEnclosingAndOuterEnclosingBracesFormatter extends Formatter { + + private static final RemoveEnclosingBracesFormatter REMOVE_ENCLOSING_BRACES_FORMATTER = new RemoveEnclosingBracesFormatter(); + + @Override + public String getName() { + return Localization.lang("Remove word enclosing braces"); + } + + @Override + public String getKey() { + return "remove_enclosing_and_outer_enclosing_braces"; + } + + @Override + public String getDescription() { + return Localization.lang("Removes braces encapsulating a complete word and the complete field content."); + } + + @Override + public String getExampleInput() { + return "{In {CDMA}}"; + } + + @Override + public String format(String input) { + Objects.requireNonNull(input); + if (StringUtil.isBlank(input)) { + return input; + } + + if (!input.contains("{")) { + return input; + } + + // We need to first remove the outer braces to have double braces at the last word working (e.g., {In {CDMA}}) + input = REMOVE_ENCLOSING_BRACES_FORMATTER.format(input); + + String[] split = input.split(" "); + StringJoiner result = new StringJoiner(" "); + for (String s : split) { + if ((s.length() > 2) && s.startsWith("{") && s.endsWith("}")) { + // quick solution (which we don't do): just remove first "{" and last "}" + // however, it might be that s is like {A}bbb{c}, where braces may not be removed + + String inner = s.substring(1, s.length() - 1); + + if (inner.contains("}")) { + if (properBrackets(inner)) { + s = inner; + } + } else { + // no inner curly brackets found, no check needed, inner can just be used as s + s = inner; + } + } + result.add(s); + } + return result.toString(); + } + + /** + * @return true iff the brackets in s are properly paired + */ + private boolean properBrackets(String s) { + // nested construct is there, check for "proper" nesting + int i = 0; + int level = 0; + while (i < s.length()) { + char c = s.charAt(i); + switch (c) { + case '{': + level++; + break; + case '}': + level--; + if (level == -1) { // improper nesting + return false; + } + break; + default: + break; + } + i++; + } + return level == 0; + } +} diff --git a/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java b/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java index 7b85d1534cd..23e5c077d8b 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/AstrophysicsDataSystem.java @@ -17,7 +17,7 @@ import org.jabref.logic.formatter.bibtexfields.ClearFormatter; import org.jabref.logic.formatter.bibtexfields.NormalizeMonthFormatter; import org.jabref.logic.formatter.bibtexfields.NormalizeNamesFormatter; -import org.jabref.logic.formatter.bibtexfields.RemoveBracesFormatter; +import org.jabref.logic.formatter.bibtexfields.RemoveEnclosingBracesFormatter; import org.jabref.logic.formatter.bibtexfields.RemoveNewlinesFormatter; import org.jabref.logic.help.HelpFile; import org.jabref.logic.importer.EntryBasedParserFetcher; @@ -149,9 +149,9 @@ public Parser getParser() { @Override public void doPostCleanup(BibEntry entry) { - new FieldFormatterCleanup(StandardField.ABSTRACT, new RemoveBracesFormatter()).cleanup(entry); + new FieldFormatterCleanup(StandardField.ABSTRACT, new RemoveEnclosingBracesFormatter()).cleanup(entry); new FieldFormatterCleanup(StandardField.ABSTRACT, new RemoveNewlinesFormatter()).cleanup(entry); - new FieldFormatterCleanup(StandardField.TITLE, new RemoveBracesFormatter()).cleanup(entry); + new FieldFormatterCleanup(StandardField.TITLE, new RemoveEnclosingBracesFormatter()).cleanup(entry); new FieldFormatterCleanup(StandardField.AUTHOR, new NormalizeNamesFormatter()).cleanup(entry); new FieldFormatterCleanup(StandardField.MONTH, new NormalizeMonthFormatter()).cleanup(entry); diff --git a/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java b/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java index 4826239e3e6..c34277add1e 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/CrossRef.java @@ -11,7 +11,7 @@ import org.jabref.logic.cleanup.FieldFormatterCleanup; import org.jabref.logic.formatter.bibtexfields.ClearFormatter; -import org.jabref.logic.formatter.bibtexfields.RemoveBracesFormatter; +import org.jabref.logic.formatter.bibtexfields.RemoveEnclosingBracesFormatter; import org.jabref.logic.importer.EntryBasedParserFetcher; import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.IdBasedParserFetcher; @@ -46,7 +46,7 @@ public class CrossRef implements IdParserFetcher, EntryBasedParserFetcher, private static final String API_URL = "https://api.crossref.org/works"; - private static final RemoveBracesFormatter REMOVE_BRACES_FORMATTER = new RemoveBracesFormatter(); + private static final RemoveEnclosingBracesFormatter REMOVE_BRACES_FORMATTER = new RemoveEnclosingBracesFormatter(); @Override public String getName() { diff --git a/src/main/java/org/jabref/logic/importer/fetcher/INSPIREFetcher.java b/src/main/java/org/jabref/logic/importer/fetcher/INSPIREFetcher.java index 67f9e84afc0..86d27594a84 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/INSPIREFetcher.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/INSPIREFetcher.java @@ -11,7 +11,7 @@ import org.jabref.logic.cleanup.FieldFormatterCleanup; import org.jabref.logic.formatter.bibtexfields.ClearFormatter; -import org.jabref.logic.formatter.bibtexfields.RemoveBracesFormatter; +import org.jabref.logic.formatter.bibtexfields.RemoveEnclosingBracesFormatter; import org.jabref.logic.help.HelpFile; import org.jabref.logic.importer.EntryBasedFetcher; import org.jabref.logic.importer.FetcherException; @@ -76,7 +76,7 @@ public void doPostCleanup(BibEntry entry) { new FieldFormatterCleanup(new UnknownField("SLACcitation"), new ClearFormatter()).cleanup(entry); // Remove braces around content of "title" field - new FieldFormatterCleanup(StandardField.TITLE, new RemoveBracesFormatter()).cleanup(entry); + new FieldFormatterCleanup(StandardField.TITLE, new RemoveEnclosingBracesFormatter()).cleanup(entry); new FieldFormatterCleanup(StandardField.TITLE, new LatexToUnicodeFormatter()).cleanup(entry); } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/ZbMATH.java b/src/main/java/org/jabref/logic/importer/fetcher/ZbMATH.java index 70afa7847c1..c8398a1e8f3 100644 --- a/src/main/java/org/jabref/logic/importer/fetcher/ZbMATH.java +++ b/src/main/java/org/jabref/logic/importer/fetcher/ZbMATH.java @@ -9,7 +9,7 @@ import org.jabref.logic.cleanup.FieldFormatterCleanup; import org.jabref.logic.cleanup.MoveFieldCleanup; -import org.jabref.logic.formatter.bibtexfields.RemoveBracesFormatter; +import org.jabref.logic.formatter.bibtexfields.RemoveEnclosingBracesFormatter; import org.jabref.logic.importer.EntryBasedParserFetcher; import org.jabref.logic.importer.FetcherException; import org.jabref.logic.importer.IdBasedParserFetcher; @@ -131,7 +131,7 @@ public Parser getParser() { public void doPostCleanup(BibEntry entry) { new MoveFieldCleanup(new UnknownField("msc2010"), StandardField.KEYWORDS).cleanup(entry); new MoveFieldCleanup(AMSField.FJOURNAL, StandardField.JOURNAL).cleanup(entry); - new FieldFormatterCleanup(StandardField.JOURNAL, new RemoveBracesFormatter()).cleanup(entry); - new FieldFormatterCleanup(StandardField.TITLE, new RemoveBracesFormatter()).cleanup(entry); + new FieldFormatterCleanup(StandardField.JOURNAL, new RemoveEnclosingBracesFormatter()).cleanup(entry); + new FieldFormatterCleanup(StandardField.TITLE, new RemoveEnclosingBracesFormatter()).cleanup(entry); } } diff --git a/src/main/java/org/jabref/logic/layout/format/HTMLChars.java b/src/main/java/org/jabref/logic/layout/format/HTMLChars.java index 5bfe60c7bd1..9776f49a504 100644 --- a/src/main/java/org/jabref/logic/layout/format/HTMLChars.java +++ b/src/main/java/org/jabref/logic/layout/format/HTMLChars.java @@ -4,14 +4,14 @@ import java.util.Objects; import java.util.regex.Pattern; -import org.jabref.logic.layout.LayoutFormatter; +import org.jabref.logic.layout.ParamLayoutFormatter; import org.jabref.logic.util.strings.HTMLUnicodeConversionMaps; import org.jabref.model.strings.StringUtil; /** - * This formatter escapes characters so they are suitable for HTML. + * This formatter escapes characters so that they are suitable for HTML. */ -public class HTMLChars implements LayoutFormatter { +public class HTMLChars implements ParamLayoutFormatter { private static final Map HTML_CHARS = HTMLUnicodeConversionMaps.LATEX_HTML_CONVERSION_MAP; /** @@ -23,6 +23,15 @@ public class HTMLChars implements LayoutFormatter { * */ private static final Pattern HTML_ENTITY_PATTERN = Pattern.compile("&(?!(?:[a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6});)"); + private boolean keepCurlyBraces = false; + + @Override + public void setArgument(String arg) { + if ("keepCurlyBraces".equalsIgnoreCase(arg)) { + this.keepCurlyBraces = true; + } + } + @Override public String format(String inField) { String field = normalizedField(inField); @@ -49,7 +58,7 @@ public String format(String inField) { escaped = true; incommand = true; currentCommand = new StringBuilder(); - } else if (!incommand && ((c == '{') || (c == '}'))) { + } else if (!this.keepCurlyBraces && !incommand && ((c == '{') || (c == '}'))) { // Swallow the brace. } else if (Character.isLetter(c) || StringUtil.SPECIAL_COMMAND_CHARS.contains(String.valueOf(c))) { escaped = false; @@ -73,7 +82,7 @@ public String format(String inField) { String commandBody; if (c == '{') { String part = StringUtil.getPart(field, i, false); - i += part.length(); + i += this.keepCurlyBraces ? part.length() + 1 : part.length(); commandBody = part; } else { commandBody = field.substring(i, i + 1); @@ -83,7 +92,6 @@ public String format(String inField) { sb.append(Objects.requireNonNullElse(result, commandBody)); incommand = false; - escaped = false; } else { // Are we already at the end of the string? if ((i + 1) == field.length()) { @@ -109,11 +117,14 @@ public String format(String inField) { String tag = getHTMLTag(command); if (!tag.isEmpty()) { String part = StringUtil.getPart(field, i, true); + if (this.keepCurlyBraces && (c == '{' || (c == '}'))) { + i++; + } i += part.length(); sb.append('<').append(tag).append('>').append(part).append("'); } else if (c == '{') { String argument = StringUtil.getPart(field, i, true); - i += argument.length(); + i += this.keepCurlyBraces ? argument.length() + 1 : argument.length(); // handle common case of general latex command String result = HTML_CHARS.get(command + argument); // If found, then use translated version. If not, then keep @@ -133,10 +144,14 @@ public String format(String inField) { } else if (c == '}') { // This end brace terminates a command. This can be the case in // constructs like {\aa}. The correct behaviour should be to - // substitute the evaluated command and swallow the brace: + // substitute the evaluated command. String result = HTML_CHARS.get(command); // If the command is unknown, just print it: sb.append(Objects.requireNonNullElse(result, command)); + // We only keep the brace if we are in 'KEEP' mode. + if (this.keepCurlyBraces) { + sb.append(c); + } } else { String result = HTML_CHARS.get(command); sb.append(Objects.requireNonNullElse(result, command)); @@ -170,7 +185,7 @@ private String normalizedField(String inField) { .replaceAll("[\\n]{2,}", "

") // Replace double line breaks with

.replace("\n", "
") // Replace single line breaks with
.replace("\\$", "$") // Replace \$ with $ - .replaceAll("\\$([^$]*)\\$", "\\{$1\\}"); + .replaceAll("\\$([^$]*)\\$", this.keepCurlyBraces ? "\\\\{$1\\\\}" : "$1}"); } private String getHTMLTag(String latexCommand) { diff --git a/src/main/java/org/jabref/logic/msbib/MSBibConverter.java b/src/main/java/org/jabref/logic/msbib/MSBibConverter.java index d565195e6fc..1870254e933 100644 --- a/src/main/java/org/jabref/logic/msbib/MSBibConverter.java +++ b/src/main/java/org/jabref/logic/msbib/MSBibConverter.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.Optional; -import org.jabref.logic.formatter.bibtexfields.RemoveBracesFormatter; +import org.jabref.logic.formatter.bibtexfields.RemoveEnclosingBracesFormatter; import org.jabref.model.entry.AuthorList; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.Month; @@ -18,7 +18,7 @@ public class MSBibConverter { private static final String MSBIB_PREFIX = "msbib-"; private static final String BIBTEX_PREFIX = "BIBTEX_"; - private static final RemoveBracesFormatter REMOVE_BRACES_FORMATTER = new RemoveBracesFormatter(); + private static final RemoveEnclosingBracesFormatter REMOVE_BRACES_FORMATTER = new RemoveEnclosingBracesFormatter(); private MSBibConverter() { } diff --git a/src/main/java/org/jabref/logic/msbib/MsBibAuthor.java b/src/main/java/org/jabref/logic/msbib/MsBibAuthor.java index bcceef9657f..9285a4d9d05 100644 --- a/src/main/java/org/jabref/logic/msbib/MsBibAuthor.java +++ b/src/main/java/org/jabref/logic/msbib/MsBibAuthor.java @@ -1,11 +1,11 @@ package org.jabref.logic.msbib; -import org.jabref.logic.formatter.bibtexfields.RemoveBracesFormatter; +import org.jabref.logic.formatter.bibtexfields.RemoveEnclosingBracesFormatter; import org.jabref.model.entry.Author; public class MsBibAuthor { - private static final RemoveBracesFormatter REMOVE_BRACES_FORMATTER = new RemoveBracesFormatter(); + private static final RemoveEnclosingBracesFormatter REMOVE_BRACES_FORMATTER = new RemoveEnclosingBracesFormatter(); private String firstName; private String middleName; diff --git a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java index 851b084b866..6683a67637a 100644 --- a/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java +++ b/src/main/java/org/jabref/logic/openoffice/backend/NamedRangeReferenceMark.java @@ -2,6 +2,7 @@ import java.util.Optional; +import org.jabref.model.openoffice.DocumentAnnotation; import org.jabref.model.openoffice.backend.NamedRange; import org.jabref.model.openoffice.uno.CreationException; import org.jabref.model.openoffice.uno.NoDocumentException; @@ -101,8 +102,8 @@ private static void createReprInDocument(XTextDocument doc, : left + right; cursor.getText().insertString(cursor, bracketedContent, true); - - UnoReferenceMark.create(doc, refMarkName, cursor, true /* absorb */); + DocumentAnnotation documentAnnotation = new DocumentAnnotation(doc, refMarkName, cursor, true /* absorb */); + UnoReferenceMark.create(documentAnnotation); // eat the first inserted space cursorBefore.goRight((short) 1, true); diff --git a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java index 99e71e13725..47e63414c78 100644 --- a/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java +++ b/src/main/java/org/jabref/logic/openoffice/frontend/UpdateBibliography.java @@ -4,6 +4,7 @@ import org.jabref.logic.openoffice.style.OOBibStyle; import org.jabref.logic.openoffice.style.OOFormatBibliography; +import org.jabref.model.openoffice.DocumentAnnotation; import org.jabref.model.openoffice.ootext.OOText; import org.jabref.model.openoffice.ootext.OOTextIntoOO; import org.jabref.model.openoffice.style.CitedKeys; @@ -67,7 +68,8 @@ private static void createBibTextSection2(XTextDocument doc) // Alternatively, we could receive a cursor. XTextCursor textCursor = doc.getText().createTextCursor(); textCursor.gotoEnd(false); - UnoTextSection.create(doc, BIB_SECTION_NAME, textCursor, false); + DocumentAnnotation annotation = new DocumentAnnotation(doc, BIB_SECTION_NAME, textCursor, false); + UnoTextSection.create(annotation); } /** @@ -129,7 +131,8 @@ private static void populateBibTextSection(XTextDocument doc, initialParagraph.setString(""); UnoBookmark.removeIfExists(doc, BIB_SECTION_END_NAME); - UnoBookmark.create(doc, BIB_SECTION_END_NAME, cursor, true); + DocumentAnnotation documentAnnotation = new DocumentAnnotation(doc, BIB_SECTION_END_NAME, cursor, true); + UnoBookmark.create(documentAnnotation); cursor.collapseToEnd(); } diff --git a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java index c38e3d37ed0..aff4c1ce84c 100644 --- a/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java +++ b/src/main/java/org/jabref/logic/openoffice/style/OOBibStyleGetCitationMarker.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.Optional; -import org.jabref.logic.formatter.bibtexfields.RemoveBracesFormatter; +import org.jabref.logic.formatter.bibtexfields.RemoveEnclosingBracesFormatter; import org.jabref.model.database.BibDatabase; import org.jabref.model.entry.Author; import org.jabref.model.entry.AuthorList; @@ -24,7 +24,7 @@ class OOBibStyleGetCitationMarker { - private static final RemoveBracesFormatter REMOVE_BRACES_FORMATTER = new RemoveBracesFormatter(); + private static final RemoveEnclosingBracesFormatter REMOVE_BRACES_FORMATTER = new RemoveEnclosingBracesFormatter(); private OOBibStyleGetCitationMarker() { } diff --git a/src/main/java/org/jabref/logic/pdf/search/PdfIndexer.java b/src/main/java/org/jabref/logic/pdf/search/PdfIndexer.java index 7b66b1c62ca..45750ac5b84 100644 --- a/src/main/java/org/jabref/logic/pdf/search/PdfIndexer.java +++ b/src/main/java/org/jabref/logic/pdf/search/PdfIndexer.java @@ -16,11 +16,11 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.LinkedFile; -import org.jabref.model.pdf.search.EnglishStemAnalyzer; import org.jabref.model.pdf.search.SearchFieldConstants; import org.jabref.preferences.FilePreferences; import com.google.common.annotations.VisibleForTesting; +import org.apache.lucene.analysis.en.EnglishAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexNotFoundException; @@ -130,7 +130,7 @@ private void initializeIndexWriterAndReader(IndexWriterConfig.OpenMode mode) { indexWriter = new IndexWriter( indexDirectory, new IndexWriterConfig( - new EnglishStemAnalyzer()).setOpenMode(mode)); + new EnglishAnalyzer()).setOpenMode(mode)); } catch (IOException e) { LOGGER.error("Could not initialize the IndexWriter", e); // FIXME: This can also happen if another instance of JabRef is launched in parallel. @@ -205,7 +205,7 @@ public void addToIndex(BibEntry entry, Collection linkedFiles, boole private void doCommit() { try { - getIndexWriter().ifPresent(Unchecked.consumer(writer -> writer.commit())); + getIndexWriter().ifPresent(Unchecked.consumer(IndexWriter::commit)); } catch (UncheckedIOException e) { LOGGER.warn("Could not commit changes to the index.", e); } @@ -274,7 +274,6 @@ private void addToIndex(BibEntry entry, LinkedFile linkedFile, boolean shouldCom LOGGER.debug("Could not find {}", linkedFile.getLink()); return; } - LOGGER.debug("Adding {} to index", linkedFile.getLink()); try { // Check if a document with this path is already in the index try { @@ -283,17 +282,21 @@ private void addToIndex(BibEntry entry, LinkedFile linkedFile, boolean shouldCom TopDocs topDocs = searcher.search(query, 1); // If a document was found, check if is less current than the one in the FS if (topDocs.scoreDocs.length > 0) { - Document doc = reader.document(topDocs.scoreDocs[0].doc); + Document doc = reader.storedFields().document(topDocs.scoreDocs[0].doc); long indexModificationTime = Long.parseLong(doc.getField(SearchFieldConstants.MODIFIED).stringValue()); BasicFileAttributes attributes = Files.readAttributes(resolvedPath.get(), BasicFileAttributes.class); if (indexModificationTime >= attributes.lastModifiedTime().to(TimeUnit.SECONDS)) { - LOGGER.debug("File {} is already indexed", linkedFile.getLink()); + LOGGER.debug("File {} is already indexed and up-to-date.", linkedFile.getLink()); return; + } else { + LOGGER.debug("File {} is already indexed but outdated. Removing from index.", linkedFile.getLink()); + removeFromIndex(linkedFile.getLink()); } } } catch (IndexNotFoundException e) { LOGGER.debug("Index not found. Continuing.", e); } + LOGGER.debug("Adding {} to index", linkedFile.getLink()); // If no document was found, add the new one Optional> pages = new DocumentReader(entry, filePreferences).readLinkedPdf(this.databaseContext, linkedFile); if (pages.isPresent()) { @@ -328,7 +331,7 @@ public Set getListOfFilePaths() { MatchAllDocsQuery query = new MatchAllDocsQuery(); TopDocs allDocs = searcher.search(query, Integer.MAX_VALUE); for (ScoreDoc scoreDoc : allDocs.scoreDocs) { - Document doc = reader.document(scoreDoc.doc); + Document doc = reader.storedFields().document(scoreDoc.doc); paths.add(doc.getField(SearchFieldConstants.PATH).stringValue()); } } catch (IOException e) { diff --git a/src/main/java/org/jabref/logic/pdf/search/PdfSearcher.java b/src/main/java/org/jabref/logic/pdf/search/PdfSearcher.java index 40acc97f8af..fb6afccc29b 100644 --- a/src/main/java/org/jabref/logic/pdf/search/PdfSearcher.java +++ b/src/main/java/org/jabref/logic/pdf/search/PdfSearcher.java @@ -7,11 +7,12 @@ import java.util.Optional; import org.jabref.gui.LibraryTab; -import org.jabref.model.pdf.search.EnglishStemAnalyzer; import org.jabref.model.pdf.search.PdfSearchResults; import org.jabref.model.pdf.search.SearchResult; import org.jabref.model.strings.StringUtil; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.en.EnglishAnalyzer; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; @@ -31,7 +32,7 @@ public final class PdfSearcher { private static final Logger LOGGER = LoggerFactory.getLogger(LibraryTab.class); private final PdfIndexer indexer; - private EnglishStemAnalyzer englishStemAnalyzer = new EnglishStemAnalyzer(); + private final Analyzer englishAnalyzer = new EnglishAnalyzer(); private PdfSearcher(PdfIndexer indexer) { this.indexer = indexer; @@ -65,7 +66,7 @@ public PdfSearchResults search(final String searchString, final int maxHits) thr return new PdfSearchResults(); } try (IndexReader reader = DirectoryReader.open(optionalIndexWriter.get())) { - Query query = new MultiFieldQueryParser(PDF_FIELDS, englishStemAnalyzer).parse(searchString); + Query query = new MultiFieldQueryParser(PDF_FIELDS, englishAnalyzer).parse(searchString); IndexSearcher searcher = new IndexSearcher(reader); TopDocs results = searcher.search(query, maxHits); for (ScoreDoc scoreDoc : results.scoreDocs) { diff --git a/src/main/java/org/jabref/logic/texparser/CitationFinder.java b/src/main/java/org/jabref/logic/texparser/CitationFinder.java deleted file mode 100644 index fac31569876..00000000000 --- a/src/main/java/org/jabref/logic/texparser/CitationFinder.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.jabref.logic.texparser; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; -import java.util.List; -import java.util.stream.Stream; - -import org.jabref.model.texparser.Citation; -import org.jabref.model.texparser.LatexParserResults; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CitationFinder { - - private static final Logger LOGGER = LoggerFactory.getLogger(CitationFinder.class); - private static final String TEX_EXT = ".tex"; - private final Path directory; - - private LatexParserResults latexParserResults; - - public CitationFinder(Path directory) { - this.directory = directory; - } - - public Collection searchAndParse(String citeKey) throws IOException { - if (latexParserResults == null) { - if (!Files.exists(directory)) { - throw new IOException("Current search directory does not exist: %s".formatted(directory)); - } - - List texFiles = searchDirectory(directory); - LOGGER.debug("Found tex files: {}", texFiles); - latexParserResults = new DefaultLatexParser().parse(texFiles); - } - - return latexParserResults.getCitationsByKey(citeKey); - } - - /** - * @param directory the directory to search for. It is recursively searched. - */ - private List searchDirectory(Path directory) { - LOGGER.debug("Searching directory {}", directory); - try (Stream paths = Files.walk(directory)) { - return paths.filter(Files::isRegularFile) - .filter(path -> path.toString().endsWith(TEX_EXT)) - .toList(); - } catch (IOException e) { - LOGGER.error("Error while searching files", e); - return List.of(); - } - } -} diff --git a/src/main/java/org/jabref/logic/texparser/DefaultLatexParser.java b/src/main/java/org/jabref/logic/texparser/DefaultLatexParser.java index 385c3b8b94e..1e24671c21e 100644 --- a/src/main/java/org/jabref/logic/texparser/DefaultLatexParser.java +++ b/src/main/java/org/jabref/logic/texparser/DefaultLatexParser.java @@ -12,7 +12,6 @@ import java.nio.file.Path; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -50,12 +49,6 @@ public class DefaultLatexParser implements LatexParser { private static final Pattern INCLUDE_PATTERN = Pattern.compile( "\\\\(?:include|input)\\{(?<%s>[^\\}]*)\\}".formatted(INCLUDE_GROUP)); - private final LatexParserResults latexParserResults; - - public DefaultLatexParser() { - this.latexParserResults = new LatexParserResults(); - } - @Override public LatexParserResult parse(String citeString) { Path path = Path.of(""); @@ -70,6 +63,7 @@ public Optional parse(Path latexFile) { LOGGER.error("File does not exist: {}", latexFile); return Optional.empty(); } + LatexParserResult latexParserResult = new LatexParserResult(latexFile); try (InputStream inputStream = Files.newInputStream(latexFile); @@ -99,20 +93,9 @@ public Optional parse(Path latexFile) { @Override public LatexParserResults parse(List latexFiles) { - for (Path latexFile : latexFiles) { - if (!latexParserResults.isParsed(latexFile)) { - parse(latexFile).ifPresent(parsedTex -> latexParserResults.add(latexFile, parsedTex)); - } - } - - Set nonParsedNestedFiles = latexParserResults.getNonParsedNestedFiles(); - // Parse all "non-parsed" files referenced by TEX files, recursively. - if (!nonParsedNestedFiles.isEmpty()) { - // modifies class variable latexParserResults - parse(nonParsedNestedFiles.stream().toList()); - } - - return latexParserResults; + LatexParserResults results = new LatexParserResults(); + latexFiles.forEach(file -> parse(file).ifPresent(result -> results.add(file, result))); + return results; } /** diff --git a/src/main/java/org/jabref/logic/util/io/FileNameUniqueness.java b/src/main/java/org/jabref/logic/util/io/FileNameUniqueness.java index 2c1456d13cd..44a73e0ae71 100644 --- a/src/main/java/org/jabref/logic/util/io/FileNameUniqueness.java +++ b/src/main/java/org/jabref/logic/util/io/FileNameUniqueness.java @@ -11,7 +11,12 @@ import org.jabref.gui.DialogService; import org.jabref.logic.l10n.Localization; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class FileNameUniqueness { + private static final Logger LOGGER = LoggerFactory.getLogger(FileNameUniqueness.class); + private static final Pattern DUPLICATE_MARK_PATTERN = Pattern.compile("(.*) \\(\\d+\\)"); /** @@ -61,7 +66,6 @@ public static String getNonOverWritingFileName(Path targetDirectory, String file * @throws IOException Fail when the file is not exist or something wrong when reading the file */ public static boolean isDuplicatedFile(Path directory, Path fileName, DialogService dialogService) throws IOException { - Objects.requireNonNull(directory); Objects.requireNonNull(fileName); Objects.requireNonNull(dialogService); @@ -83,10 +87,11 @@ public static boolean isDuplicatedFile(Path directory, Path fileName, DialogServ while (Files.exists(originalFile)) { if (com.google.common.io.Files.equal(originalFile.toFile(), duplicateFile.toFile())) { - if (duplicateFile.toFile().delete()) { + try { + Files.delete(duplicateFile); dialogService.notify(Localization.lang("File '%1' is a duplicate of '%0'. Keeping '%0'", originalFileName, fileName)); - } else { - dialogService.notify(Localization.lang("File '%1' is a duplicate of '%0'. Keeping both due to deletion error", originalFileName, fileName)); + } catch (IOException e) { + LOGGER.error("File '{}' is a duplicate of '{}'. Could not delete '{}'.", fileName, originalFileName, fileName); } return true; } diff --git a/src/main/java/org/jabref/migrations/PreferencesMigrations.java b/src/main/java/org/jabref/migrations/PreferencesMigrations.java index 92d60f49632..5a364ad06c5 100644 --- a/src/main/java/org/jabref/migrations/PreferencesMigrations.java +++ b/src/main/java/org/jabref/migrations/PreferencesMigrations.java @@ -331,6 +331,7 @@ protected static void upgradePreviewStyle(JabRefPreferences prefs) { String currentPreviewStyle = prefs.get(JabRefPreferences.PREVIEW_STYLE); String migratedStyle = currentPreviewStyle.replace("\\begin{review}

Review: \\format[HTMLChars]{\\review} \\end{review}", "\\begin{comment}

Comment: \\format[Markdown,HTMLChars]{\\comment} \\end{comment}") .replace("\\format[HTMLChars]{\\comment}", "\\format[Markdown,HTMLChars]{\\comment}") + .replace("\\format[Markdown,HTMLChars]{\\comment}", "\\format[Markdown,HTMLChars(keepCurlyBraces)]{\\comment}") .replace("\\bibtextype\\begin{bibtexkey} (\\bibtexkey)", "\\bibtextype\\begin{citationkey} (\\citationkey)") .replace("\\end{bibtexkey}
__NEWLINE__", "\\end{citationkey}

__NEWLINE__"); prefs.put(JabRefPreferences.PREVIEW_STYLE, migratedStyle); diff --git a/src/main/java/org/jabref/model/database/BibDatabaseContext.java b/src/main/java/org/jabref/model/database/BibDatabaseContext.java index b43f7069a24..f8d4c3dfdcf 100644 --- a/src/main/java/org/jabref/model/database/BibDatabaseContext.java +++ b/src/main/java/org/jabref/model/database/BibDatabaseContext.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; import org.jabref.architecture.AllowedToUseLogic; @@ -33,7 +34,7 @@ * and the options relevant for this file in Defaults. *

*

- * To get an instance for a .bib file, use {@link org.jabref.logic.importer.fileformat.BibtexParser}. + * To get an instance for a .bib file, use {@link org.jabref.logic.importer.fileformat.BibtexParser}. *

*/ @AllowedToUseLogic("because it needs access to shared database features") @@ -44,6 +45,12 @@ public class BibDatabaseContext { private final BibDatabase database; private MetaData metaData; + /** + * Generate a random UID for unique of the concrete context + * In contrast to hashCode this stays unique + */ + private final String uid = "bibdatabasecontext_" + UUID.randomUUID(); + /** * The path where this database was last saved to. */ @@ -127,9 +134,9 @@ public boolean isBiblatexMode() { */ public boolean isStudy() { return this.getDatabasePath() - .map(path -> path.getFileName().toString().equals(Crawler.FILENAME_STUDY_RESULT_BIB) && - Files.exists(path.resolveSibling(StudyRepository.STUDY_DEFINITION_FILE_NAME))) - .orElse(false); + .map(path -> path.getFileName().toString().equals(Crawler.FILENAME_STUDY_RESULT_BIB) && + Files.exists(path.resolveSibling(StudyRepository.STUDY_DEFINITION_FILE_NAME))) + .orElse(false); } /** @@ -286,4 +293,13 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(database, metaData, path, location); } + + /** + * Get the generated UID for the current context. Can be used to distinguish contexts with changing metadata etc + * + * @return The generated UID in UUIDv4 format with the prefix bibdatabasecontext_ + */ + public String getUid() { + return uid; + } } diff --git a/src/main/java/org/jabref/model/entry/Author.java b/src/main/java/org/jabref/model/entry/Author.java index e4511e7ef07..5892ca0b457 100644 --- a/src/main/java/org/jabref/model/entry/Author.java +++ b/src/main/java/org/jabref/model/entry/Author.java @@ -3,6 +3,8 @@ import java.util.Objects; import java.util.Optional; +import org.jabref.architecture.AllowedToUseLogic; +import org.jabref.logic.formatter.bibtexfields.RemoveWordEnclosingAndOuterEnclosingBracesFormatter; import org.jabref.model.strings.LatexToUnicodeAdapter; import org.jabref.model.strings.StringUtil; @@ -11,6 +13,7 @@ *

* Current usage: only methods getLastOnly, getFirstLast, and getLastFirst are used; all other methods are provided for completeness. */ +@AllowedToUseLogic("because it needs to use formatter") public class Author { /** @@ -20,6 +23,8 @@ public class Author { */ public static final Author OTHERS = new Author("", "", null, "others", null); + public static final RemoveWordEnclosingAndOuterEnclosingBracesFormatter FORMATTER = new RemoveWordEnclosingAndOuterEnclosingBracesFormatter(); + private final String givenName; private final String givenNameAbbreviated; private final String namePrefix; @@ -41,17 +46,37 @@ public class Author { public Author(String givenName, String givenNameAbbreviated, String namePrefix, String familyName, String nameSuffix) { boolean keepBracesAtLastPart = StringUtil.isBlank(givenName) && StringUtil.isBlank(givenNameAbbreviated) && StringUtil.isBlank(namePrefix) && !StringUtil.isBlank(familyName) && StringUtil.isBlank(nameSuffix); - this.givenName = addDotIfAbbreviation(removeStartAndEndBraces(givenName)); - this.givenNameAbbreviated = removeStartAndEndBraces(givenNameAbbreviated); - this.namePrefix = removeStartAndEndBraces(namePrefix); + if (!StringUtil.isBlank(givenName)) { + this.givenName = addDotIfAbbreviation(FORMATTER.format(givenName)); + } else { + this.givenName = null; + } + if (!StringUtil.isBlank(givenNameAbbreviated)) { + this.givenNameAbbreviated = FORMATTER.format(givenNameAbbreviated); + } else { + this.givenNameAbbreviated = null; + } + if (!StringUtil.isBlank(namePrefix)) { + this.namePrefix = FORMATTER.format(namePrefix); + } else { + this.namePrefix = null; + } if (keepBracesAtLastPart) { // We do not remove braces here to keep institutions protected // https://github.com/JabRef/jabref/issues/10031 this.familyName = familyName; } else { - this.familyName = removeStartAndEndBraces(familyName); + if (!StringUtil.isBlank(familyName)) { + this.familyName = FORMATTER.format(familyName); + } else { + this.familyName = null; + } + } + if (!StringUtil.isBlank(nameSuffix)) { + this.nameSuffix = FORMATTER.format(nameSuffix); + } else { + this.nameSuffix = null; } - this.nameSuffix = removeStartAndEndBraces(nameSuffix); } public static String addDotIfAbbreviation(String name) { @@ -165,94 +190,6 @@ public boolean equals(Object other) { return false; } - /** - * @return true iff the brackets in s are properly paired - */ - private boolean properBrackets(String s) { - // nested construct is there, check for "proper" nesting - int i = 0; - int level = 0; - while (i < s.length()) { - char c = s.charAt(i); - switch (c) { - case '{': - level++; - break; - case '}': - level--; - if (level == -1) { // improper nesting - return false; - } - break; - default: - break; - } - i++; - } - return level == 0; - } - - /** - * Removes start and end brace both at the complete string and at beginning/end of a word - *

- * E.g., - *

    - *
  • {Vall{\'e}e Poussin} -> Vall{\'e}e Poussin
  • - *
  • {Vall{\'e}e} {Poussin} -> Vall{\'e}e Poussin
  • - *
  • Vall{\'e}e Poussin -> Vall{\'e}e Poussin
  • - *
- */ - private String removeStartAndEndBraces(String name) { - if (StringUtil.isBlank(name)) { - return null; - } - - if (!name.contains("{")) { - return name; - } - - String[] split = name.split(" "); - StringBuilder b = new StringBuilder(); - for (String s : split) { - if ((s.length() > 2) && s.startsWith("{") && s.endsWith("}")) { - // quick solution (which we don't do: just remove first "{" and last "}" - // however, it might be that s is like {A}bbb{c}, where braces may not be removed - - // inner - String inner = s.substring(1, s.length() - 1); - - if (inner.contains("}")) { - if (properBrackets(inner)) { - s = inner; - } - } else { - // no inner curly brackets found, no check needed, inner can just be used as s - s = inner; - } - } - b.append(s).append(' '); - } - // delete last - b.deleteCharAt(b.length() - 1); - - // now, all inner words are cleared - // case {word word word} remains - // as above, we have to be aware of {w}ord word wor{d} and {{w}ord word word} - - String newName = b.toString(); - - if (newName.startsWith("{") && newName.endsWith("}")) { - String inner = newName.substring(1, newName.length() - 1); - if (properBrackets(inner)) { - return inner; - } else { - return newName; - } - } else { - return newName; - } - } - /** * Returns the first name of the author stored in this object ("First"). * @@ -304,7 +241,7 @@ public Optional getNameSuffix() { * @return 'von Last' */ public String getNamePrefixAndFamilyName() { - if (namePrefix == null) { + if (namePrefix == null || "".equals(namePrefix)) { return getFamilyName().orElse(""); } else { return familyName == null ? namePrefix : namePrefix + ' ' + familyName; diff --git a/src/main/java/org/jabref/model/entry/BibEntry.java b/src/main/java/org/jabref/model/entry/BibEntry.java index 19b33f257fb..b52b827579f 100644 --- a/src/main/java/org/jabref/model/entry/BibEntry.java +++ b/src/main/java/org/jabref/model/entry/BibEntry.java @@ -45,6 +45,7 @@ import org.jabref.model.strings.StringUtil; import org.jabref.model.util.MultiKeyMap; +import com.google.common.annotations.VisibleForTesting; import com.google.common.eventbus.EventBus; import com.tobiasdiez.easybind.EasyBind; import com.tobiasdiez.easybind.optional.OptionalBinding; @@ -353,19 +354,22 @@ private Optional genericGetResolvedFieldOrAlias(Field field, BibDatabase } /** - * Returns this entry's ID. + * Returns this entry's ID. It is used internally to distinguish different BibTeX entries. + *

+ * It is not the citation key (which is stored in the {@link InternalField#KEY_FIELD} and also known as BibTeX key). */ public String getId() { return id; } /** - * Sets this entry's identifier (ID). It is used internally to distinguish different BibTeX entries. It is not the citation key. The BibTexKey is the {@link InternalField#KEY_FIELD}. + * Sets this entry's identifier (ID). *

* The entry is also updated in the shared database - provided the database containing it doesn't veto the change. * * @param id The ID to be used */ + @VisibleForTesting public void setId(String id) { Objects.requireNonNull(id, "Every BibEntry must have an ID"); @@ -613,11 +617,11 @@ public Optional setField(Field field, String value, EntriesEventSou } String oldValue = getField(field).orElse(null); - boolean isNewField = oldValue == null; if (value.equals(oldValue)) { return Optional.empty(); } + boolean isNewField = oldValue == null; changed = true; invalidateFieldCache(field); @@ -1062,15 +1066,15 @@ public BibEntry withFiles(List files) { * Gets a list of linked files. * * @return the list of linked files, is never null but can be empty. - * Changes to the underlying list will have no effect on the entry itself. Use {@link #addFile(LinkedFile)} + * Changes to the underlying list will have no effect on the entry itself. Use {@link #addFile(LinkedFile)}. */ public List getFiles() { - // Extract the path Optional oldValue = getField(StandardField.FILE); if (oldValue.isEmpty()) { return new ArrayList<>(); // Return new ArrayList because emptyList is immutable } + // Extract the path return FileFieldParser.parse(oldValue.get()); } diff --git a/src/main/java/org/jabref/model/openoffice/DocumentAnnotation.java b/src/main/java/org/jabref/model/openoffice/DocumentAnnotation.java new file mode 100644 index 00000000000..2d7037f7199 --- /dev/null +++ b/src/main/java/org/jabref/model/openoffice/DocumentAnnotation.java @@ -0,0 +1,15 @@ +package org.jabref.model.openoffice; + +import com.sun.star.text.XTextDocument; +import com.sun.star.text.XTextRange; + +/** + * Represents a document annotation. + * + * @param doc The document + * @param name For the ReferenceMark, Bookmark, TextSection. If the name is already in use, LibreOffice may change the name. + * @param range Cursor marking the location or range for the thing to be inserted. + * @param absorb ReferenceMark, Bookmark and TextSection can incorporate a text range. If absorb is true, the text in the range becomes part of the thing. If absorb is false, the thing is inserted at the end of the range. + */ +public record DocumentAnnotation(XTextDocument doc, String name, XTextRange range, boolean absorb) { +} diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java b/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java index 5f4f5ea40d7..0f87ddcd21b 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoBookmark.java @@ -2,6 +2,8 @@ import java.util.Optional; +import org.jabref.model.openoffice.DocumentAnnotation; + import com.sun.star.container.NoSuchElementException; import com.sun.star.container.XNameAccess; import com.sun.star.container.XNamed; @@ -52,16 +54,13 @@ public static Optional getAnchor(XTextDocument doc, String name) *

* In LibreOffice the another name is in "{name}{number}" format. * - * @param name For the bookmark. - * @param range Cursor marking the location or range for the bookmark. - * @param absorb Shall we incorporate range? * @return The XNamed interface of the bookmark. * result.getName() should be checked by the caller, because its name may differ from the one requested. */ - public static XNamed create(XTextDocument doc, String name, XTextRange range, boolean absorb) + public static XNamed create(DocumentAnnotation documentAnnotation) throws CreationException { - return UnoNamed.insertNamedTextContent(doc, "com.sun.star.text.Bookmark", name, range, absorb); + return UnoNamed.insertNamedTextContent("com.sun.star.text.Bookmark", documentAnnotation); } /** diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java b/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java index d3bf46668d1..3352150ba85 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoNamed.java @@ -1,10 +1,10 @@ package org.jabref.model.openoffice.uno; +import org.jabref.model.openoffice.DocumentAnnotation; + import com.sun.star.container.XNamed; import com.sun.star.lang.XMultiServiceFactory; import com.sun.star.text.XTextContent; -import com.sun.star.text.XTextDocument; -import com.sun.star.text.XTextRange; public class UnoNamed { @@ -17,20 +17,13 @@ private UnoNamed() { * @param service For example "com.sun.star.text.ReferenceMark", "com.sun.star.text.Bookmark" or "com.sun.star.text.TextSection". *

* Passed to this.asXMultiServiceFactory().createInstance(service) The result is expected to support the XNamed and XTextContent interfaces. - * @param name For the ReferenceMark, Bookmark, TextSection. If the name is already in use, LibreOffice may change the name. - * @param range Marks the location or range for the thing to be inserted. - * @param absorb ReferenceMark, Bookmark and TextSection can incorporate a text range. If absorb is true, the text in the range becomes part of the thing. If absorb is false, the thing is inserted at the end of the range. * @return The XNamed interface, in case we need to check the actual name. */ - static XNamed insertNamedTextContent(XTextDocument doc, - String service, - String name, - XTextRange range, - boolean absorb) + static XNamed insertNamedTextContent(String service, DocumentAnnotation documentAnnotation) throws CreationException { - XMultiServiceFactory msf = UnoCast.cast(XMultiServiceFactory.class, doc).get(); + XMultiServiceFactory msf = UnoCast.cast(XMultiServiceFactory.class, documentAnnotation.doc()).get(); Object xObject; try { @@ -41,12 +34,12 @@ static XNamed insertNamedTextContent(XTextDocument doc, XNamed xNamed = UnoCast.cast(XNamed.class, xObject) .orElseThrow(() -> new IllegalArgumentException("Service is not an XNamed")); - xNamed.setName(name); + xNamed.setName(documentAnnotation.name()); // get XTextContent interface XTextContent xTextContent = UnoCast.cast(XTextContent.class, xObject) .orElseThrow(() -> new IllegalArgumentException("Service is not an XTextContent")); - range.getText().insertTextContent(range, xTextContent, absorb); + documentAnnotation.range().getText().insertTextContent(documentAnnotation.range(), xTextContent, documentAnnotation.absorb()); return xNamed; } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java b/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java index 12e36fae46a..0b9f2adc6c0 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoReferenceMark.java @@ -5,6 +5,8 @@ import java.util.List; import java.util.Optional; +import org.jabref.model.openoffice.DocumentAnnotation; + import com.sun.star.container.NoSuchElementException; import com.sun.star.container.XNameAccess; import com.sun.star.container.XNamed; @@ -105,16 +107,14 @@ public static Optional getAnchor(XTextDocument doc, String name) /** * Insert a new reference mark at the provided cursor position. *

- * If {@code absorb} is true, the text in the cursor range will become the text with gray background. + * If {@code documentAnnotation.getAbsorb} is true, the text in the cursor range will become the text with gray background. *

* Note: LibreOffice 6.4.6.2 will create multiple reference marks with the same name without error or renaming. Its GUI does not allow this, but we can create them programmatically. In the GUI, clicking on any of those identical names will move the cursor to the same mark. * - * @param name For the reference mark. - * @param range Cursor marking the location or range for the reference mark. */ - public static XNamed create(XTextDocument doc, String name, XTextRange range, boolean absorb) + public static XNamed create(DocumentAnnotation documentAnnotation) throws CreationException { - return UnoNamed.insertNamedTextContent(doc, "com.sun.star.text.ReferenceMark", name, range, absorb); + return UnoNamed.insertNamedTextContent("com.sun.star.text.ReferenceMark", documentAnnotation); } } diff --git a/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java b/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java index 1717795cfa7..9e0c3fddf2d 100644 --- a/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java +++ b/src/main/java/org/jabref/model/openoffice/uno/UnoTextSection.java @@ -2,6 +2,8 @@ import java.util.Optional; +import org.jabref.model.openoffice.DocumentAnnotation; + import com.sun.star.container.NoSuchElementException; import com.sun.star.container.XNameAccess; import com.sun.star.container.XNamed; @@ -63,17 +65,13 @@ public static Optional getAnchor(XTextDocument doc, String name) /** * Create a text section with the provided name and insert it at the provided cursor. - * - * @param name The desired name for the section. - * @param range The location to insert at. - *

- * If an XTextSection by that name already exists, LibreOffice (6.4.6.2) creates a section with a name different from what we requested, in "Section {number}" format. + * If an XTextSection by that name already exists, LibreOffice (6.4.6.2) creates a section with a name different from what we requested, in "Section {number}" format */ - public static XNamed create(XTextDocument doc, String name, XTextRange range, boolean absorb) + public static XNamed create(DocumentAnnotation documentAnnotation) throws CreationException { - return UnoNamed.insertNamedTextContent(doc, "com.sun.star.text.TextSection", name, range, absorb); + return UnoNamed.insertNamedTextContent("com.sun.star.text.TextSection", documentAnnotation); } } diff --git a/src/main/java/org/jabref/model/pdf/search/EnglishStemAnalyzer.java b/src/main/java/org/jabref/model/pdf/search/EnglishStemAnalyzer.java deleted file mode 100644 index 1dcccbb6583..00000000000 --- a/src/main/java/org/jabref/model/pdf/search/EnglishStemAnalyzer.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.jabref.model.pdf.search; - -import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.analysis.LowerCaseFilter; -import org.apache.lucene.analysis.StopFilter; -import org.apache.lucene.analysis.TokenStream; -import org.apache.lucene.analysis.Tokenizer; -import org.apache.lucene.analysis.core.DecimalDigitFilter; -import org.apache.lucene.analysis.en.EnglishAnalyzer; -import org.apache.lucene.analysis.en.PorterStemFilter; -import org.apache.lucene.analysis.standard.StandardTokenizer; - -public class EnglishStemAnalyzer extends Analyzer { - - @Override - protected TokenStreamComponents createComponents(String fieldName) { - Tokenizer source = new StandardTokenizer(); - TokenStream filter = new LowerCaseFilter(source); - filter = new StopFilter(filter, EnglishAnalyzer.ENGLISH_STOP_WORDS_SET); - filter = new DecimalDigitFilter(filter); - filter = new PorterStemFilter(filter); - return new TokenStreamComponents(source, filter); - } -} - diff --git a/src/main/java/org/jabref/model/pdf/search/SearchResult.java b/src/main/java/org/jabref/model/pdf/search/SearchResult.java index 7a79191e94c..aeb70ff6779 100644 --- a/src/main/java/org/jabref/model/pdf/search/SearchResult.java +++ b/src/main/java/org/jabref/model/pdf/search/SearchResult.java @@ -7,7 +7,9 @@ import org.jabref.model.entry.BibEntry; +import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.en.EnglishAnalyzer; import org.apache.lucene.index.IndexableField; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; @@ -46,14 +48,16 @@ public SearchResult(IndexSearcher searcher, Query query, ScoreDoc scoreDoc) thro Highlighter highlighter = new Highlighter(new SimpleHTMLFormatter("", ""), new QueryScorer(query)); - try (TokenStream contentStream = new EnglishStemAnalyzer().tokenStream(CONTENT, content)) { + try (Analyzer analyzer = new EnglishAnalyzer(); + TokenStream contentStream = analyzer.tokenStream(CONTENT, content)) { TextFragment[] frags = highlighter.getBestTextFragments(contentStream, content, true, 10); this.contentResultStringsHtml = Arrays.stream(frags).map(TextFragment::toString).collect(Collectors.toList()); } catch (InvalidTokenOffsetsException e) { this.contentResultStringsHtml = List.of(); } - try (TokenStream annotationStream = new EnglishStemAnalyzer().tokenStream(ANNOTATIONS, annotations)) { + try (Analyzer analyzer = new EnglishAnalyzer(); + TokenStream annotationStream = analyzer.tokenStream(ANNOTATIONS, annotations)) { TextFragment[] frags = highlighter.getBestTextFragments(annotationStream, annotations, true, 10); this.annotationsResultStringsHtml = Arrays.stream(frags).map(TextFragment::toString).collect(Collectors.toList()); } catch (InvalidTokenOffsetsException e) { @@ -62,7 +66,7 @@ public SearchResult(IndexSearcher searcher, Query query, ScoreDoc scoreDoc) thro } private String getFieldContents(IndexSearcher searcher, ScoreDoc scoreDoc, String field) throws IOException { - IndexableField indexableField = searcher.doc(scoreDoc.doc).getField(field); + IndexableField indexableField = searcher.storedFields().document(scoreDoc.doc).getField(field); if (indexableField == null) { return ""; } diff --git a/src/main/java/org/jabref/model/texparser/LatexParserResults.java b/src/main/java/org/jabref/model/texparser/LatexParserResults.java index 63245fea4ea..d09df07408a 100644 --- a/src/main/java/org/jabref/model/texparser/LatexParserResults.java +++ b/src/main/java/org/jabref/model/texparser/LatexParserResults.java @@ -28,31 +28,20 @@ public LatexParserResults(LatexParserResult... parsedFiles) { } } - public boolean isParsed(Path texFile) { - return parsedTexFiles.containsKey(texFile); - } - public void add(Path texFile, LatexParserResult parsedFile) { parsedTexFiles.put(texFile, parsedFile); } + public LatexParserResult remove(Path texFile) { + return parsedTexFiles.remove(texFile); + } + public Set getBibFiles() { Set bibFiles = new HashSet<>(); parsedTexFiles.values().forEach(result -> bibFiles.addAll(result.getBibFiles())); return bibFiles; } - public Set getNonParsedNestedFiles() { - Set nonParsedNestedFiles = new HashSet<>(); - for (LatexParserResult result : parsedTexFiles.values()) { - nonParsedNestedFiles.addAll(result.getNestedFiles() - .stream() - .filter(nestedFile -> !parsedTexFiles.containsKey(nestedFile)) - .toList()); - } - return nonParsedNestedFiles; - } - public Multimap getCitations() { Multimap citations = HashMultimap.create(); parsedTexFiles.forEach((path, result) -> citations.putAll(result.getCitations())); @@ -65,6 +54,10 @@ public Collection getCitationsByKey(String key) { return citations; } + public void clear() { + parsedTexFiles.clear(); + } + @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/src/main/java/org/jabref/model/util/DirectoryMonitor.java b/src/main/java/org/jabref/model/util/DirectoryMonitor.java new file mode 100644 index 00000000000..0678704c8a1 --- /dev/null +++ b/src/main/java/org/jabref/model/util/DirectoryMonitor.java @@ -0,0 +1,25 @@ +package org.jabref.model.util; + +import org.apache.commons.io.monitor.FileAlterationListener; +import org.apache.commons.io.monitor.FileAlterationObserver; + +public interface DirectoryMonitor { + /** + * Add an observer to the monitor. + * + * @param observer The directory to observe. + * @param listener The listener to invoke when the directory changes. + */ + void addObserver(FileAlterationObserver observer, FileAlterationListener listener); + + /** + * Remove an observer from the monitor. + * + * @param observer The directory to stop monitoring. + */ + void removeObserver(FileAlterationObserver observer); + + void start(); + + void shutdown(); +} diff --git a/src/main/java/org/jabref/model/util/DirectoryMonitorManager.java b/src/main/java/org/jabref/model/util/DirectoryMonitorManager.java new file mode 100644 index 00000000000..ba3c6546a8f --- /dev/null +++ b/src/main/java/org/jabref/model/util/DirectoryMonitorManager.java @@ -0,0 +1,36 @@ +package org.jabref.model.util; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.monitor.FileAlterationListener; +import org.apache.commons.io.monitor.FileAlterationObserver; + +public class DirectoryMonitorManager { + + private final DirectoryMonitor directoryMonitor; + private final List observers = new ArrayList<>(); + + public DirectoryMonitorManager(DirectoryMonitor directoryMonitor) { + this.directoryMonitor = directoryMonitor; + } + + public void addObserver(FileAlterationObserver observer, FileAlterationListener listener) { + directoryMonitor.addObserver(observer, listener); + observers.add(observer); + } + + public void removeObserver(FileAlterationObserver observer) { + directoryMonitor.removeObserver(observer); + observers.remove(observer); + } + + /** + * Unregister all observers associated with this manager from the directory monitor. + * This method should be called when the library is closed to stop watching observers. + */ + public void unregister() { + observers.forEach(directoryMonitor::removeObserver); + observers.clear(); + } +} diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 55de7b43b54..59b01569a34 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -173,6 +173,10 @@ public class JabRefPreferences implements PreferencesService { public static final String BIBLATEX_DEFAULT_MODE = "biblatexMode"; public static final String NAMES_AS_IS = "namesAsIs"; public static final String ENTRY_EDITOR_HEIGHT = "entryEditorHeightFX"; + /** + * Holds the horizontal divider position of the preview view when it is shown inside the entry editor + */ + public static final String ENTRY_EDITOR_PREVIEW_DIVIDER_POS = "entryEditorPreviewDividerPos"; public static final String AUTO_RESIZE_MODE = "autoResizeMode"; public static final String WINDOW_MAXIMISED = "windowMaximised"; public static final String WINDOW_FULLSCREEN = "windowFullscreen"; @@ -602,6 +606,7 @@ private JabRefPreferences() { defaults.put(WINDOW_FULLSCREEN, Boolean.FALSE); defaults.put(AUTO_RESIZE_MODE, Boolean.FALSE); // By default disable "Fit table horizontally on the screen" defaults.put(ENTRY_EDITOR_HEIGHT, 0.65); + defaults.put(ENTRY_EDITOR_PREVIEW_DIVIDER_POS, 0.5); defaults.put(NAMES_AS_IS, Boolean.FALSE); // "Show names unchanged" defaults.put(NAMES_FIRST_LAST, Boolean.FALSE); // "Show 'Firstname Lastname'" defaults.put(NAMES_NATBIB, Boolean.TRUE); // "Natbib style" @@ -821,7 +826,7 @@ private JabRefPreferences() { "\\begin{pages}
p. \\format[FormatPagesForHTML]{\\pages}\\end{pages}__NEWLINE__" + "\\begin{abstract}

Abstract: \\format[HTMLChars]{\\abstract} \\end{abstract}__NEWLINE__" + "\\begin{owncitation}

Own citation: \\format[HTMLChars]{\\owncitation} \\end{owncitation}__NEWLINE__" + - "\\begin{comment}

Comment: \\format[Markdown,HTMLChars]{\\comment}\\end{comment}__NEWLINE__" + + "\\begin{comment}

Comment: \\format[Markdown,HTMLChars(keepCurlyBraces)]{\\comment}\\end{comment}__NEWLINE__" + "__NEWLINE__"); // set default theme @@ -1488,7 +1493,8 @@ public EntryEditorPreferences getEntryEditorPreferences() { getBoolean(AUTOLINK_FILES_ENABLED), EntryEditorPreferences.JournalPopupEnabled.fromString(get(JOURNAL_POPUP)), getBoolean(SHOW_SCITE_TAB), - getBoolean(SHOW_USER_COMMENTS_FIELDS)); + getBoolean(SHOW_USER_COMMENTS_FIELDS), + getDouble(ENTRY_EDITOR_PREVIEW_DIVIDER_POS)); EasyBind.listen(entryEditorPreferences.entryEditorTabs(), (obs, oldValue, newValue) -> storeEntryEditorTabs(newValue)); // defaultEntryEditorTabs are read-only @@ -1503,7 +1509,7 @@ public EntryEditorPreferences getEntryEditorPreferences() { EasyBind.listen(entryEditorPreferences.enableJournalPopupProperty(), (obs, oldValue, newValue) -> put(JOURNAL_POPUP, newValue.toString())); EasyBind.listen(entryEditorPreferences.shouldShowLSciteTabProperty(), (obs, oldValue, newValue) -> putBoolean(SHOW_SCITE_TAB, newValue)); EasyBind.listen(entryEditorPreferences.showUserCommentsFieldsProperty(), (obs, oldValue, newValue) -> putBoolean(SHOW_USER_COMMENTS_FIELDS, newValue)); - + EasyBind.listen(entryEditorPreferences.previewWidthDividerPositionProperty(), (obs, oldValue, newValue) -> putDouble(ENTRY_EDITOR_PREVIEW_DIVIDER_POS, newValue.doubleValue())); return entryEditorPreferences; } diff --git a/src/main/resources/csl-styles b/src/main/resources/csl-styles index d9ca25977a1..434df0ad75d 160000 --- a/src/main/resources/csl-styles +++ b/src/main/resources/csl-styles @@ -1 +1 @@ -Subproject commit d9ca25977a10d6717d0fc219c1b982863a23a0e7 +Subproject commit 434df0ad75d7416bb651ad1f08826a5152c77a27 diff --git a/src/main/resources/l10n/JabRef_de.properties b/src/main/resources/l10n/JabRef_de.properties index 9d6ee1620c2..770d0680f26 100644 --- a/src/main/resources/l10n/JabRef_de.properties +++ b/src/main/resources/l10n/JabRef_de.properties @@ -61,7 +61,7 @@ Added\ group\ "%0".=Gruppe "%0" hinzugefügt. Added\ string\:\ '%0'=Zeichenkette hinzugefügt\: '%0' Added\ string=String hinzugefügt Edit\ strings=Strings bearbeiten -Duplicate\ string\ name\:\ '%0'=Doppelter String-Name\: '%0'l +Duplicate\ string\ name\:\ '%0'=Doppelter String-Name\: '%0' Modified\ string=Veränderter String Modified\ string\:\ '%0' =String geändert\: '%0' New\ string=Neuer String @@ -276,7 +276,6 @@ Donate\ to\ JabRef=An JabRef spenden Download\ file=Datei herunterladen -Downloaded\ website\ as\ an\ HTML\ file.=Webseite als HTML-Datei herunterladen. duplicate\ removal=Duplikate entfernen @@ -370,7 +369,6 @@ Manage\ field\ names\ &\ content=Feldnamen & Inhalt verwalten Field\ to\ group\ by=Sortierfeld Filter=Filter -Filter\ groups=Gruppen filtern Success\!\ Finished\ writing\ metadata.=Erfolgreich\! Das Schreiben der Metadaten ist abgeschlossen. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Fehler beim Schreiben von Metadaten. Details finden Sie im Fehlerprotokoll. @@ -1878,7 +1876,6 @@ Could\ not\ copy\ file=Datei konnte nicht kopiert werden Copied\ %0\ files\ of\ %1\ successfully\ to\ %2=Kopierte %0 Dateien von %1 erfolgreich nach %2 Rename\ failed=Umbenennen fehlgeschlagen JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef kann nicht auf die Datei zugreifen, da sie von einem anderen Prozess verwendet wird. -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=Konsolenausgabe anzeigen (nur wenn der Launcher verwendet wird) Remove\ line\ breaks=Entfernen der Zeilenumbrüche Removes\ all\ line\ breaks\ in\ the\ field\ content.=Entfernen aller Zeilenumbrüche im Inhalt des Feldes. @@ -2045,7 +2042,6 @@ No\ LaTeX\ files\ containing\ this\ entry\ were\ found.=Es wurden keine LaTeX-Da Selected\ entry\ does\ not\ have\ an\ associated\ citation\ key.=Der ausgewählte Eintrag hat keinen zugehörigen Zitationsschlüssel. Current\ search\ directory\:=Aktuelles Suchverzeichnis\: Set\ LaTeX\ file\ directory=LaTeX-Dateiverzeichnis festlegen -Refresh=Aktualisieren Import\ entries\ from\ LaTeX\ files=Einträge aus LaTeX-Dateien importieren Import\ new\ entries=Neue Einträge importieren Group\ color=Gruppenfarbe @@ -2157,7 +2153,7 @@ Text\ editor=Text Editor Search\ ShortScience=Durchsuche ShortScience Unable\ to\ open\ ShortScience.=Kann keine Verbindung zu ShortScience herstellen. -Shared\ database=Geteilte SQL-Datenbank +Shared\ database=Geteilte Bibliothek (SQL) Lookup=Nachschlagen Access\ date\ of\ the\ address\ specified\ in\ the\ url\ field.=Zugriffsdatum der im URL-Feld angegebenen Adresse. @@ -2401,7 +2397,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=timestamp in Feld 'modi New\ entry\ by\ type=Neuer Eintrag nach Typ File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=Datei '%1' ist ein Duplikat von '%0'. Behalte '%0' -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=Datei '%1' ist ein Duplikat von '%0'. Beide werden aufgrund eines Löschfehlers beibehalten Enable\ field\ formatters=Feldformatierer aktivieren Entry\ Type=Eintragstyp diff --git a/src/main/resources/l10n/JabRef_el.properties b/src/main/resources/l10n/JabRef_el.properties index 344fc48add5..3a142fc59fb 100644 --- a/src/main/resources/l10n/JabRef_el.properties +++ b/src/main/resources/l10n/JabRef_el.properties @@ -261,7 +261,6 @@ Donate\ to\ JabRef=Δωρεά στο JabRef Download\ file=Κατέβασμα αρχείου -Downloaded\ website\ as\ an\ HTML\ file.=Ολοκληρώθηκε η λήψη της ιστοσελίδας υπό τη μορφή HTML αρχείου. duplicate\ removal=διαγραφή διπλότυπων @@ -333,7 +332,6 @@ Field\ names\ are\ not\ allowed\ to\ contain\ white\ spaces\ or\ certain\ charac Field\ to\ group\ by=Ομαδοποίηση με το πεδίο Filter=Φίλτρο -Filter\ groups=Φιλτράρισμα ομάδων Success\!\ Finished\ writing\ metadata.=Επιτυχία\! Η εγγραφή των μεταδεδομένων ολοκληρώθηκε. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Σφάλμα κατά την καταγραφή μεταδεδομένων. Ανατρέξτε στο ιστορικό σφαλμάτων για λεπτομέρειες. diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 21233b43e2f..21c49a26efb 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -276,7 +276,8 @@ Donate\ to\ JabRef=Donate to JabRef Download\ file=Download file -Downloaded\ website\ as\ an\ HTML\ file.=Downloaded website as an HTML file. +Download\ '%0'\ was\ a\ HTML\ file.\ Removed.=Download '%0' was a HTML file. Removed. +Download\ '%0'\ was\ a\ HTML\ file.\ Keeping\ URL.=Download '%0' was a HTML file. Keeping URL. duplicate\ removal=duplicate removal @@ -1479,6 +1480,8 @@ Protect\ terms=Protect terms Add\ enclosing\ braces=Add enclosing braces Add\ braces\ encapsulating\ the\ complete\ field\ content.=Add braces encapsulating the complete field content. Remove\ enclosing\ braces=Remove enclosing braces +Remove\ word\ enclosing\ braces=Remove word enclosing braces +Removes\ braces\ encapsulating\ a\ complete\ word\ and\ the\ complete\ field\ content.=Removes braces encapsulating a complete word and the complete field content. Removes\ braces\ encapsulating\ the\ complete\ field\ content.=Removes braces encapsulating the complete field content. Removes\ all\ balanced\ {}\ braces\ around\ words.=Removes all balanced {} braces around words. Shorten\ DOI=Shorten DOI @@ -1879,7 +1882,6 @@ Could\ not\ copy\ file=Could not copy file Copied\ %0\ files\ of\ %1\ successfully\ to\ %2=Copied %0 files of %1 successfully to %2 Rename\ failed=Rename failed JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef cannot access the file because it is being used by another process. -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=Show console output (only when the launcher is used) Remove\ line\ breaks=Remove line breaks Removes\ all\ line\ breaks\ in\ the\ field\ content.=Removes all line breaks in the field content. @@ -2043,10 +2045,10 @@ LaTeX\ Citations=LaTeX Citations Search\ citations\ for\ this\ entry\ in\ LaTeX\ files=Search citations for this entry in LaTeX files No\ citations\ found=No citations found No\ LaTeX\ files\ containing\ this\ entry\ were\ found.=No LaTeX files containing this entry were found. +Current\ search\ directory\ does\ not\ exist\:\ %0= Current search directory does not exist: %0 Selected\ entry\ does\ not\ have\ an\ associated\ citation\ key.=Selected entry does not have an associated citation key. Current\ search\ directory\:=Current search directory: Set\ LaTeX\ file\ directory=Set LaTeX file directory -Refresh=Refresh Import\ entries\ from\ LaTeX\ files=Import entries from LaTeX files Import\ new\ entries=Import new entries Group\ color=Group color @@ -2402,7 +2404,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=Convert timestamp field New\ entry\ by\ type=New entry by type File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=File '%1' is a duplicate of '%0'. Keeping '%0' -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=File '%1' is a duplicate of '%0'. Keeping both due to deletion error Enable\ field\ formatters=Enable field formatters Entry\ Type=Entry Type diff --git a/src/main/resources/l10n/JabRef_es.properties b/src/main/resources/l10n/JabRef_es.properties index e6caaddff68..1b7b209e39b 100644 --- a/src/main/resources/l10n/JabRef_es.properties +++ b/src/main/resources/l10n/JabRef_es.properties @@ -273,7 +273,6 @@ Donate\ to\ JabRef=Donar a JabRef Download\ file=Descargar archivo -Downloaded\ website\ as\ an\ HTML\ file.=Se descargó el sitio web como un archivo HTML. duplicate\ removal=eliminación de duplicados @@ -361,7 +360,6 @@ Manage\ field\ names\ &\ content=Gestionar nombres y contenido de los campos Field\ to\ group\ by=Agrupar a partir de campo Filter=Filtro -Filter\ groups=Filtros Success\!\ Finished\ writing\ metadata.=¡Éxito\! Se ha terminado de escribir metadatos. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Error al escribir los metadatos. Vea el registro de errores para más detalles. @@ -1832,7 +1830,6 @@ Finished\ copying=Copia finalizada Could\ not\ copy\ file=No se puede copiar el archivo Rename\ failed=Error al renombrar JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef no puede acceder al archivo porque está siendo utilizado por otro proceso. -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=Mostrar la salida de la consola (sólo cuando se utiliza el lanzador) Remove\ line\ breaks=Eliminar saltos de línea Removes\ all\ line\ breaks\ in\ the\ field\ content.=Eliminar todos los saltos de línea en el contenido del campo. @@ -1996,7 +1993,6 @@ No\ LaTeX\ files\ containing\ this\ entry\ were\ found.=No se han encontrado arc Selected\ entry\ does\ not\ have\ an\ associated\ citation\ key.=La entrada seleccionada no tiene asociada una clave de cita. Current\ search\ directory\:=Directorio de búsqueda actual\: Set\ LaTeX\ file\ directory=Establecer directorio de archivos LaTeX -Refresh=Actualizar Import\ entries\ from\ LaTeX\ files=Importar entradas desde archivos LaTeX Import\ new\ entries=Importar nuevas entradas Group\ color=Color del grupo @@ -2333,7 +2329,6 @@ Download\ operation\ canceled.=Operación de descarga cancelada. New\ entry\ by\ type=Entrada nueva por tipo File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=El archivo «%1» es un duplicado de «%0». Se conserva «%0» -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=El archivo «%1» es un duplicado de «%0». Se conservan ambos a causa de un error de eliminación Enable\ field\ formatters=Habilitar formateadores de campos Entry\ Type=Tipo de entrada diff --git a/src/main/resources/l10n/JabRef_fr.properties b/src/main/resources/l10n/JabRef_fr.properties index 6ad66b016ff..f08192f2c43 100644 --- a/src/main/resources/l10n/JabRef_fr.properties +++ b/src/main/resources/l10n/JabRef_fr.properties @@ -219,6 +219,7 @@ Custom\ entry\ types\ found\ in\ file=Types d'entrées personnalisées trouvées Customize\ entry\ types=Personnaliser les types d'entrées +Customize\ keyboard\ shortcuts=Personnaliser les raccourcis clavier Cut=Couper @@ -275,7 +276,6 @@ Donate\ to\ JabRef=Faire un don à JabRef Download\ file=Télécharger le fichier -Downloaded\ website\ as\ an\ HTML\ file.=Site web téléchargé en tant que fichier HTML. duplicate\ removal=Suppression des doublons @@ -328,6 +328,10 @@ Export\ preferences\ to\ file=Exporter les préférences vers un fichier Export\ to\ clipboard=Exporter vers le presse-papiers Export\ to\ text\ file.=Exporter vers un fichier texte. +Extract\ references\ from\ file\ (online)=Extraire les références du fichier (en ligne) +Extract\ references\ from\ file\ (offline)=Extraire les références du fichier (hors ligne) +Extract\ References\ (online)=Extraire les références (en ligne) +Extract\ References\ (offline)=Extraire les références (hors ligne) Processing\ PDF(s)=Traitement des PDF(s) en cours Processing\ a\ large\ number\ of\ files=Traitement d'un grand nombre de fichiers You\ are\ about\ to\ process\ %0\ files.\ Continue?=Vous êtes sur le point de traiter %0 fichiers. Continuer ? @@ -365,7 +369,6 @@ Manage\ field\ names\ &\ content=Gérer les noms de champs et leur contenu Field\ to\ group\ by=Champ à grouper par Filter=Choix des filtres -Filter\ groups=Filtrer les groupes Success\!\ Finished\ writing\ metadata.=Succès \! Écriture des métadonnées terminée. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Erreur lors de l'écriture des métadonnées. Consultez le journal des erreurs pour plus de détails. @@ -506,7 +509,9 @@ Keep\ subgroups=Conserver les sous-groupes Key\ pattern=Paramétrage des clefs +Keyboard\ shortcuts=Raccourcis clavier +Keyboard\ shortcuts\ changed=Raccourcis clavier modifiés keys\ in\ library=clefs dans le fichier @@ -1240,11 +1245,13 @@ You\ have\ to\ choose\ exactly\ two\ entries\ to\ merge.=Vous devez choisir exac Add\ timestamp\ to\ modified\ entries\ (field\ "modificationdate")=Ajouter un horodatage aux entrées modifiées (champ "modificationdate") Add\ timestamp\ to\ new\ entries\ (field\ "creationdate")=Ajouter un horodatage aux nouvelles entrées (champ "creationdate") +All\ keyboard\ shortcuts\ will\ be\ reset\ to\ their\ defaults.=Tous les raccourcis clavier seront réinitialisés à leurs valeurs par défaut. Automatically\ set\ file\ links=Configurer automatiquement les liens de fichier Finished\ automatically\ setting\ external\ links.=La définition automatique des liens externes est terminée. Changed\ %0\ entries.=%0 entrées modifiées. +Resetting\ all\ keyboard\ shortcuts=Réinitialisation de tous les raccourcis clavier Open\ folder=Ouvrir le répertoire Export\ sort\ order=Exporter l'ordre de tri @@ -1869,7 +1876,6 @@ Could\ not\ copy\ file=Le fichier n'a pas pu être copié Copied\ %0\ files\ of\ %1\ successfully\ to\ %2=%0 fichiers sur %1 ont été copiés avec succès vers %2 Rename\ failed=Échec du renommage JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef ne peut pas accéder au fichier parce qu'il est utilisé par un autre processus. -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=Afficher la sortie de la console (uniquement nécessaire quand l'application est utilisée) Remove\ line\ breaks=Supprimer les sauts de ligne Removes\ all\ line\ breaks\ in\ the\ field\ content.=Supprime tous les sauts de ligne du contenu d'un champ. @@ -2036,7 +2042,6 @@ No\ LaTeX\ files\ containing\ this\ entry\ were\ found.=Aucun fichier LaTeX cont Selected\ entry\ does\ not\ have\ an\ associated\ citation\ key.=L'entrée sélectionnée n'a pas de clef de citation associée. Current\ search\ directory\:=Répertoire de recherche actuel \: Set\ LaTeX\ file\ directory=Configurer le répertoire des fichiers LaTeX -Refresh=Rafraîchir Import\ entries\ from\ LaTeX\ files=Importer des entrées à partir des fichiers LaTeX Import\ new\ entries=Importer de nouvelles entrées Group\ color=Couleur du groupe @@ -2392,7 +2397,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=Convertir le champ 'tim New\ entry\ by\ type=Nouvelle entrée par type File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=Le fichier '%1' est un doublon de '%0'. Conservation de '%0' -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=Le fichier '%1' est un doublon de '%0'. Conservation des deux en raison d'une erreur de suppression Enable\ field\ formatters=Activer les formateurs de champs Entry\ Type=Type d’entrée @@ -2585,6 +2589,8 @@ Writing\ metadata\ to\ %0=Écriture des métadonnées sur %0 Get\ more\ themes...=Obtenir plus de thèmes... +Miscellaneous=Divers +File-related=Lié au fichier Add\ selected\ entries\ to\ database=Ajouter les entrées sélectionnées au fichier The\ selected\ entry\ doesn't\ have\ a\ DOI\ linked\ to\ it.\ Lookup\ a\ DOI\ and\ try\ again.=L'entrée sélectionnée ne contient pas de DOI. Recherchez un DOI et réessayez. diff --git a/src/main/resources/l10n/JabRef_it.properties b/src/main/resources/l10n/JabRef_it.properties index 3a01e3b6340..5ad4fb78896 100644 --- a/src/main/resources/l10n/JabRef_it.properties +++ b/src/main/resources/l10n/JabRef_it.properties @@ -219,6 +219,7 @@ Custom\ entry\ types\ found\ in\ file=Tipi di voce personalizzati trovati nel fi Customize\ entry\ types=Personalizza tipi di voce +Customize\ keyboard\ shortcuts=Personalizza le scorciatoie da tastiera Cut=Taglia @@ -275,7 +276,6 @@ Donate\ to\ JabRef=Fai una donazione a JabRef Download\ file=Scarica il file -Downloaded\ website\ as\ an\ HTML\ file.=Sito web scaricato come file HTML. duplicate\ removal=rimozione di doppioni @@ -328,6 +328,10 @@ Export\ preferences\ to\ file=Esporta preferenze in un file Export\ to\ clipboard=Esporta negli appunti Export\ to\ text\ file.=Esporta su file di testo. +Extract\ references\ from\ file\ (online)=Estrai i riferimenti dal file (online) +Extract\ references\ from\ file\ (offline)=Estrai i riferimenti dal file (offline) +Extract\ References\ (online)=Estrai Riferimenti (online) +Extract\ References\ (offline)=Estrai i riferimenti dal file (offline) Processing\ PDF(s)=Elaborazione PDF Processing\ a\ large\ number\ of\ files=Elaborazione di un gran numero di file You\ are\ about\ to\ process\ %0\ files.\ Continue?=Stai per elaborare %0 file. Continuare? @@ -365,7 +369,6 @@ Manage\ field\ names\ &\ content=Gestione nomi di campi e contenuto Field\ to\ group\ by=Campo di raggruppamento Filter=Filtro -Filter\ groups=Filtra gruppi Success\!\ Finished\ writing\ metadata.=Completato con successo\! Scrivere i metadati. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Errore durante la scrittura dei metadati. Vedere il registro degli errori per i dettagli. @@ -506,7 +509,9 @@ Keep\ subgroups=Mantieni sottogruppi Key\ pattern=Modello delle chiavi +Keyboard\ shortcuts=Scorciatoie da tastiera +Keyboard\ shortcuts\ changed=Scorciatoie da tastiera cambiate keys\ in\ library=Chiavi nella libreria @@ -1240,11 +1245,13 @@ You\ have\ to\ choose\ exactly\ two\ entries\ to\ merge.=È necessario seleziona Add\ timestamp\ to\ modified\ entries\ (field\ "modificationdate")=Aggiungi timestamp alle voci modificate (campo "modificationdate") Add\ timestamp\ to\ new\ entries\ (field\ "creationdate")=Aggiungi timestamp alle nuove voci (campo "creationdate") +All\ keyboard\ shortcuts\ will\ be\ reset\ to\ their\ defaults.=Tutte le scorciatoie da tastiera verranno reimpostate ai valori predefiniti. Automatically\ set\ file\ links=Impostazione automatica dei collegamenti ai file Finished\ automatically\ setting\ external\ links.=Impostazione automatica dei collegamenti esterni terminata. Changed\ %0\ entries.=Modificate %0 voci. +Resetting\ all\ keyboard\ shortcuts=Ripristino di tutte le scorciatoie da tastiera Open\ folder=Apri cartella Export\ sort\ order=Esporta il modo di ordinamento @@ -1869,7 +1876,6 @@ Could\ not\ copy\ file=Impossibile copiare il file Copied\ %0\ files\ of\ %1\ successfully\ to\ %2=%0 file su %1 sono stati copiati correttamente in %2 Rename\ failed=Rinomina non riuscita JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef non può accedere al file perché questo è utilizzato da un altro processo. -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=Mostra l'output della console (solo quando viene utilizzato il launcher) Remove\ line\ breaks=Rimuovi interruzioni di riga Removes\ all\ line\ breaks\ in\ the\ field\ content.=Rimuove tutte le interruzioni di riga nel contenuto del campo. @@ -2036,7 +2042,6 @@ No\ LaTeX\ files\ containing\ this\ entry\ were\ found.=Nessun file LaTeX conten Selected\ entry\ does\ not\ have\ an\ associated\ citation\ key.=La voce selezionata non ha una chiave BibTeX associata. Current\ search\ directory\:=Cartella di ricerca corrente\: Set\ LaTeX\ file\ directory=Imposta la cartella dei file LaTeX -Refresh=Aggiorna Import\ entries\ from\ LaTeX\ files=Importa le voci da file LaTeX Import\ new\ entries=Importa nuove voci Group\ color=Colore del gruppo @@ -2392,7 +2397,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=Converti il campo times New\ entry\ by\ type=Nuova voce per tipo File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=Il file '%1' è un duplicato di '%0'. Mantenere '%0' -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=Il file '%1' è un duplicato di '%0'. Mantenere entrambi a causa di errore di cancellazione Enable\ field\ formatters=Abilita formatori di campo Entry\ Type=Tipo di voce @@ -2585,6 +2589,8 @@ Writing\ metadata\ to\ %0=Scrittura dei metadati su %0 Get\ more\ themes...=Ottieni altri temi... +Miscellaneous=Varie +File-related=Correlato al file Add\ selected\ entries\ to\ database=Aggiungi le voci selezionate al database The\ selected\ entry\ doesn't\ have\ a\ DOI\ linked\ to\ it.\ Lookup\ a\ DOI\ and\ try\ again.=La voce selezionata non ha un DOI collegato ad essa. Cerca un DOI e riprova. diff --git a/src/main/resources/l10n/JabRef_ja.properties b/src/main/resources/l10n/JabRef_ja.properties index 8c590e3d08a..745f29b2877 100644 --- a/src/main/resources/l10n/JabRef_ja.properties +++ b/src/main/resources/l10n/JabRef_ja.properties @@ -263,7 +263,6 @@ Donate\ to\ JabRef=JabRefに寄付 Download\ file=ファイルをダウンロード -Downloaded\ website\ as\ an\ HTML\ file.=ウェブサイトを HTML ファイルとしてダウンロードしました. duplicate\ removal=重複を削除 @@ -342,7 +341,6 @@ Manage\ field\ names\ &\ content=フィールド名・フィールド値の管 Field\ to\ group\ by=グループ化するフィールド Filter=フィルタ -Filter\ groups=グループ絞り込み Success\!\ Finished\ writing\ metadata.=成功しました!メタデータの書き込みを終了しました. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=メタデータ書き込み中にエラー.詳細についてはエラーログを参照してください. @@ -1761,7 +1759,6 @@ Finished\ copying=コピーを終了しました Could\ not\ copy\ file=ファイルをコピーできませんでした Rename\ failed=改名に失敗しました JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=ファイルが他のプロセスによって使用されているため,JabRefがアクセスすることができません. -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=コンソール出力を表示(ロンチャーを使用する場合のみ) Remove\ line\ breaks=改行を取り除く Removes\ all\ line\ breaks\ in\ the\ field\ content.=フィールドの内容から改行を全て取り除きます. @@ -2261,7 +2258,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=timestampフィール New\ entry\ by\ type=型別の新規項目 File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=ファイル「%1」は「%0」と重複しています.「%0」を保持します -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=ファイル「%1」は「%0」と重複しています.削除エラーが起こりましたので両方保持します Enable\ field\ formatters=フィールド整形の定義を有効にする Entry\ Type=項目型 diff --git a/src/main/resources/l10n/JabRef_ko.properties b/src/main/resources/l10n/JabRef_ko.properties index a82bd22c4c7..de01984d61c 100644 --- a/src/main/resources/l10n/JabRef_ko.properties +++ b/src/main/resources/l10n/JabRef_ko.properties @@ -250,7 +250,6 @@ Donate\ to\ JabRef=JabRef에 기부하기 Download\ file=파일 다운로드 -Downloaded\ website\ as\ an\ HTML\ file.=웹사이트를 HTML 파일로 다운로드했습니다. duplicate\ removal=중복 제거 @@ -327,7 +326,6 @@ Manage\ field\ names\ &\ content=필드 이름 및 콘텐츠 관리 Field\ to\ group\ by=필드를 그룹화 하다 Filter=필터 -Filter\ groups=그룹 필터링 Generate=생성 @@ -1670,7 +1668,6 @@ Finished\ copying=복사 완료 Could\ not\ copy\ file=파일을 복사할 수 없습니다 Rename\ failed=이름 변경 실패 JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef는 다른 프로세스에서 사용 중인 파일에 접근할 수 없습니다 -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=콘솔 출력 표시(런처를 사용하는 경우에만) Remove\ line\ breaks=줄바꿈 제거 Removes\ all\ line\ breaks\ in\ the\ field\ content.=필드 내용에서 모든 줄 바꿈을 제거합니다. @@ -2149,7 +2146,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=타임스탬프 필드 New\ entry\ by\ type=유형별 새 항목 File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=%1 파일은 %0과 동일합니다. %0을 유지합니다. -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=%1 파일은 %0과 동일합니다. \n삭제 오류로 인해 두 파일을 유지합니다. Enable\ field\ formatters=필드 포맷터 활성화 Entry\ Type=목록 유형 diff --git a/src/main/resources/l10n/JabRef_nl.properties b/src/main/resources/l10n/JabRef_nl.properties index 5e56cbf18e2..c9c575a3c56 100644 --- a/src/main/resources/l10n/JabRef_nl.properties +++ b/src/main/resources/l10n/JabRef_nl.properties @@ -267,7 +267,6 @@ Donate\ to\ JabRef=Doneer aan JabRef Download\ file=Download bestand -Downloaded\ website\ as\ an\ HTML\ file.=Gedownloade website als een HTML-bestand. duplicate\ removal=duplicaten verwijderen @@ -348,7 +347,6 @@ Manage\ field\ names\ &\ content=Beheer veldnamen & inhoud Field\ to\ group\ by=Veld te groeperen op Filter=Filteren -Filter\ groups=Filter groepen Success\!\ Finished\ writing\ metadata.=Gelukt\! Metagegevens schrijven voltooid. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Fout tijdens het schrijven van metadata. Bekijk het foutenlogboek voor meer informatie. @@ -1824,7 +1822,6 @@ Could\ not\ copy\ file=Kan het bestand niet kopiëren Copied\ %0\ files\ of\ %1\ successfully\ to\ %2=%0 bestanden van %1 succesvol gekopieerd naar %2 Rename\ failed=Hernoemen mislukt JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef kan geen toegang krijgen tot dit bestand omdat het door een ander proces wordt gebruikt. -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=Toon console-uitvoer (alleen wanneer de launcher wordt gebruikt) Remove\ line\ breaks=Verwijder regeleinden Removes\ all\ line\ breaks\ in\ the\ field\ content.=Verwijder alle regeleinden in de veldinhoud. @@ -2332,7 +2329,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=Converteer tijdsstempel New\ entry\ by\ type=Nieuwe invoer per type File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=Bestand '%1' is een duplicaat van '%0'. Behoud '%0' -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=Bestand '%1' is een duplicaat van '%0'. Beide behouden omdat de fout bij het verwijderen is Enable\ field\ formatters=Veld formatteerder inschakelen Entry\ Type=Invoer Type diff --git a/src/main/resources/l10n/JabRef_pl.properties b/src/main/resources/l10n/JabRef_pl.properties index a83d5f1fcc3..90ccfa12663 100644 --- a/src/main/resources/l10n/JabRef_pl.properties +++ b/src/main/resources/l10n/JabRef_pl.properties @@ -69,6 +69,7 @@ Resolve\ BibTeX\ strings=Rozwiąż ciągi znaków BibTeX The\ label\ of\ the\ string\ cannot\ be\ a\ number.=Etykieta ciągu znaków nie może być liczbą. The\ label\ of\ the\ string\ cannot\ contain\ spaces.=Etykieta ciągu znaków nie może zawierać spacji. The\ label\ of\ the\ string\ cannot\ contain\ the\ '\#'\ character.=Etykieta ciągu znaków nie może zawierać znaku '\#'. +A\ string\ with\ the\ label\ '%0'\ already\ exists.=Ciąg znaków z etykietą '%0' już istnieje. All\ entries=Wszystkie wpisy @@ -202,6 +203,7 @@ Custom\ entry\ types\ found\ in\ file=Znaleziono niestandardowe typy wpisów w p Customize\ entry\ types=Dostosuj typy wpisów +Customize\ keyboard\ shortcuts=Dostosuj skróty klawiaturowe Cut=Wytnij @@ -258,7 +260,6 @@ Donate\ to\ JabRef=Wspomóż JabRef Download\ file=Pobierz plik -Downloaded\ website\ as\ an\ HTML\ file.=Pobrano stronę internetową jako plik HTML. duplicate\ removal=usuwanie duplikatów @@ -311,6 +312,10 @@ Export\ preferences\ to\ file=Eksportuj ustawienia do pliku Export\ to\ clipboard=Eksportuj do schowka Export\ to\ text\ file.=Eksportuj do pliku tekstowego. +Extract\ references\ from\ file\ (online)=Wyodrębnij referencje z pliku (online) +Extract\ references\ from\ file\ (offline)=Wyodrębnij referencje z pliku (offline) +Extract\ References\ (online)=Wyodrębnij referencje (online) +Extract\ References\ (offline)=Wyodrębnij referencje (offline) Processing\ PDF(s)=Przetwarzanie PDF(ów) Processing\ a\ large\ number\ of\ files=Przetwarzanie dużej liczby plików You\ are\ about\ to\ process\ %0\ files.\ Continue?=Zamierzasz przetworzyć %0 plików. Czy chcesz kontynuować? @@ -348,7 +353,6 @@ Manage\ field\ names\ &\ content=Zarządzaj nazwami pól i ich zawartością Field\ to\ group\ by=Pole do grupowania według Filter=Filtruj -Filter\ groups=Filtruj grupy Success\!\ Finished\ writing\ metadata.=Sukces\! Zakończono zapisywanie metadanych. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Błąd podczas zapisywania metadanych. Aby uzyskać szczegółowe informacje, zobacz dziennik błędów. @@ -366,6 +370,7 @@ Generated\ citation\ key\ for=Wygenerowano klucz cytowania dla Generating\ citation\ key\ for=Generowanie klucza cytowania dla Invalid\ citation\ key=Nieprawidłowy klucz cytowania +Jump\ to\ entry\ in\ library=Przejdź do wpisu w bibliotece Autolink\ files\ with\ names\ starting\ with\ the\ citation\ key=Automatycznie podpinaj pliki z nazwami zaczynającymi się od klucza cytowania Autolink\ only\ files\ that\ match\ the\ citation\ key=Automatycznie podpinaj tylko pliki, które pasują do klucza cytowania @@ -435,6 +440,7 @@ Online\ help=Pomoc online JabRef\ Language\ (Provides\ for\ better\ recommendations\ by\ giving\ an\ indication\ of\ user's\ preferred\ language.)=Język JabRef (zapewnia lepsze rekomendacje poprzez wskazanie preferowanego języka użytkownika). JabRef\ preferences=Preferencje JabRef +JabRef\ Version\ (Required\ to\ ensure\ backwards\ compatibility\ with\ Mr.\ DLib's\ Web\ Service)=Wersja JabRef (wymagana do zapewnienia kompatybilności wstecznej z Mr. DLib's Web Service) Journal\ abbreviations=Skróty czasopism Journal\ lists\:=Listy czasopism\: @@ -468,7 +474,9 @@ Keep\ subgroups=Zachowaj podgrupy Key\ pattern=Układ klawiszy +Keyboard\ shortcuts=Skróty klawiaturowe +Keyboard\ shortcuts\ changed=Zmieniono skróty klawiaturowe keys\ in\ library=klucze w bibliotece @@ -768,7 +776,10 @@ Empty\ search\ ID=Pusty identyfikator wyszukiwania The\ given\ search\ ID\ was\ empty.=Podany identyfikator wyszukiwania był pusty. Clear\ search=Wyczyść wyszukiwanie Search\ document\ identifier\ online=Szukaj identyfikatora dokumentu online +Search\ for\ unlinked\ local\ files=Wyszukaj niepowiązane pliki lokalne +Search\ full\ text\ documents\ online=Wyszukaj pełne teksty dokumentów online Search\ term\ is\ empty.=Szukane wyrażenie jest puste. +Invalid\ regular\ expression.=Nieprawidłowe wyrażenie regularne. Searching\ for\ a\ keyword=Wyszukiwanie słowa kluczowego Search\ across\ libraries\ in\ a\ new\ window=Szukaj w bibliotekach, wynik pokaż w nowym oknie @@ -797,6 +808,7 @@ Show\ BibTeX\ source\ by\ default=Domyślnie pokaż źródło BibTeX Attached\ files=Załączone pliki Show\ confirmation\ dialog\ when\ deleting\ entries=Pokaż okno dialogowe potwierdzenia podczas usuwania wpisów +Show\ confirmation\ dialog\ when\ deleting\ attached\ files=Pokaż okno dialogowe potwierdzenia podczas usuwania plików Persist\ password\ between\ sessions=Zachowaj hasło między sesjami @@ -1130,9 +1142,11 @@ Parse=Parsuj Result=Rezultat You\ have\ to\ choose\ exactly\ two\ entries\ to\ merge.=Musisz wybrać dokładnie dwa wpisy do scalenia. +All\ keyboard\ shortcuts\ will\ be\ reset\ to\ their\ defaults.=Wszystkie skróty klawiaturowe zostaną zresetowane do wartości domyślnych. Changed\ %0\ entries.=Zmieniono %0 wpisów. +Resetting\ all\ keyboard\ shortcuts=Resetowanie wszystkich skrótów klawiaturowych Open\ folder=Otwórz folder Export\ sort\ order=Eksportuj kolejność sortowania @@ -1578,7 +1592,6 @@ files=pliki No\ citations\ found=Nie znaleziono cytatów Current\ search\ directory\:=Aktualny katalog wyszukiwania\: -Refresh=Odśwież Import\ entries\ from\ LaTeX\ files=Importuj wpisy z plików LaTeX Import\ new\ entries=Importuj nowe wpisy @@ -1744,6 +1757,8 @@ Finished\ writing\ metadata\ for\ library\ %0\ (%1\ succeeded,\ %2\ skipped,\ %3 Processing...=Przetwarzanie... +Miscellaneous=Pozostałe +File-related=Związane z plikami Cancel\ search=Anuluj wyszukiwanie Select\ entry=Wybierz wpis diff --git a/src/main/resources/l10n/JabRef_pt.properties b/src/main/resources/l10n/JabRef_pt.properties index 03189da0ac7..6a496f87516 100644 --- a/src/main/resources/l10n/JabRef_pt.properties +++ b/src/main/resources/l10n/JabRef_pt.properties @@ -261,7 +261,6 @@ Donate\ to\ JabRef=Doar para JabRef Download\ file=Download do arquivo -Downloaded\ website\ as\ an\ HTML\ file.=Site baixado como arquivo HTML. duplicate\ removal=remoção de duplicatas @@ -338,7 +337,6 @@ Manage\ field\ names\ &\ content=Gerenciar nomes e conteúdo dos campos Field\ to\ group\ by=Campos como critério de agrupamento Filter=Filtro -Filter\ groups=Filtrar grupos Success\!\ Finished\ writing\ metadata.=Sucesso\! Escrita de metadados finalizada. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Erro ao escrever metadados. Consulte o log de erros para obter detalhes. diff --git a/src/main/resources/l10n/JabRef_pt_BR.properties b/src/main/resources/l10n/JabRef_pt_BR.properties index 94fed9e733e..7f7fdc2cfaf 100644 --- a/src/main/resources/l10n/JabRef_pt_BR.properties +++ b/src/main/resources/l10n/JabRef_pt_BR.properties @@ -271,7 +271,6 @@ Donate\ to\ JabRef=Doar para JabRef Download\ file=Download do arquivo -Downloaded\ website\ as\ an\ HTML\ file.=Site baixado como arquivo HTML. duplicate\ removal=remoção de duplicatas @@ -361,7 +360,6 @@ Manage\ field\ names\ &\ content=Gerenciar nomes e conteúdo dos campos Field\ to\ group\ by=Campos como critério de agrupamento Filter=Filtro -Filter\ groups=Filtrar grupos Success\!\ Finished\ writing\ metadata.=Sucesso\! Terminou a escrita de metadados. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Erro ao escrever metadados. Consulte o log de erros para obter detalhes. @@ -1864,7 +1862,6 @@ Could\ not\ copy\ file=Não foi possível copiar o arquivo Copied\ %0\ files\ of\ %1\ successfully\ to\ %2=%0 arquivos de %1 copiados com sucesso para %2 Rename\ failed=Falha ao renomear JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef não pode acessar o arquivo porque ele está sendo usado por outro processo. -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=Mostrar saída do console (somente quando o launcher é usado) Remove\ line\ breaks=Remover quebras de linha Removes\ all\ line\ breaks\ in\ the\ field\ content.=Remove todas as quebras de linha do conteúdo do campo. @@ -2031,7 +2028,6 @@ No\ LaTeX\ files\ containing\ this\ entry\ were\ found.=Nenhum arquivo LaTeX con Selected\ entry\ does\ not\ have\ an\ associated\ citation\ key.=O item selecionado não tem uma chave de citação associada. Current\ search\ directory\:=Diretório de pesquisa atual\: Set\ LaTeX\ file\ directory=Definir diretório de arquivos LaTeX -Refresh=Atualizar Import\ entries\ from\ LaTeX\ files=Importar referências a partir dos arquivos LaTeX Import\ new\ entries=Importar novas referências Group\ color=Cor do grupo @@ -2387,7 +2383,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=Converter campo timesta New\ entry\ by\ type=Nova entrada por tipo File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=Arquivo '%1' é uma duplicata de '%0'. Mantendo '%0' -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=Arquivo '%1' é uma duplicata de '%0'. Mantendo ambos devido a um erro de exclusão Enable\ field\ formatters=Ativar formatadores de campo Entry\ Type=Tipo de referência diff --git a/src/main/resources/l10n/JabRef_ru.properties b/src/main/resources/l10n/JabRef_ru.properties index 1b92c78994a..bfbf49c6868 100644 --- a/src/main/resources/l10n/JabRef_ru.properties +++ b/src/main/resources/l10n/JabRef_ru.properties @@ -255,7 +255,6 @@ Donate\ to\ JabRef=Поддержать JabRef Download\ file=Загрузить файл -Downloaded\ website\ as\ an\ HTML\ file.=Загружен сайт в формате HTML. duplicate\ removal=удаление дубликатов @@ -334,7 +333,6 @@ Manage\ field\ names\ &\ content=Управлять названиями пол Field\ to\ group\ by=Поле для группировки Filter=Фильтр -Filter\ groups=Фильтровать группы Success\!\ Finished\ writing\ metadata.=Успех\! Запись метаданных завершена. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Ошибка при записи метаданных. Смотрите журнал ошибок для подробностей. @@ -442,6 +440,10 @@ Journal\ abbreviations=Сокращения для журналов Journal\ lists\:=Список журналов\: Remove\ journal\ '%0'=Удалить журнал '%0' +Year=Год +Categories=Категории +ISSN=ISSN +Publisher=Издатель Keep\ both=Оставить оба @@ -1729,7 +1731,6 @@ Finished\ copying=Копирование завершено Could\ not\ copy\ file=Невозможно скопировать файл Rename\ failed=Не удалось переименовать JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef не может получить доступ к файлу, так как он используется другим процессом. -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=Показывать вывод консоли (только при использовании лаунчера) Remove\ line\ breaks=Удалить разрывы строк Removes\ all\ line\ breaks\ in\ the\ field\ content.=Удаляет все разрывы строк в содержимом поля. @@ -2226,7 +2227,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=Преобразова New\ entry\ by\ type=Новая запись по типу File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=Файл '%1' является дубликатом '%0'. Оставлен '%0' -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=Файл '%1' является дубликатом '%0'. Оставлены оба из-за ошибки удаления Enable\ field\ formatters=Включить форматирование полей Entry\ Type=Тип записи diff --git a/src/main/resources/l10n/JabRef_sv.properties b/src/main/resources/l10n/JabRef_sv.properties index 569743a1f1c..e4ba746185c 100644 --- a/src/main/resources/l10n/JabRef_sv.properties +++ b/src/main/resources/l10n/JabRef_sv.properties @@ -249,7 +249,6 @@ Donate\ to\ JabRef=Donera till JabRef Download\ file=Ladda ned fil -Downloaded\ website\ as\ an\ HTML\ file.=Webbsida nedladdad som html-fil. duplicate\ removal=ta bort dubbletter @@ -328,7 +327,6 @@ Manage\ field\ names\ &\ content=Hantera fältnamn & innehåll Field\ to\ group\ by=Fält att använda för gruppering Filter=Filtrera -Filter\ groups=Filtrera grupper Generate=Generera diff --git a/src/main/resources/l10n/JabRef_tr.properties b/src/main/resources/l10n/JabRef_tr.properties index abb65c2f6ae..bfac7f74ea2 100644 --- a/src/main/resources/l10n/JabRef_tr.properties +++ b/src/main/resources/l10n/JabRef_tr.properties @@ -269,7 +269,6 @@ Donate\ to\ JabRef=JabRef'e bağış yap Download\ file=Dosya indir -Downloaded\ website\ as\ an\ HTML\ file.=Web sitesi bir HTML dosyası olarak indirildi. duplicate\ removal=çift nüsha silme @@ -356,7 +355,6 @@ Manage\ field\ names\ &\ content=Alan ad ve içeriklerini yönet Field\ to\ group\ by=Gruplanacak alan Filter=Süzgeç -Filter\ groups=Grupları filtrele Success\!\ Finished\ writing\ metadata.=Başarılı\! Metaverisi yazma bitirildi. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=Metaverisi yazılırken hata. Ayrıntılar için hata kayıtlarına bakın. @@ -1843,7 +1841,6 @@ Could\ not\ copy\ file=Dosya kopyalanamadı Copied\ %0\ files\ of\ %1\ successfully\ to\ %2=%1 dosyanın %0'i başarıyla %2'e kopyalandı Rename\ failed=Yeniden adlandırma başarısız oldu JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef dosyaya erişemiyor çünkü dosya başka bir süreç tarafından kullanılıyor. -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=Konsol çıktısını göster (yalnızca başlatıcı kullanıldığında) Remove\ line\ breaks=Satır sonlarını kaldır Removes\ all\ line\ breaks\ in\ the\ field\ content.=Alan içeriğindeki tüm satır sonlarını kaldırır. @@ -2363,7 +2360,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=Tarih bilgisi alanını New\ entry\ by\ type=Türle yeni girdi File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'='%1' dosyası '%0'ın tıpkısı. '%0' tutuluyor -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error='%1' dosyası '%0'ın tıpkısı. Silme hatası nedeniyle ikisi de tutuluyor Enable\ field\ formatters=Alan biçimlendiricilerini etkinleştir Entry\ Type=Girdi Türü diff --git a/src/main/resources/l10n/JabRef_uk.properties b/src/main/resources/l10n/JabRef_uk.properties index 5ccd342ef5d..b39e7976f6b 100644 --- a/src/main/resources/l10n/JabRef_uk.properties +++ b/src/main/resources/l10n/JabRef_uk.properties @@ -60,6 +60,7 @@ Added\ group\ "%0".=Додана група Added\ string\:\ '%0'=Додано рядок Added\ string=Додано рядок +Must\ not\ be\ empty\!=Не може бути порожнім\! All\ entries=Всі записи @@ -90,12 +91,20 @@ Application\ to\ push\ entries\ to=Заявка на push матеріалів +Cancel=Скасувати +Cannot\ create\ group=Не вдається створити групу +Cannot\ create\ group.\ Please\ create\ a\ library\ first.=Не вдається створити групу. Будь ласка, спочатку створіть бібліотеку. +Cannot\ open\ folder\ as\ the\ file\ is\ an\ online\ link.=Не вдається відкрити папку, оскільки файл є посиланням. +case\ insensitive=без урахування регістру +case\ sensitive=з урахуванням регістру +Case\ sensitive=З урахуванням регістру +change\ assignment\ of\ entries=змінити призначення записів @@ -107,24 +116,51 @@ Application\ to\ push\ entries\ to=Заявка на push матеріалів +Open\ /\ close\ entry\ editor=Відкрити / закрити редактор записів +Close\ dialog=Закрити діалог +Close\ the\ current\ library=Закрити поточну бібліотеку +Close\ window=Закрити вікно +Comments=Коментарі +Contained\ in=Міститься в +Content=Зміст +Copy=Копіювати +Copy\ title=Копіювати заголовок +Copy\ citation\ (html)=Копіювати цитування (html) +Copy\ citation\ (text)=Копіювати цитування (текст) +Copy\ citation\ key=Копіювати ключ цитування +Copy\ citation\ key\ and\ link=Копіювати ключ цитування та посилання +Copy\ citation\ key\ and\ title=Копіювати ключ цитування та заголовок +Copy\ citation\ key\ with\ configured\ cite\ command=Копіювати ключ цитування з налаштованою командою цитування +Copy\ to\ clipboard=Копіювати в буфер обміну +Could\ not\ call\ executable=Не вдалося викликати виконавчий файл +Could\ not\ export\ preferences=Не вдалося експортувати налаштування +Could\ not\ find\ a\ suitable\ import\ format.=Не вдалося знайти відповідний формат імпорту. +Could\ not\ import\ preferences=Не вдалося імпортувати налаштування +Could\ not\ instantiate\ %0=Не вдалося створити %0 +Could\ not\ instantiate\ %0\ %1=Не вдалося створити %0 %1 +Could\ not\ instantiate\ %0.\ Have\ you\ chosen\ the\ correct\ package\ path?=Не вдалося створити %0. Ви обрали правильний шлях до пакету? +Could\ not\ print\ preview=Не вдалося надрукувати попередній перегляд +Create\ custom\ fields\ for\ each\ BibTeX\ entry=Створити власні поля для кожного запису BibTeX +Current\ content\:\ %0=Поточний вміст\: %0 +Current\ value\:\ %0=Поточне значення\: %0 diff --git a/src/main/resources/l10n/JabRef_vi.properties b/src/main/resources/l10n/JabRef_vi.properties index 49979058375..7187fdcf00f 100644 --- a/src/main/resources/l10n/JabRef_vi.properties +++ b/src/main/resources/l10n/JabRef_vi.properties @@ -284,7 +284,6 @@ Field\ name=Tên dữ liệu Field\ to\ group\ by=Dữ liệu gộp nhóm theo Filter=Lọc -Filter\ groups=Nhóm bộ lọc Generate=Tạo diff --git a/src/main/resources/l10n/JabRef_zh_CN.properties b/src/main/resources/l10n/JabRef_zh_CN.properties index 6bd3ae6c20f..f2276cf4d21 100644 --- a/src/main/resources/l10n/JabRef_zh_CN.properties +++ b/src/main/resources/l10n/JabRef_zh_CN.properties @@ -267,7 +267,6 @@ Donate\ to\ JabRef=Donate Download\ file=下载文件 -Downloaded\ website\ as\ an\ HTML\ file.=将网站下载为HTML文件。 duplicate\ removal=移除重复 @@ -348,7 +347,6 @@ Manage\ field\ names\ &\ content=管理字段名称和内容 Field\ to\ group\ by=用来分组的字段 Filter=筛选 -Filter\ groups=筛选组 Success\!\ Finished\ writing\ metadata.=Success\! Finished writing metadata. Error\ while\ writing\ metadata.\ See\ the\ error\ log\ for\ details.=写入元数据时出错。详细信息请参阅错误日志。 @@ -1812,7 +1810,6 @@ Could\ not\ copy\ file=无法复制文件 Copied\ %0\ files\ of\ %1\ successfully\ to\ %2=Copied %0 files of %1 successfully to %2 Rename\ failed=重命名失败 JabRef\ cannot\ access\ the\ file\ because\ it\ is\ being\ used\ by\ another\ process.=JabRef 无法访问该文件, 因为另一个进程正在使用它。 -Show\ console\ output\ (only\ when\ the\ launcher\ is\ used)=Show console output (only when the launcher is used) Remove\ line\ breaks=移除换行符 Removes\ all\ line\ breaks\ in\ the\ field\ content.=移除字段内容中的所有换行符。 @@ -2320,7 +2317,6 @@ Convert\ timestamp\ field\ to\ field\ 'modificationdate'=将 timestamp 字段转 New\ entry\ by\ type=按类型创建新条目 File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ '%0'=文件%1是%0的重复。保留%0 -File\ '%1'\ is\ a\ duplicate\ of\ '%0'.\ Keeping\ both\ due\ to\ deletion\ error=文件%1是%0的重复。由于删除错误同时保留两者。 Enable\ field\ formatters=启用字段格式化器 Entry\ Type=条目类型 diff --git a/src/main/resources/l10n/JabRef_zh_TW.properties b/src/main/resources/l10n/JabRef_zh_TW.properties index e75a7a1d852..050baf57ba6 100644 --- a/src/main/resources/l10n/JabRef_zh_TW.properties +++ b/src/main/resources/l10n/JabRef_zh_TW.properties @@ -280,7 +280,6 @@ Field\ names\ are\ not\ allowed\ to\ contain\ white\ spaces\ or\ certain\ charac Filter=篩選 -Filter\ groups=篩選群組 Generate=生成 diff --git a/src/test/java/org/jabref/cli/JabRefCLITest.java b/src/test/java/org/jabref/cli/JabRefCLITest.java index dfcd8478cd7..8fcbbd01013 100644 --- a/src/test/java/org/jabref/cli/JabRefCLITest.java +++ b/src/test/java/org/jabref/cli/JabRefCLITest.java @@ -133,19 +133,6 @@ void successfulParsingOfBibtexImportLong() throws Exception { assertEquals(bibtex, cli.getBibtexImport()); } - @Test - void wrapStringList() { - List given = List.of("html", "simplehtml", "docbook5", "docbook4", "din1505", "bibordf", "tablerefs", "listrefs", - "tablerefsabsbib", "harvard", "iso690rtf", "iso690txt", "endnote", "oocsv", "ris", "misq", "yaml", "bibtexml", "oocalc", "ods", - "MSBib", "mods", "xmp", "pdf", "bib"); - String expected = """ - Available export formats: html, simplehtml, docbook5, docbook4, din1505, bibordf, tablerefs, - listrefs, tablerefsabsbib, harvard, iso690rtf, iso690txt, endnote, oocsv, ris, misq, yaml, bibtexml, - oocalc, ods, MSBib, mods, xmp, pdf, bib"""; - - assertEquals(expected, "Available export formats: " + JabRefCLI.wrapStringList(given, 26)); - } - @Test void alignStringTable() { List> given = List.of( diff --git a/src/test/java/org/jabref/gui/fieldeditors/LinkedFileViewModelTest.java b/src/test/java/org/jabref/gui/fieldeditors/LinkedFileViewModelTest.java index 78b765ec3f6..18efb37fd94 100644 --- a/src/test/java/org/jabref/gui/fieldeditors/LinkedFileViewModelTest.java +++ b/src/test/java/org/jabref/gui/fieldeditors/LinkedFileViewModelTest.java @@ -29,7 +29,6 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.preferences.FilePreferences; import org.jabref.preferences.PreferencesService; -import org.jabref.testutils.category.FetcherTest; import org.jabref.testutils.category.GUITest; import org.junit.jupiter.api.AfterEach; @@ -37,6 +36,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; import org.testfx.framework.junit5.ApplicationExtension; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -181,8 +183,12 @@ void deleteWhenDialogCancelledReturnsFalseAndDoesNotRemoveFile() { assertTrue(Files.exists(tempFile)); } - @Test - void downloadHtmlFileCausesWarningDisplay() throws MalformedURLException { + @ParameterizedTest + @CsvSource({ + "true, Download 'https://www.google.com/' was a HTML file. Keeping URL.", + "false, Download 'https://www.google.com/' was a HTML file. Removed." + }) + void downloadHtmlFileCausesWarningDisplay(Boolean keepHtmlLink, String warningText) throws MalformedURLException { when(filePreferences.shouldStoreFilesRelativeToBibFile()).thenReturn(true); when(filePreferences.getFileNamePattern()).thenReturn("[citationkey]"); when(filePreferences.getFileDirectoryPattern()).thenReturn("[entrytype]"); @@ -194,39 +200,9 @@ void downloadHtmlFileCausesWarningDisplay() throws MalformedURLException { LinkedFileViewModel viewModel = new LinkedFileViewModel(linkedFile, entry, databaseContext, new CurrentThreadTaskExecutor(), dialogService, preferences); - viewModel.download(); + viewModel.download(keepHtmlLink); - verify(dialogService, atLeastOnce()).notify("Downloaded website as an HTML file."); - } - - @FetcherTest - @Test - void downloadHtmlWhenLinkedFilePointsToHtml() throws MalformedURLException { - // use google as test url, wiley is protected by CloudFlare - String url = "https://google.com"; - String fileType = StandardExternalFileType.URL.getName(); - linkedFile = new LinkedFile(new URL(url), fileType); - - when(filePreferences.shouldStoreFilesRelativeToBibFile()).thenReturn(true); - when(filePreferences.getFileNamePattern()).thenReturn("[citationkey]"); - when(filePreferences.getFileDirectoryPattern()).thenReturn("[entrytype]"); - - databaseContext.setDatabasePath(tempFile); - - LinkedFileViewModel viewModel = new LinkedFileViewModel(linkedFile, entry, databaseContext, new CurrentThreadTaskExecutor(), dialogService, preferences); - - viewModel.download(); - - List linkedFiles = entry.getFiles(); - - for (LinkedFile file: linkedFiles) { - if ("Misc/asdf.html".equalsIgnoreCase(file.getLink())) { - assertEquals("URL", file.getFileType()); - return; - } - } - // If the file was not found among the linked files to the entry - fail(); + verify(dialogService, atLeastOnce()).notify(warningText); } @Test @@ -290,9 +266,11 @@ void mimeTypeStringWithParameterIsReturnedAsWithoutParameter() { assertEquals("URL", actual); } - @Test - @FetcherTest - void downloadPdfFileWhenLinkedFilePointsToPdfUrl() throws MalformedURLException { + // We cannot use "@FetcherTest" annotation, because a @FetcherTest does not fire up a GUI environment (which is needed for this test) + // @FetcherTest + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void downloadPdfFileWhenLinkedFilePointsToPdfUrl(boolean keepHtml) throws MalformedURLException { linkedFile = new LinkedFile(new URL("http://arxiv.org/pdf/1207.0408v1"), "pdf"); // Needed Mockito stubbing methods to run test when(filePreferences.shouldStoreFilesRelativeToBibFile()).thenReturn(true); @@ -302,7 +280,9 @@ void downloadPdfFileWhenLinkedFilePointsToPdfUrl() throws MalformedURLException databaseContext.setDatabasePath(tempFile); LinkedFileViewModel viewModel = new LinkedFileViewModel(linkedFile, entry, databaseContext, new CurrentThreadTaskExecutor(), dialogService, preferences); - viewModel.download(); + + // TODO: Rewrite using WireMock + viewModel.download(keepHtml); // Loop through downloaded files to check for filetype='pdf' List linkedFiles = entry.getFiles(); diff --git a/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java b/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java index 5f7f2369134..bdb27ee2fc1 100644 --- a/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java +++ b/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java @@ -61,7 +61,7 @@ void rootGroupIsAllEntriesByDefault() { @Test void rootGroupIsSelectedByDefault() { - assertEquals(groupTree.rootGroupProperty().get().getGroupNode(), stateManager.getSelectedGroup(databaseContext).getFirst()); + assertEquals(groupTree.rootGroupProperty().get().getGroupNode(), stateManager.getSelectedGroups(databaseContext).getFirst()); } @Test diff --git a/src/test/java/org/jabref/gui/linkedfile/DownloadLinkedFileActionTest.java b/src/test/java/org/jabref/gui/linkedfile/DownloadLinkedFileActionTest.java index 80791f86bba..067b21f0cc2 100644 --- a/src/test/java/org/jabref/gui/linkedfile/DownloadLinkedFileActionTest.java +++ b/src/test/java/org/jabref/gui/linkedfile/DownloadLinkedFileActionTest.java @@ -26,6 +26,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; @@ -33,6 +35,11 @@ import static org.mockito.Mockito.when; class DownloadLinkedFileActionTest { + + // Required for keepsHtmlEntry + @TempDir + Path tempFolder; + private BibEntry entry; private final BibDatabaseContext databaseContext = mock(BibDatabaseContext.class); @@ -84,8 +91,9 @@ void replacesLinkedFiles(@TempDir Path tempFolder) throws Exception { assertEquals(List.of(new LinkedFile("", tempFolder.resolve("asdf.pdf"), "PDF", url)), entry.getFiles()); } - @Test - void doesntReplaceSourceURL(@TempDir Path tempFolder) throws Exception { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void doesntReplaceSourceURL(boolean keepHtml) throws Exception { String url = "http://arxiv.org/pdf/1207.0408v1"; LinkedFile linkedFile = new LinkedFile(new URL(url), ""); @@ -120,9 +128,62 @@ void doesntReplaceSourceURL(@TempDir Path tempFolder) throws Exception { dialogService, preferences.getFilePreferences(), new CurrentThreadTaskExecutor(), - Path.of(linkedFile.getLink()).getFileName().toString()); + Path.of(linkedFile.getLink()).getFileName().toString(), + keepHtml); downloadLinkedFileAction2.execute(); assertEquals(List.of(new LinkedFile("", tempFolder.resolve("asdf.pdf"), "PDF", url)), entry.getFiles()); } + + @Test + void keepsHtmlEntry(@TempDir Path tempFolder) throws Exception { + String url = "https://blog.fefe.de/?ts=98e04151"; + + LinkedFile linkedFile = new LinkedFile(new URL(url), ""); + when(databaseContext.getFirstExistingFileDir(any())).thenReturn(Optional.of(tempFolder)); + when(filePreferences.getFileNamePattern()).thenReturn("[citationkey]"); + when(filePreferences.getFileDirectoryPattern()).thenReturn(""); + + entry.setFiles(List.of(linkedFile)); + + BibEntry expected = (BibEntry) entry.clone(); + + DownloadLinkedFileAction downloadLinkedFileAction = new DownloadLinkedFileAction( + databaseContext, + entry, + linkedFile, + linkedFile.getLink(), + dialogService, + preferences.getFilePreferences(), + new CurrentThreadTaskExecutor()); + downloadLinkedFileAction.execute(); + + assertEquals(expected, entry); + } + + @Test + void removesHtmlEntry(@TempDir Path tempFolder) throws Exception { + String url = "https://blog.fefe.de/?ts=98e04151"; + + LinkedFile linkedFile = new LinkedFile(new URL(url), ""); + when(databaseContext.getFirstExistingFileDir(any())).thenReturn(Optional.of(tempFolder)); + when(filePreferences.getFileNamePattern()).thenReturn("[citationkey]"); + when(filePreferences.getFileDirectoryPattern()).thenReturn(""); + + entry.setFiles(List.of(linkedFile)); + + DownloadLinkedFileAction downloadLinkedFileAction = new DownloadLinkedFileAction( + databaseContext, + entry, + linkedFile, + linkedFile.getLink(), + dialogService, + preferences.getFilePreferences(), + new CurrentThreadTaskExecutor(), + "", + false); + downloadLinkedFileAction.execute(); + + assertEquals(new BibEntry().withCitationKey("asdf"), entry); + } } diff --git a/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveBracesFormatterTest.java b/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveEnclosingBracesFormatterTest.java similarity index 90% rename from src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveBracesFormatterTest.java rename to src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveEnclosingBracesFormatterTest.java index 37eff160e67..45f6e87373d 100644 --- a/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveBracesFormatterTest.java +++ b/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveEnclosingBracesFormatterTest.java @@ -9,9 +9,9 @@ /** * Tests in addition to the general tests from {@link org.jabref.logic.formatter.FormatterTest} */ -public class RemoveBracesFormatterTest { +public class RemoveEnclosingBracesFormatterTest { - private final RemoveBracesFormatter formatter = new RemoveBracesFormatter(); + private final RemoveEnclosingBracesFormatter formatter = new RemoveEnclosingBracesFormatter(); @ParameterizedTest @CsvSource({ diff --git a/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveWordEnclosingAndOuterEnclosingBracesFormatterTest.java b/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveWordEnclosingAndOuterEnclosingBracesFormatterTest.java new file mode 100644 index 00000000000..036268c7929 --- /dev/null +++ b/src/test/java/org/jabref/logic/formatter/bibtexfields/RemoveWordEnclosingAndOuterEnclosingBracesFormatterTest.java @@ -0,0 +1,31 @@ +package org.jabref.logic.formatter.bibtexfields; + +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; + +public class RemoveWordEnclosingAndOuterEnclosingBracesFormatterTest { + private final RemoveWordEnclosingAndOuterEnclosingBracesFormatter formatter = new RemoveWordEnclosingAndOuterEnclosingBracesFormatter(); + + @ParameterizedTest + @CsvSource({ + "A test B, {A} test {B}", + "A and B, {{A} and {B}}", + "{w}ord word wor{d}, {w}ord word wor{d}", + "{w}ord word word, {{w}ord word word}", + "{w}ord word wor{d}, {w}ord word {wor{d}}", + "Vall{\\'e}e Poussin, {Vall{\\'e}e} {Poussin}", + "Vall{\\'e}e Poussin, {Vall{\\'e}e Poussin}", + "Vall{\\'e}e Poussin, Vall{\\'e}e Poussin" + }) + public void format(String expected, String input) { + assertEquals(expected, formatter.format(input)); + } + + @Test + public void formatExample() { + assertEquals("In CDMA", formatter.format(formatter.getExampleInput())); + } +} diff --git a/src/test/java/org/jabref/logic/importer/fetcher/BiodiversityLibraryTest.java b/src/test/java/org/jabref/logic/importer/fetcher/BiodiversityLibraryTest.java index b5d4fb4f584..b4cf58532f9 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/BiodiversityLibraryTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/BiodiversityLibraryTest.java @@ -100,7 +100,7 @@ public void performSearch() throws FetcherException { .withField(StandardField.URL, "https://www.biodiversitylibrary.org/part/356490") .withField(StandardField.DATE, "2023") .withField(StandardField.VOLUME, "227") - .withField(StandardField.PAGES, "89-97") + .withField(StandardField.PAGES, "89--97") .withField(StandardField.DOI, "10.3897/phytokeys.227.104703"); assertEquals(expected, fetcher.performSearch("Amanoa condorensis (Phyllanthaceae)").getFirst()); diff --git a/src/test/java/org/jabref/logic/importer/fetcher/CiteSeerTest.java b/src/test/java/org/jabref/logic/importer/fetcher/CiteSeerTest.java index 647c18d789d..8b5b4f125cd 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/CiteSeerTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/CiteSeerTest.java @@ -13,18 +13,25 @@ import org.jabref.model.entry.types.StandardEntryType; import org.jabref.testutils.category.FetcherTest; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assumptions.assumeFalse; @FetcherTest class CiteSeerTest { private CiteSeer fetcher = new CiteSeer(); + @BeforeAll + static void ensureCiteSeerIsAvailable() throws Exception { + assumeFalse(List.of().equals(new CiteSeer().performSearch("title:\"Rigorous Derivation from Landau-de Gennes Theory to Ericksen-leslie Theory\" AND pageSize:1"))); + } + @Test void searchByQueryFindsEntryRigorousDerivation() throws Exception { String title = "RIGOROUS DERIVATION FROM LANDAU-DE GENNES THEORY TO ERICKSEN-LESLIE THEORY"; diff --git a/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java b/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java index 287b65e857d..d67744df55d 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/CrossRefTest.java @@ -132,7 +132,6 @@ public void performSearchByIdFindsPaperWithoutTitle() throws Exception { entry.setField(StandardField.JOURNAL, "Indo-Iranian Journal"); entry.setField(StandardField.NUMBER, "2"); entry.setField(StandardField.PUBLISHER, "Brill"); - entry.setField(StandardField.KEYWORDS, "Political Science and International Relations, Linguistics and Language, Philosophy"); assertEquals(Optional.of(entry), fetcher.performSearchById("10.1023/a:1003473214310")); } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/IEEETest.java b/src/test/java/org/jabref/logic/importer/fetcher/IEEETest.java index 965fde89a1a..eeffcf5aa98 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/IEEETest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/IEEETest.java @@ -16,18 +16,25 @@ import org.jabref.model.entry.types.StandardEntryType; import org.jabref.testutils.category.FetcherTest; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.Answers; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @FetcherTest class IEEETest implements SearchBasedFetcherCapabilityTest, PagedSearchFetcherTest { - private final BibEntry IGOR_NEWCOMERS = new BibEntry(StandardEntryType.InProceedings) + private static ImportFormatPreferences importFormatPreferences; + + private static ImporterPreferences importerPreferences; + + private static final BibEntry IGOR_NEWCOMERS = new BibEntry(StandardEntryType.InProceedings) .withField(StandardField.AUTHOR, "Igor Steinmacher and Tayana Uchoa Conte and Christoph Treude and Marco Aurélio Gerosa") .withField(StandardField.DATE, "14-22 May 2016") .withField(StandardField.YEAR, "2016") @@ -45,65 +52,76 @@ class IEEETest implements SearchBasedFetcherCapabilityTest, PagedSearchFetcherTe .withField(StandardField.FILE, ":https\\://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=7886910:PDF"); private IEEE fetcher; - private BibEntry entry; - @BeforeEach - void setUp() { - ImportFormatPreferences importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); + @BeforeAll + static void ensureIeeeIsAvailable() throws Exception { + importFormatPreferences = mock(ImportFormatPreferences.class, Answers.RETURNS_DEEP_STUBS); when(importFormatPreferences.bibEntryPreferences().getKeywordSeparator()).thenReturn(','); - ImporterPreferences importerPreferences = mock(ImporterPreferences.class); + importerPreferences = mock(ImporterPreferences.class); when(importerPreferences.getApiKeys()).thenReturn(FXCollections.emptyObservableSet()); + IEEE ieee = new IEEE(importFormatPreferences, importerPreferences); + + assumeFalse(List.of().equals(ieee.performSearch("article_number:8801912"))); + } + + @BeforeEach + void setUp() { fetcher = new IEEE(importFormatPreferences, importerPreferences); - entry = new BibEntry(); } @Test + @Disabled("IEEE seems to block us") void findByDOI() throws Exception { - entry.setField(StandardField.DOI, "10.1109/ACCESS.2016.2535486"); + BibEntry entry = new BibEntry().withField(StandardField.DOI, "10.1109/ACCESS.2016.2535486"); assertEquals(Optional.of(new URL("https://ieeexplore.ieee.org/ielx7/6287639/7419931/07421926.pdf?tp=&arnumber=7421926&isnumber=7419931&ref=")), fetcher.findFullText(entry)); } @Test + @Disabled("IEEE seems to block us") void findByDocumentUrl() throws Exception { - entry.setField(StandardField.URL, "https://ieeexplore.ieee.org/document/7421926/"); + BibEntry entry = new BibEntry().withField(StandardField.URL, "https://ieeexplore.ieee.org/document/7421926/"); assertEquals(Optional.of(new URL("https://ieeexplore.ieee.org/ielx7/6287639/7419931/07421926.pdf?tp=&arnumber=7421926&isnumber=7419931&ref=")), fetcher.findFullText(entry)); } @Test + @Disabled("IEEE seems to block us") void findByURL() throws Exception { - entry.setField(StandardField.URL, "https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=7421926&ref="); + BibEntry entry = new BibEntry().withField(StandardField.URL, "https://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=7421926&ref="); assertEquals(Optional.of(new URL("https://ieeexplore.ieee.org/ielx7/6287639/7419931/07421926.pdf?tp=&arnumber=7421926&isnumber=7419931&ref=")), fetcher.findFullText(entry)); } @Test + @Disabled("IEEE blocks us - works in browser") void findByOldURL() throws Exception { - entry.setField(StandardField.URL, "https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=7421926"); + BibEntry entry = new BibEntry().withField(StandardField.URL, "https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=7421926"); assertEquals(Optional.of(new URL("https://ieeexplore.ieee.org/ielx7/6287639/7419931/07421926.pdf?tp=&arnumber=7421926&isnumber=7419931&ref=")), fetcher.findFullText(entry)); } @Test + @Disabled("IEEE seems to block us") void findByDOIButNotURL() throws Exception { - entry.setField(StandardField.DOI, "10.1109/ACCESS.2016.2535486"); - entry.setField(StandardField.URL, "http://dx.doi.org/10.1109/ACCESS.2016.2535486"); + BibEntry entry = new BibEntry() + .withField(StandardField.DOI, "10.1109/ACCESS.2016.2535486") + .withField(StandardField.URL, "http://dx.doi.org/10.1109/ACCESS.2016.2535486"); assertEquals(Optional.of(new URL("https://ieeexplore.ieee.org/ielx7/6287639/7419931/07421926.pdf?tp=&arnumber=7421926&isnumber=7419931&ref=")), fetcher.findFullText(entry)); } @Test void notFoundByURL() throws Exception { - entry.setField(StandardField.URL, "http://dx.doi.org/10.1109/ACCESS.2016.2535486"); + BibEntry entry = new BibEntry().withField(StandardField.URL, "http://dx.doi.org/10.1109/ACCESS.2016.2535486"); assertEquals(Optional.empty(), fetcher.findFullText(entry)); } @Test void notFoundByDOI() throws Exception { - entry.setField(StandardField.DOI, "10.1021/bk-2006-WWW.ch014"); + BibEntry entry = new BibEntry().withField(StandardField.DOI, "10.1021/bk-2006-WWW.ch014"); assertEquals(Optional.empty(), fetcher.findFullText(entry)); } @@ -124,8 +142,9 @@ void searchResultHasNoKeywordTerms() throws Exception { .withField(StandardField.VOLUME, "16") .withField(StandardField.KEYWORDS, "Batteries, Generators, Economics, Power quality, State of charge, Harmonic analysis, Control systems, Battery, diesel generator (DG), distributed generation, power quality, photovoltaic (PV), voltage source converter (VSC)"); - List fetchedEntries = fetcher.performSearch("article_number:8801912"); // article number - fetchedEntries.forEach(entry -> entry.clearField(StandardField.ABSTRACT)); // Remove abstract due to copyright); + List fetchedEntries = fetcher.performSearch("article_number:8801912"); + // Abstract should not be included in JabRef tests (copyrighted) + fetchedEntries.forEach(entry -> entry.clearField(StandardField.ABSTRACT)); assertEquals(Collections.singletonList(expected), fetchedEntries); } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/ISIDOREFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/ISIDOREFetcherTest.java index 1b24ea179c9..6fd5239ce0c 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/ISIDOREFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/ISIDOREFetcherTest.java @@ -67,8 +67,8 @@ public void checkThesis() throws FetcherException { List actual = fetcher.performSearch("Mapping English L2 errors: an integrated system and textual approach"); - // Fetcher returns the same entry twice. - assertEquals(List.of(expected, expected), actual); + // Fetcher returns the same entry twice. Since 2024, it also returns an additional entry. We just ignore this for now. + assertEquals(expected, actual.getFirst()); } @Test diff --git a/src/test/java/org/jabref/logic/importer/fetcher/ScholarArchiveFetcherTest.java b/src/test/java/org/jabref/logic/importer/fetcher/ScholarArchiveFetcherTest.java index 0af2c4f0eb4..3ebe337344c 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/ScholarArchiveFetcherTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/ScholarArchiveFetcherTest.java @@ -9,6 +9,7 @@ import org.jabref.testutils.category.FetcherTest; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -22,16 +23,10 @@ public class ScholarArchiveFetcherTest { @BeforeEach public void setUp() { fetcher = new ScholarArchiveFetcher(); - bibEntry = new BibEntry(StandardEntryType.InCollection) - .withField(StandardField.TITLE, "Query expansion using associated queries") - .withField(StandardField.AUTHOR, "Billerbeck, Bodo and Scholer, Falk and Williams, Hugh E. and Zobel, Justin") - .withField(StandardField.VOLUME, "0") - .withField(StandardField.DOI, "10.1145/956863.956866") - .withField(StandardField.JOURNAL, "Proceedings of the twelfth international conference on Information and knowledge management - CIKM '03") - .withField(StandardField.PUBLISHER, "ACM Press") - .withField(StandardField.TYPE, "paper-conference") - .withField(StandardField.YEAR, "2003") - .withField(StandardField.URL, "https://web.archive.org/web/20170810164449/http://goanna.cs.rmit.edu.au/~jz/fulltext/cikm03.pdf"); + bibEntry = new BibEntry(StandardEntryType.InProceedings) + .withField(StandardField.TITLE, "BPELscript: A Simplified Script Syntax for WS-BPEL 2.0") + .withField(StandardField.AUTHOR, "Marc Bischof and Oliver Kopp and Tammo van Lessen and Frank Leymann ") + .withField(StandardField.YEAR, "2009"); } @Test @@ -40,8 +35,9 @@ public void getNameReturnsCorrectName() { } @Test + @Disabled("We seem to be blocked") public void performSearchReturnsExpectedResults() throws FetcherException { - List fetchedEntries = fetcher.performSearch("query"); + List fetchedEntries = fetcher.performSearch("bpelscript"); fetchedEntries.forEach(entry -> entry.clearField(StandardField.ABSTRACT)); assertTrue(fetchedEntries.contains(bibEntry), "Found the following entries " + fetchedEntries); } diff --git a/src/test/java/org/jabref/logic/importer/fetcher/SemanticScholarTest.java b/src/test/java/org/jabref/logic/importer/fetcher/SemanticScholarTest.java index 32045374354..04f53818564 100644 --- a/src/test/java/org/jabref/logic/importer/fetcher/SemanticScholarTest.java +++ b/src/test/java/org/jabref/logic/importer/fetcher/SemanticScholarTest.java @@ -44,12 +44,10 @@ public class SemanticScholarTest implements PagedSearchFetcherTest { .withField(StandardField.VENUE, "International Conference on Software Engineering"); private SemanticScholar fetcher; - private BibEntry entry; @BeforeEach void setUp() { fetcher = new SemanticScholar(importerPreferences); - entry = new BibEntry(); } @Test @@ -64,7 +62,7 @@ void getDocument() throws IOException, FetcherException { @Disabled("Returns a DOI instead of the required link") @DisabledOnCIServer("CI server is unreliable") void fullTextFindByDOI() throws Exception { - entry.setField(StandardField.DOI, "10.1038/nrn3241"); + BibEntry entry = new BibEntry().withField(StandardField.DOI, "10.1038/nrn3241"); assertEquals( Optional.of(new URI("https://europepmc.org/articles/pmc4907333?pdf=render").toURL()), fetcher.findFullText(entry) @@ -73,6 +71,7 @@ void fullTextFindByDOI() throws Exception { @Test @DisabledOnCIServer("CI server is unreliable") + @Disabled("Sometimes, does not find any thing") void fullTextFindByDOIAlternate() throws Exception { assertEquals( Optional.of(new URI("https://pdfs.semanticscholar.org/7f6e/61c254bc2df38a784c1228f56c13317caded.pdf").toURL()), @@ -89,8 +88,8 @@ void fullTextSearchOnEmptyEntry() throws IOException, FetcherException { @Test @DisabledOnCIServer("CI server is unreliable") void fullTextNotFoundByDOI() throws IOException, FetcherException { - entry = new BibEntry().withField(StandardField.DOI, DOI); - entry.setField(StandardField.DOI, "10.1021/bk-2006-WWW.ch014"); + BibEntry entry = new BibEntry().withField(StandardField.DOI, DOI) + .withField(StandardField.DOI, "10.1021/bk-2006-WWW.ch014"); assertEquals(Optional.empty(), fetcher.findFullText(entry)); } @@ -98,7 +97,7 @@ void fullTextNotFoundByDOI() throws IOException, FetcherException { @Test @DisabledOnCIServer("CI server is unreliable") void fullTextFindByArXiv() throws Exception { - entry = new BibEntry().withField(StandardField.EPRINT, "1407.3561") + BibEntry entry = new BibEntry().withField(StandardField.EPRINT, "1407.3561") .withField(StandardField.ARCHIVEPREFIX, "arXiv"); assertEquals( Optional.of(new URI("https://arxiv.org/pdf/1407.3561.pdf").toURL()), @@ -130,6 +129,7 @@ void getURLForQueryWithLucene() throws QueryNodeParseException, MalformedURLExce } @Test + @Disabled("We seem to be blocked") void searchByQueryFindsEntry() throws Exception { BibEntry master = new BibEntry(StandardEntryType.Article) .withField(StandardField.AUTHOR, "Tobias Diez") @@ -145,6 +145,7 @@ void searchByQueryFindsEntry() throws Exception { } @Test + @Disabled("We seem to be blocked") void searchByPlainQueryFindsEntry() throws Exception { List fetchedEntries = fetcher.performSearch("Overcoming Open Source Project Entry Barriers with a Portal for Newcomers"); // Abstract should not be included in JabRef tests @@ -153,6 +154,7 @@ void searchByPlainQueryFindsEntry() throws Exception { } @Test + @Disabled("We seem to be blocked") void searchByQuotedQueryFindsEntry() throws Exception { List fetchedEntries = fetcher.performSearch("\"Overcoming Open Source Project Entry Barriers with a Portal for Newcomers\""); // Abstract should not be included in JabRef tests @@ -166,6 +168,7 @@ public void performSearchByEmptyQuery() throws Exception { } @Test + @Disabled("We seem to be blocked") public void findByEntry() throws Exception { BibEntry barrosEntry = new BibEntry(StandardEntryType.Article) .withField(StandardField.TITLE, "Formalising BPMN Service Interaction Patterns") @@ -175,7 +178,7 @@ public void findByEntry() throws Exception { .withField(StandardField.URL, "https://www.semanticscholar.org/paper/3bb026fd67db7d8e0e25de3189d6b7031b12783e") .withField(StandardField.VENUE, "The Practice of Enterprise Modeling"); - entry.withField(StandardField.TITLE, "Formalising BPMN Service Interaction Patterns"); + BibEntry entry = new BibEntry().withField(StandardField.TITLE, "Formalising BPMN Service Interaction Patterns"); BibEntry actual = fetcher.performSearch(entry).getFirst(); // Abstract should not be included in JabRef tests actual.clearField(StandardField.ABSTRACT); diff --git a/src/test/java/org/jabref/logic/importer/fileformat/PdfGrobidImporterTest.java b/src/test/java/org/jabref/logic/importer/fileformat/PdfGrobidImporterTest.java index 33977668ee3..5f9153434fa 100644 --- a/src/test/java/org/jabref/logic/importer/fileformat/PdfGrobidImporterTest.java +++ b/src/test/java/org/jabref/logic/importer/fileformat/PdfGrobidImporterTest.java @@ -14,6 +14,7 @@ import org.jabref.testutils.category.FetcherTest; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.Answers; @@ -47,10 +48,12 @@ public void getExtensions() { } @Test + @Disabled("Currently does not return anything") public void importEntries() throws URISyntaxException { Path file = Path.of(PdfGrobidImporterTest.class.getResource("LNCS-minimal.pdf").toURI()); List bibEntries = importer.importDatabase(file).getDatabase().getEntries(); + // TODO: Rewrite using our logic of full BibEntries assertEquals(1, bibEntries.size()); BibEntry be0 = bibEntries.getFirst(); diff --git a/src/test/java/org/jabref/logic/layout/format/HTMLCharsTest.java b/src/test/java/org/jabref/logic/layout/format/HTMLCharsTest.java index a31c04c7205..045ab4b5adf 100644 --- a/src/test/java/org/jabref/logic/layout/format/HTMLCharsTest.java +++ b/src/test/java/org/jabref/logic/layout/format/HTMLCharsTest.java @@ -3,6 +3,7 @@ import java.util.stream.Stream; import org.jabref.logic.layout.LayoutFormatter; +import org.jabref.logic.layout.ParamLayoutFormatter; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,10 +16,13 @@ public class HTMLCharsTest { private LayoutFormatter layout; + private ParamLayoutFormatter layoutKeep; @BeforeEach public void setUp() { layout = new HTMLChars(); + layoutKeep = new HTMLChars(); + layoutKeep.setArgument("keepCurlyBraces"); } private static Stream provideBasicFormattingData() { @@ -37,6 +41,7 @@ private static Stream provideBasicFormattingData() { Arguments.of("ä", "{\\\"a}"), Arguments.of("ä", "\\\"a"), Arguments.of("Ç", "{\\c{C}}"), + Arguments.of("Ç", "\\c{C}"), Arguments.of("&Oogon;ı", "\\k{O}\\i"), Arguments.of("ñ ñ í ı ı", "\\~{n} \\~n \\'i \\i \\i") ); @@ -144,4 +149,23 @@ private static Stream provideHTMLEntityFormattingData() { void hTMLEntityFormatting(String expected, String input) { assertEquals(expected, layout.format(input)); } + + private static Stream provideBracesKeepFormattingData() { + return Stream.of( + Arguments.of("{ä}", "{\\\"{a}}"), + Arguments.of("{ä}", "{\\\"a}"), + Arguments.of("{Ç}", "{\\c{C}}"), + Arguments.of("{hallo}", "{\\emph hallo}"), + Arguments.of("{hallo}", "{\\textbf hallo}"), + Arguments.of("{hallo}", "{\\bf hallo}"), + Arguments.of("{'}", "{\\textquotesingle}"), + Arguments.of("{{ test }}", "{{ test }}") + ); + } + + @ParameterizedTest + @MethodSource("provideBracesKeepFormattingData") + void bracesKeepFormatting(String expected, String input) { + assertEquals(expected, layoutKeep.format(input)); + } } diff --git a/src/test/java/org/jabref/logic/layout/format/MarkdownFormatterTest.java b/src/test/java/org/jabref/logic/layout/format/MarkdownFormatterTest.java index e5e1f9281e6..2bfb5ef4caa 100644 --- a/src/test/java/org/jabref/logic/layout/format/MarkdownFormatterTest.java +++ b/src/test/java/org/jabref/logic/layout/format/MarkdownFormatterTest.java @@ -1,10 +1,14 @@ package org.jabref.logic.layout.format; +import java.util.stream.Stream; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; class MarkdownFormatterTest { @@ -17,23 +21,74 @@ void setUp() { } @Test - void formatWhenFormattingPlainTextThenReturnsTextWrappedInParagraph() { - assertEquals("

Hello World

", markdownFormatter.format("Hello World")); - } - - @Test - void formatWhenFormattingComplexMarkupThenReturnsOnlyOneLine() { - assertFalse(markdownFormatter.format("Markup\n\n* list item one\n* list item 2\n\n rest").contains("\n")); + void formatWhenFormattingNullThenThrowsException() { + Exception exception = assertThrows(NullPointerException.class, () -> markdownFormatter.format(null)); + assertEquals("Field Text should not be null, when handed to formatter", exception.getMessage()); } - @Test - void formatWhenFormattingEmptyStringThenReturnsEmptyString() { - assertEquals("", markdownFormatter.format("")); + private static Stream provideMarkdownAndHtml() { + return Stream.of( + Arguments.of("Hello World", "

Hello World

"), + Arguments.of(""" + Markup + + * list item one + * list item two + + rest + """, + "

Markup

  • list item one
  • list item two

rest

" + ), + Arguments.of(""" + ``` + Hello World + ``` + """, + "
Hello World 
" + ), + Arguments.of(""" + First line + + Second line + + ```java + String test; + ``` + """, + "

First line

Second line

String test; 
" + ), + Arguments.of(""" + Some text. + ```javascript + let test = "Hello World"; + ``` + + ```java + String test = "Hello World"; + ``` + Some more text. + """, + "

Some text.

let test = "Hello World"; " +
+                            "
String test = "Hello World"; " +
+                            "

Some more text.

" + ), + Arguments.of(""" + Some text. + + ```java + int foo = 0; + foo = 1; + + ``` + """, + "

Some text.

int foo = 0; foo = 1;  
" + ) + ); } - @Test - void formatWhenFormattingNullThenThrowsException() { - Exception exception = assertThrows(NullPointerException.class, () -> markdownFormatter.format(null)); - assertEquals("Field Text should not be null, when handed to formatter", exception.getMessage()); + @ParameterizedTest + @MethodSource("provideMarkdownAndHtml") + void formatWhenFormattingCodeBlockThenReturnsCodeBlockInHtml(String markdown, String expectedHtml) { + assertEquals(expectedHtml, markdownFormatter.format(markdown)); } } diff --git a/src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java b/src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java index 0a89c471788..9b3f8f0c145 100644 --- a/src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java +++ b/src/test/java/org/jabref/migrations/PreferencesMigrationsTest.java @@ -84,8 +84,8 @@ void previewStyleReviewToComment() { String newPreviewStyle = "__NEWLINE__" + "Customized preview style using reviews and comments:__NEWLINE__" - + "\\begin{comment}

Comment: \\format[Markdown,HTMLChars]{\\comment} \\end{comment}__NEWLINE__" - + "\\begin{comment} Something: \\format[Markdown,HTMLChars]{\\comment} special \\end{comment}__NEWLINE__" + + "\\begin{comment}

Comment: \\format[Markdown,HTMLChars(keepCurlyBraces)]{\\comment} \\end{comment}__NEWLINE__" + + "\\begin{comment} Something: \\format[Markdown,HTMLChars(keepCurlyBraces)]{\\comment} special \\end{comment}__NEWLINE__" + "
__NEWLINE__"; when(prefs.get(JabRefPreferences.PREVIEW_STYLE)).thenReturn(oldPreviewStyle);