From 00b74c8b29eb60741725c2729c10ff1ae6cee274 Mon Sep 17 00:00:00 2001 From: Dan Knauss <273554+dknauss@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:38:18 -0600 Subject: [PATCH 1/3] Drop the period citeproc-php adds after a "?" or "!" title MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit citeproc-php moves a title's "." suffix inside the closing quote without checking for terminal punctuation, so Chicago (both styles) and MLA rendered "What Is It?.”". Strip a period that follows "?" or "!" (with or without a closing quote) in the formatter's text normalization. Commas are kept, as CMOS allows after a question-mark title. citeproc-js already renders these titles correctly; the server output now matches it. Existing style goldens are unchanged. Co-Authored-By: Claude Opus 5.5 --- bibliography-builder.php | 5 +++ tests/phpunit/CslStyleGoldenTest.php | 61 ++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/bibliography-builder.php b/bibliography-builder.php index e163d00..3543124 100644 --- a/bibliography-builder.php +++ b/bibliography-builder.php @@ -1163,6 +1163,11 @@ function bibliography_builder_normalize_formatted_text( $text, $style ) { $text = str_replace( array( '”,', '”.' ), array( ',”', '.”' ), $text ); } + // A title ending in "?" or "!" takes no period after it, but citeproc-php + // adds the title's "." suffix anyway ("What Is It?.”"). Commas stay: CMOS + // keeps one after a question-mark title (“Are You a Doctor?,” in notes). + $text = preg_replace( '/([?!])([”’]?)\./u', '$1$2', $text ); + // citeproc-php initializes only capitalized parts of a hyphenated given // name, so "Ji-woo" comes out "J.- woo". Initialize the second part too. $text = preg_replace_callback( diff --git a/tests/phpunit/CslStyleGoldenTest.php b/tests/phpunit/CslStyleGoldenTest.php index 257c5a4..5927d39 100644 --- a/tests/phpunit/CslStyleGoldenTest.php +++ b/tests/phpunit/CslStyleGoldenTest.php @@ -152,6 +152,67 @@ public function test_current_edition_rules_outside_the_corpus() { ); } + /** + * A title ending in "?" or "!" already closes its element, so no period + * follows it (CMOS 18 6.133; MLA 9 1.3). citeproc-php moves the title's + * "." suffix inside the closing quote without checking, giving "?.”". + */ + public function test_title_ending_in_question_or_exclamation_mark_takes_no_period() { + $webpage = array( + 'type' => 'webpage', + 'title' => 'What Are Reduced Penetrance and Variable Expressivity?', + 'container-title' => 'MedlinePlus', + 'URL' => 'https://example.org/q', + 'issued' => array( 'date-parts' => array( array( 2021, 7, 28 ) ) ), + ); + $article = array( + 'type' => 'article-journal', + 'title' => 'Diabetes Technology: What’s New in 2025?', + 'author' => array( + array( + 'family' => 'Moss', + 'given' => 'Ann', + ), + ), + 'container-title' => 'Diabetes Care', + 'volume' => '48', + 'page' => '1-10', + 'issued' => array( 'date-parts' => array( array( 2025 ) ) ), + ); + $exclaim = array( + 'type' => 'webpage', + 'title' => 'Stop the Presses!', + 'container-title' => 'Site', + 'URL' => 'https://example.org/b', + 'issued' => array( 'date-parts' => array( array( 2024 ) ) ), + ); + $items = array( $webpage, $article, $exclaim ); + + $this->assertSame( + array( + '“What Are Reduced Penetrance and Variable Expressivity?” MedlinePlus. July 28, 2021. https://example.org/q.', + 'Moss, Ann. “Diabetes Technology: What’s New in 2025?” Diabetes Care 48 (2025): 1–10.', + '“Stop the Presses!” Site. 2024. https://example.org/b.', + ), + bibliography_builder_format_csl_items( $items, 'chicago-notes-bibliography' ) + ); + + $this->assertSame( + array( + '“What Are Reduced Penetrance and Variable Expressivity?” MedlinePlus, 28 July 2021, https://example.org/q.', + 'Moss, Ann. “Diabetes Technology: What’s New in 2025?” Diabetes Care, vol. 48, 2025, pp. 1–10.', + '“Stop the Presses!” Site, 2024, https://example.org/b.', + ), + bibliography_builder_format_csl_items( $items, 'mla-9' ) + ); + + foreach ( array_keys( bibliography_builder_get_formatter_style_definitions() ) as $style_key ) { + foreach ( bibliography_builder_format_csl_items( $items, $style_key ) as $text ) { + $this->assertDoesNotMatchRegularExpression( '/[?!][”’]?\./u', $text, $style_key ); + } + } + } + /** * The formatter reads vendor/citation-style-language/, a Composer copy of * packages/. A stale copy once hid a style regression; fail on any drift. From a2abc5ff3b373b8d7fa0d5fce86caad201fe8385 Mon Sep 17 00:00:00 2001 From: Dan Knauss <273554+dknauss@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:38:37 -0600 Subject: [PATCH 2/3] Note the terminal-punctuation fix in the changelog Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc9cae1..a3d61df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **Names with particles sort where each style puts them.** A surname that opens with a lowercase particle, such as "van Duijnhoven", "de la Cruz", "von Neumann", or "d'Alembert", was sorted under the particle in every style. Chicago (both styles) and Vancouver sort by the surname proper, so Chicago lists "Duijnhoven, Elza van" under D, and the formatter now renders it that way. APA, MLA, Harvard, and the other styles keep the particle in front and still sort under V. A capitalized form ("Van Buren") is part of the surname and always sorts under V. Names are stored as entered, so exports and Edit fields are unchanged. Bibliographies saved earlier stay valid in the editor and take the new order the next time they are saved. ([#142](https://github.com/dknauss/Borges/issues/142)) +- **No period after a title that ends in "?" or "!".** Chicago (both styles) and MLA rendered a quoted title such as "What Are Reduced Penetrance and Variable Expressivity?" as `“…Expressivity?.”`, with a period inside the closing quote. The title now closes with its own mark: `“…Expressivity?”`. A comma after such a title is kept, as Chicago allows. Bibliographies saved earlier keep the old text until they are reformatted or saved again. ## [1.11.0] - 2026-10-14 From f40c90a0ba07e589742f3b0aff204ae283a46be7 Mon Sep 17 00:00:00 2001 From: Dan Knauss <273554+dknauss@users.noreply.github.com> Date: Mon, 19 Oct 2026 15:21:40 -0600 Subject: [PATCH 3/3] Update current metrics for code lUpdate current-metrics line counts to match the repositoryines Updated line counts for main plugin file and first-party PHP.Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01V1qSnyqMzCKxQbVdnTpoVX --- docs/current-metrics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/current-metrics.md b/docs/current-metrics.md index 243a31e..159e9cb 100644 --- a/docs/current-metrics.md +++ b/docs/current-metrics.md @@ -11,8 +11,8 @@ Source and LOC figures last verified: **2026-10-06** against the heading-level b | Metric | Value | Re-derivation command | |---|---|---| -| Main plugin file (`bibliography-builder.php`) | **2,208** | `wc -l bibliography-builder.php` | -| All first-party PHP (excl. vendor, tests, scripts, playground, packages, output, node_modules, generated `build/`) | **10,084** | `find . -name '*.php' -not -path './vendor/*' -not -path './node_modules/*' -not -path './tests/*' -not -path './packages/*' -not -path './scripts/*' -not -path './playground/*' -not -path './output/*' -not -path './build/*' -print0 \| xargs -0 wc -l \| tail -1` | +| Main plugin file (`bibliography-builder.php`) | **2,213** | `wc -l bibliography-builder.php` | +| All first-party PHP (excl. vendor, tests, scripts, playground, packages, output, node_modules, generated `build/`) | **10,089** | `find . -name '*.php' -not -path './vendor/*' -not -path './node_modules/*' -not -path './tests/*' -not -path './packages/*' -not -path './scripts/*' -not -path './playground/*' -not -path './output/*' -not -path './build/*' -print0 \| xargs -0 wc -l \| tail -1` | | JS source (`src/`, excl. `*.test.js`) | **11,381** | `find ./src -name '*.js' -not -name '*.test.js' -print0 \| xargs -0 wc -l \| tail -1` | | Shipped frontend runtime (`build/view.js`, minified) | **1,449 bytes** | `npm run build` then `wc -c < build/view.js` |